#!/usr/bin/perl -w # # p4_ls for Safari # Copyright (c) 1999 by Barrie Slaymaker, rbs@telerama.com # # You may distribute under the terms of either the GNU General Public # License or the Artistic License, as specified in the README file. # # # Takes an absolute path and uses it as a wildcard spec to generate a listing # of a directory in the archives. # use strict ; use vars qw( $opt_out_file $opt_depot_list $opt_file_list ) ; use Getopt::Long ; use File::Basename ; use File::Path ; use HTML::Entities ; my $progname = basename( $0 ) ; GetOptions( 'depot-list=s', 'file-list=s', 'out-file|o=s', ) ; my ( $spec ) = ( @ARGV ) ; my $up_to_project = $ENV{SAF_UP_TO_PROJECT} ; my $up_to_rev = $ENV{SAF_UP_TO_REV} ; my $up_to_filter = $ENV{SAF_UP_TO_FILTER} ; my $filter = $ENV{SAF_FILTER} ; my $file = $ENV{SAF_FILE} ; my $rev = $ENV{SAF_REV} ; $spec = '//' unless defined( $spec ) && length( $spec ) ; die "path name must be absolute (start with a '/')" unless substr( $spec, 0, 1 ) eq '/' ; # # Allow a /* here for forwards compatability, so we can implement # real globbing. # $spec =~ s/\*$//g ; if ( defined( $opt_out_file ) && length( $opt_out_file ) ) { my ( undef, $dir ) = fileparse( $opt_out_file ) ; mkpath( $dir, 0, 0775 ) unless -d $dir ; open( OUTPUT, ">$opt_out_file" ) or die "$!: $opt_out_file" ; $SIG{__DIE__} = sub { close( OUTPUT ) ; print( STDERR "Removing $opt_out_file\n" ) ; unlink( $opt_out_file ) ; } } else { open( OUTPUT, ">&STDOUT" ) ; } # make /a in to //a $spec = "/$spec" unless substr( $spec, 0, 2 ) eq '//' ; # make sure there's a / on the end $spec .= '/' unless substr( $spec, -1 ) eq '/' ; my $list_depots = $spec eq '//' ; my $headers ; my $files = {} ; my $title ; my $align ; if ( $list_depots ) { $title = "Depots" ; $headers = [ 'Depot', 'Description' ] ; $align = [ 'LEFT', 'LEFT' ] ; my %depots ; my $p4 = defined( $opt_depot_list ) ? "<$opt_depot_list" : "p4 depots |" ; open( P4, "$p4" ) or die "$progname: $!: '$p4'" ; while ( <P4> ) { chomp ; my ( $type, $depot, $date, undef, undef, $subdir, $desc ) = split( /\s+/, $_, 7 ) ; next unless defined $desc ; $desc =~ s/^'(.*)\s*'$/$1/ ; my $depot_key = lc( $depot ) ; $depot .= '/' ; $files->{ $depot_key } = [ join( '', qq{<A HREF="$depot">}, encode_entities( $depot ), '</A>', ), encode_entities( $desc ), ] ; } close( P4 ) or die length( $! ) ? "$progname: $! closing '$p4'" : "'$p4' returned $?" ; $files->{'depot'} = [ qq{<A HREF="depot/">depot/</A>}, 'default depot' ] unless %$files ; } else { # # We need to list all of the files here because the 'p4 files' command # does not list out subdirs. This means that we need to discern subdirs # ourself by scanning all files. # $title = "Files in $spec" ; $headers = [ 'File', 'Rev', '', 'Description', 'Change', '', 'Type' ] ; $align = [ 'LEFT', 'RIGHT', 'CENTER', 'LEFT', 'RIGHT', 'CENTER', 'LEFT' ] ; my $p4 = defined( $opt_file_list ) ? "<$opt_file_list" : "p4 files //... |"; open( P4, "$p4" ) or die "$progname $! opening '$p4'" ; my $spec_len = length( $spec ) ; my %seen ; my $dir_entry = {} ; while ( <P4> ) { chomp ; my ( $name, $frev, $desc ) = /^([^#]+)(#\d+)\s*-\s*(.*?)\s*$/ ; next unless defined $desc ; next unless substr( $name, 0, $spec_len ) eq $spec ; $name = substr( $name, $spec_len ) ; my $is_dir = $name =~ s@/.*@/@ ; my $full_name = $spec . $name ; $full_name =~ s@^//@/@ ; $desc =~ s/^\s*// ; $desc =~ s/\s*$// ; my $type = '' ; if ( $desc =~ s@\s*\((\w+)\)\s*$@@ ) { $type = $1 ; } my $change = '' ; if ( $desc =~ s/(.*?)\s*change (\d+)\s*(.*)/$1$3/ ) { $change = $2 ; } if ( %$dir_entry && $name ne $dir_entry->{NAME} ) { emit_dir( $files, $dir_entry ) ; $seen{$dir_entry->{NAME}} = 1 ; $dir_entry = {} ; } my $is_deleted = $desc eq 'delete' ; if ( $is_dir ) { unless ( %$dir_entry ) { $dir_entry->{FULL_NAME} = $full_name ; $dir_entry->{NAME} = $name ; } $dir_entry->{NON_EMPTY} ||= ! $is_deleted ; $dir_entry->{MAX_CHANGE} = $change if ( ! defined $dir_entry->{MAX_CHANGE} || $change > $dir_entry->{MAX_CHANGE} ) ; next ; } next if $seen{$name} ; $seen{name} = 1 ; $files->{lc( $name )} = [ name_anchor( $name, $is_deleted ), join( '', "<TT>", encode_entities( $frev ), "</TT>" ), filelog_anchor( $full_name ), encode_entities( $desc ), change_anchor( $full_name, $change, $is_deleted ), desc_anchor( $change ), encode_entities( $type ), ] ; strike_all( @{$files->{lc( $name )}} ) if $is_deleted ; } emit_dir( $files, $dir_entry ) if ( %$dir_entry ) ; close( P4 ) or die length( $! ) ? "$progname: $! closing '$p4'" : "'$p4' returned $?" ; } # # We accumulate all output, then print it in a burst so that no output leaks # out in the event of an error, and so we can use common formatting code. # my $output = format_list( $align, $headers, $files ) ; print OUTPUT qq{<HTML> <HEAD> <TITLE>$title</TITLE> </HEAD> <BODY> <TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0"> $output </TABLE> </BODY> </HTML> } or die "$!: $opt_out_file" ; close( OUTPUT ) or die "$!: $opt_out_file" ; 0 ; ############################################################################### sub name_anchor { my( $name, $deleted ) = @_ ; my $name_anchor = encode_entities( $name ) ; $name_anchor = qq{<A HREF="$name">$name_anchor</A>} unless $deleted ; return $name_anchor ; } sub filelog_anchor { my $full_name = shift ; return qq{[ <A HREF="${up_to_project}_head/$filter$full_name?filter=filelog">filelog</A> ]} ; } sub change_anchor { my ( $full_name, $change, $deleted ) = @_ ; my $change_anchor = join( '', '<TT>', encode_entities( "\@$change" ), "</TT>" ) ; $change_anchor = qq{<A HREF="$up_to_project\@$change/$filter$full_name">$change_anchor</A>} unless $deleted ; return $change_anchor ; } sub desc_anchor { my $change = shift ; return qq{[ <A HREF="${up_to_project}_head/changes/$change.html">desc</A> ]}; } sub emit_dir { my ( $files, $dir_entry ) = @_ ; my $name = $dir_entry->{NAME} ; $files->{lc( $name )} = [ name_anchor( $name ), "<BR>", "<BR>", "<BR>", change_anchor( $dir_entry->{FULL_NAME}, $dir_entry->{MAX_CHANGE}, ! $dir_entry->{NON_EMPTY} ), desc_anchor( $dir_entry->{MAX_CHANGE}) , "<BR>", ] ; strike_all( @{$files->{lc( $name )}} ) unless $dir_entry->{NON_EMPTY} ; } sub strike_all { map{ $_ = "<STRIKE>$_</STRIKE>" } @_ ; } sub format_list { my ( $align, $headers, $files ) = @_ ; my @output ; my $format = join( '', map{ "<TH ALIGN=\"$_\">%s</TH>" } "RIGHT", @$align ) ; push( @output, '<TR ALIGN="LEFT">', sprintf( $format, '<BR>', @$headers ), "</TR>\n" ) ; my $grey = '"#B0FFB0"' ; my $greybar_rows = 5 ; my $line_number = 1 ; $format =~ s/TH/TD/g ; for ( sort keys %$files ) { my $bgcolor = int( ( $line_number - 1 ) / $greybar_rows) % 2 ? " BGCOLOR=$grey" : "" ; push( @output, "<TR$bgcolor>", sprintf( $format, $line_number, @{$files->{$_}} ), "</TR>\n" ) ; ++$line_number ; } return join( '', @output ) ; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#7 | 201 | Barrie Slaymaker |
More format tweaks, fixed bug that made change number a link for deleted items. |
||
#6 | 197 | Barrie Slaymaker |
Added change numbers to directory lines, made strikeout apply to all fields for deleted lines. |
||
#5 | 184 | Barrie Slaymaker |
The change number is now a link even when for a deleted file. It is still put in <STRIKE> if the file is deleted. |
||
#4 | 182 | Barrie Slaymaker |
Cleaned up directory listing so that directories no longer have meaningless links to change number, filelog, etc. Also made directory names appear in <STRIKE> when they contain no files that have not been deleted. |
||
#3 | 168 | Barrie Slaymaker | Added YAPC paper, slides | ||
#2 | 165 | Barrie Slaymaker | Applied Greg KH's license patch. | ||
#1 | 162 | Barrie Slaymaker | First code & documentation checkin |