File Coverage

/usr/local/bin/greple
Criterion Covered Total %
statement 384 708 54.2
branch 103 400 25.7
condition 34 152 22.3
subroutine 50 91 54.9
pod n/a
total 571 1351 42.2


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             ##
3             ## greple: extensible grep with lexical expression and region handling
4             ##
5             ## Since Mar 29 1991
6             ##
7              
8 17     17   113470 use v5.24;
  17         66  
9 17     17   103 use warnings;
  17         27  
  17         1098  
10              
11 17     17   9156 use File::stat;
  17         199014  
  17         1413  
12 17     17   12128 use IO::Handle;
  17         133958  
  17         1113  
13 17     17   10939 use Pod::Usage;
  17         1389673  
  17         2672  
14 17     17   11003 use Text::ParseWords qw(shellwords);
  17         29913  
  17         1406  
15 17     17   119 use List::Util qw(min max first sum uniq shuffle notall pairs pairmap);
  17         27  
  17         2615  
16 17     17   10116 use Hash::Util qw(lock_keys);
  17         64393  
  17         115  
17 17     17   1969 use Cwd qw(getcwd abs_path);
  17         26  
  17         867  
18 17     17   74 use Carp;
  17         24  
  17         789  
19              
20 17     17   8851 use utf8;
  17         5068  
  17         129  
21 17     17   578 use Encode;
  17         27  
  17         2014  
22 17     17   8474 use Encode::Guess;
  17         76476  
  17         72  
23 17     17   9595 use open IO => ':utf8', ':std';
  17         24261  
  17         114  
24              
25 17     17   12474 use Data::Dumper;
  17         158880  
  17         1810  
26             {
27 17     17   3801892 no warnings 'redefine';
  17         148  
  17         25  
  17         4885  
28 17     0   209 *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
  0         0  
  0         0  
29 17         60 $Data::Dumper::Useperl = 1;
30             }
31              
32             ##
33             ## Setup greple/lib to be a module directory if exists.
34             ##
35             BEGIN {
36 17 50   17   1252 if (my $lib = abs_path($0) =~ s{/(?:script/|bin/)?\w+$}{/lib}r) {
37 17 50       772 unshift @INC, $lib if -d "$lib/App/Greple";
38             }
39             }
40              
41 17     17   10110 use Getopt::EX::Loader;
  17         1537312  
  17         1268  
42 17     17   224 use Getopt::EX::Func qw(parse_func callable);
  17         36  
  17         1088  
43              
44 17     17   8596 use App::Greple;
  17         2518  
  17         736  
45 17     17   7880 use App::Greple::Common;
  17         8180  
  17         1148  
46 17     17   8312 use App::Greple::Util qw(shellquote);
  17         22681  
  17         1227  
47 17     17   12746 use App::Greple::Grep;
  17         306098  
  17         1805  
48 17     17   174 use App::Greple::Regions;
  17         40  
  17         2106  
49 17     17   109 use App::Greple::Pattern;
  17         33  
  17         1204  
50 17     17   11274 use App::Greple::Pattern::Holder;
  17         81748  
  17         1226  
51 17     17   9786 use App::Greple::Filter;
  17         27863  
  17         118762  
