diff options
author | Florian Ragwitz <rafl@debian.org> | 2010-12-10 00:08:42 +0100 |
---|---|---|
committer | Florian Ragwitz <rafl@debian.org> | 2010-12-11 17:54:29 +0100 |
commit | d6f31ecf904f01f8a2aab2bca55e33b1b51e0b4d (patch) | |
tree | 79675cb0381bfd58dcee8d5e02b524ba01944b3b /dist | |
parent | 8172cd69ba298e1a50dd0f1f791202b524bc3a66 (diff) | |
download | perl-d6f31ecf904f01f8a2aab2bca55e33b1b51e0b4d.tar.gz |
Dual-life File::CheckTree
Diffstat (limited to 'dist')
-rw-r--r-- | dist/File-CheckTree/lib/File/CheckTree.pm | 238 | ||||
-rw-r--r-- | dist/File-CheckTree/t/CheckTree.t | 181 |
2 files changed, 419 insertions, 0 deletions
diff --git a/dist/File-CheckTree/lib/File/CheckTree.pm b/dist/File-CheckTree/lib/File/CheckTree.pm new file mode 100644 index 0000000000..bc072f4a84 --- /dev/null +++ b/dist/File-CheckTree/lib/File/CheckTree.pm @@ -0,0 +1,238 @@ +package File::CheckTree; + +use 5.006; +use Cwd; +use Exporter; +use File::Spec; +use warnings; +use strict; + +our $VERSION = '4.4'; +our @ISA = qw(Exporter); +our @EXPORT = qw(validate); + +=head1 NAME + +File::CheckTree - run many filetest checks on a tree + +=head1 SYNOPSIS + + use File::CheckTree; + + $num_warnings = validate( q{ + /vmunix -e || die + /boot -e || die + /bin cd + csh -ex + csh !-ug + sh -ex + sh !-ug + /usr -d || warn "What happened to $file?\n" + }); + +=head1 DESCRIPTION + +The validate() routine takes a single multiline string consisting of +directives, each containing a filename plus a file test to try on it. +(The file test may also be a "cd", causing subsequent relative filenames +to be interpreted relative to that directory.) After the file test +you may put C<|| die> to make it a fatal error if the file test fails. +The default is C<|| warn>. The file test may optionally have a "!' prepended +to test for the opposite condition. If you do a cd and then list some +relative filenames, you may want to indent them slightly for readability. +If you supply your own die() or warn() message, you can use $file to +interpolate the filename. + +Filetests may be bunched: "-rwx" tests for all of C<-r>, C<-w>, and C<-x>. +Only the first failed test of the bunch will produce a warning. + +The routine returns the number of warnings issued. + +=head1 AUTHOR + +File::CheckTree was derived from lib/validate.pl which was +written by Larry Wall. +Revised by Paul Grassie <F<grassie@perl.com>> in 2002. + +=head1 HISTORY + +File::CheckTree used to not display fatal error messages. +It used to count only those warnings produced by a generic C<|| warn> +(and not those in which the user supplied the message). In addition, +the validate() routine would leave the user program in whatever +directory was last entered through the use of "cd" directives. +These bugs were fixed during the development of perl 5.8. +The first fixed version of File::CheckTree was 4.2. + +=cut + +my $Warnings; + +sub validate { + my ($starting_dir, $file, $test, $cwd, $oldwarnings); + + $starting_dir = cwd; + + $cwd = ""; + $Warnings = 0; + + foreach my $check (split /\n/, $_[0]) { + my ($testlist, @testlist); + + # skip blanks/comments + next if $check =~ /^\s*#/ || $check =~ /^\s*$/; + + # Todo: + # should probably check for invalid directives and die + # but earlier versions of File::CheckTree did not do this either + + # split a line like "/foo -r || die" + # so that $file is "/foo", $test is "-r || die" + # (making special allowance for quoted filenames). + if ($check =~ m/^\s*"([^"]+)"\s+(.*?)\s*$/ or + $check =~ m/^\s*'([^']+)'\s+(.*?)\s*$/ or + $check =~ m/^\s*(\S+?)\s+(\S.*?)\s*$/) + { + ($file, $test) = ($1,$2); + } + else { + die "Malformed line: '$check'"; + }; + + # change a $test like "!-ug || die" to "!-Z || die", + # capturing the bundled tests (e.g. "ug") in $2 + if ($test =~ s/ ^ (!?-) (\w{2,}) \b /$1Z/x) { + $testlist = $2; + # split bundled tests, e.g. "ug" to 'u', 'g' + @testlist = split(//, $testlist); + } + else { + # put in placeholder Z for stand-alone test + @testlist = ('Z'); + } + + # will compare these two later to stop on 1st warning w/in a bundle + $oldwarnings = $Warnings; + + foreach my $one (@testlist) { + # examples of $test: "!-Z || die" or "-w || warn" + my $this = $test; + + # expand relative $file to full pathname if preceded by cd directive + $file = File::Spec->catfile($cwd, $file) + if $cwd && !File::Spec->file_name_is_absolute($file); + + # put filename in after the test operator + $this =~ s/(-\w\b)/$1 "\$file"/g; + + # change the "-Z" representing a bundle with the $one test + $this =~ s/-Z/-$one/; + + # if it's a "cd" directive... + if ($this =~ /^cd\b/) { + # add "|| die ..." + $this .= ' || die "cannot cd to $file\n"'; + # expand "cd" directive with directory name + $this =~ s/\bcd\b/chdir(\$cwd = '$file')/; + } + else { + # add "|| warn" as a default disposition + $this .= ' || warn' unless $this =~ /\|\|/; + + # change a generic ".. || die" or ".. || warn" + # to call valmess instead of die/warn directly + # valmess will look up the error message from %Val_Message + $this =~ s/ ^ ( (\S+) \s+ \S+ ) \s* \|\| \s* (die|warn) \s* $ + /$1 || valmess('$3', '$2', \$file)/x; + } + + { + # count warnings, either from valmess or '-r || warn "my msg"' + # also, call any pre-existing signal handler for __WARN__ + my $orig_sigwarn = $SIG{__WARN__}; + local $SIG{__WARN__} = sub { + ++$Warnings; + if ( $orig_sigwarn ) { + $orig_sigwarn->(@_); + } + else { + warn "@_"; + } + }; + + # do the test + eval $this; + + # re-raise an exception caused by a "... || die" test + if (my $err = $@) { + # in case of any cd directives, return from whence we came + if ($starting_dir ne cwd) { + chdir($starting_dir) || die "$starting_dir: $!"; + } + die $err; + } + } + + # stop on 1st warning within a bundle of tests + last if $Warnings > $oldwarnings; + } + } + + # in case of any cd directives, return from whence we came + if ($starting_dir ne cwd) { + chdir($starting_dir) || die "chdir $starting_dir: $!"; + } + + return $Warnings; +} + +my %Val_Message = ( + 'r' => "is not readable by uid $>.", + 'w' => "is not writable by uid $>.", + 'x' => "is not executable by uid $>.", + 'o' => "is not owned by uid $>.", + 'R' => "is not readable by you.", + 'W' => "is not writable by you.", + 'X' => "is not executable by you.", + 'O' => "is not owned by you.", + 'e' => "does not exist.", + 'z' => "does not have zero size.", + 's' => "does not have non-zero size.", + 'f' => "is not a plain file.", + 'd' => "is not a directory.", + 'l' => "is not a symbolic link.", + 'p' => "is not a named pipe (FIFO).", + 'S' => "is not a socket.", + 'b' => "is not a block special file.", + 'c' => "is not a character special file.", + 'u' => "does not have the setuid bit set.", + 'g' => "does not have the setgid bit set.", + 'k' => "does not have the sticky bit set.", + 'T' => "is not a text file.", + 'B' => "is not a binary file." +); + +sub valmess { + my ($disposition, $test, $file) = @_; + my $ferror; + + if ($test =~ / ^ (!?) -(\w) \s* $ /x) { + my ($neg, $ftype) = ($1, $2); + + $ferror = "$file $Val_Message{$ftype}"; + + if ($neg eq '!') { + $ferror =~ s/ is not / should not be / || + $ferror =~ s/ does not / should not / || + $ferror =~ s/ not / /; + } + } + else { + $ferror = "Can't do $test $file.\n"; + } + + die "$ferror\n" if $disposition eq 'die'; + warn "$ferror\n"; +} + +1; diff --git a/dist/File-CheckTree/t/CheckTree.t b/dist/File-CheckTree/t/CheckTree.t new file mode 100644 index 0000000000..d12d60cb02 --- /dev/null +++ b/dist/File-CheckTree/t/CheckTree.t @@ -0,0 +1,181 @@ +#!./perl -w + +use Test::More tests => 23; + +use strict; + +require overload; + +use File::CheckTree; +use File::Spec; # used to get absolute paths + +# We assume that we start from the dist/File-CheckTree in the perl repository, +# or the dist root directory for the CPAN version. + + +#### TEST 1 -- No warnings #### +# usings both relative and full paths, indented comments + +{ + my ($num_warnings, $path_to_libFileCheckTree); + $path_to_libFileCheckTree = File::Spec->rel2abs( + File::Spec->catfile('lib', 'File', 'CheckTree.pm'), + ); + + my @warnings; + local $SIG{__WARN__} = sub { push @warnings, "@_" }; + + eval { + $num_warnings = validate qq{ + lib -d +# comment, followed "blank" line (w/ whitespace): + + # indented comment, followed blank line (w/o whitespace): + + lib/File/CheckTree.pm -f + '$path_to_libFileCheckTree' -e || warn + }; + }; + + diag($_) for @warnings; + is( $@, '' ); + is( scalar @warnings, 0 ); + is( $num_warnings, 0 ); +} + + +#### TEST 2 -- One warning #### + +{ + my ($num_warnings, @warnings); + + local $SIG{__WARN__} = sub { push @warnings, "@_" }; + + eval { + $num_warnings = validate qq{ + lib -f + lib/File/CheckTree.pm -f + }; + }; + + is( $@, '' ); + is( scalar @warnings, 1 ); + like( $warnings[0], qr/lib is not a plain file/); + is( $num_warnings, 1 ); +} + + +#### TEST 3 -- Multiple warnings #### +# including first warning only from a bundle of tests, +# generic "|| warn", default "|| warn" and "|| warn '...' " + +{ + my ($num_warnings, @warnings); + + local $SIG{__WARN__} = sub { push @warnings, "@_" }; + + eval { + $num_warnings = validate q{ + lib -effd + lib/File/CheckTree.pm -f || die + lib/File/CheckTree.pm -d || warn + lib -f || warn "my warning: $file\n" + }; + }; + + is( $@, '' ); + is( scalar @warnings, 3 ); + like( $warnings[0], qr/lib is not a plain file/); + like( $warnings[1], qr{lib/File/CheckTree.pm is not a directory}); + like( $warnings[2], qr/my warning: lib/); + is( $num_warnings, 3 ); +} + + +#### TEST 4 -- cd directive #### +# cd directive followed by relative paths, followed by full paths +{ + my ($num_warnings, @warnings, $path_to_lib, $path_to_dist); + $path_to_lib = File::Spec->rel2abs(File::Spec->catdir('lib')); + $path_to_dist = File::Spec->rel2abs(File::Spec->curdir); + + local $SIG{__WARN__} = sub { push @warnings, "@_" }; + + eval { + $num_warnings = validate qq{ + lib -d || die + '$path_to_lib' cd + File -e + File -f + '$path_to_dist' cd + lib/File/CheckTree.pm -ef + lib/File/CheckTree.pm -d || warn + '$path_to_lib' -d || die + }; + }; + + is( $@, '' ); + is( scalar @warnings, 2 ); + like( $warnings[0], qr/File is not a plain file/); + like( $warnings[1], qr/CheckTree\.pm is not a directory/); + is( $num_warnings, 2 ); +} + + +#### TEST 5 -- Exception #### +# test with generic "|| die" +{ + my $num_warnings; + + eval { + $num_warnings = validate q{ + lib -ef || die + lib/File/CheckTree.pm -d + }; + }; + + like($@, qr/lib is not a plain file/); +} + + +#### TEST 6 -- Exception #### +# test with "|| die 'my error message'" +{ + my $num_warnings; + + eval { + $num_warnings = validate q{ + lib -ef || die "yadda $file yadda...\n" + lib/File/CheckTree.pm -d + }; + }; + + like($@, qr/yadda lib yadda/); + is( $num_warnings, undef ); +} + +#### TEST 7 -- Quoted file names #### +{ + my $num_warnings; + eval { + $num_warnings = validate q{ + "a file with whitespace" !-ef + 'a file with whitespace' !-ef + }; + }; + + is ( $@, '', 'No errors mean we compile correctly'); +} + +#### TEST 8 -- Malformed query #### +{ + my $num_warnings; + eval { + $num_warnings = validate q{ + a file with whitespace !-ef + }; + }; + + like( $@, qr/syntax error/, + 'We got a syntax error for a malformed file query' ); +} |