File Coverage

blib/lib/App/Greple/subst.pm
Criterion Covered Total %
statement 197 215 91.6
branch 53 94 56.3
condition 26 46 56.5
subroutine 27 28 96.4
pod 0 6 0.0
total 303 389 77.8


line stmt bran cond sub pod time code
1             =encoding utf8
2              
3             =head1 NAME
4              
5             subst - Greple module for text search and substitution
6              
7             =head1 VERSION
8              
9             Version 2.37
10              
11             =head1 SYNOPSIS
12              
13             greple -Msubst --dict I<dictionary> [ options ]
14              
15             Dictionary:
16             --dict dictionary file
17             --dictdata dictionary data
18             --dictpair dictionary entry pair
19              
20             Check:
21             --check=[ng,ok,any,outstand,all,none]
22             --select=N
23             --linefold
24             --stat
25             --with-stat
26             --stat-style=[default,dict]
27             --stat-item={match,expect,number,ok,ng,dict}=[0,1]
28             --subst
29             --[no-]warn-overlap
30             --[no-]warn-include
31              
32             File Update:
33             --diff
34             --diffcmd command
35             --create
36             --replace
37             --overwrite
38              
39             =head1 DESCRIPTION
40              
41             This B<greple> module supports check and substitution of text files
42             based on dictionary data.
43              
44             Dictionary file is given by B<--dict> option and each line contains
45             matching pattern and expected string pairs.
46              
47             greple -Msubst --dict DICT
48              
49             If the dictionary file contains following data:
50              
51             colou?r color
52             cent(er|re) center
53              
54             above command finds the first pattern which does not match the second
55             string, that is "colour" and "centre" in this case.
56              
57             In practice, the last two elements of a space-separated string are
58             treated as a pattern and a replacement string, respectively.
59              
60             Dictionary data can also be written separated by C<//> as follows:
61              
62             colou?r // color
63             cent(er|re) // center
64              
65             There must be spaces before and after the C<//>. In this format,
66             strings before and after it are treated as a pattern and replacement
67             string, rather than last two element. Leading spaces and spaces
68             before and after C<//> are ignored, but all other whitespace is valid.
69              
70             You can use same file by B<greple>'s B<-f> option and string after
71             C<//> is ignored as a comment in that case.
72              
73             greple -f DICT ...
74              
75             Option B<--dictdata> can be used to provide dictionary data in the
76             command line.
77              
78             greple -Msubst \
79             --dictdata $'colou?r color\ncent(er|re) center\n'
80              
81             Dictionary entry starting with a sharp sign (C<#>) is a comment and
82             ignored.
83              
84             Option B<--dictpair> can be used to provide raw dictionary entries in
85             the command line. In this case, no processing is done regarding
86             whitespace or comments.
87              
88             greple -Msubst \
89             --dictpair 'colou?r' color \
90             --dictpair 'cent(er|re)' center
91              
92             =head2 Overlapped pattern
93              
94             When the matched string is same or shorter than previously matched
95             string by another pattern, it is simply ignored (B<--no-warn-include>
96             by default). So, if you have to declare conflicted patterns, place
97             the longer pattern earlier.
98              
99             If the matched string overlaps with previously matched string, it is
100             warned (B<--warn-overlap> by default) and ignored.
101              
102             =head2 Terminal color
103              
104             This version uses L<Getopt::EX::termcolor> module. It sets option
105             B<--light-screen> or B<--dark-screen> depending on the terminal on
106             which the command run, or B<TERM_BGCOLOR> environment variable.
107              
108             Some terminals (eg: "Apple_Terminal" or "iTerm") are detected
109             automatically and no action is required. Otherwise set
110             B<TERM_BGCOLOR> environment to #000000 (black) to #FFFFFF (white)
111             digit depending on terminal background color.
112              
113             =head1 OPTIONS
114              
115             =over 7
116              
117             =item B<--dict>=I<file>
118              
119             Specify dictionary file.
120              
121             =item B<--dictdata>=I<data>
122              
123             Specify dictionary data by text.
124              
125             =item B<--dictpair> I<pattern> I<replacement>
126              
127             Specify dictionary entry pair. This option takes two parameters. The
128             first is a pattern and the second is a substitution string.
129              
130             =item B<--check>=C<outstand>|C<ng>|C<ok>|C<any>|C<all>|C<none>
131              
132             Option B<--check> takes argument from C<ng>, C<ok>, C<any>,
133             C<outstand>, C<all> and C<none>.
134              
135             With default value C<outstand>, command will show information about
136             both expected and unexpected words only when unexpected word was found
137             in the same file.
138              
139             With value C<ng>, command will show information about unexpected
140             words. With value C<ok>, you will get information about expected
141             words. Both with value C<any>.
142              
143             Value C<all> and C<none> make sense only when used with B<--stat>
144             option, and display information about never matched pattern.
145              
146             =item B<--select>=I<N>
147              
148             Select I<N>th entry from the dictionary. Argument is interpreted by
149             L<Getopt::EX::Numbers> module. Range can be defined like
150             B<--select>=C<1:3,7:9>. You can get numbers by B<--stat> option.
151              
152             =item B<--linefold>
153              
154             If the target data is folded in the middle of text, use B<--linefold>
155             option. It creates regex patterns which matches string spread across
156             lines. Substituted text does not include newline, though. Because it
157             confuses regex behavior somewhat, avoid to use if possible.
158              
159             =item B<--stat>
160              
161             =item B<--with-stat>
162              
163             Print statistical information. Works with B<--check> option.
164              
165             Option B<--with-stat> print statistics after normal output, while
166             B<--stat> print only statistics.
167              
168             =item B<--stat-style>=C<default>|C<dict>
169              
170             Using B<--stat-style=dict> option with B<--stat> and B<--check=any>,
171             you can get dictionary style output for your working document.
172              
173             =item B<--stat-item> I<item>=[0,1]
174              
175             Specify which item is shown up in stat information. Default values
176             are:
177              
178             match=1
179             expect=1
180             number=1
181             ng=1
182             ok=1
183             dict=0
184              
185             If you don't need to see pattern field, use like this:
186              
187             --stat-item match=0
188              
189             Multiple parameters can be set at once:
190              
191             --stat-item match=number=0,ng=1,ok=1
192              
193             =item B<--subst>
194              
195             Substitute unexpected matched pattern to expected string. Newline
196             character in the matched string is ignored. Pattern without
197             replacement string is not changed.
198              
199             =item B<--[no-]warn-overlap>
200              
201             Warn overlapped pattern.
202             Default on.
203              
204             =item B<--[no-]warn-include>
205              
206             Warn included pattern.
207             Default off.
208              
209             =back
210              
211             =head2 FILE UPDATE OPTIONS
212              
213             =over 7
214              
215             =item B<--diff>
216              
217             =item B<--diffcmd>=I<command>
218              
219             Option B<--diff> produce diff output of original and converted text.
220              
221             Specify diff command name used by B<--diff> option. Default is "diff
222             -u".
223              
224             =item B<--create>
225              
226             Create new file and write the result. Suffix ".new" is appended to
227             original filename.
228              
229             =item B<--replace>
230              
231             Replace the target file by converted result. Original file is renamed
232             to backup name with ".bak" suffix.
233              
234             =item B<--overwrite>
235              
236             Overwrite the target file by converted result with no backup.
237              
238             =back
239              
240             =head1 DICTIONARY
241              
242             This module includes example dictionaries. They are installed share
243             directory and accessed by B<--exdict> option.
244              
245             greple -Msubst --exdict jtca-katakana-guide-3.dict
246              
247             =over 7
248              
249             =item B<--exdict> I<dictionary>
250              
251             Use I<dictionary> flie in the distribution as a dictionary file.
252              
253             =item B<--exdictdir>
254              
255             Show dictionary directory.
256              
257             =item B<--exdict> jtca-katakana-guide-3.dict
258              
259             =item B<--jtca-katakana-guide>
260              
261             Created from following guideline document.
262              
263             外来語(カタカナ)表記ガイドライン 第3版
264             制定:2015年8月
265             発行:2015年9月
266             一般財団法人テクニカルコミュニケーター協会
267             Japan Technical Communicators Association
268             https://jtca.org/tcwp/wp-content/uploads/2023/06/katakana_guide_3_20171222.pdf
269              
270             =item B<--jtca>
271              
272             Customized B<--jtca-katakana-guide>. Original dictionary is
273             automatically generated from published data. This dictionary is
274             customized for practical use.
275              
276             =item B<--exdict> jtf-style-guide-3.dict
277              
278             =item B<--jtf-style-guide>
279              
280             Created from following guideline document.
281              
282             JTF日本語標準スタイルガイド(翻訳用)
283             第3.0版
284             2019年8月20日
285             一般社団法人 日本翻訳連盟(JTF)
286             翻訳品質委員会
287             https://www.jtf.jp/jp/style_guide/pdf/jtf_style_guide.pdf
288              
289             =item B<--jtf>
290              
291             Customized B<--jtf-style-guide>. Original dictionary is automatically
292             generated from published data. This dictionary is customized for
293             practical use.
294              
295             =item B<--exdict> sccc2.dict
296              
297             =item B<--sccc2>
298              
299             Dictionary used for "C/C++ セキュアコーディング 第2版" published in
300             2014.
301              
302             https://www.jpcert.or.jp/securecoding_book_2nd.html
303              
304             =item B<--exdict> ms-style-guide.dict
305              
306             =item B<--ms-style-guide>
307              
308             Dictionary generated from Microsoft localization style guide.
309              
310             https://www.microsoft.com/ja-jp/language/styleguides
311              
312             Data is generated from this article:
313              
314             https://www.atmarkit.co.jp/news/200807/25/microsoft.html
315              
316             =item B<--microsoft>
317              
318             Customized B<--ms-style-guide>. Original dictionary is automatically
319             generated from published data. This dictionary is customized for
320             practical use.
321              
322             Amendment dictionary can be found
323             L<here|https://github.com/kaz-utashiro/greple-subst/blob/master/share/ms-amend.dict>.
324             Please raise an issue or send a pull-request if you have request to update.
325              
326             =back
327              
328             =head1 JAPANESE
329              
330             This module is originaly made for Japanese text editing support.
331              
332             =head2 KATAKANA
333              
334             Japanese KATAKANA word have a lot of variants to describe same word,
335             so unification is important but it's quite tiresome work. In the next
336             example,
337              
338             イ[エー]ハトー?([ヴブボ]ォ?) // イーハトーヴォ
339              
340             left pattern matches all following words.
341              
342             イエハトブ
343             イーハトヴ
344             イーハトーヴ
345             イーハトーヴォ
346             イーハトーボ
347             イーハトーブ
348              
349             This module helps to detect and correct them.
350              
351             =head1 INSTALL
352              
353             =head2 CPANMINUS
354              
355             $ cpanm App::Greple::subst
356              
357             =head1 SEE ALSO
358              
359             L<https://github.com/kaz-utashiro/greple>
360              
361             L<https://github.com/kaz-utashiro/greple-subst>
362              
363             L<https://github.com/kaz-utashiro/greple-update>
364              
365             L<https://www.jtca.org/standardization/katakana_guide_3_20171222.pdf>
366              
367             L<https://www.jtf.jp/jp/style_guide/styleguide_top.html>,
368             L<https://www.jtf.jp/jp/style_guide/pdf/jtf_style_guide.pdf>
369              
370             L<https://www.microsoft.com/ja-jp/language/styleguides>,
371             L<https://www.atmarkit.co.jp/news/200807/25/microsoft.html>
372              
373             L<文化庁 国語施策・日本語教育 国語施策情報 内閣告示・内閣訓令 外来語の表記|https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kijun/naikaku/gairai/index.html>
374              
375             L<https://qiita.com/kaz-utashiro/items/85add653a71a7e01c415>
376              
377             L<イーハトーブ|https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%BC%E3%83%8F%E3%83%88%E3%83%BC%E3%83%96>
378              
379             =head1 AUTHOR
380              
381             Kazumasa Utashiro
382              
383             =head1 LICENSE
384              
385             Copyright ©︎ 2017-2025 Kazumasa Utashiro.
386              
387             This library is free software; you can redistribute it and/or modify
388             it under the same terms as Perl itself.
389              
390             =cut
391              
392              
393 22     22   401946 use v5.14;
  22         91  
394             package App::Greple::subst;
395              
396             our $VERSION = '2.37';
397              
398 22     22   170 use warnings;
  22         48  
  22         1357  
399 22     22   1195 use utf8;
  22         600  
  22         245  
400 22     22   1995 use open IO => ':utf8';
  22         1635  
  22         291  
401              
402 22     22   2548 use Exporter 'import';
  22         45  
  22         2707  
403             our @EXPORT = qw(
404             &subst_initialize
405             &subst_begin
406             &subst_diff
407             &subst_update
408             &subst_show_stat
409             &subst_search
410             );
411             our %EXPORT_TAGS = ( );
412             our @EXPORT_OK = qw();
413              
414 22     22   149 use Carp;
  22         58  
  22         2111  
415 22     22   799 use Data::Dumper;
  22         10357  
  22         1829  
416 22     22   717 use Text::ParseWords qw(shellwords);
  22         2106  
  22         1412  
417 22     22   153 use Encode;
  22         47  
  22         2150  
418 22     22   133 use List::Util qw(max sum mesh);
  22         89  
  22         2025  
419 22     22   785 use Getopt::EX::Colormap qw(colorize);
  22         97586  
  22         1512  
420 22     22   142 use Getopt::EX::LabeledParam;
  22         38  
  22         1071  
421 22     22   1004 use App::Greple::Common;
  22         560  
  22         1274  
422 22     22   786 use App::Greple::Pattern;
  22         2816  
  22         2086  
423 22     22   26454 use App::Greple::subst::Dict;
  22         105  
  22         1227  
424              
425 22     22   12221 use File::Share qw(:all);
  22         762390  
  22         26612  
426             $ENV{GREPLE_SUBST_DICT} //= dist_dir 'App-Greple-subst';
427              
428             our $debug = 0;
429             our $opt_subst = 0;
430             our @opt_subst_from;
431             our @opt_subst_to;
432             our @opt_dictfile;
433             our @opt_dictdata;
434             our $opt_printdict;
435             our $opt_dictname;
436             our $opt_check = 'outstand';
437             our @opt_format;
438             our @default_opt_format = ( '%s' );
439             our $opt_subst_select;
440             our $opt_linefold;
441             our $opt_ignore_space = 0;
442             our $opt_warn_overlap = 1;
443             our $opt_warn_include = 0;
444             our $opt_stat_style = "default";
445             our @opt_stat_item;
446             our %opt_stat_item = (
447             map( { $_ => 1 } qw(match expect number ng ok) ),
448             map( { $_ => 0 } qw(dict) ),
449             );
450             our $opt_show_comment = 0;
451             our $opt_show_numbers = 1;
452             our $opt_show_dictdir = 0;
453              
454             my %stat;
455              
456             my $current_file;
457             my $ignorechar_re;
458             my @dicts;
459              
460             sub debug {
461 0     0 0 0 $debug = 1;
462             }
463              
464             sub subst_initialize {
465              
466 21 50   21 0 449 state $once_called++ and return;
467              
468 21 50       73 if ($opt_show_dictdir) {
469 0         0 say "$ENV{GREPLE_SUBST_DICT}";
470 0         0 exit;
471             }
472              
473             Getopt::EX::LabeledParam
474 21         127 ->new(HASH => \%opt_stat_item)
475             ->load_params(@opt_stat_item);
476              
477 21 50       1157 @opt_format = @default_opt_format if @opt_format == 0;
478              
479 21 50       109 $ignorechar_re = $opt_ignore_space ? qr/\s+/ : qr/\R+/;
480              
481 21         126 my $config = { linefold => $opt_linefold,
482             dictname => $opt_dictname,
483             printdict => $opt_printdict };
484 21 100       77 if (@opt_subst_from) {
485 2 50       7 die if @opt_subst_from != @opt_subst_to;
486 2         33 push @dicts, App::Greple::subst::Dict->new(
487             DATA => [ mesh \@opt_subst_from, \@opt_subst_to ],
488             CONFIG => $config,
489             );
490             }
491 21         80 for my $data (@opt_dictdata) {
492 2         33 push @dicts, App::Greple::subst::Dict->new(
493             DATA => $data,
494             CONFIG => $config,
495             );
496             }
497 21         74 for my $file (@opt_dictfile) {
498 17 50       468 if (-d $file) {
499 0         0 warn "$file is directory\n";
500 0         0 next;
501             }
502 17         243 push @dicts, App::Greple::subst::Dict->new(
503             FILE => $file,
504             CONFIG => $config,
505             );
506             }
507              
508 21 50       296 if (@dicts == 0) {
509 0         0 warn "Module -Msubst requires dictionary data.\n";
510 0         0 main::usage();
511 0         0 die;
512             }
513             }
514              
515             sub subst_begin {
516 21     21 0 384 my %arg = @_;
517 21 50       204 $current_file = delete $arg{&FILELABEL} or die;
518             }
519              
520 22     22   277 use Text::VisualWidth::PP;
  22         51  
  22         1521  
521 22     22   170 use Text::VisualPrintf qw(vprintf vsprintf);
  22         52  
  22         15806  
522              
523             sub vwidth {
524 60 50 33 60 0 399 if (not defined $_[0] or length $_[0] == 0) {
525 0         0 return 0;
526             }
527 60         227 Text::VisualWidth::PP::width $_[0];
528             }
529              
530             my @match_list;
531              
532             sub subst_show_stat {
533 3     3 0 51 my %arg = @_;
534 3         10 my($from_max, $to_max) = (0, 0);
535 3         7 my $i = -1;
536 3         6 my @show_list;
537 3         8 for my $dict (@dicts) {
538 3         34 my @fromto = $dict->words;
539 3         44 my @show;
540 3         7 for my $p (@fromto) {
541 39         75 $i++;
542 39   50     98 $p // die;
543 39 100       223 if ($p->is_comment) {
544 9 50       20 push @show, [ $i, $p, {} ] if $opt_show_comment;
545 9         26 next;
546             }
547 30   50     176 my($from_re, $to) = ($p->string, $p->correct // '');
548 30   50     123 my $hash = $match_list[$i] // {};
549 30         65 my @keys = keys %{$hash};
  30         171  
550 30         80 my @ng = grep { $_ ne $to } @keys;
  30         109  
551 30         57 my @ok = grep { $_ eq $to } @keys;
  30         67  
552 30 0       178 if ($opt_check eq 'none' ) { next if @keys != 0 }
  0 50       0  
    50          
    50          
    50          
    50          
    0          
553 0 0       0 elsif ($opt_check eq 'any' ) { next if @keys == 0 }
554 0 0       0 elsif ($opt_check eq 'ok' ) { next if @ok == 0 }
555 0 0       0 elsif ($opt_check eq 'ng' ) { next if @ng == 0 }
556 30 50       84 elsif ($opt_check eq 'outstand') { next if @ng == 0 }
557             elsif ($opt_check eq 'all') { }
558 0         0 else { die }
559 30         81 $from_max = max $from_max, vwidth $from_re;
560 30         51143 $to_max = max $to_max , vwidth $to;
561 30         2947 push @show, [ $i, $p, $hash ];
562             }
563 3         28 push @show_list, [ $dict => \@show ];
564             }
565 3 50       21 if ($opt_show_numbers) {
566 22     22   222 no warnings 'uninitialized';
  22         48  
  22         17460  
567             printf "HIT_PATTERN=%d/%d NG=%d, OK=%d, TOTAL=%d\n",
568             $stat{hit}, $stat{total},
569 3         67 $stat{ng}, $stat{ok}, $stat{ng} + $stat{ok};
570             }
571 3         17 for my $show_list (@show_list) {
572 3         10 my($dict, $show) = @{$show_list};
  3         13  
573 3 50       17 next if @$show == 0;
574 3         11 my $dict_format = ">>> %s <<<\n";
575 3 100       18 if ($opt_stat_item{dict}) {
576 1         15 print colorize('000/L24E', sprintf($dict_format, $dict->NAME));
577             }
578 3         122 for my $item (@$show) {
579 30         106 my($i, $p, $hash) = @$item;
580 30 50       149 if ($p->is_comment) {
581 0 0       0 say $p->comment if $opt_show_comment;
582 0         0 next;
583             }
584 30   50     194 my($from_re, $to) = ($p->string, $p->correct // '');
585 30         66 my @keys = keys %{$hash};
  30         133  
586 30 50       100 if ($opt_stat_style eq 'dict') {
587 0   0     0 vprintf("%-${from_max}s // %s", $from_re // '', $to // '');
      0        
588             } else {
589 0         0 my @ng = sort { $hash->{$b} <=> $hash->{$a} } grep { $_ ne $to } @keys
  30         158  
590 30 50       175 if $opt_stat_item{ng};
591 30         81 my @ok = grep { $_ eq $to } @keys
592 30 50       177 if $opt_stat_item{ok};
593 30 50 50     252 vprintf("%${from_max}s => ", $from_re // '') if $opt_stat_item{match};
594 30 50 50     25972 vprintf("%-${to_max}s", $to // '') if $opt_stat_item{expect};
595 30 50       16911 vprintf(" %4d:", $i + 1) if $opt_stat_item{number};
596 30         8479 for my $key (@ng, @ok) {
597 30 50       162 my $index = $key eq $to ? $i * 2 + 1 : $i * 2;
598             printf(" %s(%s)",
599             main::index_color($index, $key),
600 30 50       144 colorize($key eq $to ? 'DB' : 'DR', $hash->{$key})
601             );
602             }
603             }
604 30         3136 print "\n";
605             }
606             }
607 3         53 $_ = "";
608             }
609              
610 22     22   927 use App::Greple::Regions qw(match_regions merge_regions filter_regions);
  22         3708  
  22         25711  
611              
612             sub subst_search {
613 21     21 0 3362 my $text = $_;
614 21         145 my %arg = @_;
615 21 50       298 $current_file = delete $arg{&FILELABEL} or die;
616              
617 21         52 my @matched;
618 21         58 my $index = -1;
619 21         54 my @effective;
620 21         159 my $ng = {ng=>1, any=>1, all=>1, none=>1}->{$opt_check} ;
621 21         119 my $ok = { ok=>1, any=>1, all=>1, none=>1}->{$opt_check} ;
622 21         73 my $outstand = $opt_check eq 'outstand';
623 21         67 for my $dict (@dicts) {
624 21         120 for my $p ($dict->words) {
625 197         11345 $index++;
626 197   50     399 $p // next;
627 197 100       596 next if $p->is_comment;
628 164   50     561 my($from_re, $to) = ($p->string, $p->correct // '');
629 164         427 my @match = match_regions pattern => $p->regex;
630              
631             ##
632             ## Remove all overlapped matches.
633             ##
634 164         11471 my($in, $over, $out, $im, $om) = filter_regions \@match, \@matched;
635 164         11257 @match = @$out;
636 164         570 for my $warn (
637             [ "Include", $in, $im, $opt_warn_include ],
638             [ "Overlap", $over, $om, $opt_warn_overlap ],
639             ) {
640 328         594 my($kind, $list, $match, $show) = @$warn;
641 328 100 100     1215 $show and @$list or next;
642 2         13 for my $i (keys @$list) {
643 2         6 my($a, $b) = ($list->[$i], $match->[$i]);
644 2         14 warn sprintf("%s \"%s\" with \"%s\" by #%d /%s/ in %s at %d\n",
645             $kind,
646             substr($_, $a->[0], $a->[1] - $a->[0]),
647             substr($_, $b->[0], $b->[1] - $b->[0]),
648             $index + 1, $p->string,
649             $current_file,
650             $a->[0],
651             );
652             }
653             }
654              
655 164         509 $stat{total}++;
656 164 100       398 $stat{hit}++ if @match;
657 164 50 66     418 next if @match == 0 and $opt_check ne 'all';
658              
659 164   50     693 my $hash = $match_list[$index] //= {};
660             my $callback = sub {
661 166     166   427 my($ms, $me, $i, $matched) = @_;
662 166 100       735 $stat{$i % 2 ? 'ok' : 'ng'}++;
663 166         1476 my $s = $matched =~ s/$ignorechar_re//gr;
664 166         725 $hash->{$s}++;
665 166         449 my $format = @opt_format[ $i % @opt_format ];
666 166 100 100     1627 sprintf($format,
667             ($opt_subst && $to ne '' && $s ne $to) ?
668             $to : $matched);
669 164         955 };
670 164         291 my(@ok, @ng);
671 164         377 for (@match) {
672 182         675 my $matched = substr $text, $_->[0], $_->[1] - $_->[0];
673 182 100       1172 if ($matched =~ s/$ignorechar_re//gr ne $to) {
674 175         550 $_->[2] = $index * 2;
675 175         563 push @ng, $_;
676             } else {
677 7         20 $_->[2] = $index * 2 + 1;
678 7         13 push @ok, $_;
679             }
680 182         691 $_->[3] = $callback;
681             }
682 164 100 66     813 $effective[ $index * 2 ] = 1 if $ng || ( @ng && $outstand );
      66        
683 164 100 66     735 $effective[ $index * 2 + 1 ] = 1 if $ok || ( @ng && $outstand );
      66        
684              
685 164         755 @matched = merge_regions { nojoin => 1 }, @matched, @match;
686             }
687             }
688             ##
689             ## --select
690             ##
691 21 100       1920 if (my $select = $opt_subst_select) {
692 2         7 my $max = sum map { int $_->words } @dicts;
  2         12  
693 22     22   221 use Getopt::EX::Numbers;
  22         52  
  22         9475  
694 2         57 my $numbers = Getopt::EX::Numbers->new(min => 1, max => $max);
695 2         122 my @select;
696 2         4 for (my @select_index = do {
697 6         21 map { $_ * 2, $_ * 2 + 1 }
698 6         15 map { $_ - 1 }
699 6         272 grep { $_ <= $max }
700 2         16 map { $numbers->parse($_)->sequence }
  3         112  
701             split /,/, $select;
702             }) {
703 12         26 $select[$_] = 1;
704             }
705 2         71 @matched = grep $select[$_->[2]], @matched;
706             }
707 21         264 grep $effective[$_->[2]], @matched;
708             }
709              
710             1;
711              
712             __DATA__
713              
714             builtin dict=s @opt_dictfile
715             builtin dictdata=s @opt_dictdata
716             builtin subst-from=s @opt_subst_from
717             builtin subst-to=s @opt_subst_to
718             builtin stat-style=s $opt_stat_style
719             builtin stat-item=s @opt_stat_item
720             builtin printdict! $opt_printdict
721             builtin dictname! $opt_dictname
722             builtin subst-format=s @opt_format
723             builtin subst! $opt_subst
724             builtin check=s $opt_check
725             builtin subst-select=s $opt_subst_select
726             builtin linefold! $opt_linefold
727             builtin warn-overlap! $opt_warn_overlap
728             builtin warn-include! $opt_warn_include
729             builtin ignore-space! $opt_ignore_space
730             builtin show-comment! $opt_show_comment
731             builtin exdictdir! $opt_show_dictdir
732              
733             # override greple's original option
734             option --select --subst-select
735              
736             option default \
737             -Mtermcolor::bg(default=100,light=--subst-color-light,dark=--subst-color-dark) \
738             --prologue subst_initialize \
739             --begin subst_begin \
740             --le +&subst_search --no-regioncolor
741              
742             option --dictpair \
743             --subst-from $<shift> --subst-to $<shift>
744              
745             ##
746             ## Now these options are implemented by -Mupdate module
747             ## --diffcmd, -U are built-in options
748             ##
749             autoload -Mupdate \
750             --update::diff \
751             --update::create \
752             --update::update \
753             --update::discard
754              
755             option --diff --subst --update::diff
756             option --create --subst --update::create
757             option --replace --subst --update::update --with-backup
758             option --overwrite --subst --update::update
759              
760             option --with-stat --epilogue subst_show_stat
761             option --stat --update::discard --with-stat
762              
763             autoload -Msubst::dyncmap --dyncmap
764              
765             help --subst-color-light light terminal color
766             option --subst-color-light --colormap --dyncmap \
767             range=0-2,except=000:111:222,shift=3,even="555D/%s",odd="IU;000/%s"
768              
769             help --subst-color-dark dark terminal color
770             option --subst-color-dark --colormap --dyncmap \
771             range=2-4,except=222:333:444,shift=1,even="D;L01/%s",odd="IU;%s/L01"
772              
773             ##
774             ## Handle included sample dictionaries.
775             ##
776              
777             option --exdict --dict $ENV{GREPLE_SUBST_DICT}/$<shift>
778              
779             option --jtca-katakana-guide --exdict jtca-katakana-guide-3.dict
780             option --jtf-style-guide --exdict jtf-style-guide-3.dict
781             option --ms-style-guide --exdict ms-style-guide.dict
782              
783             option --sccc2 --exdict sccc2.dict
784             option --jtca --exdict jtca.dict
785             option --jtf --exdict jtf.dict
786             option --microsoft --exdict ms-amend.dict --exdict ms-style-guide.dict
787             option --macos --exdict macos.dict
788              
789             # deprecated. don't use.
790             option --ms --microsoft
791              
792             option --all-sample-dict --jtf --jtca --microsoft
793              
794             option --all-katakana --exdict all-katakana.dict
795              
796             option --dumpdict --printdict --prologue 'sub{exit}'
797              
798             # LocalWords: subst Greple greple ng ok outstand linefold dict diff
799             # LocalWords: regex Kazumasa Utashiro