File Coverage

/usr/local/bin/optex
Criterion Covered Total %
statement 173 302 57.2
branch 32 112 28.5
condition 17 45 37.7
subroutine 28 36 77.7
pod n/a
total 250 495 50.5


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2              
3 15     15   99218 use App::optex;
  15         2240  
  15         787  
4 15         2566034 my $version = $App::optex::VERSION;
5              
6              
7             =encoding utf8
8              
9             =head1 NAME
10              
11             optex - General purpose command option wrapper
12              
13             =head1 VERSION
14              
15             Version 1.0601
16              
17             =head1 SYNOPSIS
18              
19             B I [ B<-M>I ] ...
20              
21             or I -> B symlink, or
22              
23             B I [ -l | -m ] ...
24              
25             --link, --ln create symlink
26             --unlink, --rm remove symlink
27             --ls list link files
28             --rc list rc files
29             --nop, -x disable option processing
30             --[no]module disable module option on arguments
31              
32             =cut
33              
34              
35 15     15   197 use v5.14;
  15         51  
36 15     15   83 use warnings;
  15         126  
  15         772  
37              
38 15     15   8094 use utf8;
  15         4682  
  15         103  
39 15     15   10149 use Encode;
  15         307413  
  15         1629  
40 15     15   7424 use open IO => ':utf8';
  15         20630  
  15         83  
41              
42 15     15   10344 use Pod::Usage;
  15         866534  
  15         2264  
43 15     15   11282 use Data::Dumper;
  15         130280  
  15         25192  
44 15         58 $Data::Dumper::Sortkeys = 1;
45 15     15   152 use Cwd qw(abs_path);
  15         59  
  15         1131  
46 15     15   91 use List::Util qw(uniq max);
  15         27  
  15         4161  
47 15     15   10189 use Text::ParseWords qw(shellwords);
  15         25082  
  15         1132  
48 15     15   8324 use IO::File;
  15         121414  
  15         1930  
49 15     15   7952 use App::optex::Config;
  15         235193  
  15         34397  
50              
51 15     15   763 binmode STDOUT, ":encoding(utf8)";
  15         15086  
  15         279  
  15         90  
52 15         16279 binmode STDERR, ":encoding(utf8)";
53              
54 15         413 our $rcloader;
55 15   33     141 our $debug //= $ENV{DEBUG_OPTEX};
56 15         23 our $no_operation;
57 15         39 our $mod_opt = '-M';
58 15         28 our $mod_arg = 1; # Process -M option in target command
59 15         23 our $exit_code;
60              
61 15   33     116 my $command_path = $ENV{OPTEX_INVOKED_AS} // $0;
62 15 50       162 my($cmd_dir, $cmd_name) = ($command_path =~ m{ (.*) / ([^/]+) $ }x) or die;
63 15 50       800 my($abs_dir, $abs_name) = (abs_path($0) =~ m{ (.*) / ([^/]+) $ }x) or die;
64 15         68 my $env_MODULE_PATH = sprintf '%s_MODULE_PATH', uc($abs_name);
65 15         43 my $env_ROOT = sprintf '%s_ROOT', uc($abs_name);
66 15         37 my $env_BINDIR = sprintf '%s_BINDIR', uc($abs_name);
67 15         37 my $env_CONFIG = sprintf '%s_CONFIG', uc($abs_name);
68              
69 15 50       63 my $HOME = $ENV{HOME} or die "No \$HOME.\n";
70 15   33     105 my $config_dir = $ENV{$env_ROOT} || "${HOME}/.${abs_name}.d";
71 15         31 my $module_dir = $config_dir;
72 15   33     82 my $bin_dir = $ENV{$env_BINDIR} || "$config_dir/bin";
73 15   33     161 my $config_file = $ENV{$env_CONFIG} || "$config_dir/config.toml";
74              
75             ##
76             ## decode @ARGV
77             ##
78 15 50       96 @ARGV = map { utf8::is_utf8($_) ? $_ : decode('utf8', $_) } @ARGV;
  67         1267  
