File Coverage

blib/lib/App/Greple/select.pm
Criterion Covered Total %
statement 21 64 32.8
branch 0 22 0.0
condition n/a
subroutine 7 22 31.8
pod 0 8 0.0
total 28 116 24.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             select - Greple module to select files
4              
5             =head1 SYNOPSIS
6              
7             greple -Mdig -Mselect ... --dig .
8              
9             FILENAME
10             --suffix file suffixes
11             --select-name regex match for file name
12             --select-path regex match for file path
13             --x-suffix exclusive version of --suffix
14             --x-select-name exclusive version of --select-name
15             --x-select-path exclusive version of --select-path
16              
17             DATA
18             --shebang included in #! line
19             --select-data regex match for file data
20             --select-longer=# contain lines longer than #
21             --x-shebang exclusive version of --shebang
22             --x-select-data exclusive version of --select-data
23             --x-select-longer exclusive version of --select-longer
24              
25             =head1 DESCRIPTION
26              
27             Greple's B<-Mselect> module allows to filter files using their name
28             and content data. It is usually supposed to be used along with
29             B<-Mfind> or B<-Mdig> module.
30              
31             For example, next command scan files end with C<.pl> and C<.pm> under
32             current directory.
33              
34             greple -Mdig -Mselect --suffix=pl,pm foobar --dig .
35              
36             This is almost equivalent to the next command using B<--dig> option
37             with extra conditional expression for B command.
38              
39             greple -Mdig foobar --dig . -name '*.p[lm]'
40              
41             The problems is that the above command does not search perl command
42             script without suffixes. Next command looks for both files looking at
43             C<#!> (shebang) line.
44              
45             greple -Mdig -Mselect --suffix=pl,pm --shebang perl foobar --dig .
46              
47             Generic option B<--select-name>, B<--select-path> and B<--select-data>
48             take regular expression and works for arbitrary use.
49              
50             =head2 ORDER and DEFAULT
51              
52             Besides normal inclusive rules, there is exclusive rules which start
53             with B<--x-> option name.
54              
55             As for the order of rules, all exclusive rules are checked first, then
56             inclusive rules are applied.
57              
58             When no rules are matched, default action is taken. If no inclusive
59             rule exists, it is selected. Otherwise discarded.
60              
61             =head1 OPTIONS
62              
63             =head2 FILENAME
64              
65             =over 7
66              
67             =item B<--suffix>=I
68              
69             Specify one or more file name suffixes connecting by comma (C<,>).
70             They will be converted to C expression and compared
71             to the file name.
72              
73             =item B<--select-name>=I
74              
75             Specify regular expression and it is compared to the file name. Next
76             command search Makefiles under any directory.
77              
78             greple -Mselect --select-name '^Makefile.*'
79              
80             =item B<--select-path>=I
81              
82             Specify regular expression and it is compared to the file path.
83              
84             =item B<--x-suffix>=I
85              
86             =item B<--x-select-name>=I
87              
88             =item B<--x-select-path>=I
89              
90             These are reverse version of corresponding options. File is not
91             selected when matched.
92              
93             =back
94              
95             =head2 DATA
96              
97             =over 7
98              
99             =item B<--shebang>=I
100              
101             This option test if a given string is included in the first line of
102             the file start with C<#!> (aka shebang) mark. Multiple names can be
103             specified connecting by command (C<,>). Given string is converted to
104             the next regular expression:
105              
106             /\A #! .*\b (aa|bb|cc)/x
107              
108             =item B<--select-data>=I
109              
110             Specify regular expression and it is compared to the file content
111             data. Multi-line modifier is enabled by default.
112              
113             =item B<--x-shebang>=I
114              
115             =item B<--x-select-data>=I
116              
117             These are reverse version of corresponding options. File is not
118             selected when matched.
119              
120             =item B<--select-longer>=I
121              
122             =item B<--x-select-longer>=I
123              
124             Search or ignore files which contain lines longer than I.
125              
126             =back
127              
128             =head2 SEE ALSO
129              
130             L
131              
132             =cut
133              
134             package App::Greple::select;
135              
136 1     1   106730 use v5.14;
  1         12  
137 1     1   7 use warnings;
  1         1  
  1         30  
