File Coverage

blib/lib/App/Greple/subst.pm
Criterion Covered Total %
statement 62 210 29.5
branch 0 90 0.0
condition 0 46 0.0
subroutine 21 28 75.0
pod 0 6 0.0
total 83 380 21.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.3301
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   815 use v5.14;
  1         4  
372             package App::Greple::subst;
373              
374             our $VERSION = '2.3301';
375              
376 1     1   6 use warnings;
  1         2  
  1         25  
377 1     1   629 use utf8;
  1         14  
  1         6  
378 1     1   499 use open IO => ':utf8';
  1         1209  
  1         5  
379              
380 1     1   97 use Exporter 'import';
  1         1  
  1         89  
381             our @EXPORT = qw(
382             &subst_initialize
383             &subst_begin
384             &subst_diff
385             &subst_update
386             &subst_show_stat
387             &subst_search
388             );
389             our %EXPORT_TAGS = ( );
390             our @EXPORT_OK = qw();
391              
392 1     1   7 use Carp;
  1         2  
  1         97  
393 1     1   602 use Data::Dumper;
  1         6785  
  1         64  
394 1     1   514 use Text::ParseWords qw(shellwords);
  1         1309  
  1         59  
395 1     1   590 use Encode;
  1         10087  
  1         81  
396 1     1   507 use Getopt::EX::Colormap qw(colorize);
  1         20954  
  1         67  
397 1     1   8 use Getopt::EX::LabeledParam;
  1         3  
  1         53  
398 1     1   502 use App::Greple::Common;
  1         353  
  1         99  
399 1     1   481 use App::Greple::Pattern;
  1         23395  
  1         116  
400 1     1   523 use App::Greple::subst::Dict;
  1         4  
  1         47  
401              
402 1     1   438 use File::Share qw(:all);
  1         26592  
  1         956  
403             $ENV{GREPLE_SUBST_DICT} //= dist_dir 'App-Greple-subst';
404              
405             our $debug = 0;
406             our $remember_data = 1;
407             our $opt_subst = 0;
408             our @opt_subst_from;
409             our @opt_subst_to;
410             our @opt_dictfile;
411             our @opt_dictdata;
412             our $opt_printdict;
413             our $opt_dictname;
414             our $opt_check = 'outstand';
415             our @opt_format;
416             our @default_opt_format = ( '%s' );
417             our $opt_subst_select;
418             our $opt_linefold;
419             our $opt_ignore_space = 1;
420             our $opt_warn_overlap = 1;
421             our $opt_warn_include = 0;
422             our $opt_stat_style = "default";
423             our @opt_stat_item;
424             our %opt_stat_item = (
425             map( { $_ => 1 } qw(match expect number ng ok) ),
426             map( { $_ => 0 } qw(dict) ),
427             );
428             our $opt_show_comment = 0;
429             our $opt_show_numbers = 1;
430             my %stat;
431              
432             my $current_file;
433             my $contents;
434             my $ignorechar_re;
435             my @dicts;
436              
437             sub debug {
438 0     0 0   $debug = 1;
439             }
440              
441             sub subst_initialize {
442              
443 0 0   0 0   state $once_called++ and return;
444              
445 0           Getopt::EX::LabeledParam
446             ->new(HASH => \%opt_stat_item)
447             ->load_params(@opt_stat_item);
448              
449 0 0         @opt_format = @default_opt_format if @opt_format == 0;
450              
451 0 0         $ignorechar_re = $opt_ignore_space ? qr/\s+/ : qr/\R+/;
452              
453 0           my $config = { linefold => $opt_linefold,
454             dictname => $opt_dictname,
455             printdict => $opt_printdict };
456 0           for my $data (@opt_dictdata) {
457 0           push @dicts, App::Greple::subst::Dict->new(
458             DATA => $data,
459             CONFIG => $config,
460             );
461             }
462 0           for my $file (@opt_dictfile) {
463 0 0         if (-d $file) {
464 0           warn "$file is directory\n";
465 0           next;
466             }
467 0           push @dicts, App::Greple::subst::Dict->new(
468             FILE => $file,
469             CONFIG => $config,
470             );
471             }
472              
473 0 0         if (@dicts == 0) {
474 0           warn "Module -Msubst requires dictionary data.\n";
475 0           main::usage();
476 0           die;
477             }
478             }
479              
480             sub subst_begin {
481 0     0 0   my %arg = @_;
482 0 0         $current_file = delete $arg{&FILELABEL} or die;
483 0 0         $contents = $_ if $remember_data;
484             }
485              
486 1     1   10 use Text::VisualWidth::PP;
  1         2  
  1         57  
