Let Perl::Tidy be your first code reviewon every Subversion checkin! In a multideveloper project, relying on developers to follow the coding standards without fail and to run perltidy against all of their code ("Enforce Local Style" [Hack #7]) before every checkin is unrealistic, especially because this is tedious work. Fortunately, this is an automatable process. If you use Subversion (or Svk), it's easy to write a hook that checks code for tidiness, however you define it. The Hack
Within your Subversion repository, copy the hooks/post-commit.tmpl file to hooks/post-commitunless you already have the file. Remove all code that runs other commands (again, unless you're already using it). Add a single line: perl /usr/local/bin/check_tidy_file.pl "$REPOS" "$REV" Adjust the file path appropriately. Make the hooks/post-commit file executable with chmod +x on Unix. Finally, save the check_tidy_file.pl program to the path you used in the file. The program is: #!/usr/bin/perl use strict; use warnings; use Perl::Tidy; use File::Temp; use File::Spec::Functions; my $svnlook = '/usr/bin/svnlook'; my $diff = '/usr/bin/diff -u'; # eat the arguments so as not to confuse Perl::Tidy my ($repo, $rev) = @ARGV; @ARGV = ( ); my @diffs; for my $changed_file (get_changed_perl_files( $repo, $rev )) { my $source = get_revision( $repo, $rev, $changed_file ); Perl::Tidy::perltidy( source => \\$source, destination => \\(my $dest) ); push @diffs, get_diff( $changed_file, $source, $dest ); } report_diffs( @diffs ); sub get_changed_perl_files { my ($repo, $rev) = @_; my @files; for my $change (\Q$svnlook changed $repo -r $rev\Q) { my ($status, $file) = split( /\\s+/, $change ); next unless $file =~ /\\.p[lm]\\z/; push @files, $file; } return @files; } sub get_revision { my ($repo, $rev, $file) = @_; return scalar \Q$svnlook cat $repo -r $rev $file\Q; } sub get_diff { my $filename = shift; return if $_[0] eq $_[1]; my $dir = File::Temp::tempdir( ); my @files = map { catdir( $dir, $filename . $_ ) } qw( .orig .tidy ); for my $file (@files) { open( my $out, '>', $file ) or die "Couldn't write $file: $!\\n"; print $out shift; close $out; } return scalar \Q$diff @files\Q; } sub report_diffs { for my $diff (@_) { warn "Error:\\n$diff\\n"; } } When Subversion finishes committing a checkin to the repository, it calls the hooks/post-commit script, which itself launches other programs, passing the repository path and the number of the just-committed revision. This program uses the svnlook command to find the modified files, skipping everything that's not a Perl program or module (files ending in .pl or .pm). For each of these files, it grabs the entire contents from the just-completed revision and runs it through Perl::Tidy (the actual engine of the perltidy utility). If the resulting file is the same as the revision, everything is fine. Otherwise, it runs a diff utility to see the changes necessary to make the file tidy. From there, report_diffs( ) receives a list of these differences. Hacking the HackAs it is now, the program is only useful when run directly with the path to the repository and a revision number. It could instead write the differences to a file, automatically check in the revised versions in a new checkin, or e-mail the diffs to a list of programmers. To use a .perltidyrc file with the tidier program, add the perltidy => $rcfile_path arguments to the perltidy( ) call, where $rcfile_path contains the path to the .perltidyrc file to use. |