File Coverage

blib/lib/App/File/Grepper.pm
Criterion Covered Total %
statement 15 73 20.5
branch 0 60 0.0
condition 0 12 0.0
subroutine 5 9 55.5
pod 0 1 0.0
total 20 155 12.9


line stmt bran cond sub pod time code
1             #! perl
2              
3             package App::File::Grepper;
4              
5 1     1   73512 use warnings;
  1         3  
  1         30  
6 1     1   4 use strict;
  1         2  
  1         36  
7              
8             =head1 NAME
9              
10             App::File::Grepper - Greps files for pattern
11              
12             =cut
13              
14             our $VERSION = '1.02';
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, as are common editor backups.
76              
77             =back
78              
79             =cut
80              
81 1     1   4 use File::Find;
  1         3  
  1         42  
82 1     1   1467 use Term::ANSIColor;
  1         6990  
  1         555  
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;^/(.*);
    0          
    0          
111             ? $ignorecase ? qr/$1/i : qr/$1/
112             : $ignorecase ? qr/\Q$pat\E/i : qr/\Q$pat\E/;
113 0 0         warn("PAT: $pat\n") if $opts->{debug};
114              
115             *hilite = ( !$edit && -t STDOUT )
116 0     0     ? sub { color('red bold').$_[0].color('reset') }
117 0 0 0 0     : sub { $_[0] };
  0            
118              
119 0           my $filter;
120 0 0         if ( defined $opts->{filter} ) {
121 0           $filter = $opts->{filter};
122 0           $filter = qr/$filter/;
123             }
124 0           my $exclude;
125 0 0         if ( defined $opts->{exclude} ) {
126 0           $exclude = $opts->{exclude};
127 0           $exclude = qr/$exclude/;
128             }
129             else {
130 0           $exclude = qr{ (?: ^\# | ^\.\.?(?!/) | ~$ ) }x;
131             }
132              
133 0           binmode( STDOUT, ":utf8" );
134              
135             my $grepper = sub {
136              
137             # Prune VC dirs. Always.
138 0 0 0 0     if ( -d $_ && $_ =~ /^(RCS|CVS|\.svn|\.git|\.hg)$/ ) {
139 0           $File::Find::prune = 1;
140 0           return;
141             }
142              
143             # Files only.
144 0 0         return unless -f $_;
145              
146             # Handle include/exclude filters.
147 0 0 0       if ( $exclude && ( $_ =~ $exclude ) ) {
148 0 0         warn("EXCL: $_\n") if $opts->{debug};
149 0           return;
150             }
151 0 0 0       if ( $filter && ( $_ !~ $filter ) ) {
152 0 0         warn("FLTR: $_\n") if $opts->{debug};
153 0           return;
154             }
155 0 0         warn("FILE: $_\n") if $opts->{debug};
156              
157 0           my $file = $_;
158              
159             # Okay, we've got one.
160 0 0         open( my $fh, '<', $file )
161             or warn("$File::Find::name: $!\n"), return;
162              
163 0 0         unless ( -T $fh ) {
164 0 0         warn("[Binary: $File::Find::name]\n") if $opts->{verbose};
165 0           return;
166             }
167              
168 0           binmode( $fh, 'raw' );
169              
170 1     1   1275 use Encode qw(decode);
  1         9324  
  1         306  
171              
172 0           while ( <$fh> ) {
173              
174 0 0         eval {
175 0           $_ = decode( 'UTF-8', $_, 1 );
176             }
177             or $_ = decode( 'iso-8859-1', $_ );
178              
179 0 0         next unless s/^(.*?)($pat)/"$1".hilite($2)/ge;
  0            
180              
181 0 0         if ( $edit eq 'vi') {
    0          
    0          
182 0 0         system( "vi",
183             $ignorecase ? ( "+set ignorecase" ) : (),
184             "+/$opat",
185             $file );
186             }
187             elsif ( $edit eq 'emacs') {
188 0           system( "emacsclient",
189             "+$.:" . (1+length($1)),
190             $file );
191             }
192             elsif ( $edit eq 'view') {
193 0 0         system( "less",
194             $ignorecase ? ( "-i" ) : (),
195             "+/$opat",
196             $file );
197             }
198 0 0         last if $edit;
199              
200 0           print( $File::Find::name, ':',
201             $., ':',
202             1+length($1), ':',
203             $_
204             );
205             }
206              
207 0           close($fh);
208 0           };
209              
210 0           find( { wanted => $grepper,
211             no_chdir => 1,
212             }, @dirs );
213             }
214              
215             =head1 AUTHOR
216              
217             Johan Vromans, C<< >>
218              
219             =head1 BUGS
220              
221             Please report any bugs or feature requests to C, or through
222             the web interface at L. I will be notified, and then you'll
223             automatically be notified of progress on your bug as I make changes.
224              
225             =head1 SUPPORT
226              
227             Development of this module takes place on GitHub:
228             L.
229              
230             You can find documentation for this module with the perldoc command.
231              
232             perldoc App::Packager
233              
234             Please report any bugs or feature requests using the issue tracker on
235             GitHub.
236              
237             =back
238              
239             =head1 ACKNOWLEDGEMENTS
240              
241             This program was inspired by C not having a B<-e> option.
242              
243             =head1 COPYRIGHT & LICENSE
244              
245             Copyright 2012,2016,2022 Johan Vromans, all rights reserved.
246              
247             This program is free software; you can redistribute it and/or modify it
248             under the same terms as Perl itself.
249              
250              
251             =cut
252              
253             1; # End of App::File::Grepper