79              
80             ##
81             ## load config file
82             ##
83 15         365 my $config = App::optex::Config::load_config($config_file);
84 15   50     1484 my $alias = $config->{alias} //= {};
85 15   50     116 my $nomodule = $config->{"no-module"} //= [];
86 15         31 my %nomodule = do {
87 15 50       61 if (ref $nomodule eq 'ARRAY') {
    0          
88 15         25 map { $_ => 1 } @{$nomodule};
  0         0  
  15         53  
89             }
90             elsif (ref $nomodule eq 'HASH') {
91 0         0 %{$nomodule};
  0         0  
92             }
93             else {
94 0         0 ($nomodule => 1);
95             }
96             };
97              
98             ##
99             ## setup Getopt::EX
100             ##
101 15         8168 require Getopt::EX::Loader;
102 15         1237938 $rcloader = Getopt::EX::Loader->new(
103             BASECLASS => [ '', 'App::optex', 'Getopt::EX' ],
104             IGNORE_NO_MODULE => 1,
105             );
106              
107 15         880 load_rc("$config_dir/default.rc");
108              
109             ##
110             ## setup module search path
111             ##
112             my @private_mod_path = (
113 15         30 do {
114 15 50       79 if (my $mod_path = $ENV{$env_MODULE_PATH}) {
115 0         0 split /:/, $mod_path;
116             } else {
117 15         46 ();
118             }
119             },
120             $module_dir,
121             );
122              
123 15         85 prepend_path(@private_mod_path);
124              
125             ##
126             ## get target command name
127             ##
128 15         22 my $target_name = do {
129 15 50       63 if ($cmd_name ne $abs_name) {
130 0         0 $cmd_name;
131             } else {
132 15         69 self_option(\@ARGV);
133 12 100       3686 if (@ARGV) {
134 11         69 shift @ARGV;
135             } else {
136 1         6 usage();
137 0         0 exit 1;
138             }
139             }
140             };
141              
142             ##
143             ## alias
144             ##
145 11         41 my $aliased_name;
146 11 50       105 if (my $alias = $alias->{$target_name}) {
147 0         0 my($name, @opts) = do {
148 0 0       0 if (ref $alias eq 'ARRAY') {
149 0         0 @{$alias};
  0         0  
150             } else {
151 0         0 shellwords $alias;
152             }
153             };
154 0 0       0 if ($name ne '') {
155 0         0 $aliased_name = $name;
156 0         0 unshift @ARGV, @opts;
157             }
158             }
159              
160 11 50       57 if ($nomodule{$target_name}) {
161 0         0 $mod_arg = 0;
162             }
163              
164             ##
165             ## prepare command specific module path
166             ##
167             my @command_mod_path =
168 11         53 grep { -d $_ } map { "$_/$target_name" } @private_mod_path;
  11         282  
  11         67  
