File Coverage

blib/lib/App/File/Grepper.pm
Criterion Covered Total %
statement 15 66 22.7
branch 0 50 0.0
condition 0 12 0.0
subroutine 5 9 55.5
pod 0 1 0.0
total 20 138 14.4


line stmt bran cond sub pod time code
1             #! perl
2              
3             package App::File::Grepper;
4              
5 1     1   13559 use warnings;
  1         1  
  1         30  
6 1     1   3 use strict;
  1         1  
  1         36  
7              
8             =head1 NAME
9              
10             App::File::Grepper - Greps files for pattern
11              
12             =cut
13              
14             our $VERSION = '1.00';
15              
16             =head1 SYNOPSIS
17              
18             Runs a File::Find on the specified directories, and greps all text
19             files for a pattern.
20              
21             use App::File::Grepper;
22             App::File::Grepper->main( $options, @dirs );
23              
24             =head1 RATIONALE
25              
26             There are many tools that can do this, e.g. C. However none of
27             these can call an editor when a file matches the search argument and
28             that is something B often need.
29              
30             =head1 OPTIONS
31              
32             The first argument to the main() method is a reference to a hash with
33             options.
34              
35             =over 4
36              
37             =item pattern
38              
39             The pattern to grep. If it starts with a slash it is interpreted as a
40             perl pattern. Otherwise it is assumed to be a literal text to grep
41             for.
42              
43             If the text does not contain any uppercase letters, matching will be
44             done case-insensitive unless overridden by option ignorecase.
45              
46             =item ignorecase
47              
48             If defined, matching will be case-insensitive according to the value
49             of ignorecase.
50              
51             =item edit-with-emacs
52              
53             Pass each file where the pattern is found to the emacs editor client.
54              
55             =item edit-with-vi
56              
57             Pass each file where the pattern is found to the vi editor.
58              
59             =item view
60              
61             Pass each file where the pattern is found to the less viewer.
62              
63             =item filter
64              
65             A perl pattern to select which files must be processed. Note that this
66             pattern is applied to the basename of each file, not the full path.
67              
68             =item exclude
69              
70             A perl pattern to select which files must be rejected. Note that this
71             pattern is applied to the basename of each file, not the full path.
72             Also, this pattern is applied before the filter pattern.
73              
74             Version control directories C, C, C<.svn>, C<.git> and
75             C<.hg> are always excluded.
76              
77             =back
78              
79             =cut
80              
81 1     1   3 use File::Find;
  1         4  
  1         56  
82 1     1   633 use Term::ANSIColor;
  1         5139  
  1         366  
