📄 cvs2cl.pl
字号:
if ($this_line =~ /\.$/) { $this_line .= " "; $length_remaining -= 2; } else # not a sentence end { $this_line .= " "; $length_remaining -= 1; } } # Unconditionally indicate that loop has run at least once. $first_time = 0; $wrapped_text .= "${user_indent}${this_line}"; } # One last bit of padding. $wrapped_text .= "\n"; return $wrapped_text;}sub xml_escape (){ my $txt = shift; $txt =~ s/&/&/g; $txt =~ s/</</g; $txt =~ s/>/>/g; return $txt;}sub maybe_read_user_map_file (){ my %expansions; if ($User_Map_File) { open (MAPFILE, "<$User_Map_File") or die ("Unable to open $User_Map_File ($!)"); while (<MAPFILE>) { next if /^\s*#/; # Skip comment lines. next if not /:/; # Skip lines without colons. # It is now safe to split on ':'. my ($username, $expansion) = split ':'; chomp $expansion; $expansion =~ s/^'(.*)'$/$1/; $expansion =~ s/^"(.*)"$/$1/; # If it looks like the expansion has a real name already, then # we toss the username we got from CVS log. Otherwise, keep # it to use in combination with the email address. if ($expansion =~ /^\s*<{0,1}\S+@.*/) { # Also, add angle brackets if none present if (! ($expansion =~ /<\S+@\S+>/)) { $expansions{$username} = "$username <$expansion>"; } else { $expansions{$username} = "$username $expansion"; } } else { $expansions{$username} = $expansion; } } close (MAPFILE); } return %expansions;}sub parse_options (){ # Check this internally before setting the global variable. my $output_file; # If this gets set, we encountered unknown options and will exit at # the end of this subroutine. my $exit_with_admonishment = 0; while (my $arg = shift (@ARGV)) { if ($arg =~ /^-h$|^-help$|^--help$|^--usage$|^-?$/) { $Print_Usage = 1; } elsif ($arg =~ /^--debug$/) { # unadvertised option, heh $Debug = 1; } elsif ($arg =~ /^--version$/) { $Print_Version = 1; } elsif ($arg =~ /^-g$|^--global-opts$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; # Don't assume CVS is called "cvs" on the user's system: $Log_Source_Command =~ s/(^\S*)/$1 $narg/; } elsif ($arg =~ /^-l$|^--log-opts$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; $Log_Source_Command .= " $narg"; } elsif ($arg =~ /^-f$|^--file$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; $output_file = $narg; } elsif ($arg =~ /^--fsf$/) { $FSF_Style = 1; } elsif ($arg =~ /^-U$|^--usermap$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; $User_Map_File = $narg; } elsif ($arg =~ /^-W$|^--window$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; $Max_Checkin_Duration = $narg; } elsif ($arg =~ /^-I$|^--ignore$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; push (@Ignore_Files, $narg); } elsif ($arg =~ /^-C$|^--case-insensitive$/) { $Case_Insensitive = 1; } elsif ($arg =~ /^-R$|^--regexp$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; $Regexp_Gate = $narg; } elsif ($arg =~ /^--stdout$/) { $Output_To_Stdout = 1; } elsif ($arg =~ /^--version$/) { $Print_Version = 1; } elsif ($arg =~ /^-d$|^--distributed$/) { $Distributed = 1; } elsif ($arg =~ /^-P$|^--prune$/) { $Prune_Empty_Msgs = 1; } elsif ($arg =~ /^-S$|^--separate-header$/) { $After_Header = "\n\n"; } elsif ($arg =~ /^--no-wrap$/) { $No_Wrap = 1; } elsif ($arg =~ /^--gmt$|^--utc$/) { $UTC_Times = 1; } elsif ($arg =~ /^-w$|^--day-of-week$/) { $Show_Day_Of_Week = 1; } elsif ($arg =~ /^-r$|^--revisions$/) { $Show_Revisions = 1; } elsif ($arg =~ /^-t$|^--tags$/) { $Show_Tags = 1; } elsif ($arg =~ /^-b$|^--branches$/) { $Show_Branches = 1; } elsif ($arg =~ /^-F$|^--follow$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; push (@Follow_Branches, $narg); } elsif ($arg =~ /^--stdin$/) { $Input_From_Stdin = 1; } elsif ($arg =~ /^--header$/) { my $narg = shift (@ARGV) || die "$arg needs argument.\n"; $ChangeLog_Header = &slurp_file ($narg); if (! defined ($ChangeLog_Header)) { $ChangeLog_Header = ""; } } elsif ($arg =~ /^--xml$/) { $XML_Output = 1; } elsif ($arg =~ /^--hide-filenames$/) { $Hide_Filenames = 1; $After_Header = ""; } else { # Just add a filename as argument to the log command $Log_Source_Command .= " $arg"; } } ## Check for contradictions... if ($Output_To_Stdout && $Distributed) { print STDERR "cannot pass both --stdout and --distributed\n"; $exit_with_admonishment = 1; } if ($Output_To_Stdout && $output_file) { print STDERR "cannot pass both --stdout and --file\n"; $exit_with_admonishment = 1; } # Or if any other error message has already been printed out, we # just leave now: if ($exit_with_admonishment) { &usage (); exit (1); } elsif ($Print_Usage) { &usage (); exit (0); } elsif ($Print_Version) { &version (); exit (0); } ## Else no problems, so proceed. if ($Output_To_Stdout) { undef $Log_File_Name; # not actually necessary } elsif ($output_file) { $Log_File_Name = $output_file; }}sub slurp_file (){ my $filename = shift || die ("no filename passed to slurp_file()"); my $retstr; open (SLURPEE, "<${filename}") or die ("unable to open $filename ($!)"); my $saved_sep = $/; undef $/; $retstr = <SLURPEE>; $/ = $saved_sep; close (SLURPEE); return $retstr;}sub debug (){ if ($Debug) { my $msg = shift; print STDERR $msg; }}sub version (){ print "cvs2cl.pl version ${VERSION}; distributed under the GNU GPL.\n";}sub usage (){ &version (); print <<'END_OF_INFO';Generate GNU-style ChangeLogs in CVS working copies.Notes about the output format(s): The default output of cvs2cl.pl is designed to be compact, formally unambiguous, but still easy for humans to read. It is largely self-explanatory, I hope; the one abbreviation that might not be obvious is "utags". That stands for "universal tags" -- a universal tag is one held by all the files in a given change entry. If you need output that's easy for a program to parse, use the --xml option. Note that with XML output, just about all available information is included with each change entry, whether you asked for it or not, on the theory that your parser can ignore anything it's not looking for.Notes about the options and arguments (the actual options are listedlast in this usage message): * The -I and -F options may appear multiple times. * To follow trunk revisions, use "-F trunk" ("-F TRUNK" also works). This is okay because no would ever, ever be crazy enough to name a branch "trunk", right? Right. * For the -U option, the UFILE should be formatted like CVSROOT/users. That is, each line of UFILE looks like this jrandom:jrandom@red-bean.com or maybe even like this jrandom:'Jesse Q. Random <jrandom@red-bean.com>' Don't forget to quote the portion after the colon if necessary. * Many people want to filter by date. To do so, invoke cvs2cl.pl like this: cvs2cl.pl -l "-d'DATESPEC'" where DATESPEC is any date specification valid for "cvs log -d". (Note that CVS 1.10.7 and below requires there be no space between -d and its argument).Options/Arguments: -h, -help, --help, or -? Show this usage and exit --version Show version and exit -r, --revisions Show revision numbers in output -b, --branches Show branch names in revisions when possible -t, --tags Show tags (symbolic names) in output --stdin Read from stdin, don't run cvs log --stdout Output to stdout not to ChangeLog -d, --distributed Put ChangeLogs in subdirs -f FILE, --file FILE Write to FILE instead of "ChangeLog" --fsf Use this if log data is in FSF ChangeLog style -W SECS, --window SECS Window of time within which log entries unify -U UFILE, --usermap UFILE Expand usernames to email addresses from UFILE -R REGEXP, --regexp REGEXP Include only entries that match REGEXP -I REGEXP, --ignore REGEXP Ignore files whose names match REGEXP -C, --case-insensitive Any regexp matching is done case-insensitively -F BRANCH, --follow BRANCH Show only revisions on or ancestral to BRANCH -S, --separate-header Blank line between each header and log message --no-wrap Don't auto-wrap log message (recommend -S also) --gmt, --utc Show times in GMT/UTC instead of local time -w, --day-of-week Show day of week --header FILE Get ChangeLog header from FILE ("-" means stdin) --xml Output XML instead of ChangeLog format --hide-filenames Don't show filenames (ignored for XML output) -P, --prune Don't show empty log messages -g OPTS, --global-opts OPTS Invoke like this "cvs OPTS log ..." -l OPTS, --log-opts OPTS Invoke like this "cvs ... log OPTS" FILE1 [FILE2 ...] Show only log information for the named FILE(s)See http://www.red-bean.com/cvs2cl for maintenance and bug info.END_OF_INFO}__END__=head1 NAMEcvs2cl.pl - produces GNU-style ChangeLogs in CVS working copies, by running "cvs log" and parsing the output. Shared log entries are unified in an intuitive way.=head1 DESCRIPTIONThis script generates GNU-style ChangeLog files from CVS loginformation. Basic usage: just run it inside a working copy and aChangeLog will appear. It requires repository access (i.e., 'cvs log'must work). Run "cvs2cl.pl --help" to see more advanced options.See http://www.red-bean.com/cvs2cl for updates, and for instructionson getting anonymous CVS access to this script.Maintainer: Karl Fogel <kfogel@red-bean.com>Please report bugs to <bug-cvs2cl@red-bean.com>.=head1 READMEThis script generates GNU-style ChangeLog files from CVS loginformation. Basic usage: just run it inside a working copy and aChangeLog will appear. It requires repository access (i.e., 'cvs log'must work). Run "cvs2cl.pl --help" to see more advanced options.See http://www.red-bean.com/cvs2cl for updates, and for instructionson getting anonymous CVS access to this script.Maintainer: Karl Fogel <kfogel@red-bean.com>Please report bugs to <bug-cvs2cl@red-bean.com>.=head1 PREREQUISITESThis script requires C<Text::Wrap>, C<Time::Local>, andC<File::Basename>.It also seems to require C<Perl 5.004_04> or higher.=pod OSNAMESany=pod SCRIPT CATEGORIESVersion_Control/CVS=cut-*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*-Note about a bug-slash-opportunity:-----------------------------------There's a bug in Text::Wrap, which affects cvs2cl. This scriptreveals it: #!/usr/bin/perl -w use Text::Wrap; my $test_text = "This script demonstrates a bug in Text::Wrap. The very long line following this paragraph will be relocated relative to the surrounding text: ==================================================================== See? When the bug happens, we'll get the line of equal signs below this paragraph, even though it should be above."; # Print out the test text with no wrapping: print "$test_text"; print "\n"; print "\n"; # Now print it out wrapped, and see the bug: print wrap ("\t", " ", "$test_text"); print "\n"; print "\n";If the line of equal signs were one shorter, then the bug doesn'thappen. Interesting.Anyway, rather than fix this in Text::Wrap, we might as well write anew wrap() which has the following much-needed features:* initial indentation, like current Text::Wrap()* subsequent line indentation, like current Text::Wrap()* user chooses among: force-break long words, leave them alone, or die()?* preserve existing indentation: chopped chunks from an indented line are indented by same (like this line, not counting the asterisk!)* optional list of things to preserve on line starts, default ">"Note that the last two are essentially the same concept, so unify inimplementation and give a good interface to controlling them.And how about:Optionally, when encounter a line pre-indented by same as previousline, then strip the newline and refill, but indent by the same.Yeah...
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -