File Coverage

/usr/local/bin/greple
Criterion Covered Total %
statement 394 708 55.6
branch 111 400 27.7
condition 36 152 23.6
subroutine 53 91 58.2
pod n/a
total 594 1351 43.9


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 26     26   134204 use v5.24;
  26         111  
9 26     26   151 use warnings;
  26         37  
  26         1473  
10              
11 26     26   13122 use File::stat;
  26         251451  
  26         1807  
12 26     26   15047 use IO::Handle;
  26         173637  
  26         1695  
13 26     26   14535 use Pod::Usage;
  26         1867327  
  26         4127  
14 26     26   16131 use Text::ParseWords qw(shellwords);
  26         45047  
  26         2253  
15 26     26   197 use List::Util qw(min max first sum uniq shuffle notall pairs pairmap);
  26         42  
  26         5922  
16 26     26   15327 use Hash::Util qw(lock_keys);
  26         95787  
  26         178  
17 26     26   2806 use Cwd qw(getcwd abs_path);
  26         36  
  26         1343  
18 26     26   126 use Carp;
  26         36  
  26         1362  
19              
20 26     26   13768 use utf8;
  26         7590  
  26         155  
21 26     26   932 use Encode;
  26         157  
  26         2361  
22 26     26   12408 use Encode::Guess;
  26         111238  
  26         100  
23 26     26   14236 use open IO => ':utf8', ':std';
  26         34821  
  26         148  
24              
25 26     26   18322 use Data::Dumper;
  26         206899  
  26         2661  
26             {
27 26     26   5130073 no warnings 'redefine';
  26         260  
  26         40  
  26         6589  
28 26     0   297 *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
  0         0  
  0         0  
29 26         90 $Data::Dumper::Useperl = 1;
30             }
31              
32             ##
33             ## Setup greple/lib to be a module directory if exists.
34             ##
35             BEGIN {
36 26 50   26   1723 if (my $lib = abs_path($0) =~ s{/(?:script/|bin/)?\w+$}{/lib}r) {
37 26 50       1338 unshift @INC, $lib if -d "$lib/App/Greple";
38             }
39             }
40              
41 26     26   14588 use Getopt::EX::Loader;
  26         2113426  
  26         1900  
42 26     26   258 use Getopt::EX::Func qw(parse_func callable);
  26         45  
  26         1508  
43              
44 26     26   11892 use App::Greple;
  26         3478  
  26         998  
45 26     26   10491 use App::Greple::Common;
  26         11678  
  26         1547  
46 26     26   12124 use App::Greple::Util qw(shellquote);
  26         31092  
  26         1889  
47 26     26   13973 use App::Greple::Grep;
  26         392372  
  26         2313  
48 26     26   200 use App::Greple::Regions;
  26         65  
  26         2088  
49 26     26   137 use App::Greple::Pattern;
  26         63  
  26         1470  
50 26     26   13968 use App::Greple::Pattern::Holder;
  26         103673  
  26         1473  
51 26     26   11997 use App::Greple::Filter;
  26         35369  
  26         146874  