169              
170 11         164 prepend_path(@command_mod_path, @private_mod_path);
171              
172 11 50 66     144 if ($mod_arg and @ARGV > 0 and $ARGV[0] eq $mod_opt) {
      66        
173 0         0 show_modules();
174 0         0 exit;
175             }
176              
177             ##
178             ## load command specific rc file
179             ##
180 11 50       52 unless ($no_operation) {
181 11         91 load_rc("$config_dir/$target_name.rc");
182 11         104 $rcloader->configure(PARSE_MODULE_OPT => $mod_arg);
183 11         569 $rcloader->deal_with(\@ARGV);
184             }
185              
186 11   33     20726 my $exec_name = $aliased_name || search_path($target_name);
187 11 50       61 unless (defined $exec_name) {
188 0         0 warn "$abs_name: $target_name: command not found\n";
189 0         0 exit 127;
190             }
191              
192 11 50       54 warn "$abs_name: exec $exec_name @ARGV\n" if $debug;
193              
194             ##
195             ## execute target command
196             ##
197 11         728616 my $status = system $exec_name, @ARGV;
198              
199             END {
200 15 50   15   119086 if (defined $exit_code) {
    100          
201 0         0 $? = $exit_code;
202             } elsif (defined $status) {
203 11         155 $? = $status >> 8;
204             }
205 15         774 close STDERR;
206 15         0 close STDOUT;
207             }
208              
209             ######################################################################
210              
211             sub load_rc {
212 26     26   73 my $rc = shift;
213 26         162 $rcloader->load(FILE => $rc);
214 26 50       732 warn "$abs_name: load $rc\n" if $debug;
215             }
216              
217             sub search_path {
218 11     11   35 my $new = shift;
219 11 50 33     131 return $new if $new =~ m{/} && -x $new;
220              
221             # Scan PATH and return the first executable that is not the running
222             # optex binary itself (avoid re-entering the wrapper and looping).
223 11         654 my $self_abs = abs_path("$abs_dir/$abs_name");
224              
225 11   50     277 for my $path (split /:+/, $ENV{PATH} // '') {
226 81         143 my $candidate = "$path/$new";
227 81 100       913 -x $candidate or next;
228 11 50 33     83 next if $path eq $cmd_dir and $new eq $cmd_name;
229              
230 11   33     32 my $cand_abs = eval { abs_path($candidate) } || $candidate;
231 11 50       62 next if $cand_abs eq $self_abs;
232 11         74 return $candidate;
233             }
234              
235 0         0 return;
236             }
237              
238             sub self_option {
239 15     15   33 my $argv = shift;
240              
241 15         85 local $rcloader = Getopt::EX::Loader->new(
242             BASECLASS => [ '', 'App::optex', 'Getopt::EX' ],
243             );
244 15         734 $rcloader->load(FILE => "$config_dir/$abs_name.rc");
245 15         362 $rcloader->deal_with($argv);
246              
247 15     15   11883 use Getopt::Long qw(GetOptionsFromArray);
  15         205100  
  15         74  
248 14         1076203 Getopt::Long::Configure(qw"bundling require_order");
249 15     15   15230 use Getopt::EX::Hashed qw(has); {
  15         136329  
  15         158  
250 14         4503 Getopt::EX::Hashed->configure(DEFAULT => [ is => 'ro' ]);
  14         857  
251 14     0   1390 has debug => " ! d " , action => sub { $debug = $_[1] };
  0         0  
252 14         1982 has version => " v " ;
253 14         928 has man => " h " ;
254 14         2031 has link => " ln " ;
255 14         927 has unlink => " rm " ;
256 14         819 has force => " f " ;
257 14         857 has ls => " " ;
258 14         896 has rc => " " ;
259 14         1103 has long => " l " ;
260 14         1269 has path => " p " ;
261 14         1292 has cat => " m " ;
262 14         2532 has M => " " ;
263 14     0   1081 has module => " ! " , action => sub { $mod_arg = $_[1] };
  0         0  
264 14     0   1025 has nop => " ! x " , action => sub { $no_operation = $_[1] };
  0         0  
265 14     0   1181 has exit => " =i " , action => sub { $exit_code = $_[1] } ;
  0         0  
266 15     15   6220 }; no Getopt::EX::Hashed;
  15         26  
  15         74  
267 14         1202 my $opt = Getopt::EX::Hashed->new->reset();
268 14 50       15634 GetOptionsFromArray($argv, $opt->optspec, $rcloader->builtins) or usage();
269              
270 14 50 33     42745 if ($opt->man) {
    100          
    50          
    50          
    50          
    50          
271 0         0 exec "perldoc $abs_name";
272 0         0 die "exec: $!";
273             }
274             elsif ($opt->version) {
275 2         72 print $version, "\n";
276 2         21 exit;
277             }
278             elsif ($opt->link || $opt->unlink) {
279 0         0 _symlink($argv, $opt);
280 0         0 exit 0;
281             }
282             elsif ($opt->ls) {
283 0         0 _ls($argv, $opt);
284 0         0 exit 0;
285             }
286             elsif ($opt->rc) {
287 0         0 _rc($argv, $opt);
288 0         0 exit 0;
289             }
290             elsif ($opt->M) {
291 0         0 show_modules();
292 0         0 exit 0;
293             }
294              
295 12         822 return;
296             }
297              
298             sub _symlink {
299 0     0   0 my($argv, $op) = @_;
300 0 0       0 -d $bin_dir or die "Directory $bin_dir does not exist.\n";
301              
302 0   0     0 my $script_path = $ENV{OPTEX_SCRIPT_PATH} // $0;
303 0         0 my @target = @$argv;
304 0         0 for my $target (@target) {
305 0         0 my $link = "$bin_dir/$target";
306 0 0       0 if ($op->{link}) {
    0          
307 0 0       0 -f $link and do { warn "$link already exists.\n"; next };
  0         0  
  0         0  
308 0 0       0 symlink $script_path, $link or die "$link: $!\n";
309 0         0 print "$link -> $script_path\n";
310             }
311             elsif ($op->{unlink}) {
312 0 0       0 -l $link or do { warn "$link is not symlink\n"; next };
  0         0  
  0         0  
313 0 0       0 if ((my $name = readlink $link) ne $script_path ) {
314 0 0       0 if (not $op->{force}) {
315 0         0 warn
316             "$link has unexpected link: -> $name\n" .
317             "Use -f option to force unlink.\n" ;
318 0         0 next;
319             }
320             }
321 0 0       0 unlink $link or die "$link: $!\n";
322 0         0 print "$link removed.\n";
323             }
324             }
325             }
326              
327 15     15   21888 use Text::VisualWidth::PP 'vwidth';
  15         66222  
  15         1792  
