| 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\.(xx|yy|zz)$/> 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 |  | 69428 | use v5.14; | 
|  | 1 |  |  |  |  | 10 |  | 
| 137 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 41 |  | 
| 138 | 1 |  |  | 1 |  | 544 | use Hash::Util qw(lock_keys); | 
|  | 1 |  |  |  |  | 2945 |  | 
|  | 1 |  |  |  |  | 8 |  | 
| 139 | 1 |  |  | 1 |  | 746 | use Data::Dumper; | 
|  | 1 |  |  |  |  | 7021 |  | 
|  | 1 |  |  |  |  | 66 |  | 
| 140 | 1 |  |  | 1 |  | 375 | use App::Greple::Common; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 664 |  | 
| 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 | 2 | my $class = shift; | 
| 159 | 1 |  |  |  |  | 4 | my $obj = bless { include => [], | 
| 160 |  |  |  |  |  |  | exclude => [] }, $class; | 
| 161 | 1 |  |  |  |  | 9 | lock_keys %$obj; | 
| 162 | 1 |  |  |  |  | 16 | $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 |  | 8 | use List::Util qw(reduce); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 630 |  | 
| 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__ |