83              
84             sub main {
85              
86 0     0 0   my $self = shift;
87 0 0         unshift(@_, $self) unless UNIVERSAL::isa( $self, __PACKAGE__ );
88 0           my $opts = shift;
89 0           my @dirs = @_;
90              
91 0           my $pat = $opts->{pattern};
92 0           my $edit = "";
93 0 0         if ( $opts->{'edit-with-emacs'} ) {
    0          
    0          
94 0           $edit = 'emacs';
95             }
96             elsif ( $opts->{'edit-with-vi'} ) {
97 0           $edit = 'vi';
98             }
99             elsif ( $opts->{'view'} ) {
100 0           $edit = 'less';
101             }
102              
103             my $ignorecase =
104             defined($opts->{ignorecase})
105             ? $opts->{ignorecase}
106 0 0         : $pat !~ /[A-Z]/;
107              
108 0           my $opat = $pat;
109              
110 0 0         $pat = $pat =~ m;^/(.*); ? qr/$1/ :
    0          
111             $ignorecase ? qr/\Q$pat\E/i : qr/\Q$pat\E/;
112              
113             *hilite = ( !$edit && -t STDOUT )
114 0     0     ? sub { color('red bold').$_[0].color('reset') }
115 0 0 0 0     : sub { $_[0] };
  0            
116              
117 0           my $filter;
118 0 0         if ( defined $opts->{filter} ) {
119 0           $filter = $opts->{filter};
120 0           $filter = qr/$filter/;
121             }
122 0           my $exclude;
123 0 0         if ( defined $opts->{exclude} ) {
124 0           $exclude = $opts->{exclude};
125 0           $exclude = qr/$exclude/;
126             }
127              
128 0           binmode( STDOUT, ":utf8" );
129              
130             my $grepper = sub {
131              
132             # Prune VC dirs. Always.
133 0 0 0 0     if ( -d $_ && $_ =~ /^(RCS|CVS|\.svn|\.git|\.hg)$/ ) {
134 0           $File::Find::prune = 1;
135 0           return;
136             }
137              
138             # Files only.
139 0 0         return unless -f $_;
140              
141             # Handle include/exclude filters.
142 0 0 0       return if $exclude && ( $_ =~ $exclude );
143 0 0 0       return if $filter && ( $_ !~ $filter );
144              
145 0           my $file = $_;
146              
147             # Okay, we've got one.
148 0 0         open( my $fh, '<', $file )
149             or warn("$File::Find::name: $!\n"), return;
150              
151 0 0         unless ( -T $fh ) {
152 0 0         warn("[Binary: $File::Find::name]\n") if $opts->{verbose};
153 0           return;
154             }
155              
156 0           binmode( $fh, 'raw' );
157              
158 1     1   539 use Encode qw(decode);
  1         6866  
  1         286  
159              
160 0           while ( <$fh> ) {
161              
162 0 0         eval {
163 0           $_ = decode( 'UTF-8', $_, 1 );
164             }
165             or $_ = decode( 'iso-8859-1', $_ );
166              
167 0 0         next unless s/^(.*?)($pat)/"$1".hilite($2)/ge;
  0            
168              
169 0 0         if ( $edit eq 'vi') {
    0          
    0          
170 0 0         system( "vi",
171             $ignorecase ? ( "+set ignorecase" ) : (),
172             "+/$opat",
173             $file );
174             }
175             elsif ( $edit eq 'emacs') {
176 0           system( "emacsclient",
177             "+$.:" . (1+length($1)),
178             $file );
179             }
180             elsif ( $edit eq 'view') {
181 0 0         system( "less",
182             $ignorecase ? ( "-i" ) : (),
183             "+/$opat",
184             $file );
185             }
186 0 0         last if $edit;
187              
188 0           print( $File::Find::name, ':',
189             $., ':',
190             1+length($1), ':',
191             $_
192             );
193             }
194              
195 0           close($fh);
196 0           };
197              
198 0           find( { wanted => $grepper,
199             no_chdir => 1,
200             }, @dirs );
201             }
202              
203             =head1 AUTHOR
204              
205             Johan Vromans, C<< >>
206              
207             =head1 BUGS
208              
209             Please report any bugs or feature requests to C, or through
210             the web interface at L. I will be notified, and then you'll
211             automatically be notified of progress on your bug as I make changes.
212              
213             =head1 SUPPORT
214              
215             You can find documentation for this module with the perldoc command.
216              
217             perldoc App::File::Grepper
218              
219              
220             You can also look for information at:
221              
222             =over 4
223              
224             =item * RT: CPAN's request tracker
225              
226             L
227              
228             =item * CPAN Ratings
229              
230             L
231              
232             =item * Search CPAN
233              
234             L
235              
236             =back
237              
238             =head1 ACKNOWLEDGEMENTS
239              
240             This program was inspired by C not having a B<-e> option.
241              
242             =head1 COPYRIGHT & LICENSE
243              
244             Copyright 2012,2016 Johan Vromans, all rights reserved.
245              
246             This program is free software; you can redistribute it and/or modify it
247             under the same terms as Perl itself.
248              
249              
250             =cut
251              
252             1; # End of App::File::Grepper