328              
329             sub _ls {
330 0     0   0 my($argv, $op) = @_;
331              
332 15     15   7536 use IO::Dir;
  15         176555  
  15         12401  
333 0 0       0 my $dir = IO::Dir->new($bin_dir) or die "$bin_dir: $!\n";
334 0         0 my @dirent = do {
335 0         0 sort { $a cmp $b }
336 0         0 map { decode 'utf8', $_ }
337 0         0 grep { not /^\./ }
  0         0  
338             $dir->read;
339             };
340 0         0 $dir->close;
341              
342 0         0 my @aliases = sort keys %{$alias};
  0         0  
343              
344 0         0 my $max = max map { vwidth $_ } @dirent, @aliases;
  0         0  
345              
346 0         0 print "[link]\n";
347 0         0 for my $ent (@dirent) {
348 0         0 my $path = "$bin_dir/$ent";
349 0 0       0 if (-l $path) {
350 0         0 print "\t";
351 0 0       0 print $op->{path} ? $path : $ent;
352 0 0       0 if ($op->{long}) {
353 0         0 print ' ' x ($max - vwidth $ent);
354 0         0 my $target = do {
355             # if (my $val = $alias->{$ent}) {
356             # ref($val) eq 'ARRAY' ? join(' ', @$val) : $val;
357             # } else {
358 0         0 readlink $path;
359             # }
360             };
361 0         0 print " -> ", $target;
362             }
363 0         0 print "\n";
364             }
365 0 0       0 warn "$ent: not exist\n" unless -f $path;
366             }
367              
368 0         0 print "[alias]\n";
369 0         0 for my $key (@aliases) {
370 0         0 my $val = $alias->{$key};
371 0 0       0 my $command = ref($val) eq 'ARRAY' ? join(' ', @$val) : $val;
372 0         0 print "\t";
373 0         0 print $key;
374 0 0       0 if ($op->{long}) {
375 0         0 print ' ' x ($max - vwidth $key);
376 0         0 printf " => %s", $command;
377             }
378 0         0 print "\n";
379             }
380             }
381              
382             sub _rc {
383 0     0   0 my($argv, $op) = @_;
384 0         0 my @command = @$argv;
385 0         0 my @rc = map { s/(?
  0         0  
386 0         0 my %rc = map { ($_ => 1) } @rc;
  0         0  
387 15     15   148 use IO::Dir;
  15         23  
  15         3623  
388 0 0       0 my $dir = IO::Dir->new($config_dir) or die "$config_dir: $!\n";
389 0         0 my @dirent = do {
390 0 0       0 grep { %rc == 0 or $rc{$_} }
391 0 0       0 grep { /\.rc$/ }
  0         0  
392             @rc ? @rc : sort $dir->read
393             };
394 0         0 $dir->close;
395              
396 0         0 for my $ent (@dirent) {
397 0         0 my $path = "$config_dir/$ent";
398 0 0       0 if ($op->{cat}) {
399 15     15   119 use IO::File;
  15         31  
  15         11089  
400 0 0       0 my $fh = IO::File->new($path) or do {
401 0         0 warn "$path: $!\n";
402 0         0 next;
403             };
404 0         0 while (<$fh>) {
405 0 0       0 print "$ent:" if @dirent > 1;
406 0         0 print;
407             }
408 0         0 $fh->close;
409             } else {
410 0 0       0 print $op->{long} ? $path : $ent;
411 0         0 print "\n";
412             }
413             }
414             }
415              
416             ######################################################################
417              
418             sub usage {
419 1     1   19 pod2usage(-verbose => 0,
420             -message => <<" EOS" =~ s/^\s+//mgr
421             Use `perldoc $abs_name` for document.
422             Use `$abs_name [command] ${mod_opt}help` for available options.
423             EOS
424             );
425             }
426              
427 11     15   481 my @ORIG_INC; BEGIN { @ORIG_INC = @INC }
  15         39982  
428 11         2493 my @mod_path;
429             sub prepend_path {
430 26     26   201 @mod_path = uniq @_;
431 26         411 @INC = (@mod_path, @ORIG_INC);
432             }
433              
434             sub show_modules {
435             my $path = @_ ? shift : [
436             @mod_path,
437 0 0   0     grep { -d } map { "$_/App/optex" } @INC,
  0            
  0            
438             ];
439 0           print "MODULES:\n";
440 0           for my $path (@$path) {
441 0           my($name) = $path =~ m:([^/]+)$:;
442 0           my @module = do {
443             # grep { not /\bdefault\.pm$/ }
444 0           glob "$path/[a-z0-9]*.pm";
445             };
446 0 0         next unless @module;
447 0           print " $path\n";
448 0           for my $mod (@module) {
449 0           printf " ${mod_opt}%s\n", $mod =~ /([^\/]*)\.pm/;
450             }
451 0           print "\n";
452             }
453             }
454              
455             =head1 DESCRIPTION
456              
457             B is a general purpose command option handling wrapper
458             utilizing Perl module L. It enables user to define their
459             own option aliases for any commands on the system, and provide module
460             style extensibility.
461              
462             Target command is given as an argument:
463              
464             % optex command
465              
466             or as a symbolic linked file to B:
467              
468             command -> optex
469              
470             If the configuration file F<~/.optex.d/>IF<.rc> exists, it is
471             evaluated before execution and command arguments are pre-processed
472             using it.
473              
474              
475             =head2 OPTION ALIASES
476              
477             Think of macOS's C command, which does not have C<-I[TIMESPEC]>
478             option. Using B, these can be implemented by preparing
479             following setting in F<~/.optex.d/date.rc> file.
480              
481             option -I -Idate
482             option -Idate +%F
483             option -Iseconds +%FT%T%z
484             option -Iminutes +%FT%H:%M%z
485             option -Ihours +%FT%H%z
486              
487             option --iso-8601 -I
488             option --iso-8601=date -Idate
489             option --iso-8601=seconds -Iseconds
490             option --iso-8601=minutes -Iminutes
491             option --iso-8601=hours -Ihours
492              
493             Then next command will work as expected.
494              
495             % optex date -Iseconds
496              
497             If a symbolic link C<< date -> optex >> is found in command search
498             path, you can use it just same as standard command, but with
499             unsupported options.
500              
501             % date -Iseconds
502              
503             Common configuration is stored in F<~/.optex.d/default.rc> file, and
504             those rules are applied to all commands executed through B.
505              
506             Actually, C<--iso-8601> option can be defined simpler as this:
507              
508             option --iso-8601 -I$
509              
510             This works fine almost always, but fails with sole C<--iso-8601>
511             option preceding other option like this:
512              
513             % date --iso-8601 -u
514              
515             =head2 COMMAND ALIASES
516              
517             B's command alias is no different from the alias function of
518             shell, but it is effective in that it can be executed as a command
519             from a tool or script, and can be managed collectively in a
520             configuration file.
521              
522             Command aliases can be set in the configuration file
523             (F<~/.optex.d/config.toml>) like this:
524              
525             [alias]
526             tc = "optex -Mtextconv"
527              
528             You can make symbolic link from C to C like this:
529              
530             % optex --ln tc
531              
532             And include F<$HOME/.optex.d/bin> in your C evnironment.
533              
534             The C module can be used to convert files given as arguments
535             to plain text. Defined in this way, Word files can be compared as
536             follows.
537              
538             % tc diff A.docx B.docx
539              
540             Alias name is used to find rc file and module directory. In the above
541             example, F<~/.optex.d/tc.rc> and F<~/.optex.d/tc/> will be referred.
542              
543             It is also possible to write shell scripts in the config file. The
544             following example implements the C-shell C command.
545              
546             [alias]
547             repeat = [ 'bash', '-c', '''
548             while getopts 'c:i:' OPT; do
549             case $OPT in
550             c) count=$OPTARG;;
551             i) sleep=$OPTARG;;
552             esac
553             done; shift $((OPTIND - 1))
554             case $1 in
555             [0-9]*) count=$1; shift;;
556             esac
557             while ((count--)); do
558             eval "$*"
559             [ "$sleep" ] && (( count > 0 )) && sleep $sleep
560             done
561             ''', 'repeat' ]
562              
563             Read L section.
564              
565             =head2 MACROS
566              
567             Complex string can be composed using macro C. Next example is
568             an awk script to count vowels in the text, to be declared in file
569             F<~/.optex.d/awk.rc>.
570              
571             define __delete__ /[bcdfgkmnpsrtvwyz]e( |$)/
572             define __match__ /ey|y[aeiou]*|[aeiou]+/
573             define __count_vowels__ <
574             {
575             s = tolower($0);
576             gsub(__delete__, " ", s);
577             for (count=0; match(s, __match__); count++) {
578             s=substr(s, RSTART + RLENGTH);
579             }
580             print count " " $0;
581             }
582             EOS
583             option --vowels __count_vowels__
584              
585             This can be used like this:
586              
587             % awk --vowels /usr/share/dict/words
588              
589             When setting complex option, C directive is useful. C
590             works almost same as C
591             scope, and not available for command line option.
592              
593             expand repository ( -name .git -o -name .svn -o -name RCS )
594             expand no_dots ! -name .*
595             expand no_version ! -name *,v
596             expand no_backup ! -name *~
597             expand no_image ! -iname *.jpg ! -iname *.jpeg \
598             ! -iname *.gif ! -iname *.png
599             expand no_archive ! -iname *.tar ! -iname *.tbz ! -iname *.tgz
600             expand no_pdf ! -iname *.pdf
601              
602             option --clean \
603             repository -prune -o \
604             -type f \
605             no_dots \
606             no_version no_backup \
607             no_image \
608             no_archive \
609             no_pdf
610              
611             % find . --clean -print
612              
613              
614             =head2 MODULES
615              
616             B also supports module extension. In the example of C,
617             module file is found at F<~/.optex.d/date/> directory. If default
618             module, F<~/.optex.d/date/default.pm> exists, it is loaded
619             automatically on every execution.
620              
621             This is a normal Perl module, so package declaration and the final
622             true value is necessary. Between them, you can put any kind of Perl
623             code. For example, next program set environment variable C to
624             C before executing C command.
625              
626             package default;
627             $ENV{LANG} = 'C';
628             1;
629              
630             % /bin/date
631             2017年 10月22日 日曜日 18時00分00秒 JST
632              
633             % date
634             Sun Oct 22 18:00:00 JST 2017
635              
636             Other modules are loaded using C<-M> option. Unlike other options,
637             C<-M> have to be placed at the beginning of argument list. Module
638             files in F<~/.optex.d/date/> directory are used only for C
639             command. If the module is placed on F<~/.optex.d/> directory, it can
640             be used from all commands.
641              
642             If you want use C<-Mes> module, make a file F<~/.optex.d/es.pm> with
643             following content.
644              
645             package es;
646             $ENV{LANG} = 'es_ES';
647             1;
648              
649             % date -Mes
650             domingo, 22 de octubre de 2017, 18:00:00 JST
651              
652             When the specified module was not found in library path, B
653             ignores the option and stops argument processing immediately. Ignored
654             options are passed through to the target command.
655              
656             Module is also used with subroutine call. Suppose
657             F<~/.optex.d/env.pm> module look like:
658              
659             package env;
660             sub setenv {
661             while (($a, $b) = splice @_, 0, 2) {
662             $ENV{$a} = $b;
663             }
664             }
665             1;
666              
667             Then it can be used in more generic fashion. In the next example,
668             first format is easy to read, but second one is more easy to type
669             because it does not have special characters to be escaped.
670              
671             % date -Menv::setenv(LANG=de_DE) # need shell quote
672             % date -Menv::setenv=LANG=de_DE # alternative format
673             So 22 Okt 2017 18:00:00 JST
674              
675             Option aliases can be also declared in the module, at the end of file,
676             following special literal C<__DATA__>. Using this, you can prepare
677             multiple set of options for different purposes. Think about generic
678             B module:
679              
680             package i18n;
681             1;
682             __DATA__
683             option --cn -Menv::setenv(LANG=zh_CN) // 中国語 - 簡体字
684             option --tw -Menv::setenv(LANG=zh_TW) // 中国語 - 繁体字
685             option --us -Menv::setenv(LANG=en_US) // 英語
686             option --fr -Menv::setenv(LANG=fr_FR) // フランス語
687             option --de -Menv::setenv(LANG=de_DE) // ドイツ語
688             option --it -Menv::setenv(LANG=it_IT) // イタリア語
689             option --jp -Menv::setenv(LANG=ja_JP) // 日本語
690             option --kr -Menv::setenv(LANG=ko_KR) // 韓国語
691             option --br -Menv::setenv(LANG=pt_BR) // ポルトガル語 - ブラジル
692             option --es -Menv::setenv(LANG=es_ES) // スペイン語
693             option --ru -Menv::setenv(LANG=ru_RU) // ロシア語
694              
695             This can be used like:
696              
697             % date -Mi18n --tw
698             2017年10月22日 週日 18時00分00秒 JST
699              
700             You can declare autoload module in your F<~/.optex.d/optex.rc> like:
701              
702             autoload -Mi18n --cn --tw --us --fr --de --it --jp --kr --br --es --ru
703              
704             Then you can use them without module option. In this case, option
705             C<--ru> is replaced by C<-Mi18n --ru> automatically.
706              
707             % date --ru
708             воскресенье, 22 октября 2017 г. 18:00:00 (JST)
709              
710             Module C is implemented as L and included in
711             this distribution. So it can be used as above without additional
712             installation.
713              
714             Modules can also define built-in options using C directive in
715             the C<__DATA__> section. Built-in options are processed by
716             L and must be specified before the target command name.
717             For example:
718              
719             optex -Mxform --xform-visible=2 cat file
720              
721             Here C<--xform-visible> is a built-in option defined in the C
722             module.
723              
724             =head1 STANDARD MODULES
725              
726             Standard modules are installed at C, and they can be
727             addressed with and without C prefix.
728              
729             =over 4
730              
731             =item -MB
732              
733             Print available option list. Option name is printed with substitution
734             form, or help message if defined. Use B<-x> option to omit help
735             message.
736              
737             Option B<--man> or B<-h> will print document if available. Option
738             B<-l> will print module path. Option B<-m> will show the module
739             itself. When used after other modules, print information about the
740             last declared module. Next command show the document about B
741             module.
742              
743             % optex -Mfirst -Msecond -Mhelp --man
744              
745             =item -MB
746              
747             Print debug messages.
748              
749             =item -MB
750              
751             Module to manipulate command argument.
752             See L for detail.
753              
754             =item -MB
755              
756             Module to implement command input/output filters.
757             See L for detail.
758              
759             =back
760              
761             =head1 Getopt::EX MODULES
762              
763             In addition to its own modules, B can also use C
764             modules. The standard C modules installed are these.
765              
766             =over 4
767              
768             =item -MB (L)
769              
770             You can display a Greek calendar by doing the following:
771              
772             optex -Mi18n cal --gr
773              
774             =back
775              
776             =head1 OPTIONS
777              
778             These options are not effective when B was executed from
779             symbolic link.
780              
781             =over 4
782              
783              
784             =item B<--link>, B<--ln> [ I ]
785              
786             Create symbolic link in F<~/.optex.d/bin> directory.
787              
788              
789             =item B<--unlink>, B<--rm> [ B<-f> ] [ I ]
790              
791             Remove symbolic link in F<~/.optex.d/bin> directory.
792              
793              
794             =item B<--ls> [ B<-l> ] [ I ]
795              
796             List symbolic link files in F<~/.optex.d/bin> directory.
797              
798              
799             =item B<--rc> [ B<-l> ] [ B<-m> ] [ I ]
800              
801             List rc files in F<~/.optex.d> directory.
802              
803              
804             =item B<--nop>, B<-x> I
805              
806             Stop option manipulation. Use full pathname otherwise.
807              
808              
809             =item B<-->[B]B
810              
811             B deals with module option (-M) on target command by default.
812             However, there is a command which also uses same option for own
813             purpose. Option B<--nomodule> disables that behavior. Other option
814             interpretation is still effective, and there is no problem using
815             module option in rc or module files.
816              
817              
818             =item B<--exit> I
819              
820             Usually B exits with status of executed command. This option
821             override it and force to exit with specified status code.
822              
823              
824             =back
825              
826              
827             =head1 CONFIGURATION FILE
828              
829             When starting up, B reads configuration file
830             F<~/.optex.d/config.toml> which is supposed to be written in TOML
831             format.
832              
833             =head2 PARAMETERS
834              
835             =over 4
836              
837             =item B
838              
839             Set commands for which B does not interpret module option
840             B<-M>. If the target command is found in this list, it is executed as
841             if option B<--no-module> is given to B.
842              
843             no-module = [
844             "greple",
845             "pgrep",
846             ]
847              
848             =item B
849              
850             Set command aliases. Example:
851              
852             [alias]
853             pgrep = [ "greple", "-Mperl", "--code" ]
854             hello = "echo -n 'hello world!'"
855              
856             Command alias can be invoked either from symbolic link and command
857             argument.
858              
859             =item B
860              
861             Load extra TOML fragments before applying the main configuration.
862             Accepts either a string or an array. Each entry may be a literal path
863             or a glob pattern. Tilde is expanded, and relative paths are resolved
864             against the file that declared the include. Includes are processed
865             depth-first; hashes merge recursively, arrays append, and scalars use
866             last-in-wins semantics. The main F is applied last so it
867             can override included values.
868              
869             include = [
870             "~/.optex.d/config.d/*.toml",
871             "local.toml",
872             ]
873              
874             Include directives can nest; cycles (including self-globs) are detected
875             and reported as errors.
876              
877             =back
878              
879              
880             =head1 FILES AND DIRECTORIES
881              
882             =over 4
883              
884              
885             =item F
886              
887             System module directory.
888              
889              
890             =item F<~/.optex.d/>
891              
892             Personal root directory.
893              
894              
895             =item F<~/.optex.d/config.toml>
896              
897             Configuration file.
898              
899              
900             =item F<~/.optex.d/default.rc>
901              
902             Common startup file.
903              
904              
905             =item F<~/.optex.d/>IF<.rc>
906              
907             Startup file for I.
908              
909              
910             =item F<~/.optex.d/>IF
911              
912             Module directory for I.
913              
914              
915             =item F<~/.optex.d/>IF
916              
917             Default module for I.
918              
919              
920             =item F<~/.optex.d/bin>
921              
922             Default directory to store symbolic links.
923              
924             This is not necessary, but it seems a good idea to make special
925             directory to contain symbolic links for B, placing it in your
926             command search path. Then you can easily add/remove it from the path,
927             or create/remove symbolic links.
928              
929             =back
930              
931              
932             =head1 ENVIRONMENT
933              
934             =over 4
935              
936             =item OPTEX_ROOT
937              
938             Override default root directory F<~/.optex.d>.
939              
940             =item OPTEX_CONFIG
941              
942             Override default configuration file F.
943              
944             =item OPTEX_MODULE_PATH
945              
946             Set module paths separated by colon (C<:>). These are inserted before
947             standard path.
948              
949             =item OPTEX_BINDIR
950              
951             Override default symbolic link directory F.
952              
953             =item OPTEX_SCRIPT_PATH
954              
955             Specify the path to use when creating symbolic links with C<--ln>.
956             If not set, C<$0> is used. This is useful when B is invoked
957             through a wrapper script (e.g., Homebrew installation) and you want
958             the symbolic links to point to the wrapper rather than the actual
959             script.
960              
961             =item OPTEX_INVOKED_AS
962              
963             Specify the original command path used to invoke B. This is
964             useful when B is invoked through a wrapper script and the
965             original C<$0> is lost. When a symbolic link to the wrapper is
966             executed, the wrapper should set this variable to its own C<$0> so
967             that B can determine the correct command name.
968              
969             =back
970              
971              
972             =head1 SEE ALSO
973              
974             L, L, L
975              
976             L
977              
978             L
979              
980             =head1 AUTHOR
981              
982             Kazumasa Utashiro
983              
984              
985             =head1 LICENSE
986              
987             You can redistribute it and/or modify it under the same terms
988             as Perl itself.
989              
990             Copyright ©︎ 2017-2026 Kazumasa Utashiro
991              
992              
993             =cut
994              
995              
996             # LocalWords: optex rc iso greple awk pdf LANG ENV Oct JST domingo
997             # LocalWords: setenv autoload PERLLIB BINDIR Utashiro Kazumasa