File Coverage

blib/lib/App/Greple/subst.pm
Criterion Covered Total %
statement 62 222 27.9
branch 0 102 0.0
condition 0 46 0.0
subroutine 21 31 67.7
pod 0 9 0.0
total 83 410 20.2


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