📄 cvs2cl.pl
字号:
$utag = $self->escape($utag); # the usual paranoia $beauty .= "<utag>${utag}</utag>\n"; } } if ($common_dir) { $common_dir = $self->escape($common_dir); $beauty .= "<commondir>${common_dir}</commondir>\n"; } # That's enough for XML, time to go home: return $beauty;}# -------------------------------------sub output_tagdate { # NOT YET DONE}# -------------------------------------sub output_entry { my $self = shift; my ($fh, $entry) = @_; print $fh "<entry>\n$entry</entry>\n\n";}# -------------------------------------sub format_body { my $self = shift; my ($msg, $files, $qunklist) = @_; $msg = $self->preprocess_msg_text($msg); return $files . $msg;}# ----------------------------------------------------------------------------package CVS::Utils::ChangeLog::EntrySet::Output;use Carp qw( croak );use File::Basename qw( fileparse );# Class Utility Functions -------------{ # form closuremy @weekdays = (qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday));sub weekday_en { my $class = shift; return $weekdays[$_[0]];}}# Abstract Subrs ----------------------sub wday { croak "Whoops. Abtract method call (wday).\n" }sub pretty_file_list { croak "Whoops. Abtract method call (pretty_file_list).\n" }sub output_tagdate { croak "Whoops. Abtract method call (output_tagdate).\n" }sub header_line { croak "Whoops. Abtract method call (header_line).\n" }# Instance Subrs ----------------------sub output_header { }# -------------------------------------sub output_entry { my $self = shift; my ($fh, $entry) = @_; print $fh "$entry\n";}# -------------------------------------sub output_footer { }# -------------------------------------sub escape { return $_[1] }# -------------------------------------sub output_changelog {my $self = shift; my $class = ref $self; my ($grand_poobah) = @_; ### Process each ChangeLog while (my ($dir,$authorhash) = each %$grand_poobah) { &main::debug ("DOING DIR: $dir\n"); # Here we twist our hash around, from being # author => time => message => filelist # in %$authorhash to # time => author => message => filelist # in %changelog. # # This is also where we merge entries. The algorithm proceeds # through the timeline of the changelog with a sliding window of # $Max_Checkin_Duration seconds; within that window, entries that # have the same log message are merged. # # (To save space, we zap %$authorhash after we've copied # everything out of it.) my %changelog; while (my ($author,$timehash) = each %$authorhash) { my %stamptime; foreach my $time (sort {$a <=> $b} (keys %$timehash)) { my $msghash = $timehash->{$time}; while (my ($msg,$qunklist) = each %$msghash) { my $stamptime = $stamptime{$msg}; if ((defined $stamptime) and (($time - $stamptime) < $Max_Checkin_Duration) and (defined $changelog{$stamptime}{$author}{$msg})) { push(@{$changelog{$stamptime}{$author}{$msg}}, $qunklist->files); } else { $changelog{$time}{$author}{$msg} = $qunklist->files; $stamptime{$msg} = $time; } } } } undef (%$authorhash); ### Now we can write out the ChangeLog! my ($logfile_here, $logfile_bak, $tmpfile); my $lastdate; if (! $Output_To_Stdout) { $logfile_here = $dir . $Log_File_Name; $logfile_here =~ s/^\.\/\//\//; # fix any leading ".//" problem $tmpfile = "${logfile_here}.cvs2cl$$.tmp"; $logfile_bak = "${logfile_here}.bak"; open (LOG_OUT, ">$tmpfile") or die "Unable to open \"$tmpfile\""; } else { open (LOG_OUT, ">-") or die "Unable to open stdout for writing"; } print LOG_OUT $ChangeLog_Header; my %tag_date_printed; $self->output_header(\*LOG_OUT); my @key_list = (); if($Chronological_Order) { @key_list = sort {$a <=> $b} (keys %changelog); } else { @key_list = sort {$b <=> $a} (keys %changelog); } foreach my $time (@key_list) { next if ($Delta_Mode && (($time <= $Delta_StartTime) || ($time > $Delta_EndTime && $Delta_EndTime))); # Set up the date/author line. # kff todo: do some more XML munging here, on the header # part of the entry: my (undef,$min,$hour,$mday,$mon,$year,$wday) = $UTC_Times ? gmtime($time) : localtime($time); $wday = $self->wday($wday); # XML output includes everything else, we might as well make # it always include Day Of Week too, for consistency. my $authorhash = $changelog{$time}; if ($Show_Tag_Dates) { my %tags; while (my ($author,$mesghash) = each %$authorhash) { while (my ($msg,$qunk) = each %$mesghash) { foreach my $qunkref2 (@$qunk) { if (defined ($qunkref2->tags)) { foreach my $tag (@{$qunkref2->tags}) { $tags{$tag} = 1; } } } } } # Sort here for determinism to ease testing foreach my $tag (sort keys %tags) { if ( ! defined $tag_date_printed{$tag} ) { $tag_date_printed{$tag} = $time; $self->output_tagdate(\*LOG_OUT, $time, $tag); } } } while (my ($author,$mesghash) = each %$authorhash) { # If XML, escape in outer loop to avoid compound quoting: $author = $self->escape($author); FOOBIE: # We sort here to enable predictable ordering for the testing porpoises for my $msg (sort keys %$mesghash) { my $qunklist = $mesghash->{$msg}; ## MJP: 19.xii.01 : Exclude @ignore_tags for my $ignore_tag (keys %ignore_tags) { next FOOBIE if grep($_ eq $ignore_tag, map(@{$_->{tags}}, grep(defined $_->{tags}, @$qunklist))); } ## MJP: 19.xii.01 : End exclude @ignore_tags # show only files with tag --show-tag $show_tag if ( keys %show_tags ) { next FOOBIE if !grep(exists $show_tags{$_}, map(@{$_->{tags}}, grep(defined $_->{tags}, @$qunklist))); } my $files = $self->pretty_file_list($qunklist); my $header_line; # date and author my $wholething; # $header_line + $body my $date = $self->fdatetime($time); $header_line = $self->header_line($time, $author, $lastdate); $lastdate = $date; $Text::Wrap::huge = 'overflow' if $Text::Wrap::VERSION >= 2001.0130; # Reshape the body according to user preferences. my $body = $self->format_body($msg, $files, $qunklist); $body =~ s/[ \t]+\n/\n/g; $wholething = $header_line . $body; # One last check: make sure it passes the regexp test, if the # user asked for that. We have to do it here, so that the # test can match against information in the header as well # as in the text of the log message. # How annoying to duplicate so much code just because I # can't figure out a way to evaluate scalars on the trailing # operator portion of a regular expression. Grrr. if ($Case_Insensitive) { unless ( $Regexp_Gate and ( $wholething !~ /$Regexp_Gate/oi ) ) { $self->output_entry(\*LOG_OUT, $wholething); } } else { unless ( $Regexp_Gate and ( $wholething !~ /$Regexp_Gate/o ) ) { $self->output_entry(\*LOG_OUT, $wholething); } } } } } $self->output_footer(\*LOG_OUT); close (LOG_OUT); if ( ! $Output_To_Stdout ) { # If accumulating, append old data to new before renaming. But # don't append the most recent entry, since it's already in the # new log due to CVS's idiosyncratic interpretation of "log -d". if ($Cumulative && -f $logfile_here) { open NEW_LOG, ">>$tmpfile" or die "trouble appending to $tmpfile ($!)"; open OLD_LOG, "<$logfile_here" or die "trouble reading from $logfile_here ($!)"; my $started_first_entry = 0; my $passed_first_entry = 0; while (<OLD_LOG>) { if ( ! $passed_first_entry ) { if ( ( ! $started_first_entry ) and /^(\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d)/ ) { $started_first_entry = 1; } elsif ( /^(\d\d\d\d-\d\d-\d\d\s+\d\d:\d\d)/ ) { $passed_first_entry = 1; print NEW_LOG $_; } } else { print NEW_LOG $_; } } close NEW_LOG; close OLD_LOG; } if ( -f $logfile_here ) { rename $logfile_here, $logfile_bak; } rename $tmpfile, $logfile_here; } }}# -------------------------------------# Don't call this wrap, because with 5.5.3, that clashes with the# (unconditional :-( ) export of wrap() from Text::Wrapsub mywrap { my $self = shift; my ($indent1, $indent2, @text) = @_; # If incoming text looks preformatted, don't get clever my $text = Text::Wrap::wrap($indent1, $indent2, @text); if ( grep /^\s+/m, @text ) { return $text; } my @lines = split /\n/, $text; $indent2 =~ s!^((?: {8})+)!"\t" x (length($1)/8)!e; $lines[0] =~ s/^$indent1\s+/$indent1/; s/^$indent2\s+/$indent2/ for @lines[1..$#lines]; my $newtext = join "\n", @lines; $newtext .= "\n" if substr($text, -1) eq "\n"; return $newtext;}# -------------------------------------sub preprocess_msg_text { my $self = shift; my ($text) = @_; # Strip out carriage returns (as they probably result from DOSsy editors). $text =~ s/\r\n/\n/g; # If it *looks* like two newlines, make it *be* two newlines: $text =~ s/\n\s*\n/\n\n/g; return $text;}# -------------------------------------sub last_line_len { my $self = shift; my $files_list = shift; my @lines = split (/\n/, $files_list); my $last_line = pop (@lines); return length ($last_line);}# -------------------------------------# A custom wrap function, sensitive to some common constructs used in# log entries.sub wrap_log_entry { my $self = shift; my $text = shift; # The text to wrap. my $left_pad_str = shift; # String to pad with on the left. # These do NOT take left_pad_str into account: my $length_remaining = shift; # Amount left on current line. my $max_line_length = shift; # Amount left for a blank line. my $wrapped_text = ''; # The accumulating wrapped entry. my $user_indent = ''; # Inherited user_indent from prev line. my $first_time = 1; # First iteration of the loop? my $suppress_line_start_match = 0; # Set to disable line start checks. my @lines = split (/\n/, $text); while (@lines) # Don't use `foreach' here, it won't work. { my $this_line = shift (@lines); chomp $this_line; if ($this_line =~ /^(\s+)/) { $user_indent = $1; } else { $user_indent = ''; } # If it matches any of the line-start regexps, print a newline now... if ($suppress_line_start_match) { $suppress_line_start_match = 0; } elsif (($this_line =~ /^(\s*)\*\s+[a-zA-Z0-9]/) || ($this_line =~ /^(\s*)\* [a-zA-Z0-9_\.\/\+-]+/) || ($this_line =~ /^(\s*)\([a-zA-Z0-9_\.\/\+-]+(\)|,\s*)/) || ($this_line =~ /^(\s+)(\S+)/) || ($this_line =~ /^(\s*)- +/) || ($this_line =~ /^()\s*$/) || ($this_line =~ /^(\s*)\*\) +/) || ($this_line =~ /^(\s*)[a-zA-Z0-9](\)|\.|\:) +/)) { # Make a line break immediately, unless header separator is set # and this line is the first line in the entry, in which case # we're getting the blank line for free already and shouldn't # add an extra one. unless (($After_Header ne " ") and ($first_time)) { if ($this_line =~ /^()\s*$/) { $suppress_line_start_match = 1; $wrapped_text .= "\n${left_pad_str}"; } $wrapped_text .= "\n${left_pad_str}"; } $length_remaining = $max_line_length - (length ($user_indent)); } # Now that any user_indent has been preserved, strip off leading # whitespace, so up-folding has no ugly side-effects. $this_line =~ s/^\s*//; # Accumulate the line, and adjust parameters for next line. my $this_len = length ($this_line); if ($this_len == 0) { # Blank lines should cancel any user_indent level. $user_indent = ''; $length_remaining = $max_line_length; } elsif ($this_len >= $length_remaining) # Line too long, try breaking it. { # Walk backwards from the end. At first acceptable spot, break # a new line. my $idx = $length_remaining - 1; if ($idx < 0) { $idx = 0 }; while ($idx > 0) { if (substr ($this_line, $idx, 1) =~ /\s/) { my $line_now = substr ($this_line, 0, $idx); my $next_line = substr ($this_line, $idx); $this_line = $line_now; # Clean whitespace off the end. chomp $this_line;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -