File Coverage

blib/lib/Test/File/Content.pm
Criterion Covered Total %
statement 69 69 100.0
branch 28 34 82.3
condition n/a
subroutine 14 14 100.0
pod 2 2 100.0
total 113 119 94.9


line stmt bran cond sub pod time code
1             #
2             # This file is part of Test-File-Content
3             #
4             # This software is Copyright (c) 2012 by Moritz Onken.
5             #
6             # This is free software, licensed under:
7             #
8             # The (three-clause) BSD License
9             #
10             package Test::File::Content;
11             {
12             $Test::File::Content::VERSION = '1.0.2';
13             }
14 1     1   544 use strict;
  1         2  
  1         31  
15 1     1   4 use warnings;
  1         2  
  1         28  
16             # ABSTRACT: Tests files for their content based on their file extension
17 1     1   1118 use Test::More ();
  1         25725  
  1         23  
18 1     1   1131 use Path::Class::File;
  1         63478  
  1         32  
19 1     1   10 use File::Find ();
  1         3  
  1         20  
20              
21 1     1   6 use Exporter qw(import);
  1         2  
  1         927  
22             our @EXPORT = qw(content_like content_unlike);
23              
24             sub _parse_args {
25 13     13   21 my $type = shift;
26 13         15 my $filter = shift;
27 13 100       44 if ( ref $filter eq 'HASH' ) {
    100          
28 1         8 foreach my $k ( sort keys %$filter ) {
29 3         223 _parse_args( $type, $k, $filter->{$k}, @_ );
30             }
31             } elsif ( ref $filter eq 'ARRAY' ) {
32 3         6 for (@$filter) {
33 4         11 _parse_args( $type, $_, @_ );
34             }
35             } else {
36 9 100       31 if ( ref $filter eq 'Regexp' ) {
    100          
37 3         6 my $copy = $filter;
38 3 50   57   14 $filter = sub { return 1 if -d $_[0]; $_[0] =~ $copy };
  57         366  
  57         1870  
39             } elsif ( !ref $filter ) {
40 5         6 my $copy = $filter;
41 5 50   83   22 $filter = sub { return 1 if -d $_[0]; $_[0] =~ /\.\Q$copy\E/ };
  83         1097  
  83         1939  
42             }
43 9         13 my $rules = shift;
44 9 100       21 if ( ref $rules eq 'HASH' ) {
45 1 50       26 $rules = {
46             map {
47 2         6 $_ => ( ref $rules->{$_} eq 'Regexp'
48             ? $rules->{$_}
49             : qr/\Q$rules->{$_}\E/sm )
50             } keys %$rules };
51             } else {
52 7 50       23 $rules = [$rules] unless ( ref $rules eq 'ARRAY' );
53 7 50       65 $rules =
54 7         14 { map { $_ => ( ref $_ eq 'Regexp' ? $_ : qr/\Q$_\E/sm ) }
55             @$rules };
56             }
57 9         26 _check_files( $type, $filter, $rules, @_ );
58             }
59             }
60              
61             sub content_like {
62 5     5 1 231 _parse_args( 'like', @_ );
63              
64             }
65              
66             sub content_unlike {
67 1     1 1 205688 _parse_args( 'unlike', @_ );
68              
69             }
70              
71             sub _check_files {
72 9     9   21 my ( $type, $filter, $rules, @dirs ) = @_;
73 9 50       20 @dirs = ('.') unless(@dirs);
74 9         11 my @files;
75 9 100   144   825 my $tree = File::Find::find( sub { push(@files, $File::Find::name) if($filter->($File::Find::name)) }, @dirs );
  144         417  
76 9         83 @files = sort @files;
77 9         26 while ( my $file = shift @files ) {
78 16 100       343 next if -d $file;
79 15         81 $file = Path::Class::File->new($file);
80 15         1576 my $content = $file->slurp;
81              
82 15         3904 my @failures;
83 15         61 while ( my ( $comment, $rule ) = each %$rules ) {
84 14 100       112 if ( $type eq 'unlike' ) {
    100          
85 2         14 while ( $content =~ /$rule/g ) {
86 3         10 my $message =
87             $comment
88             . " found in "
89             . $file
90             . ' line '
91             . _line_by_pos( $content, pos($content) );
92 3         24 push( @failures, $message );
93             }
94             } elsif ( $content !~ /$rule/g ) {
95 3         11 push( @failures,
96             'file ' . $file . ' does not contain ' . $comment );
97             }
98             }
99              
100 15         146 Test::More::ok( !@failures, $file );
101 15 100       10143 Test::More::diag( join( "\n", @failures ) ) if (@failures);
102             }
103             }
104              
105             sub _line_by_pos {
106 3     3   95 my ( $file, $pos ) = @_;
107 3         4 my $i = 1;
108 3         12 while ( $file =~ /\n/g ) {
109 11 100       16 last if ( pos($file) > $pos );
110 10         48 $i++;
111             }
112 3         8 return $i;
113             }
114              
115             1;
116              
117              
118              
119             =pod
120              
121             =head1 NAME
122              
123             Test::File::Content - Tests files for their content based on their file extension
124              
125             =head1 VERSION
126              
127             version 1.0.2
128              
129             =head1 SYNOPSIS
130              
131             use Test::File::Content;
132             use Test::More;
133            
134             content_like( qr/\.pm/, qr/^#\s*ABSTRACT/, 'lib' );
135            
136             content_like( pm => '__PACKAGE__->meta->make_immutable', 'lib/MooseClasses' );
137            
138             content_unlike({
139             js => {
140             'console.log debug statement' => 'console.log',
141             'never use alert' => qr/[^\.]alert\(/,
142             },
143             tt => [
144             qr/\[% DUMP/,
145             ],
146             pl => '\$foo',
147             }, qw(lib root/templates jslib));
148            
149             done_testing;
150              
151             Example output:
152              
153             not ok 1 - lib/MyLib.pm
154             # Failed test 'lib/MyLib.pm'
155             # file lib/MyLib.pm does not contain (?-xism:^#\s*ABSTRACT)
156             ok 2 - lib/MooseClasses/Class.pm
157             not ok 3 - jslib/test.js
158             # Failed test 'jslib/test.js'
159             # console.log debug statement found in jslib/test.js line 1
160             # console.log debug statement found in jslib/test.js line 2
161             ok 4 - root/templates/test.tt
162             1..4
163              
164             =head1 DESCRIPTION
165              
166             When writing code, I tend to add a lot of debug statements like C or C.
167             Occasionally I name my variables C<$foo> and C<$bar> which is also quite a bad coding style.
168             JavaScript files may contain C or C calls, which are equally bad.
169              
170             This test can help to find statements like these and ensure that other statements are there.
171              
172             =head1 FUNCTIONS
173              
174             The following functions are exported by default:
175              
176             =head2 content_like
177              
178             =head2 content_unlike
179              
180             B \%config, @directories
181              
182             B $filter, $rule, @directories
183              
184             C<%config> consists of key value pairs where each key is a file extension (e.g. C<.pm>) and the
185             value is a C<$rule>.
186              
187             C<$filter> can either be a string literal (like the key of C<%config>), an arrayref of extensions,
188             a regular expression or even a coderef. The coderef is passed the filename as argument and
189             is expected to return a true value if the file should be looked at.
190              
191             C<$rule> can be a string literal, an arrayref of rules or a regular expression.
192              
193             C<@directories> contains a list of directories or files to look at.
194              
195             =head1 AUTHOR
196              
197             Moritz Onken
198              
199             =head1 COPYRIGHT AND LICENSE
200              
201             This software is Copyright (c) 2012 by Moritz Onken.
202              
203             This is free software, licensed under:
204              
205             The (three-clause) BSD License
206              
207             =cut
208              
209              
210             __END__