52              
53 26         87 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 26         151 my @baseclass = qw( App::Greple Getopt::EX );
174 26         425 my $rcloader = Getopt::EX::Loader
175             ->new(BASECLASS => \@baseclass);
176              
177 26         1777 my @optargs;
178             my %optargs;
179              
180             sub newopt {
181             push @optargs, pairmap {
182 2366     2366   2950 local $_ = $a;
183 2366         7982 s/\s+//g;
184 2366         4331 s/^(?=\w+-)([-\w]+)/$1 =~ tr[-][_]r . "|$1"/e; # "a-b" -> "a_b|a-b"
  364         1197  
185 2366 100 50     8126 /^(\w+)/ and $optargs{$1} = $b if ref $b ne 'CODE';
186 2366         4308 $_ => $b;
187 26     26   438 } @_;
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 26         0 my @opt_pattern;
203             sub opt_pattern {
204 30     30   90075 push @opt_pattern, [ map "$_", @_ ];
205 30         475 $opt_pattern[-1];
206             }
207              
208 26         0 my @opt_colormap;
209 13     13   89946 sub opt_colormap { push @opt_colormap, $_[1] }
210 0     0   0 sub opt_colorsub { push @opt_colormap, "sub{ $_[1] }" }
211              
212 26         220 my %opt_format = (LINE => '%d:', FILE => '%s:', BLOCK => '%s:');
213 26         127 my %opt_alert = (size => 512 * 1024, time => 2);
214 26         158 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 26         2172 ' 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 26         22669 require Getopt::Long;
415 26         344296 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 26 50       34696 configure_getopt qw(debug) if $ENV{DEBUG_GETOPT};
421 26 50       152 $Getopt::EX::Loader::debug = 1 if $ENV{DEBUG_GETOPTEX};
422              
423             ## decode
424             my @ORIG_ARGV =
425 26 50       91 @ARGV = map { utf8::is_utf8($_) ? $_ : decode('utf8', $_) } @ARGV;
  152         3155  
426              
427             ## ~/.greplerc
428 26 0 33     850 unless ((@ARGV and $ARGV[0] eq "--norc" and shift)
      33        
      33        
429             or
430             ($ENV{GREPLE_NORC}) ) {
431 0         0 $rcloader->load(FILE => "$ENV{HOME}/.greplerc");
432             }
433              
434             ## modules
435 26         243 $rcloader->deal_with(\@ARGV);
436              
437 26         15814 push @optargs, $rcloader->builtins;
438              
439             ## ENV
440 26 50       620 $ENV{'GREPLEOPTS'} and unshift @ARGV, shellwords($ENV{'GREPLEOPTS'});
441              
442              
443             ## GetOptions
444 26         101 my @SAVEDARGV = @ARGV;
445 26 50       239 $parser->getoptions(@optargs) || usage();
446              
447 26 50       25682 if ($opt_version) {
448 0         0 print "$version\n";
449 0         0 exit 0;
450             }
451              
452 26         154 our %opt_d;
453 26         197 @opt_d = map { split // } @opt_d;
  0         0  
454 26         105 @opt_d{@opt_d} = @opt_d;
455              
456 26 50       124 if ($opt_d{o}) {
457 0         0 warn "\@ARGV = ", join(' ', shellquote(@SAVEDARGV)), "\n";
458             }
459              
460             ## -m option
461 26 50       109 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 26         50 my $file_code;
479 26         74 my $default_icode = 'utf8'; # default input encoding
480 26         80 my @default_icode_list = qw(euc-jp 7bit-jis);
481 26         41 my $output_code;
482 26         50 my $default_ocode = 'utf8'; # default output encoding
483              
484 26   33     175 $output_code = $opt_ocode || $default_ocode;
485 26     26   1282 binmode STDOUT, ":encoding($output_code)";
  26         19686  
  26         454  
  26         151  
486              
487             ## show unused option characters
488 26 50       28396 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 26 50 33     324 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 26 50       100 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 26         61 $file_code = $default_icode;
552             }
553              
554             ##
555             ## --filter
556             ##
557 26 50       77 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 26         336 my $pat_holder = App::Greple::Pattern::Holder->new;
568              
569 26         249 my $FLAG_BASE = FLAG_NONE;
570 26 50       79 $FLAG_BASE |= FLAG_IGNORECASE if $opt_i;
571              
572 26 50       79 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 26 100 66     134 unless ($opt_filter or grep { $_->[0] !~ /^(not|may)/ } @opt_pattern) {
  30         175  
581 1   33     6 unshift @opt_pattern, [ le => shift @ARGV // &usage ];
582             }
583             }
584              
585 26         241 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 26         73 for (@opt_pattern) {
595 31         1756 my($attr, @opt) = @$_;
596 31         86 my $flag = $FLAG_BASE | $pat_flag{$attr};
597 31         261 $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 26         14568 my @patterns = $pat_holder->patterns;
  26         179  
606 26         253 my @posi = grep { $_->is_positive } @patterns;
  31         148  
607 26         405 my @opti = grep { $_->is_optional } @posi;
  31         130  
608 26 50       344 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 26         52 my $count_must = 0;
619 26         47 my $count_need;
620 26         51 my $count_allow = 0;
621             {
622 26         43 my $must = grep({ $_->is_required } $pat_holder->patterns);
  26         85  
  31         270  
623 26         387 my $posi = grep({ $_->is_positive } $pat_holder->patterns) - $must;
  31         195  
624 26         298 my $nega = grep({ $_->is_negative } $pat_holder->patterns);
  31         203  
625              
626 26   50     314 $count_must = $must // 0;
627 26 50       83 $count_need = $must ? 0 : $posi;
628 26         99 for (@opt_need) {
629 3 50       25 if (/^-(\d+)$/) { # --need -n
    50          
    50          
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 3         8 $count_need = $1 - $must;
637             }
638             else {
639 0         0 die "$_ is not valid count.\n"
640             }
641             }
642              
643 26         45 $count_allow = 0;
644 26         63 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 26 50   26   112 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 26         221 }->($opt_matchcount);
  0         0  
680              
681             ##
682             ## setup input/output filter
683             ##
684 26         1956 my $filter_d = App::Greple::Filter->new->parse(@opt_if);
685 26 50       583 unless ($opt_noif) {
686             $filter_d->append(
687 26     26   694 [ sub { s/\.Z$// }, 'zcat' ],
688 26     26   294 [ sub { s/\.g?z$// }, 'gunzip -c' ],
689 26     26   354 [ sub { m/\.pdf$/i }, 'pdftotext -nopgbrk - -' ],
690 26     26   315 [ sub { s/\.gpg$// }, 'gpg --quiet --no-mdc-warning --decrypt' ],
  26         267  
691             );
692             }
693              
694             ##------------------------------------------------------------
695             ## miscellaneous setups
696             ##
697              
698 26         220 my @argv_files;
699             my $start_directory;
700 26   33     139 my $need_filename = ($opt_H or $opt_l);
701 26         48 my $current_file;
702              
703 26 50       141 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 26 0 33     134 push(@ARGV, '-') unless @ARGV || @argv_files || @opt_glob || $opt_readlist;
      33        
      0        
717 26 50 33     160 if ((@ARGV > 1 or $opt_readlist) and not $opt_h) {
      33        
718 0         0 $need_filename++;
719             }
720              
721 26 50       95 $opt_filestyle = 'none' if not $need_filename;
722              
723 26 50       84 $opt_join = 1 if $opt_joinby ne "";
724              
725             ##------------------------------------------------------------
726             ## colors
727             ##
728 26         310 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 26         43 our @colors;
741              
742 26     26   248 use Getopt::EX::Colormap;
  26         45  
  26         5664  
743 26         285 my $color_handler = Getopt::EX::Colormap
744             ->new(HASH => \%colormap, LIST => \@colors)
745             ->load_params(@opt_colormap);
746              
747 26 50       7030 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 26 100       233 if ($color_handler->list == 0) {
761 13 50       182 $color_handler->append
762             ($opt_colorful ? @default_color : $default_color[0]);
763             }
764              
765 26 50       1245 if ($opt_ansicolor eq '24bit') {
766 26     26   211 no warnings 'once';
  26         41  
  26         79054  
767 0         0 $Getopt::EX::Colormap::RGB24 = 1;
768             }
769              
770 26         75 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 26   33     441 my $need_color = (($opt_color eq 'always')
787             or (($opt_color eq 'auto') and (!$opt_o and -t STDOUT)));
788              
789 26 50       93 if (!$need_color) {
790 26         80 $Getopt::EX::Colormap::NO_COLOR = 1;
791             }
792              
793 26         158 my %_esc = ( t => "\t", n => "\n", r => "\r", f => "\f" );
794             sub expand_escape {
795 78   0 78   240 $_[0] =~ s{\\(.)}{$_esc{$1} // $1}egr;
  0         0  
796             }
797              
798 26         157 $_ = expand_escape($_) for values %opt_format;
799              
800 26         56 my $blockend = "--";
801 26 50       123 if (defined $opt_blockend) {
802 0         0 $blockend = expand_escape($opt_blockend);
803             }
804              
805 26     0   140 my $_file = sub { $color_handler->color('FILE' , sprintf($opt_format{FILE}, $_[0])) };
  0         0  
806 26     232   124 my $_line = sub { $color_handler->color('LINE' , sprintf($opt_format{LINE}, $_[0])) };
  232         1046  
807 26     0   80 my $_block = sub { $color_handler->color('BLOCK', sprintf($opt_format{BLOCK}, $_[0])) };
  0         0  
808 26     0   82 my $_text = sub { $color_handler->color('TEXT' , $_[0]) };
  0         0  
809 26         169 my $_blockend = $color_handler->color('BLOCKEND', $blockend);
810 26         1391 my $_top = $color_handler->color('TOP' , $opt_frame_top);
811 26         309 my $_middle = $color_handler->color('MIDDLE' , $opt_frame_middle);
812 26         275 my $_bottom = $color_handler->color('BOTTOM' , $opt_frame_bottom);
813              
814             sub index_color {
815 98     98   396 $color_handler->index_color(@_);
816             }
817              
818             sub color {
819 0     0   0 $color_handler->color(@_);
820             }
821              
822 26         457 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 26         811 my %color_index = map { uc $_ => 1 } $opt_colorindex =~ /\w/g;
  0         0  
839 26         44 my $indexer = do {
840 26 50       95 if ($color_index{S}) {
841 0         0 @colors = shuffle @colors;
842             }
843 26 50 33     223 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 26         57 else { undef }
856             };
857 26         80 my $opt_uniqcolor = $color_index{U};
858              
859             # -dc
860 26 50       81 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 26         45 my $border_re = do {
874 26 50       109 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 26         138 qr/^/m; # line
880             }
881             };
882              
883 26 50       83 if ($opt_C) {
884 0   0     0 $opt_A ||= $opt_C;
885 0   0     0 $opt_B ||= $opt_C;
886             }
887 26         304 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 26         176 lock_keys %stat;
900              
901             ##
902             ## Setup functions
903             ##
904 26         723 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 338         986 my($cat, $opt, $pattern) = @$set;
920 338         345 for (@{$opt}) {
  338         582  
921 1 50       7 next if callable $_;
922 1 50 50     17 /^&\w+/ or next if $pattern;
923 1 50       6 $_ = parse_func($_) or die "$cat function format error: $_\n";
924             }
925             }
926              
927 26         357 my $regions = App::Greple::Regions::Holder->new;
928 26         231 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 104         196 my($opt, $flag) = @$set;
935 104         177 for my $spec (@$opt) {
936 1         6 $regions->append(FLAG => $flag, SPEC => $spec);
937             }
938             }
939              
940             ##------------------------------------------------------------
941              
942 26 50       174 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 26 50       76 if (@opt_pf) {
962 0         0 push_output_filter(\*STDOUT, @opt_pf);
963             }
964              
965 26 50 0     80 usage() and exit if defined $opt_usage;
966              
967 26 50       838 open SAVESTDIN, '<&', \*STDIN or die "open: $!";
968 26 50       500 open SAVESTDOUT, '>&', \*STDOUT or die "open: $!";
969 26 50       1664 open SAVESTDERR, '>&', \*STDERR or die "open: $!";
970              
971             sub recover_stdin {
972 26 50   26   688 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 26     26   201 close SAVESTDOUT;
984 26         174 close STDOUT;
985             }
986              
987 0     0   0 sub read_stdin { }
988              
989 26         52 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 26 50       420 if ($opt_error =~ /^(?: fatal | skip | retry )$/x) {
    0          
996 26 50       213 if ($opt_warn{read}) {
997             sub {
998 26     26   485 use warnings FATAL => 'utf8';
  26         129  
  26         4828  
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 26     26   177 use warnings FATAL => 'utf8';
  26         105  
  26         7714  
1006 26     26   50 eval { local $/; };
  26         111  
  26         1097  
1007             }
1008 26         267 }
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 26     26   198 use Term::ANSIColor::Concise qw(ansi_code);
  26         41  
  26         3948  
1026              
1027             use constant {
1028 26         160 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 26     26   169 };
  26         41  
1036              
1037 26         52 my($progress_show, $progress_reset) = do {
1038 26         66 my $n;
1039             my($s, $e) = ! $need_color ? ('', '') :
1040 26 50       162 ( ansi_code $colormap{PROGRESS}, ansi_code 'Z');
1041 26     0   321 my $print = sub { STDERR->printflush(DSC, $s, @_, $e, CR, DRC) };
  0         0  
1042 26         132 my $start = do {
1043 26 50 33     200 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 26         84 undef;
1054             }
1055             };
1056 26         95 my $end = do {
1057 26 50       75 if ($opt_d{n}) {
1058 0 0   0   0 sub { STDERR->printflush(ED) if $n }
1059 0         0 } else {
1060 26         59 undef;
1061             }
1062             };
1063 26         170 ($start, $end);
1064             };
1065              
1066             ##------------------------------------------------------------
1067             ## now ready to run.
1068             ##
1069              
1070             ## record start time
1071 26 50       115 if ($opt_d{s}) {
1072 0         0 $stat{time_start} = [times];
1073             }
1074              
1075 26         84 for (@opt_prologue) { $_->call() }
  0         0  
1076              
1077 26         107 grep_files();
1078              
1079 26         98 for (@opt_epilogue) { $_->call() }
  0         0  
1080              
1081 26 50       182 if ($opt_d{n}) {
1082 0         0 print STDERR ED;
1083             }
1084              
1085 26 0 33     81 if ($opt_uniqcolor and $opt_d{c}) {
1086 0         0 dump_uniqcolor();
1087             }
1088              
1089             ## show statistic info
1090 26 50       100 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 26         106 close_stdout;
1108              
1109 26 50       104 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 26   33     0 exit($opt_exit // ($stat{match_effective} == 0));
1116              
1117             ######################################################################
1118              
1119             sub grep_files {
1120             FILE:
1121 26     26   106 while (defined($current_file = open_nextfile())) {
1122 26         80 my $content = $slurp->();
1123 26         713 $stat{file_tried}++;
1124 26 50       113 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 26         131 my $matched = grep_data(\$content);
1151              
1152 26         101 $stat{match_effective} += $matched;
1153 26         53 $stat{file_searched}++;
1154 26         93 $stat{length} += length $content;
1155             } continue {
1156 26         645 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 26         130 recover_stdin;
1160 26         1567 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 52   66 52   1456 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 52         217 my $file;
1200 52   33     328 while (defined($file = shift(@ARGV)) ||
      66        
1201             defined($file = $opt_readlist && read_stdin)) {
1202 26 50       112 $file = decode 'utf8', $file unless utf8::is_utf8 $file;
1203 26         117 $file =~ s/\n+$//;
1204              
1205 26 50       211 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 26 50       1473 open(STDIN, '<', $file) or do {
1214 0 0       0 warn "$file: $!\n" unless -l $file;
1215 0         0 next;
1216             };
1217             }
1218              
1219 26 50       262 if (my @filters = $filter_d->get_filters($file)) {
1220 0         0 push_input_filter({ &FILELABEL => $file }, @filters);
1221             }
1222              
1223 26 50       320 if ($file_code eq 'binary') {
1224 0         0 binmode STDIN, ":raw";
1225             } else {
1226 26         346 binmode STDIN, ":encoding($file_code)";
1227             }
1228              
1229 26         1157 return $file;
1230             }
1231 26         94 undef;
1232             }
1233              
1234             ######################################################################
1235              
1236             sub grep_data {
1237 26     26   110 local *_ = shift;
1238              
1239             ##
1240             ## --begin
1241             ##
1242 26         67 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 26 50       96 $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 26         131 group_index => do { local $_ = $opt_colorindex;
1274 26 0       621 $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 26         268 join_blocks => $opt_join_blocks,
1281             )->run;
1282              
1283             ## --postgrep
1284 26         26774 for my $f (@opt_postgrep) {
1285 0         0 $f->call($grep);
1286             # remove emptied results
1287 0         0 my $ref = $grep->result_ref;
1288 0         0 @$ref = grep { @{$_} > 0 } @$ref;
  0         0  
  0         0  
1289             }
1290             ## -m
1291 26         80 for my $splice (@splicer) {
1292 0         0 $splice->($grep->result_ref);
1293             }
1294              
1295 26         120 my $matched = $grep->matched;
1296 26 50       640 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 26 50       309 $progress_reset->() if $progress_reset;
1311             # open output filter
1312 26 50       134 @opt_of && push_output_filter(
1313             { &FILELABEL => $current_file },
1314             \*STDOUT, @opt_of);
1315 26         140 output($grep);
1316 26 50       86 @opt_of && recover_stdout;
1317             }
1318              
1319             ##
1320             ## --end
1321             ##
1322 26         70 for my $f (@opt_end) {
1323 0         0 $f->call(&FILELABEL => $current_file);
1324             }
1325              
1326 26 50       127 s/./\000/gs if $opt_clean;
1327              
1328 26         792 $matched;
1329             }
1330              
1331             sub output {
1332 26     26   50 my $grep = shift;
1333 26         66 my $file = $grep->{filename};
1334              
1335 26 50       139 if ($opt_filestyle eq 'once') {
1336 0         0 print $_file->($file), "\n";
1337             }
1338              
1339 26   33     583 my $need_blockend =
1340             !$opt_all &&
1341             $blockend ne '' &&
1342             ($opt_blockend || $opt_p || $opt_A || $opt_B || @opt_block);
1343              
1344 26         51 my $line = 1;
1345 26         41 my $lastpos = 0;
1346 26         95 my @results = $grep->result;
1347 26         226 for my $rix (keys @results) {
1348 98         810 my $is_top = $rix == 0;
1349 98         199 my $is_bottom = $rix == $#results;
1350              
1351 98         195 my $result = $results[$rix];
1352 98         139 my($blk, @result) = @{$result};
  98         216  
1353 98         228 my($block_start, $block_end, $block_number) = @$blk;
1354 98   50     263 $block_number //= 0;
1355 98         584 my $block = $grep->cut($block_start, $block_end);
1356              
1357             ## --print
1358 98 50       1115 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 98 50       224 if ($opt_n) {
1370 98         223 my $gap = $grep->cut($lastpos, $block_start);
1371 98         809 $line += $gap =~ tr/\n/\n/;
1372             }
1373 98         142 $lastpos = $block_end;
1374              
1375             # when --filestyle and/or --linestyle is "separate"
1376 294         545 grep { $_ } (
1377             do {
1378 98 50       385 print $_file->($current_file)
1379             if $opt_filestyle eq 'separate';
1380             },
1381             do {
1382 98 50 33     578 print $_line->($line)
1383             if $opt_n and $opt_linestyle eq 'separate';
1384             },
1385 98 50       105 do {
1386 98 50 33     296 print $_block->($block_number)
1387             if $opt_b and $opt_blockstyle eq 'separate';
1388             },
1389             ) and print "\n";
1390              
1391 98 50       229 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 98         274 my @slice = $grep->slice_result($result);
1399 98         3905 my $mark = "\001";
1400 98         271 for my $i (keys @result) {
1401 98         265 my($start, $end, $pi, $callback) = $result[$i]->@*;
1402 98         302 local *b = \$slice[$i * 2 + 1];
1403              
1404             ## run callback function
1405 98 50       222 if ($callback) {
1406 0   0     0 $b = do {
1407 0 0       0 if (ref $callback eq 'CODE') {
    0          
1408 0         0 $callback->($start, $end, $pi, $b);
1409             }
1410             elsif (callable($callback)) {
1411 0         0 $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 98 50       206 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 98 50       180 $pi = $uniq_color->index($b) if $opt_uniqcolor;
1431 98         231 $b = index_color($pi, $b);
1432             }
1433              
1434 98         4846 $block = join '', @slice;
1435 98 50       246 next if $block eq "";
1436              
1437 98         158 my @line;
1438 98 50       211 if ($opt_n) {
1439 98 50       297 if ($opt_linestyle eq 'line') {
1440 98 50       1546 my $increment = $block =~ /[\n$mark]/ ? 1 : 0;
1441 98         1487 $block =~ s{(?:(?$mark)|(?<=\n)|\A)(?=.)}{
1442 232 50       1460 push @line, $_line->($line) unless $+{mark};
1443 232         6993 $line += $increment;
1444 232 50       2710 $+{mark} ? $opt_joinby : '';
1445             }gse;
1446             } else {
1447 0         0 $line += $block =~ tr/\n/\n/;
1448             }
1449             }
1450              
1451 98 50       452 $block = $_text->($block) if $colormap{TEXT} ne "";
1452              
1453 98 50       239 if (@line) {
1454 98         390 $block =~ s/^/shift @line/mge;
  232         610  
1455             }
1456              
1457 98 50 33     281 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 98 50       224 if ($opt_filestyle eq 'line') {
1463 0         0 my $s = $_file->($file);
1464 0         0 $block =~ s/^/$s/mg;
1465             }
1466              
1467 98 50 66     379 print "$_top\n" if $is_top && $_top ne '';
1468 98         345 print $block;
1469 98 50 33     699 print "\n" if $opt_newline and not $block =~ /\n\z/;
1470 98 50       215 print "$_blockend\n" if $need_blockend;
1471 98 100       190 if ($is_bottom) {
1472 26 50       179 print "$_bottom\n" if $_bottom ne '';
1473             } else {
1474 72 50       324 print "$_middle\n" if $_middle ne '';
1475             }
1476             }
1477             }
1478              
1479             __END__