| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Test::EOL; # git description: v2.01-2-g00e2666 | 
| 2 |  |  |  |  |  |  | # ABSTRACT: Check the correct line endings in your project | 
| 3 |  |  |  |  |  |  |  | 
| 4 | 3 |  |  | 3 |  | 93713 | use strict; | 
|  | 3 |  |  |  |  | 28 |  | 
|  | 3 |  |  |  |  | 86 |  | 
| 5 | 3 |  |  | 3 |  | 19 | use warnings; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 117 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | our $VERSION = '2.02'; | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 3 |  |  | 3 |  | 1364 | use Test::Builder; | 
|  | 3 |  |  |  |  | 121963 |  | 
|  | 3 |  |  |  |  | 93 |  | 
| 10 | 3 |  |  | 3 |  | 22 | use File::Spec; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 76 |  | 
| 11 | 3 |  |  | 3 |  | 16 | use File::Find; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 236 |  | 
| 12 | 3 |  |  | 3 |  | 20 | use Cwd qw/ cwd /; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 812 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | our $PERL    = $^X || 'perl'; | 
| 15 |  |  |  |  |  |  | our $UNTAINT_PATTERN  = qr|^([-+@\w./:\\]+)$|; | 
| 16 |  |  |  |  |  |  | our $PERL_PATTERN     = qr/^#!.*perl/; | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | my %file_find_arg = ($] <= 5.006) ? () : ( | 
| 19 |  |  |  |  |  |  | untaint => 1, | 
| 20 |  |  |  |  |  |  | untaint_pattern => $UNTAINT_PATTERN, | 
| 21 |  |  |  |  |  |  | untaint_skip => 1, | 
| 22 |  |  |  |  |  |  | ); | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | my $Test  = Test::Builder->new; | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | my $no_plan; | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | sub import { | 
| 29 | 3 |  |  | 3 |  | 25 | my $self   = shift; | 
| 30 | 3 |  |  |  |  | 6 | my $caller = caller; | 
| 31 |  |  |  |  |  |  | { | 
| 32 | 3 |  |  | 3 |  | 25 | no strict 'refs'; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 4881 |  | 
|  | 3 |  |  |  |  | 6 |  | 
| 33 | 3 |  |  |  |  | 5 | *{$caller.'::eol_unix_ok'} = \&eol_unix_ok; | 
|  | 3 |  |  |  |  | 17 |  | 
| 34 | 3 |  |  |  |  | 7 | *{$caller.'::all_perl_files_ok'} = \&all_perl_files_ok; | 
|  | 3 |  |  |  |  | 13 |  | 
| 35 |  |  |  |  |  |  | } | 
| 36 | 3 |  |  |  |  | 15 | $Test->exported_to($caller); | 
| 37 |  |  |  |  |  |  |  | 
| 38 | 3 | 50 | 33 |  |  | 37 | if ($_[0] && $_[0] eq 'no_plan') { | 
| 39 | 0 |  |  |  |  | 0 | shift; | 
| 40 | 0 |  |  |  |  | 0 | $no_plan = 1; | 
| 41 |  |  |  |  |  |  | } | 
| 42 | 3 |  |  |  |  | 16 | $Test->plan(@_); | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | sub _all_perl_files { | 
| 46 | 2 |  |  | 2 |  | 27 | my @all_files = _all_files(@_); | 
| 47 | 2 | 50 | 33 |  |  | 6 | return grep { _is_perl_module($_) || _is_perl_script($_) || _is_pod_file($_) } @all_files; | 
|  | 2 |  |  |  |  | 8 |  | 
| 48 |  |  |  |  |  |  | } | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | sub _all_files { | 
| 51 | 3 | 50 |  | 3 |  | 2535 | my @base_dirs = @_ ? @_ : cwd(); | 
| 52 | 3 | 100 |  |  |  | 20 | my $options = pop(@base_dirs) if ref $base_dirs[-1] eq 'HASH'; | 
| 53 | 3 |  |  |  |  | 15 | my @found; | 
| 54 |  |  |  |  |  |  | my $want_sub = sub { | 
| 55 | 27 |  |  | 27 |  | 248 | my @chunks = ('', File::Spec->splitdir($File::Find::name)); | 
| 56 |  |  |  |  |  |  |  | 
| 57 | 27 | 100 | 100 |  |  | 611 | return $File::Find::prune = 1 if -d $File::Find::name and | 
|  |  |  | 100 |  |  |  |  | 
| 58 |  |  |  |  |  |  | (  $chunks[-1] eq 'CVS'                                      # cvs | 
| 59 |  |  |  |  |  |  | or $chunks[-1] eq '.svn'                                    # subversion | 
| 60 |  |  |  |  |  |  | or ($chunks[-2] eq 'blib' and $chunks[-1] eq 'libdoc')      # pod doc | 
| 61 |  |  |  |  |  |  | or ($chunks[-2] eq 'blib' and $chunks[-1] =~ /^man[0-9]$/)  # pod doc | 
| 62 |  |  |  |  |  |  | or $chunks[-1] eq 'inc');                                   # Module::Install | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 22 | 100 |  |  |  | 201 | return if $chunks[-1] eq 'Build';   # autogenerated Build script | 
| 65 | 21 | 100 | 66 |  |  | 1260 | return unless (-f $File::Find::name && -r _); | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 9 |  |  |  |  | 23 | shift @chunks; | 
| 68 | 9 |  |  |  |  | 299 | push @found, File::Spec->catfile(@chunks); | 
| 69 | 3 |  |  |  |  | 38 | }; | 
| 70 | 3 |  |  |  |  | 55 | my $find_arg = { | 
| 71 |  |  |  |  |  |  | %file_find_arg, | 
| 72 |  |  |  |  |  |  | wanted   => $want_sub, | 
| 73 |  |  |  |  |  |  | no_chdir => 1, | 
| 74 |  |  |  |  |  |  | }; | 
| 75 | 3 |  |  |  |  | 530 | find( $find_arg, @base_dirs); | 
| 76 | 3 |  |  |  |  | 90 | return File::Spec->no_upwards(@found); | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | # Formats various human invisible symbols | 
| 80 |  |  |  |  |  |  | # to similar visible ones. | 
| 81 |  |  |  |  |  |  | # Perhaps ^M or something like that | 
| 82 |  |  |  |  |  |  | # would be more appropriate? | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | sub _show_whitespace { | 
| 85 | 0 |  |  | 0 |  | 0 | my $string = shift; | 
| 86 | 0 |  |  |  |  | 0 | $string =~ s/\r/[\\r]/g; | 
| 87 | 0 |  |  |  |  | 0 | $string =~ s/\t/[\\t]/g; | 
| 88 | 0 |  |  |  |  | 0 | $string =~ s/ /[\\s]/g; | 
| 89 | 0 |  |  |  |  | 0 | return $string; | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | # Format a line record for diagnostics. | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | sub _debug_line { | 
| 95 | 0 |  |  | 0 |  | 0 | my ( $options, $line ) = @_; | 
| 96 | 0 |  |  |  |  | 0 | $line->[2] =~ s/\n\z//g; | 
| 97 |  |  |  |  |  |  | return "line $line->[1]: $line->[0]" . ( | 
| 98 | 0 | 0 |  |  |  | 0 | $options->{show_lines} ? qq{: } . _show_whitespace( $line->[2] )  : q{} | 
| 99 |  |  |  |  |  |  | ); | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | sub eol_unix_ok { | 
| 103 | 7 |  |  | 7 | 1 | 1898 | my $file = shift; | 
| 104 | 7 |  |  |  |  | 10 | my $test_txt; | 
| 105 | 7 | 100 |  |  |  | 24 | $test_txt   = shift if !ref $_[0]; | 
| 106 | 7 |  | 66 |  |  | 47 | $test_txt ||= "No incorrect line endings in '$file'"; | 
| 107 | 7 | 100 |  |  |  | 21 | my $options = shift if ref $_[0] eq 'HASH'; | 
| 108 | 7 |  | 100 |  |  | 27 | $options ||= { | 
| 109 |  |  |  |  |  |  | trailing_whitespace => 0, | 
| 110 |  |  |  |  |  |  | all_reasons => 0, | 
| 111 |  |  |  |  |  |  | }; | 
| 112 | 7 |  |  |  |  | 19 | $file = _module_to_path($file); | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 7 | 50 |  |  |  | 253 | open my $fh, $file or do { $Test->ok(0, $test_txt); $Test->diag("Could not open $file: $!"); return; }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 115 |  |  |  |  |  |  | # Windows-- , default is :crlf, which hides \r\n  -_- | 
| 116 | 7 |  |  |  |  | 54 | binmode( $fh, ':raw' ); | 
| 117 | 7 |  |  |  |  | 13 | my $line = 0; | 
| 118 | 7 |  |  |  |  | 13 | my @fails; | 
| 119 | 7 |  |  |  |  | 103 | while (<$fh>) { | 
| 120 | 963 |  |  |  |  | 1080 | $line++; | 
| 121 | 963 | 50 | 66 |  |  | 2455 | if ( !$options->{trailing_whitespace} && /(\r+)$/ ) { | 
| 122 | 0 |  |  |  |  | 0 | my $match = $1; | 
| 123 | 0 |  |  |  |  | 0 | push @fails, [ _show_whitespace( $match ) , $line , $_ ]; | 
| 124 |  |  |  |  |  |  | } | 
| 125 | 963 | 50 | 66 |  |  | 1617 | if (  $options->{trailing_whitespace} && /([ \t]*\r+|[ \t]+)$/ ) { | 
| 126 | 0 |  |  |  |  | 0 | my $match = $1; | 
| 127 | 0 |  |  |  |  | 0 | push @fails, [ _show_whitespace($match), $line , $_ ]; | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  | # Minor short-circuit for people who don't need the whole file scanned | 
| 130 |  |  |  |  |  |  | # once there's an err. | 
| 131 | 963 | 50 | 33 |  |  | 2248 | last if( @fails > 0 && !$options->{all_reasons} ); | 
| 132 |  |  |  |  |  |  | } | 
| 133 | 7 | 50 |  |  |  | 24 | if( @fails ){ | 
| 134 | 0 |  |  |  |  | 0 | $Test->ok( 0, $test_txt . " on "  . _debug_line({ show_lines => 0 } , $fails[0]  )  ); | 
| 135 | 0 | 0 | 0 |  |  | 0 | if ( $options->{all_reasons} || 1 ){ | 
| 136 | 0 |  |  |  |  | 0 | $Test->diag( "  Problem Lines: "); | 
| 137 | 0 |  |  |  |  | 0 | for ( @fails ){ | 
| 138 | 0 |  |  |  |  | 0 | $Test->diag(_debug_line({ show_lines => 1 } , $_ ) ); | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  | } | 
| 141 | 0 |  |  |  |  | 0 | return 0; | 
| 142 |  |  |  |  |  |  | } | 
| 143 | 7 |  |  |  |  | 45 | $Test->ok(1, $test_txt); | 
| 144 | 7 |  |  |  |  | 2807 | return 1; | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  | sub all_perl_files_ok { | 
| 147 | 2 | 50 |  | 2 | 1 | 3880 | my $options = shift if ref $_[0] eq 'HASH'; | 
| 148 | 2 |  |  |  |  | 38 | my @files = _all_perl_files( @_ ); | 
| 149 | 2 |  |  |  |  | 12 | _make_plan(); | 
| 150 | 2 |  |  |  |  | 283 | foreach my $file ( @files ) { | 
| 151 | 2 |  |  |  |  | 8 | eol_unix_ok($file, $options); | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | sub _is_perl_module { | 
| 156 | 2 | 50 |  | 2 |  | 45 | $_[0] =~ /\.pm$/i || $_[0] =~ /::/; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | sub _is_pod_file { | 
| 160 | 0 |  |  | 0 |  | 0 | $_[0] =~ /\.pod$/i; | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | sub _is_perl_script { | 
| 164 | 0 |  |  | 0 |  | 0 | my $file = shift; | 
| 165 | 0 | 0 |  |  |  | 0 | return 1 if $file =~ /\.pl$/i; | 
| 166 | 0 | 0 |  |  |  | 0 | return 1 if $file =~ /\.t$/; | 
| 167 | 0 | 0 |  |  |  | 0 | open (my $fh, $file) or return; | 
| 168 | 0 |  |  |  |  | 0 | my $first = <$fh>; | 
| 169 | 0 | 0 | 0 |  |  | 0 | return 1 if defined $first && ($first =~ $PERL_PATTERN); | 
| 170 | 0 |  |  |  |  | 0 | return; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | sub _module_to_path { | 
| 174 | 7 |  |  | 7 |  | 12 | my $file = shift; | 
| 175 | 7 | 50 |  |  |  | 27 | return $file unless ($file =~ /::/); | 
| 176 | 0 |  |  |  |  | 0 | my @parts = split /::/, $file; | 
| 177 | 0 |  |  |  |  | 0 | my $module = File::Spec->catfile(@parts) . '.pm'; | 
| 178 | 0 |  |  |  |  | 0 | foreach my $dir (@INC) { | 
| 179 | 0 |  |  |  |  | 0 | my $candidate = File::Spec->catfile($dir, $module); | 
| 180 | 0 | 0 | 0 |  |  | 0 | next unless (-e $candidate && -f _ && -r _); | 
|  |  |  | 0 |  |  |  |  | 
| 181 | 0 |  |  |  |  | 0 | return $candidate; | 
| 182 |  |  |  |  |  |  | } | 
| 183 | 0 |  |  |  |  | 0 | return $file; | 
| 184 |  |  |  |  |  |  | } | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | sub _make_plan { | 
| 187 | 2 | 50 |  | 2 |  | 12 | return if $no_plan; | 
| 188 | 2 | 50 |  |  |  | 29 | unless ($Test->has_plan) { | 
| 189 | 2 |  |  |  |  | 832 | $Test->plan( 'no_plan' ); | 
| 190 |  |  |  |  |  |  | } | 
| 191 | 2 |  |  |  |  | 606 | $Test->expected_tests; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | sub _untaint { | 
| 195 | 0 |  |  | 0 |  |  | my @untainted = map { ($_ =~ $UNTAINT_PATTERN) } @_; | 
|  | 0 |  |  |  |  |  |  | 
| 196 | 0 | 0 |  |  |  |  | return wantarray ? @untainted : $untainted[0]; | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | 1; | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | __END__ |