138 1     1   558 use Hash::Util qw(lock_keys);
  1         3028  
  1         6  
139 1     1   821 use Data::Dumper;
  1         7299  
  1         62  
140 1     1   405 use App::Greple::Common;
  1         3  
  1         710  
141              
142             our @select_shebang;
143             our @select_suffix;
144             our @select_name;
145             our @select_path;
146             our @select_data;
147              
148             our @discard_shebang;
149             our @discard_suffix;
150             our @discard_name;
151             our @discard_path;
152             our @discard_data;
153              
154             my %opt;
155             my $select = __PACKAGE__->new();
156              
157             sub new {
158 1     1 0 3 my $class = shift;
159 1         3 my $obj = bless { include => [],
160             exclude => [] }, $class;
161 1         9 lock_keys %$obj;
162 1         36 $obj;
163             }
164              
165 0     0 0   sub include { @{shift->{include}} }
  0            
166              
167 0     0 0   sub exclude { @{shift->{exclude}} }
  0            
168              
169             package #
170             FilterEnt
171             {
172             sub new {
173 0     0     my $class = shift;
174 0           bless [ @_ ], $class;
175             }
176 0     0     sub type { shift->[0] }
177 0     0     sub regex { shift->[1] }
178 0     0     sub action { shift->[2] }
179             }
180              
181             sub add {
182 0     0 0   my $obj = shift;
183 0           my($type, $regex, $action) = @_;
184 0 0         my $list = $action ? $obj->{include} : $obj->{exclude};
185 0           push @{$list}, FilterEnt->new($type, $regex, $action);
  0            
186             }
187              
188             sub check {
189 0     0 0   my $obj = shift;
190 0           my($path, $data) = @_;
191 0           my $name = $path =~ s{\A.*/}{}r;
192 0           for my $f ($obj->exclude, $obj->include) {
193 0           my $type = $f->type;
194             my $compare = { name => \$name ,
195             path => \$path ,
196 0 0         data => $data }->{$type} or die;
197 0 0         ${$compare} =~ $f->regex and return $f->action;
  0            
198             }
199 0 0         return $obj->include ? 0 : 1;
200             }
201              
202             ############################################################
203              
204             sub opt {
205 0     0 0   while (my($k, $v) = splice @_, 0, 2) {
206 0           $opt{$k} = $v;
207             }
208             }
209              
210 1     1   13 use List::Util qw(reduce);
  1         2  
  1         636  
211              
212             sub prologue {
213 0     0 0   for (
214 0     0     [ \@select_shebang, q/,/, data => 1, sub { qr/\A\#!.*\b\Q$_[0]\E/ } ],
215 0     0     [ \@discard_shebang, q/,/, data => 0, sub { qr/\A\#!.*\b\Q$_[0]\E/ } ],
216 0     0     [ \@select_suffix, q/,/, name => 1, sub { qr/\.\Q$_[0]\E$/ } ],
217 0     0     [ \@discard_suffix, q/,/, name => 0, sub { qr/\.\Q$_[0]\E$/ } ],
218             [ \@select_data, q//, data => 1 ],
219             [ \@discard_data, q//, data => 0 ],
220             [ \@select_name, q//, name => 1 ],
221             [ \@discard_name, q//, name => 0 ],
222             [ \@select_path, q//, path => 1 ],
223             [ \@discard_path, q//, path => 0 ],
224             ) {
225 0           my($list, $split, $type, $action, $re) = @$_;
226 0           do {
227 0           map { $select->add($type, $_, $action) }
228 0 0         map { $re ? $re->($_) : qr/$_/m }
229 0 0         map { $split ? split($split, $_) : $_ }
  0            
230             @$list;
231             };
232             }
233             }
234              
235             sub select {
236 0     0 0   my %arg = @_;
237 0 0         my $name = delete $arg{&FILELABEL} or die;
238 0 0         if ($select->check($name, *_)) {
239 0 0         say $name if $opt{yes};
240 0 0         $opt{die} and die "SKIP $name";
241             } else {
242 0 0         say $name if $opt{no};
243 0           die "SKIP $name";
244             }
245             }
246              
247             1;
248              
249             __DATA__