52              
53 17         62 my $version = $App::Greple::VERSION;
54              
55             =encoding utf8
56              
57             =head1 NAME
58              
59              
60             greple - extensible grep with lexical expression and region control
61              
62              
63             =head1 VERSION
64              
65              
66             Version 10.04
67              
68              
69             =head1 SYNOPSIS
70              
71              
72             B [B<-M>I] [ B<-options> ] pattern [ file... ]
73              
74             PATTERN
75             pattern 'and +must -not ?optional &function'
76             -x, --le pattern lexical expression (same as bare pattern)
77             -e, --and pattern pattern match across line boundary
78             -r, --must pattern pattern cannot be compromised
79             -t, --may pattern pattern may exist
80             -v, --not pattern pattern not to be matched
81             -E, --re pattern regular expression
82             --fe pattern fixed expression
83             -f, --file file file contains search pattern
84             --select index select indexed pattern from -f file
85             MATCH
86             -i, --ignore-case ignore case
87             -G, --capture-group match capture groups rather than the whole pattern
88             -S, --stretch stretch the matched area to the enclosing block
89             --need=[+-]n required positive match count
90             --allow=[+-]n acceptable negative match count
91             --matchcount=n[,m] required match count for each block
92             STYLE
93             -l list filename only
94             -c print count of matched block only
95             -n print line number
96             -b print block number
97             -H, -h do or do not display filenames
98             -o print only the matching part
99             --all print entire data
100             -F, --filter use as a filter (implies --all --need=0 --exit=0)
101             -m, --max=n[,m] max count of blocks to be shown
102             -A,-B,-C [n] after/before/both match context
103             --join remove newline in the matched part
104             --joinby=string replace newline in the matched text with a string
105             --nonewline do not add newline character at the end of block
106             --filestyle=style how filenames are printed (once, separate, line)
107             --linestyle=style how line numbers are printed (separate, line)
108             --blockstyle=style how block numbers are printed (separate, line)
109             --separate set filestyle, linestyle, blockstyle "separate"
110             --format LABEL=... define the format for line number and file name
111             --frame-top top frame line
112             --frame-middle middle frame line
113             --frame-bottom bottom frame line
114             FILE
115             --glob=glob glob target files
116             --chdir=dir change directory before search
117             --readlist get filenames from stdin
118             COLOR
119             --color=when use terminal colors (auto, always, never)
120             --nocolor same as --color=never
121             --colormap=color R, G, B, C, M, Y, etc.
122             --colorsub=... shortcut for --colormap="sub{...}"
123             --colorful use default multiple colors
124             --colorindex=flags color index method: Ascend/Descend/Block/Random/Unique/Group/GP
125             --random use a random color each time (--colorindex=R)
126             --uniqcolor use a different color for each unique string (--colorindex=U)
127             --uniqsub=func preprocess function to check uniqueness
128             --ansicolor=s ANSI color 16, 256 or 24bit
129             --[no]256 same as --ansicolor 256 or 16
130             --regioncolor use different color for inside and outside regions
131             --face enable or disable visual effects
132             BLOCK
133             -p, --paragraph enable paragraph mode
134             --border=pattern specify a border pattern
135             --block=pattern specify a block of records
136             --blockend=s block-end mark (Default: "--")
137             --join-blocks join consecutive blocks that are back-to-back
138             REGION
139             --inside=pattern select matches inside of pattern
140             --outside=pattern select matches outside of pattern
141             --include=pattern limit matches to the area
142             --exclude=pattern limit matches to outside of the area
143             --strict enable strict mode for --inside/outside --block
144             CHARACTER CODE
145             --icode=name input file encoding
146             --ocode=name output file encoding
147             FILTER
148             --if,--of=filter input/output filter command
149             --pf=filter post-process filter command
150             --noif disable the default input filter
151             RUNTIME FUNCTION
152             --begin=func call a function before starting the search
153             --end=func call a function after completing the search
154             --prologue=func call a function before executing the command
155             --epilogue=func call a function after executing the command
156             --postgrep=func call a function after each grep operation
157             --callback=func callback function for each matched string
158             OTHER
159             --usage[=expand] show this help message
160             --version show version
161             --exit=n set the command exit status
162             --norc skip reading startup file
163             --man display the manual page for the command or module
164             --show display the module file contents
165             --path display the path to the module file
166             --error=action action to take after a read error occurs
167             --warn=type runtime error handling type
168             --alert [name=#] set alert parameters (size/time)
169             -d flags display info (f:file d:dir c:color m:misc s:stat)
170              
171             =cut
172              
173 17         87 my @baseclass = qw( App::Greple Getopt::EX );
174 17         311 my $rcloader = Getopt::EX::Loader
175             ->new(BASECLASS => \@baseclass);
176              
177 17         1287 my @optargs;
178             my %optargs;
179              
180             sub newopt {
181             push @optargs, pairmap {
182 1547     1547   2065 local $_ = $a;
183 1547         5565 s/\s+//g;
184 1547         3070 s/^(?=\w+-)([-\w]+)/$1 =~ tr[-][_]r . "|$1"/e; # "a-b" -> "a_b|a-b"
  238         859  
185 1547 100 50     6024 /^(\w+)/ and $optargs{$1} = $b if ref $b ne 'CODE';
186 1547         2865 $_ => $b;
187 17     17   269 } @_;
188             }
189              
190             sub opt :lvalue {
191 0     0   0 my $name = shift;
192 0 0       0 my $var = $optargs{$name} or die "$name: invalid option name\n";
193 0 0 0     0 if (ref $var eq 'SCALAR') {
    0          
194 0         0 return $$var;
195             } elsif (ref $var eq 'HASH' and @_ == 1) {
196 0         0 return $var->{+shift};
197             } else {
198 0         0 return $var;
199             }
200             }
201              
202 17         0 my @opt_pattern;
203             sub opt_pattern {
204 0     0   0 push @opt_pattern, [ map "$_", @_ ];
205 0         0 $opt_pattern[-1];
206             }
207              
208 17         0 my @opt_colormap;
209 0     0   0 sub opt_colormap { push @opt_colormap, $_[1] }
210 0     0   0 sub opt_colorsub { push @opt_colormap, "sub{ $_[1] }" }
211              
212 17         148 my %opt_format = (LINE => '%d:', FILE => '%s:', BLOCK => '%s:');
213 17         108 my %opt_alert = (size => 512 * 1024, time => 2);
214 17         115 my %opt_warn = (read => 0, skip => 1, retry => 0, begin => 0);
215              
216             newopt
217              
218             ##
219             ## PATTERN
220             ##
221             ' and |e =s ' => \&opt_pattern ,
222             ' must |r =s ' => \&opt_pattern ,
223             ' may |t =s ' => \&opt_pattern ,
224             ' not |v =s ' => \&opt_pattern ,
225             ' le |x =s ' => \&opt_pattern ,
226             ' re |E =s ' => \&opt_pattern ,
227             ' fe =s ' => \&opt_pattern ,
228             ' file |f =s ' => \ my @opt_f ,
229             ' select =s ' => \ my $opt_select ,
230              
231             ##
232             ## MATCH
233             ##
234             ' ignore-case |i ! ' => \ my $opt_i ,
235             ' need =s ' => \ my @opt_need ,
236             ' allow =s ' => \ my @opt_allow ,
237             ' matchcount |mc =s ' => \ my $opt_matchcount ,
238             ' capture-group |G ! ' => \ my $opt_capture_group ,
239             ' stretch |S ! ' => \ my $opt_stretch ,
240              
241             ##
242             ## STYLE
243             ##
244             ' files-with-matches |l ' => \ my $opt_l ,
245             ' count |c ' => \ my $opt_c ,
246             ' line-number |n ! ' => \ my $opt_n ,
247             ' block-number |b ! ' => \ my $opt_b ,
248             ' filename |H ' => \ my $opt_H ,
249             ' no-filename |h ' => \ my $opt_h ,
250             ' only-matching |o ! ' => \ my $opt_o ,
251             ' all ! ' => \ my $opt_all ,
252             ' filter |F ! ' => \ my $opt_filter ,
253             ' max-count |m =s ' => \ my $opt_m ,
254             ' after-context |A :2 ' => \(my $opt_A = 0) ,
255             ' before-context |B :2 ' => \(my $opt_B = 0) ,
256             ' context |C :2 ' => \(my $opt_C = 0) ,
257             ' join ! ' => \ my $opt_join ,
258             ' joinby =s ' => \(my $opt_joinby = "") ,
259             ' newline ! ' => \(my $opt_newline = 1) ,
260             ' filestyle |fs =s ' => \(my $opt_filestyle = 'line') ,
261             ' linestyle |ls =s ' => \(my $opt_linestyle = 'line') ,
262             ' blockstyle |bs =s ' => \(my $opt_blockstyle = 'line') ,
263             ' separate ' => sub {
264 0     0   0 opt('filestyle') = opt('linestyle') = opt('blockstyle') = $_[0];
265             },
266             ' format =s ' => \ %opt_format ,
267             ' frame-top :s ' => \(my $opt_frame_top = '') ,
268             ' frame-middle :s ' => \(my $opt_frame_middle = '') ,
269             ' frame-bottom :s ' => \(my $opt_frame_bottom = '') ,
270              
271             ##
272             ## FILE
273             ##
274             ' glob =s ' => \ my @opt_glob ,
275             ' chdir =s ' => \ my @opt_chdir ,
276             ' readlist ! ' => \ my $opt_readlist ,
277              
278             ##
279             ## COLOR
280             ##
281             ' color =s ' => \(my $opt_color = 'auto') ,
282             ' colormap |cm =s ' => \&opt_colormap ,
283             ' colorsub |cs =s ' => \&opt_colorsub ,
284             ' colorful ! ' => \(my $opt_colorful = 1) ,
285             ' colorindex |ci =s ' => \(my $opt_colorindex = '') ,
286             ' ansicolor =s ' => \(my $opt_ansicolor = '256') ,
287             ' regioncolor |rc ! ' => \ my $opt_regioncolor ,
288             ' uniqsub |us =s ' => \ my @opt_uniqsub ,
289             ' face =s ' => \ my @opt_face ,
290             ' nocolor | no-color ' => sub {
291 0     0   0 opt('color') = 'never';
292             },
293             ' 256! ' => sub {
294 0 0   0   0 opt('ansicolor') = $_[1] ? '256' : '16';
295             },
296             ' random! ' => sub {
297 0 0   0   0 if ($_[1]) { opt('colorindex') .= 'R' }
  0         0  
298 0         0 else { opt('colorindex') =~ s/R//gi }
299             },
300             ' uniqcolor |uc ' => sub {
301 0     0   0 opt('colorindex') = 'U';
302             },
303              
304             ##
305             ## BLOCK
306             ##
307             ' paragraph |p ! ' => \ my $opt_p ,
308             ' border =s ' => \ my $opt_border ,
309             ' block =s ' => \ my @opt_block ,
310             ' blockend :s ' => \(my $opt_blockend) ,
311             ' join-blocks ! ' => \(my $opt_join_blocks = 0) ,
312              
313             ##
314             ## REGION
315             ##
316             ' inside =s ' => \ my @opt_inside ,
317             ' outside =s ' => \ my @opt_outside ,
318             ' include =s ' => \ my @opt_include ,
319             ' exclude =s ' => \ my @opt_exclude ,
320             ' strict ! ' => \(my $opt_strict = 0) ,
321              
322             ##
323             ## CHARACTER CODE
324             ##
325             ' icode =s ' => \ my @opt_icode ,
326             ' ocode =s ' => \ my $opt_ocode ,
327              
328             ##
329             ## FILTER
330             ##
331             ' if =s ' => \ my @opt_if ,
332             ' of =s ' => \ my @opt_of ,
333             ' pf =s ' => \ my @opt_pf ,
334             ' noif ' => \ my $opt_noif ,
335              
336             ##
337             ## RUNTIME FUNCTION
338             ##
339             ' print =s ' => \ my @opt_print ,
340             ' continue ! ' => \ my $opt_continue ,
341             ' callback =s ' => \ my @opt_callback ,
342             ' begin =s ' => \ my @opt_begin ,
343             ' end =s ' => \ my @opt_end ,
344             ' prologue =s ' => \ my @opt_prologue ,
345             ' epilogue =s ' => \ my @opt_epilogue ,
346             ' postgrep =s ' => \ my @opt_postgrep ,
347              
348             ##
349             ## OTHERS
350             ##
351             ' usage :s ' => \ my $opt_usage ,
352             ' version ' => \ my $opt_version ,
353             ' exit =i ' => \ my $opt_exit ,
354             # norc
355             ' man | doc ' => \ my $opt_man ,
356             ' show | less ' => \ my $opt_show ,
357             ' path ' => \ my $opt_path ,
358             ' error =s ' => \(my $opt_error = 'skip') ,
359             ' alert =i ' => \ %opt_alert ,
360             ' debug |d =s ' => \ my @opt_d ,
361             'warn|w:1%' => sub {
362 0         0 map { $opt_warn{$_} = $_[2] }
363 0 0   0   0 map { $_ eq 'all' ? keys %opt_warn : $_ }
  0         0  
364             $_[1] =~ /\w+/g;
365             },
366              
367             ##
368             ## MODULE
369             ##
370             'M:s' => sub {
371 0     0   0 warn "Use -M option at the beginning with module name.\n";
372 0 0       0 if (my @modules = uniq($rcloader->modules())) {
373 0         0 warn "Available modules:\n";
374 0         0 warn "\t", join("\n\t", @modules), "\n";
375             }
376 0         0 exit 2;
377             },
378              
379             ##
380             ## UNDOCUMENTED
381             ##
382 17         3359 ' clean ! ' => \ my $opt_clean ,
383              
384             ;
385              
386             sub setopt {
387 0 0   0   0 my $opt = ref $_[0] eq 'HASH' ? shift : {};
388 0         0 my $name = shift;
389 0 0       0 if (exists $optargs{$name}) {
390 0         0 my $ref = $optargs{$name};
391 0 0       0 if (ref $ref eq 'ARRAY') {
    0          
    0          
392 0 0       0 if ($opt->{append}) {
393 0         0 push @$ref, @_;
394             } else {
395 0         0 @$ref = @_;
396             }
397             }
398             elsif (ref $ref eq 'CODE') {
399 0         0 &$ref($name, @_);
400             }
401             elsif (ref $ref eq 'SCALAR') {
402 0         0 $$ref = shift;
403             }
404             else {
405 0         0 die "Object error.";
406             }
407             }
408             }
409              
410             ##
411             ## @ARGV stuff
412             ##
413              
414 17         15769 require Getopt::Long;
415 17         241078 my $parser = Getopt::Long::Parser->new(
416             config => [ qw(bundling no_getopt_compat no_ignore_case) ],
417             );
418 0     0   0 sub configure_getopt { $parser->configure(@_) }
419              
420 17 50       23783 configure_getopt qw(debug) if $ENV{DEBUG_GETOPT};
421 17 50       93 $Getopt::EX::Loader::debug = 1 if $ENV{DEBUG_GETOPTEX};
422              
423             ## decode
424             my @ORIG_ARGV =
425 17 50       64 @ARGV = map { utf8::is_utf8($_) ? $_ : decode('utf8', $_) } @ARGV;
  155         3212  
426              
427             ## ~/.greplerc
428 17 50 33     621 unless ((@ARGV and $ARGV[0] eq "--norc" and shift)
      33        
      33        
429             or
430             ($ENV{GREPLE_NORC}) ) {
431 17         198 $rcloader->load(FILE => "$ENV{HOME}/.greplerc");
432             }
433              
434             ## modules
435 17         691 $rcloader->deal_with(\@ARGV);
436              
437 17         12153 push @optargs, $rcloader->builtins;
438              
439             ## ENV
440 17 50       482 $ENV{'GREPLEOPTS'} and unshift @ARGV, shellwords($ENV{'GREPLEOPTS'});
441              
442              
443             ## GetOptions
444 17         90 my @SAVEDARGV = @ARGV;
445 17 50       246 $parser->getoptions(@optargs) || usage();
446              
447 17 50       171176 if ($opt_version) {
448 0         0 print "$version\n";
449 0         0 exit 0;
450             }
451              
452 17         39 our %opt_d;
453 17         55 @opt_d = map { split // } @opt_d;
  0         0  
454 17         54 @opt_d{@opt_d} = @opt_d;
455              
456 17 50       83 if ($opt_d{o}) {
457 0         0 warn "\@ARGV = ", join(' ', shellquote(@SAVEDARGV)), "\n";
458             }
459              
460             ## -m option
461 17 50       78 my @splicer = (not defined $opt_m) ? () : do {
462 0         0 my @param = split /,/, $opt_m, -1;
463 0 0       0 push @param, '' if @param % 2;
464 0 0   0   0 if (notall { /^(-?\d+)?$/ } @param) {
  0         0  
465 0         0 die "$opt_m: option format error.\n";
466             }
467             map {
468 0         0 my($offset, $length) = @$_;
  0         0  
469 0 0       0 if ($length ne '') {
470 0   0 0   0 sub { splice @{+shift}, $offset || 0, $length }
  0         0  
471 0         0 } else {
472 0   0 0   0 sub { splice @{+shift}, $offset || 0 }
  0         0  
473 0         0 }
474             }
475             pairs @param;
476             };
477              
478 17         32 my $file_code;
479 17         41 my $default_icode = 'utf8'; # default input encoding
480 17         53 my @default_icode_list = qw(euc-jp 7bit-jis);
481 17         32 my $output_code;
482 17         36 my $default_ocode = 'utf8'; # default output encoding
483              
484 17   33     102 $output_code = $opt_ocode || $default_ocode;
485 17     17   1046 binmode STDOUT, ":encoding($output_code)";
  17         15011  
  17         356  
  17         110  
486              
487             ## show unused option characters
488 17 50       20545 if ($opt_d{u}) {
489 0         0 my $s = join('','0'..'9',"\n",'a'..'z',"\n",'A'..'Z',"\n");
490 0 0       0 map { /\|([0-9a-zA-Z])\b/ && $s =~ s/$1/./ } @optargs;
  0         0  
491 0         0 die $s;
492             }
493              
494             ## show man pages
495 17 50 33     205 if ($opt_man or $opt_show or $opt_path) {
      33        
496             my @module = map {
497 0 0       0 /^-M(\w+(::\w++(?![=(]))*)/ ? "App::Greple::$1" : ()
  0         0  
498             } @ORIG_ARGV;
499 0 0       0 if (@module) {
500 0         0 my $module = $module[-1];
501 0     0   0 my $jp = first { -x "$_/perldocjp" } split /:/, $ENV{PATH};
  0         0  
502 0 0       0 my $perldoc = $jp ? "perldocjp" : "perldoc";
503 0         0 $ENV{PERL5LIB} = join ':', @INC;
504 0         0 my $file = $module =~ s[::][/]gr . '.pm';
505 0 0       0 die unless $INC{$file};
506 0 0       0 if ($opt_man) {
507 0 0       0 exec "$perldoc $module" or die $!;
508             } else {
509 0         0 chomp(my $file = `$perldoc -ml $module`);
510 0 0       0 if ($opt_path) {
511 0         0 say $file;
512             } else {
513 0   0     0 my $pager = $ENV{PAGER} || 'less';
514 0 0       0 exec "$pager $file" or die $!;
515             }
516             }
517 0         0 exit;
518             }
519 0         0 pod2usage({-verbose => 2});
520 0         0 die;
521             }
522              
523             sub default_module {
524 0     0   0 my $mod = shift;
525 0         0 my $module = $mod->module;
526 0 0       0 return 1 if $module =~ /\b \.greplerc $/x;
527 0 0       0 return 1 if $module =~ /\b default $/x;
528 0         0 return 0;
529             }
530              
531             ## setup file encoding
532 17 50       70 if (@opt_icode) {
533 0         0 @opt_icode = map { split /[,\s]+/ } @opt_icode;
  0         0  
534 0 0       0 if (grep { s/^\+// } @opt_icode) {
  0         0  
535 0         0 unshift @opt_icode, @default_icode_list;
536             }
537 0         0 @opt_icode = uniq @opt_icode;
538 0 0       0 if (@opt_icode > 1) {
    0          
539 0         0 @opt_icode = grep { !/(?:auto|guess)$/i } @opt_icode;
  0         0  
540 0         0 Encode::Guess->set_suspects(@opt_icode);
541 0         0 $file_code = 'Guess';
542             }
543             elsif ($opt_icode[0] =~ /^(?:guess|auto)$/i) {
544 0         0 Encode::Guess->set_suspects(@default_icode_list);
545 0         0 $file_code = 'Guess';
546             } else {
547 0         0 $file_code = $opt_icode[0];
548             }
549             }
550             else {
551 17         45 $file_code = $default_icode;
552             }
553              
554             ##
555             ## --filter
556             ##
557 17 50       71 if ($opt_filter) {
558 0         0 $opt_all = 1;
559 0         0 push @opt_need, '0';
560 0   0     0 $opt_exit //= 0;
561             }
562              
563             ##
564             ## Patterns
565             ##
566              
567 17         282 my $pat_holder = App::Greple::Pattern::Holder->new;
568              
569 17         181 my $FLAG_BASE = FLAG_NONE;
570 17 50       64 $FLAG_BASE |= FLAG_IGNORECASE if $opt_i;
571              
572 17 50       56 if (@opt_f) {
573 0         0 for my $opt_f (@opt_f) {
574 0 0       0 $pat_holder->append({ flag => $FLAG_BASE, type => 'file',
575             $opt_select ? (select => $opt_select) : (),
576             },
577             $opt_f);
578             }
579             } else {
580 17 50 33     145 unless ($opt_filter or grep { $_->[0] !~ /^(not|may)/ } @opt_pattern) {
  0         0  
581 17   33     109 unshift @opt_pattern, [ le => shift @ARGV // &usage ];
582             }
583             }
584              
585 17         177 my %pat_flag = (
586             must => FLAG_REGEX | FLAG_COOK | FLAG_REQUIRED,
587             not => FLAG_REGEX | FLAG_COOK | FLAG_NEGATIVE,
588             may => FLAG_REGEX | FLAG_COOK | FLAG_OPTIONAL,
589             le => FLAG_REGEX | FLAG_COOK | FLAG_LEXICAL,
590             and => FLAG_REGEX | FLAG_COOK,
591             re => FLAG_REGEX,
592             fe => FLAG_NONE,
593             );
594 17         54 for (@opt_pattern) {
595 17         64 my($attr, @opt) = @$_;
596 17         56 my $flag = $FLAG_BASE | $pat_flag{$attr};
597 17         142 $pat_holder->append({ flag => $flag, type => 'pattern' }, @opt);
598             }
599             # $pat_holder->optimize;
600              
601             ##
602             ## if optional pattern exist, make all non-optional pattern as required
603             ##
604             {
605 17         11429 my @patterns = $pat_holder->patterns;
  17         113  
606 17         161 my @posi = grep { $_->is_positive } @patterns;
  17         86  
607 17         263 my @opti = grep { $_->is_optional } @posi;
  17         74  
608 17 50       245 if (@opti > 0) {
609 0         0 for my $p (grep { !$_->is_optional } @posi) {
  0         0  
610 0         0 $p->flag($p->flag | FLAG_REQUIRED);
611             }
612             }
613             }
614              
615             ##
616             ## set $count_must, $count_need and $opt_allow
617             ##
618 17         38 my $count_must = 0;
619 17         31 my $count_need;
620 17         38 my $count_allow = 0;
621             {
622 17         33 my $must = grep({ $_->is_required } $pat_holder->patterns);
  17         86  
  17         143  
623 17         227 my $posi = grep({ $_->is_positive } $pat_holder->patterns) - $must;
  17         118  
624 17         289 my $nega = grep({ $_->is_negative } $pat_holder->patterns);
  17         127  
625              
626 17   50     273 $count_must = $must // 0;
627 17 50       64 $count_need = $must ? 0 : $posi;
628 17         53 for (@opt_need) {
629 0 0       0 if (/^-(\d+)$/) { # --need -n
    0          
    0          
630 0         0 $count_need = $posi - $1;
631             }
632             elsif (/^\+(\d+)$/) { # --need +n
633 0         0 $count_need += $1;
634             }
635             elsif (/^(\d+)$/) { # --need n
636 0         0 $count_need = $1 - $must;
637             }
638             else {
639 0         0 die "$_ is not valid count.\n"
640             }
641             }
642              
643 17         33 $count_allow = 0;
644 17         44 for (@opt_allow) {
645 0 0       0 if (/^-(\d+)$/) { # --allow -n
    0          
    0          
646 0         0 $count_allow = $nega - $1;
647             }
648             elsif (/^\+(\d+)$/) { # --allow +n
649 0         0 $count_allow += $1;
650             }
651             elsif (/^(\d+)$/) { # --allow n
652 0         0 $count_allow = $1;
653             }
654             else {
655 0         0 die "$_ is not valid count.\n"
656             }
657             }
658             }
659              
660             ##
661             ## --matchcount
662             ##
663             my $count_match_sub = sub {
664 17 50   17   99 local $_ = shift or return;
665 0 0       0 /[^\d,]/ and die "$_ is not valid count.\n";
666 0 0       0 my @c = map { $_ eq '' ? 0 : int } split(/,/, $_, -1);
  0         0  
667 0 0       0 if (@c == 1) {
668 0         0 return sub { $_[0] == $c[0] };
  0         0  
669             }
670 0 0       0 push @c, -1 if @c % 2;
671             return sub {
672 0         0 my @n = @c;
673 0         0 while (my($min, $max) = splice(@n, 0, 2)) {
674 0 0       0 return 0 if $_[0] < $min;
675 0 0 0     0 return 1 if $max <= 0 || $_[0] <= $max;
676             }
677 0         0 return 0;
678             }
679 17         149 }->($opt_matchcount);
  0         0  
680              
681             ##
682             ## setup input/output filter
683             ##
684 17         289 my $filter_d = App::Greple::Filter->new->parse(@opt_if);
685 17 50       384 unless ($opt_noif) {
686             $filter_d->append(
687 17     17   477 [ sub { s/\.Z$// }, 'zcat' ],
688 17     17   180 [ sub { s/\.g?z$// }, 'gunzip -c' ],
689 17     17   247 [ sub { m/\.pdf$/i }, 'pdftotext -nopgbrk - -' ],
690 17     17   206 [ sub { s/\.gpg$// }, 'gpg --quiet --no-mdc-warning --decrypt' ],
  17         157  
691             );
692             }
693              
694             ##------------------------------------------------------------
695             ## miscellaneous setups
696             ##
697              
698 17         139 my @argv_files;
699             my $start_directory;
700 17   33     104 my $need_filename = ($opt_H or $opt_l);
701 17         33 my $current_file;
702              
703 17 50       86 if (@opt_chdir) {
    50          
704 0         0 $start_directory = getcwd;
705 0         0 @opt_chdir = uniq(map { glob $_ } @opt_chdir);
  0         0  
706 0         0 push @argv_files, splice(@ARGV);
707 0 0 0     0 unless ($opt_h or
      0        
      0        
708             (@opt_chdir == 1 and @argv_files == 1 and @opt_glob == 0)) {
709 0         0 $need_filename++;
710             }
711             }
712             elsif (@opt_glob) {
713 0         0 push @ARGV, map(glob, @opt_glob);
714             }
715              
716 17 0 33     78 push(@ARGV, '-') unless @ARGV || @argv_files || @opt_glob || $opt_readlist;
      33        
      0        
717 17 50 33     134 if ((@ARGV > 1 or $opt_readlist) and not $opt_h) {
      33        
718 0         0 $need_filename++;
719             }
720              
721 17 50       64 $opt_filestyle = 'none' if not $need_filename;
722              
723 17 50       63 $opt_join = 1 if $opt_joinby ne "";
724              
725             ##------------------------------------------------------------
726             ## colors
727             ##
728 17         203 our %colormap = (
729             FILE => "G",
730             LINE => "Y",
731             BLOCK => "B",
732             TEXT => "",
733             BLOCKEND => "/WE",
734             PROGRESS => "B",
735             TOP => "",
736             MIDDLE => "",
737             BOTTOM => "",
738             );
739              
740 17         68 our @colors;
741              
742 17     17   192 use Getopt::EX::Colormap;
  17         33  
  17         4476  
743 17         229 my $color_handler = Getopt::EX::Colormap
744             ->new(HASH => \%colormap, LIST => \@colors)
745             ->load_params(@opt_colormap);
746              
747 17 50       2116 my @default_color =
748             $opt_ansicolor eq '16'
749             ? qw(RD GD BD CD MD YD)
750             : qw(000D/544 000D/454 000D/445
751             000D/455 000D/545 000D/554
752             000D/543 000D/453 000D/435
753             000D/534 000D/354 000D/345
754             000D/444
755             000D/433 000D/343 000D/334
756             000D/344 000D/434 000D/443
757             000D/333)
758             ;
759              
760 17 50       192 if ($color_handler->list == 0) {
761 17 50       271 $color_handler->append
762             ($opt_colorful ? @default_color : $default_color[0]);
763             }
764              
765 17 50       1687 if ($opt_ansicolor eq '24bit') {
766 17     17   140 no warnings 'once';
  17         36  
  17         60446  
767 0         0 $Getopt::EX::Colormap::RGB24 = 1;
768             }
769              
770 17         49 for my $opt (@opt_face) {
771 0         0 while ($opt =~ /(?[-+=]) (?[^-+=]*) | (?[^-+=]+) /xg) {
772 0   0     0 my($mk, $s) = ($+{mk} // '', $+{s});
773 0         0 for my $c (@colors) {
774 0 0       0 if ($mk eq '-') {
    0          
    0          
775 0 0       0 $c =~ s/[\Q$s\E]//g if $s ne '';
776             } elsif ($mk eq '=') {
777 0         0 $c = $s;
778             } elsif ($s ne '') {
779 0 0       0 $c .= "^" if $c ne '';
780 0         0 $c .= $s;
781             }
782             }
783             }
784             }
785              
786 17   33     359 my $need_color = (($opt_color eq 'always')
787             or (($opt_color eq 'auto') and (!$opt_o and -t STDOUT)));
788              
789 17 50       95 if (!$need_color) {
790 17         78 $Getopt::EX::Colormap::NO_COLOR = 1;
791             }
792              
793 17         113 my %_esc = ( t => "\t", n => "\n", r => "\r", f => "\f" );
794             sub expand_escape {
795 51   0 51   169 $_[0] =~ s{\\(.)}{$_esc{$1} // $1}egr;
  0         0  
796             }
797              
798 17         112 $_ = expand_escape($_) for values %opt_format;
799              
800 17         42 my $blockend = "--";
801 17 50       87 if (defined $opt_blockend) {
802 0         0 $blockend = expand_escape($opt_blockend);
803             }
804              
805 17     0   83 my $_file = sub { $color_handler->color('FILE' , sprintf($opt_format{FILE}, $_[0])) };
  0         0  
806 17     0   67 my $_line = sub { $color_handler->color('LINE' , sprintf($opt_format{LINE}, $_[0])) };
  0         0  
807 17     0   182 my $_block = sub { $color_handler->color('BLOCK', sprintf($opt_format{BLOCK}, $_[0])) };
  0         0  
808 17     0   59 my $_text = sub { $color_handler->color('TEXT' , $_[0]) };
  0         0  
809 17         98 my $_blockend = $color_handler->color('BLOCKEND', $blockend);
810 17         900 my $_top = $color_handler->color('TOP' , $opt_frame_top);
811 17         239 my $_middle = $color_handler->color('MIDDLE' , $opt_frame_middle);
812 17         194 my $_bottom = $color_handler->color('BOTTOM' , $opt_frame_bottom);
813              
814             sub index_color {
815 193     193   1359 $color_handler->index_color(@_);
816             }
817              
818             sub color {
819 0     0   0 $color_handler->color(@_);
820             }
821              
822 17         323 my $uniq_color = UniqIndex->new(
823             ignore_newline => 1,
824             prepare => \@opt_uniqsub,
825             );
826              
827             sub dump_uniqcolor {
828 0     0   0 my $list = $uniq_color->list;
829 0         0 my $count = $uniq_color->count;
830 0         0 for my $i (keys @$list) {
831 0         0 warn sprintf("%3d (%3d) %s\n",
832             $i, $count->[$i],
833             index_color($i, $list->[$i]));
834             }
835             }
836              
837             # --colorindex
838 17         606 my %color_index = map { uc $_ => 1 } $opt_colorindex =~ /\w/g;
  0         0  
839 17         39 my $indexer = do {
840 17 50       650 if ($color_index{S}) {
841 0         0 @colors = shuffle @colors;
842             }
843 17 50 33     389 if ($color_index{A} or $color_index{D}) {
    50          
844 0         0 my $i = 0;
845             Indexer->new(
846 0     0   0 index => sub { $i++ },
847 0     0   0 reset => sub { $i = 0 },
848             block => $color_index{B},
849             reverse => $color_index{D},
850 0         0 );
851             }
852             elsif ($color_index{R}) {
853 0     0   0 Indexer->new(index => sub { int rand @colors });
  0         0  
854             }
855 17         47 else { undef }
856             };
857 17         109 my $opt_uniqcolor = $color_index{U};
858              
859             # -dc
860 17 50       177 if ($opt_d{c}) {
861             my $dump = sub {
862 0     0   0 local $_ = Dumper shift;
863 0         0 s/^\s*'\K([^'\s]+)(?=')/color($1, $1)/mge;
  0         0  
864 0         0 $_;
865 0         0 };
866 0         0 warn 'colormap = ', $dump->(\%colormap);
867 0         0 warn 'colors = ', $dump->(\@colors);
868             }
869              
870             ##
871             ## border regex
872             ##
873 17         33 my $border_re = do {
874 17 50       85 if ($opt_border) {
    50          
875 0         0 qr/$opt_border/m; # custom
876             } elsif ($opt_p) {
877 0         0 qr/(?:\A|\R)\K\R+/; # paragraph
878             } else {
879 17         169 qr/^/m; # line
880             }
881             };
882              
883 17 50       58 if ($opt_C) {
884 0   0     0 $opt_A ||= $opt_C;
885 0   0     0 $opt_B ||= $opt_C;
886             }
887 17         254 my %stat = (
888             file_searched => 0,
889             file_tried => 0,
890             length => 0,
891             match_effective => 0,
892             match_positive => 0,
893             match_negative => 0,
894             match_block => 0,
895             read_retry => 0,
896             time_start => [],
897             time_end => [],
898             );
899 17         139 lock_keys %stat;
900              
901             ##
902             ## Setup functions
903             ##
904 17         515 for my $set (
905             [ "print" , \@opt_print , 0 ],
906             [ "begin" , \@opt_begin , 0 ],
907             [ "end" , \@opt_end , 0 ],
908             [ "prologue", \@opt_prologue, 0 ],
909             [ "epilogue", \@opt_epilogue, 0 ],
910             [ "callback", \@opt_callback, 0 ],
911             [ "uniqsub" , \@opt_uniqsub , 0 ],
912             [ "postgrep", \@opt_postgrep, 0 ],
913             [ "block" , \@opt_block , 1 ], # need &
914             [ "inside" , \@opt_inside , 1 ], # need &
915             [ "outside" , \@opt_outside , 1 ], # need &
916             [ "include" , \@opt_include , 1 ], # need &
917             [ "exclude" , \@opt_exclude , 1 ], # need &
918             ) {
919 221         7520 my($cat, $opt, $pattern) = @$set;
920 221         276 for (@{$opt}) {
  221         432  
921 34 50       150 next if callable $_;
922 34 50 0     252 /^&\w+/ or next if $pattern;
923 34 50       114 $_ = parse_func($_) or die "$cat function format error: $_\n";
924             }
925             }
926              
927 17         349 my $regions = App::Greple::Regions::Holder->new;
928 17         168 for my $set (
929             [ \@opt_inside, REGION_INSIDE | REGION_UNION ],
930             [ \@opt_outside, REGION_OUTSIDE | REGION_UNION ],
931             [ \@opt_include, REGION_INSIDE | REGION_INTERSECT ],
932             [ \@opt_exclude, REGION_OUTSIDE | REGION_INTERSECT ])
933             {
934 68         111 my($opt, $flag) = @$set;
935 68         128 for my $spec (@$opt) {
936 0         0 $regions->append(FLAG => $flag, SPEC => $spec);
937             }
938             }
939              
940             ##------------------------------------------------------------
941              
942 17 50       91 if ($opt_d{m}) {
943 0         0 warn "Search pattern:\n";
944 0         0 my $i;
945 0         0 for my $pat ($pat_holder->patterns) {
946 0 0       0 my $type =
    0          
    0          
947             $pat->is_required ? 'must' :
948             $pat->is_negative ? 'not' :
949             $pat->is_positive ? 'and' : 'else';
950 0   0     0 my $target = $pat->regex // $pat->string;
951 0 0       0 warn sprintf(" %4s %1s %s\n",
    0          
952             $type,
953             $pat->is_function ? '&' : '',
954             @colors > 1 ? index_color($i++, $target) : $target);
955             }
956 0         0 warn sprintf("must = %d, need = %d, allow = %d\n",
957             $count_must, $count_need, $count_allow);
958             }
959              
960             ## push post-process filter
961 17 50       53 if (@opt_pf) {
962 0         0 push_output_filter(\*STDOUT, @opt_pf);
963             }
964              
965 17 50 0     50 usage() and exit if defined $opt_usage;
966              
967 17 50       519 open SAVESTDIN, '<&', \*STDIN or die "open: $!";
968 17 50       303 open SAVESTDOUT, '>&', \*STDOUT or die "open: $!";
969 17 50       1089 open SAVESTDERR, '>&', \*STDERR or die "open: $!";
970              
971             sub recover_stdin {
972 17 50   17   19723 open STDIN, '<&', \*SAVESTDIN or die "open: $!";
973             }
974             sub recover_stderr {
975 0 0   0   0 open STDERR, '>&', \*SAVESTDERR or die "open: $!";
976 0         0 binmode STDERR, ':encoding(utf8)';
977             }
978             sub recover_stdout {
979 0     0   0 close STDOUT;
980 0 0       0 open STDOUT, '>&', \*SAVESTDOUT or die "open: $!";
981             }
982             sub close_stdout {
983 17     17   166 close SAVESTDOUT;
984 17         207 close STDOUT;
985             }
986              
987 0     0   0 sub read_stdin { }
988              
989 17         35 my $slurp = do {
990             ##
991             ## Setting utf8 warnings fatal makes it easy to find code conversion
992             ## error, so you can choose appropriate file code or automatic code
993             ## recognition, but loose a chance to find string in unrelated area.
994             ##
995 17 50       144 if ($opt_error =~ /^(?: fatal | skip | retry )$/x) {
    0          
996 17 50       105 if ($opt_warn{read}) {
997             sub {
998 17     17   261 use warnings FATAL => 'utf8';
  17         73  
  17         3716  
999 0     0   0 my $stdin = eval { local $/; };
  0         0  
  0         0  
1000 0 0       0 warn $@ if $@;
1001 0         0 $stdin;
1002             }
1003 0         0 } else {
1004             sub {
1005 17     17   133 use warnings FATAL => 'utf8';
  17         36  
  17         5957  
1006 17     17   34 eval { local $/; };
  17         79  
  17         659  
1007             }
1008 17         118 }
1009             } elsif ($opt_error eq 'ignore') {
1010 0 0       0 if ($opt_warn{read}) {
1011 0     0   0 sub { local $/; };
  0         0  
  0         0  
1012             } else {
1013             sub {
1014 0     0   0 close STDERR;
1015 0         0 my $stdin = do { local $/; };
  0         0  
  0         0  
1016 0         0 recover_stderr;
1017 0         0 $stdin;
1018             }
1019 0         0 }
1020             } else {
1021 0         0 die "$opt_error: invalid action.\n";
1022             }
1023             };
1024              
1025 17     17   142 use Term::ANSIColor::Concise qw(ansi_code);
  17         36  
  17         3087  
1026              
1027             use constant {
1028 17         118 EL => ansi_code('{EL}'), # Erase Line
1029             ED => ansi_code('{ED}'), # Erase Display
1030             SCP => ansi_code('{SCP}'), # Save Cursor Position
1031             RCP => ansi_code('{RCP}'), # Restore Cursor Position
1032             DSC => ansi_code('{DECSC}'), # DEC Save Cursor
1033             DRC => ansi_code('{DECRC}'), # DEC Restore Cursor
1034             CR => "\r",
1035 17     17   158 };
  17         30  
1036              
1037 17         33 my($progress_show, $progress_reset) = do {
1038 17         32 my $n;
1039             my($s, $e) = ! $need_color ? ('', '') :
1040 17 50       83 ( ansi_code $colormap{PROGRESS}, ansi_code 'Z');
1041 17     0   77 my $print = sub { STDERR->printflush(DSC, $s, @_, $e, CR, DRC) };
  0         0  
1042 17         31 my $start = do {
1043 17 50 33     125 if ($opt_d{n} and $opt_d{f}) {
    50          
    50          
1044 0     0   0 sub { $print->(++$n, " ", $current_file, ED) }
1045 0         0 }
1046             elsif ($opt_d{n}) {
1047 0     0   0 sub { $print->(++$n) }
1048 0         0 }
1049             elsif ($opt_d{f}) {
1050 0     0   0 sub { STDERR->printflush($current_file, ":\n") }
1051 0         0 }
1052             else {
1053 17         36 undef;
1054             }
1055             };
1056 17         28 my $end = do {
1057 17 50       47 if ($opt_d{n}) {
1058 0 0   0   0 sub { STDERR->printflush(ED) if $n }
1059 0         0 } else {
1060 17         44 undef;
1061             }
1062             };
1063 17         138 ($start, $end);
1064             };
1065              
1066             ##------------------------------------------------------------
1067             ## now ready to run.
1068             ##
1069              
1070             ## record start time
1071 17 50       94 if ($opt_d{s}) {
1072 0         0 $stat{time_start} = [times];
1073             }
1074              
1075 17         254 for (@opt_prologue) { $_->call() }
  0         0  
1076              
1077 17         252 grep_files();
1078              
1079 17         66 for (@opt_epilogue) { $_->call() }
  0         0  
1080              
1081 17 50       165 if ($opt_d{n}) {
1082 0         0 print STDERR ED;
1083             }
1084              
1085 17 0 33     73 if ($opt_uniqcolor and $opt_d{c}) {
1086 0         0 dump_uniqcolor();
1087             }
1088              
1089             ## show statistic info
1090 17 50       84 if ($opt_d{s}) {
1091              
1092 0         0 $stat{time_end} = [times];
1093 0         0 my @s = $stat{time_start}->@*;
1094 0         0 my @e = $stat{time_end}->@*;
1095 0         0 printf(STDERR "cpu %.3fu %.3fs\n", $e[0]-$s[0], $e[1]-$s[1]);
1096              
1097 0         0 local $" = ', ';
1098 0         0 for my $k (sort keys %stat) {
1099 0         0 my $v = $stat{$k};
1100 0 0       0 print STDERR
1101             "$k: ",
1102             ref $v eq 'ARRAY' ? "(@$v)" : $v,
1103             "\n";
1104             }
1105             }
1106              
1107 17         180 close_stdout;
1108              
1109 17 50       74 if ($opt_d{p}) {
1110 0         0 open STDOUT, ">&STDERR";
1111 0         0 system "ps -lww -p $$";
1112 0         0 system "lsof -p $$";
1113             }
1114              
1115 17   33     0 exit($opt_exit // ($stat{match_effective} == 0));
1116              
1117             ######################################################################
1118              
1119             sub grep_files {
1120             FILE:
1121 17     17   201 while (defined($current_file = open_nextfile())) {
1122 17         57 my $content = $slurp->();
1123 17         440 $stat{file_tried}++;
1124 17 50       86 if (not defined $content) {
1125 0 0       0 if ($opt_error eq 'fatal') {
1126 0         0 die "ABORT on $current_file\n";
1127             }
1128 0 0       0 if ($opt_error ne 'retry') {
1129 0 0       0 warn "SKIP $current_file\n" if $opt_warn{skip};
1130 0         0 next FILE;
1131             }
1132              
1133             # Try again
1134 0         0 binmode STDIN, ':raw';
1135 0 0       0 seek STDIN, 0, 0 or do {
1136             warn "SKIP $current_file (not seekable)\n"
1137 0 0       0 if $opt_warn{skip};
1138 0         0 next FILE;
1139             };
1140 0         0 $content = $slurp->();
1141 0 0       0 if (not defined $content) {
1142 0 0       0 warn "SKIP* $current_file\n" if $opt_warn{skip};
1143 0         0 next FILE;
1144             }
1145 0 0       0 warn "RETRY $current_file\n" if $opt_warn{retry};
1146 0         0 $stat{read_retry}++;
1147 0         0 binmode STDOUT, ':raw';
1148             }
1149              
1150 17         87 my $matched = grep_data(\$content);
1151              
1152 17         133 $stat{match_effective} += $matched;
1153 17         68 $stat{file_searched}++;
1154 17         125 $stat{length} += length $content;
1155             } continue {
1156 17         557 close STDIN; # wait; # wait for 4.019 or earlier?
1157             # recover STDIN for opening '-' and some weird command which needs
1158             # STDIN opened (like unzip)
1159 17         185 recover_stdin;
1160 17         2034 binmode STDOUT, ":encoding($output_code)";
1161             }
1162             }
1163              
1164             sub usage {
1165 0     0   0 pod2usage(-verbose => 0, -exitval => "NOEXIT");
1166              
1167 0         0 my $quote = qr/[\\(){}\|\*?]/;
1168 0         0 for my $bucket ($rcloader->buckets) {
1169 0         0 my $module = $bucket->module;
1170 0         0 print " $module options:\n";
1171 0         0 for my $name ($bucket->options) {
1172 0 0 0     0 my $help = $opt_usage ? "" : $bucket->help($name) // "";
1173 0 0       0 next if $help eq 'ignore';
1174 0         0 my @option = $bucket->getopt($name, ALL => 1);
1175 0   0     0 printf(" %-20s %s\n", $name,
1176             $help || join(' ', shellquote(@option)));
1177             }
1178 0         0 print "\n";
1179             }
1180              
1181 0         0 print "Version: $version\n";
1182              
1183 0         0 exit 2;
1184             }
1185              
1186             sub open_nextfile {
1187              
1188             ##
1189             ## --chdir
1190             ##
1191 34   66 34   1535 while (@ARGV == 0 and @opt_chdir and (@argv_files or @opt_glob)) {
      0        
      33        
1192 0         0 my $dir = shift @opt_chdir;
1193 0 0       0 warn "chdir $dir/\n" if $opt_d{d};
1194 0 0       0 chdir $start_directory or die "$!: $start_directory\n";
1195 0 0       0 chdir $dir or die "$!: $dir\n";
1196 0         0 push @ARGV, @argv_files, map(glob, @opt_glob);
1197             }
1198              
1199 34         100 my $file;
1200 34   33     319 while (defined($file = shift(@ARGV)) ||
      66        
1201             defined($file = $opt_readlist && read_stdin)) {
1202 17 50       75 $file = decode 'utf8', $file unless utf8::is_utf8 $file;
1203 17         82 $file =~ s/\n+$//;
1204              
1205 17 50       126 if (0) {}
    50          
1206 0         0 elsif ($file =~ /^https?:\/\//) {
1207 0 0 0     0 open(STDIN, '-|') || exec("w3m -dump $file") || die "w3m: $!\n";
1208             }
1209             elsif ($file eq '-') {
1210             # nothing to do
1211             }
1212             else {
1213 17 50       1018 open(STDIN, '<', $file) or do {
1214 0 0       0 warn "$file: $!\n" unless -l $file;
1215 0         0 next;
1216             };
1217             }
1218              
1219 17 50       113 if (my @filters = $filter_d->get_filters($file)) {
1220 0         0 push_input_filter({ &FILELABEL => $file }, @filters);
1221             }
1222              
1223 17 50       217 if ($file_code eq 'binary') {
1224 0         0 binmode STDIN, ":raw";
1225             } else {
1226 17         221 binmode STDIN, ":encoding($file_code)";
1227             }
1228              
1229 17         738 return $file;
1230             }
1231 17         213 undef;
1232             }
1233              
1234             ######################################################################
1235              
1236             sub grep_data {
1237 17     17   52 local *_ = shift;
1238              
1239             ##
1240             ## --begin
1241             ##
1242 17         60 for my $f (@opt_begin) {
1243 0         0 eval { $f->call(&FILELABEL => $current_file) };
  0         0  
1244 0 0       0 if (my $msg = $@) {
1245 0 0       0 if ($msg =~ /^SKIP/i) {
1246 0 0       0 warn $@ if $opt_warn{begin};
1247 0         0 return 0;
1248             } else {
1249 0         0 die $msg;
1250             }
1251             }
1252             }
1253              
1254 17 50       93 $progress_show->() if $progress_show;
1255              
1256             my $grep = App::Greple::Grep->new(
1257             text => \$_,
1258             filename => $current_file,
1259             pattern => $pat_holder,
1260             regions => $regions,
1261             border => $border_re,
1262             after => $opt_A,
1263             before => $opt_B,
1264             only => $opt_o,
1265             all => $opt_all,
1266             block => \@opt_block,
1267             stretch => $opt_stretch,
1268             must => $count_must,
1269             need => $count_need,
1270             countcheck => $count_match_sub,
1271             allow => $count_allow,
1272             strict => $opt_strict,
1273 17         36 group_index => do { local $_ = $opt_colorindex;
1274 17 0       287 $opt_capture_group ? /G/i ? /P/i ? 3 : 2 : 1 : 0 },
    0          
    50          
1275             region_index => $opt_regioncolor,
1276             stat => \%stat,
1277             callback => \@opt_callback,
1278             alert_size => $opt_alert{size},
1279             alert_time => $opt_alert{time},
1280 17         66 join_blocks => $opt_join_blocks,
1281             )->run;
1282              
1283             ## --postgrep
1284 17         32284 for my $f (@opt_postgrep) {
1285 17         101 $f->call($grep);
1286             # remove emptied results
1287 17         261 my $ref = $grep->result_ref;
1288 17         262 @$ref = grep { @{$_} > 0 } @$ref;
  17         102  
  17         184  
1289             }
1290             ## -m
1291 17         106 for my $splice (@splicer) {
1292 0         0 $splice->($grep->result_ref);
1293             }
1294              
1295 17         128 my $matched = $grep->matched;
1296 17 50       568 if ($opt_l) {
    50          
    50          
1297 0 0       0 if ($matched) {
1298 0 0       0 $progress_reset->() if $progress_reset;
1299 0         0 print $current_file;
1300 0 0       0 printf ":%d", scalar $grep->result if $opt_c;
1301 0         0 print "\n";
1302             }
1303             }
1304             elsif ($opt_c) {
1305 0 0       0 $progress_reset->() if $progress_reset;
1306 0 0       0 print "$current_file:" if $need_filename;
1307 0         0 print scalar $grep->result, "\n";
1308             }
1309             elsif ($grep->result_ref->@*) {
1310 17 50       254 $progress_reset->() if $progress_reset;
1311             # open output filter
1312 17 50       193 @opt_of && push_output_filter(
1313             { &FILELABEL => $current_file },
1314             \*STDOUT, @opt_of);
1315 17         221 output($grep);
1316 17 50       76 @opt_of && recover_stdout;
1317             }
1318              
1319             ##
1320             ## --end
1321             ##
1322 17         1778 for my $f (@opt_end) {
1323 0         0 $f->call(&FILELABEL => $current_file);
1324             }
1325              
1326 17 50       127 s/./\000/gs if $opt_clean;
1327              
1328 17         6712 $matched;
1329             }
1330              
1331             sub output {
1332 17     17   55 my $grep = shift;
1333 17         85 my $file = $grep->{filename};
1334              
1335 17 50       190 if ($opt_filestyle eq 'once') {
1336 0         0 print $_file->($file), "\n";
1337             }
1338              
1339 17   33     122 my $need_blockend =
1340             !$opt_all &&
1341             $blockend ne '' &&
1342             ($opt_blockend || $opt_p || $opt_A || $opt_B || @opt_block);
1343              
1344 17         79 my $line = 1;
1345 17         39 my $lastpos = 0;
1346 17         100 my @results = $grep->result;
1347 17         197 for my $rix (keys @results) {
1348 17         60 my $is_top = $rix == 0;
1349 17         61 my $is_bottom = $rix == $#results;
1350              
1351 17         59 my $result = $results[$rix];
1352 17         41 my($blk, @result) = @{$result};
  17         94  
1353 17         58 my($block_start, $block_end, $block_number) = @$blk;
1354 17   50     190 $block_number //= 0;
1355 17         231 my $block = $grep->cut($block_start, $block_end);
1356              
1357             ## --print
1358 17 50       748 if (@opt_print) {
1359 0         0 local *_ = \$block;
1360 0         0 for my $func (@opt_print) {
1361 0         0 $_ = $func->call(&FILELABEL => $file);
1362             }
1363 0 0       0 if (not $opt_continue) {
1364 0 0       0 print $block if defined $block;
1365 0         0 next;
1366             }
1367             }
1368              
1369 17 50       109 if ($opt_n) {
1370 0         0 my $gap = $grep->cut($lastpos, $block_start);
1371 0         0 $line += $gap =~ tr/\n/\n/;
1372             }
1373 17         45 $lastpos = $block_end;
1374              
1375             # when --filestyle and/or --linestyle is "separate"
1376 51         139 grep { $_ } (
1377             do {
1378 17 50       176 print $_file->($current_file)
1379             if $opt_filestyle eq 'separate';
1380             },
1381             do {
1382 17 50 33     124 print $_line->($line)
1383             if $opt_n and $opt_linestyle eq 'separate';
1384             },
1385 17 50       50 do {
1386 17 50 33     1694 print $_block->($block_number)
1387             if $opt_b and $opt_blockstyle eq 'separate';
1388             },
1389             ) and print "\n";
1390              
1391 17 50       94 if ($indexer) {
1392 0 0       0 $indexer->reset if $indexer->block;
1393 0 0       0 for ($indexer->reverse ? reverse @result : @result) {
1394 0         0 $_->[2] = $indexer->index;
1395             }
1396             }
1397              
1398 17         133 my @slice = $grep->slice_result($result);
1399 17         2347 my $mark = "\001";
1400 17         92 for my $i (keys @result) {
1401 193         9382 my($start, $end, $pi, $callback) = $result[$i]->@*;
1402 193         750 local *b = \$slice[$i * 2 + 1];
1403              
1404             ## run callback function
1405 193 50       380 if ($callback) {
1406 193   33     204 $b = do {
1407 193 50       1227 if (ref $callback eq 'CODE') {
    50          
1408 0         0 $callback->($start, $end, $pi, $b);
1409             }
1410             elsif (callable($callback)) {
1411 193         1994 $callback->call(
1412             &FILELABEL => $file,
1413             start => $start,
1414             end => $end,
1415             index => $pi,
1416             match => $b);
1417             }
1418 0         0 else { die }
1419             } // $b;
1420             }
1421              
1422 193 50       555 if ($opt_join) {
1423 0 0 0     0 if ($opt_n and $opt_linestyle eq 'line') {
1424 0         0 $b =~ s/(?
1425             } else {
1426 0         0 $b =~ s/(?
1427             }
1428             }
1429              
1430 193 50       417 $pi = $uniq_color->index($b) if $opt_uniqcolor;
1431 193         798 $b = index_color($pi, $b);
1432             }
1433              
1434 17         1558 $block = join '', @slice;
1435 17 50       144 next if $block eq "";
1436              
1437 17         60 my @line;
1438 17 50       97 if ($opt_n) {
1439 0 0       0 if ($opt_linestyle eq 'line') {
1440 0 0       0 my $increment = $block =~ /[\n$mark]/ ? 1 : 0;
1441 0         0 $block =~ s{(?:(?$mark)|(?<=\n)|\A)(?=.)}{
1442 0 0       0 push @line, $_line->($line) unless $+{mark};
1443 0         0 $line += $increment;
1444 0 0       0 $+{mark} ? $opt_joinby : '';
1445             }gse;
1446             } else {
1447 0         0 $line += $block =~ tr/\n/\n/;
1448             }
1449             }
1450              
1451 17 50       132 $block = $_text->($block) if $colormap{TEXT} ne "";
1452              
1453 17 50       165 if (@line) {
1454 0         0 $block =~ s/^/shift @line/mge;
  0         0  
1455             }
1456              
1457 17 50 33     207 if ($opt_b and $opt_blockstyle eq 'line') {
1458 0         0 my $s = $_block->($block_number);
1459 0         0 $block =~ s/^/$s/mg;
1460             }
1461              
1462 17 50       117 if ($opt_filestyle eq 'line') {
1463 0         0 my $s = $_file->($file);
1464 0         0 $block =~ s/^/$s/mg;
1465             }
1466              
1467 17 50 33     217 print "$_top\n" if $is_top && $_top ne '';
1468 17         222 print $block;
1469 17 50 33     515 print "\n" if $opt_newline and not $block =~ /\n\z/;
1470 17 50       89 print "$_blockend\n" if $need_blockend;
1471 17 50       91 if ($is_bottom) {
1472 17 50       452 print "$_bottom\n" if $_bottom ne '';
1473             } else {
1474 0 0         print "$_middle\n" if $_middle ne '';
1475             }
1476             }
1477             }
1478              
1479             __END__