487 1     1   8 use Text::VisualPrintf qw(vprintf vsprintf);
  1         2  
  1         66  
488 1     1   6 use List::Util qw(max any sum first);
  1         5  
  1         601  
489              
490             sub vwidth {
491 0 0 0 0 0   if (not defined $_[0] or length $_[0] == 0) {
492 0           return 0;
493             }
494 0           Text::VisualWidth::PP::width $_[0];
495             }
496              
497             my @match_list;
498              
499             sub subst_show_stat {
500 0     0 0   my %arg = @_;
501 0           my($from_max, $to_max) = (0, 0);
502 0           my $i = -1;
503 0           my @show_list;
504 0           for my $dict (@dicts) {
505 0           my @fromto = $dict->words;
506 0           my @show;
507 0           for my $p (@fromto) {
508 0           $i++;
509 0   0       $p // die;
510 0 0         if ($p->is_comment) {
511 0 0         push @show, [ $i, $p, {} ] if $opt_show_comment;
512 0           next;
513             }
514 0   0       my($from_re, $to) = ($p->string, $p->correct // '');
515 0   0       my $hash = $match_list[$i] // {};
516 0           my @keys = keys %{$hash};
  0            
517 0           my @ng = grep { $_ ne $to } @keys;
  0            
518 0           my @ok = grep { $_ eq $to } @keys;
  0            
519 0 0         if ($opt_check eq 'none' ) { next if @keys != 0 }
  0 0          
    0          
    0          
    0          
    0          
    0          
520 0 0         elsif ($opt_check eq 'any' ) { next if @keys == 0 }
521 0 0         elsif ($opt_check eq 'ok' ) { next if @ok == 0 }
522 0 0         elsif ($opt_check eq 'ng' ) { next if @ng == 0 }
523 0 0         elsif ($opt_check eq 'outstand') { next if @ng == 0 }
524             elsif ($opt_check eq 'all') { }
525 0           else { die }
526 0           $from_max = max $from_max, vwidth $from_re;
527 0           $to_max = max $to_max , vwidth $to;
528 0           push @show, [ $i, $p, $hash ];
529             }
530 0           push @show_list, [ $dict => \@show ];
531             }
532 0 0         if ($opt_show_numbers) {
533 1     1   8 no warnings 'uninitialized';
  1         6  
  1         661  
534             printf "HIT_PATTERN=%d/%d NG=%d, OK=%d, TOTAL=%d\n",
535             $stat{hit}, $stat{total},
536 0           $stat{ng}, $stat{ok}, $stat{ng} + $stat{ok};
537             }
538 0           for my $show_list (@show_list) {
539 0           my($dict, $show) = @{$show_list};
  0            
540 0 0         next if @$show == 0;
541 0           my $dict_format = ">>> %s <<<\n";
542 0 0         if ($opt_stat_item{dict}) {
543 0           print colorize('000/L24E', sprintf($dict_format, $dict->NAME));
544             }
545 0           for my $item (@$show) {
546 0           my($i, $p, $hash) = @$item;
547 0 0         if ($p->is_comment) {
548 0 0         say $p->comment if $opt_show_comment;
549 0           next;
550             }
551 0   0       my($from_re, $to) = ($p->string, $p->correct // '');
552 0           my @keys = keys %{$hash};
  0            
553 0 0         if ($opt_stat_style eq 'dict') {
554 0   0       vprintf("%-${from_max}s // %s", $from_re // '', $to // '');
      0        
555             } else {
556 0           my @ng = sort { $hash->{$b} <=> $hash->{$a} } grep { $_ ne $to } @keys
  0            
557 0 0         if $opt_stat_item{ng};
558 0           my @ok = grep { $_ eq $to } @keys
559 0 0         if $opt_stat_item{ok};
560 0 0 0       vprintf("%${from_max}s => ", $from_re // '') if $opt_stat_item{match};
561 0 0 0       vprintf("%-${to_max}s", $to // '') if $opt_stat_item{expect};
562 0 0         vprintf(" %4d:", $i + 1) if $opt_stat_item{number};
563 0           for my $key (@ng, @ok) {
564 0 0         my $index = $key eq $to ? $i * 2 + 1 : $i * 2;
565             printf(" %s(%s)",
566             main::index_color($index, $key),
567 0 0         colorize($key eq $to ? 'DB' : 'DR', $hash->{$key})
568             );
569             }
570             }
571 0           print "\n";
572             }
573             }
574 0           $_ = "";
575             }
576              
577 1     1   578 use App::Greple::Regions qw(match_regions merge_regions filter_regions);
  1         2707  
  1         927  
578              
579             sub subst_search {
580 0     0 0   my $text = $_;
581 0           my %arg = @_;
582 0 0         $current_file = delete $arg{&FILELABEL} or die;
583              
584 0           my @matched;
585 0           my $index = -1;
586 0           my @effective;
587 0           my $ng = {ng=>1, any=>1, all=>1, none=>1}->{$opt_check} ;
588 0           my $ok = { ok=>1, any=>1, all=>1, none=>1}->{$opt_check} ;
589 0           my $outstand = $opt_check eq 'outstand';
590 0           for my $dict (@dicts) {
591 0           for my $p ($dict->words) {
592 0           $index++;
593 0   0       $p // next;
594 0 0         next if $p->is_comment;
595 0   0       my($from_re, $to) = ($p->string, $p->correct // '');
596 0           my @match = match_regions pattern => $p->regex;
597              
598             ##
599             ## Remove all overlapped matches.
600             ##
601 0           my($in, $over, $out, $im, $om) = filter_regions \@match, \@matched;
602 0           @match = @$out;
603 0           for my $warn (
604             [ "Include", $in, $im, $opt_warn_include ],
605             [ "Overlap", $over, $om, $opt_warn_overlap ],
606             ) {
607 0           my($kind, $list, $match, $show) = @$warn;
608 0 0 0       $show and @$list or next;
609 0           for my $i (0 .. @$list - 1) {
610 0           my($a, $b) = ($list->[$i], $match->[$i]);
611 0           warn sprintf("%s \"%s\" with \"%s\" by #%d /%s/ in %s at %d\n",
612             $kind,
613             substr($_, $a->[0], $a->[1] - $a->[0]),
614             substr($_, $b->[0], $b->[1] - $b->[0]),
615             $index + 1, $p->string,
616             $current_file,
617             $a->[0],
618             );
619             }
620             }
621              
622 0           $stat{total}++;
623 0 0         $stat{hit}++ if @match;
624 0 0 0       next if @match == 0 and $opt_check ne 'all';
625              
626 0   0       my $hash = $match_list[$index] //= {};
627             my $callback = sub {
628 0     0     my($ms, $me, $i, $matched) = @_;
629 0 0         $stat{$i % 2 ? 'ok' : 'ng'}++;
630 0           my $s = $matched =~ s/$ignorechar_re//gr;
631 0           $hash->{$s}++;
632 0           my $format = @opt_format[ $i % @opt_format ];
633 0 0 0       sprintf($format,
634             ($opt_subst && $to ne '' && $s ne $to) ?
635             $to : $matched);
636 0           };
637 0           my(@ok, @ng);
638 0           for (@match) {
639 0           my $matched = substr $text, $_->[0], $_->[1] - $_->[0];
640 0 0         if ($matched =~ s/$ignorechar_re//gr ne $to) {
641 0           $_->[2] = $index * 2;
642 0           push @ng, $_;
643             } else {
644 0           $_->[2] = $index * 2 + 1;
645 0           push @ok, $_;
646             }
647 0           $_->[3] = $callback;
648             }
649 0 0 0       $effective[ $index * 2 ] = 1 if $ng || ( @ng && $outstand );
      0        
650 0 0 0       $effective[ $index * 2 + 1 ] = 1 if $ok || ( @ng && $outstand );
      0        
651              
652 0           @matched = merge_regions { nojoin => 1 }, @matched, @match;
653             }
654             }
655             ##
656             ## --select
657             ##
658 0 0         if (my $select = $opt_subst_select) {
659 0           my $max = sum map { int $_->words } @dicts;
  0            
660 1     1   15 use Getopt::EX::Numbers;
  1         6  
  1         323  
661 0           my $numbers = Getopt::EX::Numbers->new(min => 1, max => $max);
662 0           my @select;
663 0           for (my @select_index = do {
664 0           map { $_ * 2, $_ * 2 + 1 }
665 0           map { $_ - 1 }
666 0           grep { $_ <= $max }
667 0           map { $numbers->parse($_)->sequence }
  0            
668             split /,/, $select;
669             }) {
670 0           $select[$_] = 1;
671             }
672 0           @matched = grep $select[$_->[2]], @matched;
673             }
674 0           grep $effective[$_->[2]], @matched;
675             }
676              
677             1;
678              
679             __DATA__
680              
681             builtin dict=s @opt_dictfile
682             builtin dictdata=s @opt_dictdata
683             builtin stat-style=s $opt_stat_style
684             builtin stat-item=s @opt_stat_item
685             builtin printdict! $opt_printdict
686             builtin dictname! $opt_dictname
687             builtin subst-format=s @opt_format
688             builtin subst! $opt_subst
689             builtin check=s $opt_check
690             builtin select=s $opt_subst_select
691             builtin linefold! $opt_linefold
692             builtin remember! $remember_data
693             builtin warn-overlap! $opt_warn_overlap
694             builtin warn-include! $opt_warn_include
695             builtin ignore-space! $opt_ignore_space
696             builtin show-comment! $opt_show_comment
697              
698             option default \
699             -Mtermcolor::bg(default=100,light=--subst-color-light,dark=--subst-color-dark) \
700             --prologue subst_initialize \
701             --begin subst_begin \
702             --le +&subst_search --no-regioncolor
703              
704             ##
705             ## Now these options are implemented by -Mupdate module
706             ## --diffcmd, -U are built-in options
707             ##
708             autoload -Mupdate \
709             --update::diff \
710             --update::create \
711             --update::update \
712             --update::discard
713              
714             option --diff --subst --update::diff
715             option --create --subst --update::create
716             option --replace --subst --update::update --with-backup
717             option --overwrite --subst --update::update
718              
719             option --with-stat --epilogue subst_show_stat
720             option --stat --update::discard --with-stat
721              
722             autoload -Msubst::dyncmap --dyncmap
723              
724             help --subst-color-light light terminal color
725             option --subst-color-light --colormap --dyncmap \
726             range=0-2,except=000:111:222,shift=3,even="555D/%s",odd="IU;000/%s"
727              
728             help --subst-color-dark dark terminal color
729             option --subst-color-dark --colormap --dyncmap \
730             range=2-4,except=222:333:444,shift=1,even="D;L01/%s",odd="IU;%s/L01"
731              
732             ##
733             ## Handle included sample dictionaries.
734             ##
735              
736             option --exdict --dict $ENV{GREPLE_SUBST_DICT}/$<shift>
737              
738             option --exdictdir --prologue 'sub{ say "$ENV{GREPLE_SUBST_DICT}"; exit }'
739              
740             option --jtca-katakana-guide --exdict jtca-katakana-guide-3.dict
741             option --jtf-style-guide --exdict jtf-style-guide-3.dict
742             option --ms-style-guide --exdict ms-style-guide.dict
743              
744             option --sccc2 --exdict sccc2.dict
745             option --jtca --exdict jtca.dict
746             option --jtf --exdict jtf.dict
747             option --microsoft --exdict ms-amend.dict --exdict ms-style-guide.dict
748              
749             # deprecated. don't use.
750             option --ms --microsoft
751              
752             option --all-sample-dict --jtf --jtca --microsoft
753              
754             option --all-katakana --exdict all-katakana.dict
755              
756             option --dumpdict --printdict --prologue 'sub{exit}'
757              
758             # LocalWords: subst Greple greple ng ok outstand linefold dict diff
759             # LocalWords: regex Kazumasa Utashiro