File Coverage

blib/lib/Getopt/EX/Loader.pm
Criterion Covered Total %
statement 161 207 77.7
branch 57 96 59.3
condition 4 21 19.0
subroutine 28 33 84.8
pod 4 20 20.0
total 254 377 67.3


line stmt bran cond sub pod time code
1             package Getopt::EX::Loader;
2              
3             our $VERSION = "3.03";
4              
5 6     6   260905 use v5.14;
  6         15  
6 6     6   21 use warnings;
  6         8  
  6         288  
7 6     6   27 use utf8;
  6         7  
  6         34  
8 6     6   112 use Carp;
  6         9  
  6         367  
9              
10 6     6   26 use Exporter 'import';
  6         8  
  6         478  
11             our @EXPORT = qw();
12             our %EXPORT_TAGS = ( );
13             our @EXPORT_OK = qw();
14              
15 6     6   26 use Data::Dumper;
  6         7  
  6         278  
16 6     6   51 use List::Util qw(pairmap);
  6         9  
  6         375  
17              
18 6     6   2261 use Getopt::EX::Module;
  6         13  
  6         343  
19 6     6   31 use Getopt::EX::Func qw(parse_func);
  6         8  
  6         267  
20 6     6   2373 use Getopt::EX::Colormap qw(colorize);
  6         21  
  6         17538  
21              
22             our $debug = 0;
23              
24             sub new {
25 10     10 0 465428 my $class = shift;
26              
27 10         93 my $obj = bless {
28             BUCKETS => [],
29             BASECLASS => undef,
30             MODULE_OPT => '-M',
31             DEFAULT => 'default',
32             PARSE_MODULE_OPT => 1,
33             IGNORE_NO_MODULE => 0,
34             IGNORE_NO_FILE => 1,
35             }, $class;
36              
37 10 50       64 $obj->configure(@_) if @_;
38              
39 10         28 $obj;
40             }
41              
42             our @OPTIONS = qw(
43             RCFILE
44             BASECLASS
45             MODULE_OPT
46             DEFAULT
47             PARSE_MODULE_OPT
48             IGNORE_NO_MODULE
49             IGNORE_NO_FILE
50             );
51              
52             sub configure {
53 10     10 1 16 my $obj = shift;
54 10         31 my %opt = @_;
55              
56 10         25 for my $opt (@OPTIONS) {
57 70 100       462 next if $opt eq 'RCFILE';
58 60 100       116 if (exists $opt{$opt}) {
59 10         46 $obj->{$opt} = delete $opt{$opt};
60             }
61             }
62              
63 10 100       33 if (my $rc = delete $opt{RCFILE}) {
64 1 50       4 my @rc = ref $rc eq 'ARRAY' ? @$rc : $rc;
65 1         29 for (@rc) {
66 1         4 $obj->load(FILE => $_);
67             }
68             }
69              
70 10 50       27 warn "Unknown option: ", Dumper \%opt if %opt;
71              
72 10         67 $obj;
73             }
74              
75             sub baseclass {
76 46     46 0 67 my $obj = shift;
77             @_ ? $obj->{BASECLASS} = shift
78 46 50       195 : $obj->{BASECLASS};
79             }
80              
81             sub buckets {
82 127     127 1 154 my $obj = shift;
83 127         129 @{ $obj->{BUCKETS} };
  127         247  
84             }
85              
86             sub append {
87 12     12 0 15 my $obj = shift;
88 12         27 push @{ $obj->{BUCKETS} }, @_;
  12         31  
89             }
90              
91             sub load {
92 22     22 0 29 my $obj = shift;
93 22         64 my %arg = @_;
94 22 100 66     124 if ($obj->{IGNORE_NO_FILE} and my $file = $arg{FILE}) {
95 2 50       47 return undef unless -f $file;
96             }
97 22         57 my $bucket =
98             Getopt::EX::Module->new(@_, BASECLASS => $obj->baseclass);
99 12         48 $obj->append($bucket);
100 12         49 $bucket;
101             }
102              
103             sub load_file {
104 1     1 1 12 my $obj = shift;
105 1         4 $obj->load(FILE => shift);
106             }
107              
108             sub load_module {
109 24     24 1 41 my $obj = shift;
110 24         39 my $name = shift;
111 24 100       61 if (my $mod = $obj->find_module($name)) {
112 4         15 return $mod;
113             }
114 20         54 $obj->load(MODULE => $name);
115             }
116              
117             sub find_module {
118 24     24 0 36 my $obj = shift;
119 24         34 my $name = shift;
120 24         58 my $base = $obj->baseclass;
121 24 100       71 my @class = ref $base ? @$base : $base;
122 24         52 for my $class (@class) {
123 27         51 my $module = "$class\::$name";
124 27         60 for ($obj->buckets) {
125 11 100       37 if ($_->module eq $module) {
126 4         20 return $_;
127             }
128             }
129             }
130 20         62 undef;
131             }
132              
133             sub defaults {
134 0     0 0 0 my $obj = shift;
135 0         0 map { $_->default } $obj->buckets;
  0         0  
136             }
137              
138             sub calls {
139 0     0 0 0 my $obj = shift;
140 0         0 map { $_->call } $obj->buckets;
  0         0  
141             }
142              
143             sub builtins {
144 3     3 0 3 my $obj = shift;
145 3         9 map { $_->builtin } $obj->buckets;
  4         10  
146             }
147              
148             sub hashed_builtins {
149 2     2 0 3 my $obj = shift;
150 2         4 my $hash = shift;
151             pairmap {
152 8 50   8   33 my($key) = $a =~ /^([-\w]+)/ or die;
153 8         11 $hash->{$key} = $b;
154 8         24 $a;
155 2         10 } $obj->builtins;
156             }
157              
158             sub deal_with {
159 10     10 0 2980 my $obj = shift;
160 10         19 my $argv = shift;
161              
162 10 50       55 if (my $default = $obj->{DEFAULT}) {
163 10 50       18 if (my $bucket = eval { $obj->load_module($default) }) {
  10 50       36  
164 0         0 $bucket->run_inits($argv);
165             } elsif ($@ !~ /Can't locate|need to install/) {
166 0         0 die $@;
167             }
168             }
169 10 50       72 $obj->modopt($argv) if $obj->{PARSE_MODULE_OPT};
170 10         64 $obj->expand($argv);
171 10         31 $obj;
172             }
173              
174             sub modopt {
175 35     35 0 47 my $obj = shift;
176 35         43 my $argv = shift;
177              
178 35   50     110 my $start = $obj->{MODULE_OPT} // return ();
179 35 50       64 $start eq '' and return ();
180 35         266 my $start_re = qr/\Q$start\E/;
181 35         49 my @modules;
182 35         63 while (@$argv) {
183 43 100       295 if (my($modpart) = ($argv->[0] =~ /^$start_re(.+)/)) {
184 11         39 debug_argv($argv);
185 11 50       37 if (my $mod = $obj->parseopt($modpart, $argv)) {
186 11         29 push @modules, $mod;
187             } else {
188 0         0 last;
189             }
190 11         43 next;
191             }
192 32         46 last;
193             }
194 35         92 @modules;
195             }
196              
197             sub parseopt {
198 11     11 0 18 my $obj = shift;
199 11         25 my($mod, $argv) = @_;
200 11         31 my $call;
201              
202             ##
203             ## Check -Mmod::func(arg) or -Mmod::func=arg
204             ##
205 11 50       155 if ($mod =~ s{
206             ^ (? \w+ (?: :: \w+)* )
207             (?:
208             ::
209             (?
210             \w+
211             (?: (?

[(]) | = ) ## start with '(' or '='

212             (? [^)]* ) ## optional arg list
213             (?(

) [)] | ) ## close ')' or none

214             )
215             )?
216             $
217             }{$+{name}}x) {
218 11         44 $call = $+{call};
219             }
220              
221 11 50       34 my $bucket = eval { $obj->load_module($mod) } or do {
  11         30  
222 0 0       0 if ($@ =~ /Can't locate|need to install/) {
223 0 0       0 if ($obj->{IGNORE_NO_MODULE}) {
224 0         0 return undef;
225             } else {
226 0         0 die "Can't load module \"$mod\".\n";
227             }
228             } else {
229 0         0 die $@;
230             }
231             };
232              
233 11         20 shift @$argv;
234              
235 11 50       27 if ($call) {
236 0         0 $bucket->call(join '::', $bucket->module, $call);
237             }
238              
239             ##
240             ## If &getopt is defined in module, call it and replace @ARGV.
241             ##
242 11         45 $bucket->run_inits($argv);
243              
244 11         36 $bucket;
245             }
246              
247             sub expand {
248 10     10 0 18 my $obj = shift;
249 10         16 my $argv = shift;
250              
251             ##
252             ## Insert module defaults.
253             ##
254             unshift @$argv, map {
255 10 100       41 if (my @s = $_->default()) {
  12         41  
256 6         15 my @modules = $obj->modopt(\@s);
257 6         19 [ @s, map { $_->default } @modules ];
  0         0  
258             } else {
259 6         15 ();
260             }
261             } $obj->buckets;
262              
263             ##
264             ## Expand user defined option.
265             ##
266             ARGV:
267 10         34 for (my $i = 0; $i < @$argv; $i++) {
268              
269 87 50       143 last if $argv->[$i] eq '--';
270 87         119 my $current = $argv->[$i];
271              
272 87         154 for my $bucket ($obj->buckets) {
273              
274 106         113 my @s;
275 106 100       162 if (ref $current eq 'ARRAY') {
276             ##
277             ## Expand defaults.
278             ##
279 6         16 @s = @$current;
280 6         12 $current = 'DEFAULT';
281             }
282             else {
283             ##
284             ## Try entire string match, and check --option=value.
285             ##
286 100         174 @s = $bucket->getopt($current);
287 100 100       155 if (not @s) {
288 87 100       215 $current =~ /^(.+?)=(.*)/ or next;
289 6 50       10 @s = $bucket->getopt($1) or next;
290 0         0 splice @$argv, $i, 1, ($1, $2);
291             }
292             }
293              
294 19         44 my @follow = splice @$argv, $i;
295              
296             ##
297             ## $
298             ##
299 19         104 s/\$<(-?\d+)>/$follow[$1]/ge foreach @s;
  0         0  
300              
301 19         45 shift @follow;
302              
303 19         80 debug_argv({color=>''}, $argv, undef, \@s, \@follow);
304              
305             ##
306             ## $, $, $, $, $
307             ##
308 19         40 my $modified;
309             @s = map sub {
310 19 50   19   60 $modified += s/\$/@follow ? shift @follow : ''/ge;
  4         21  
311 19 100       163 m{\A \$ < # $<
312             (? move|remove|copy|ignore ) # command
313             (?: \( (? -?\d+ ) ? # (off
314             (?: ,(? -?\d+ ))? \) )? # ,len)
315             > \z # >
316             }x or return $_;
317 5         5 $modified++;
318 5 100       27 return () if $+{cmd} eq 'ignore';
319             my $p = ($+{cmd} eq 'copy')
320 4 100       14 ? do { my @new = @follow; \@new }
  1         2  
  1         2  
321             : \@follow;
322             my @arg = @$p == 0 ? ()
323             : defined $+{len} ? splice @$p, $+{off}//0, $+{len}
324 4 50 50     28 : splice @$p, $+{off}//0;
    50 0        
325 4 100       34 ($+{cmd} eq 'remove') ? () : @arg;
326 19         127 }->(), @s;
327              
328 19         57 @s = $bucket->expand_args(@s);
329 19 100       68 debug_argv({color=>''}, $argv, undef, \@s, \@follow) if $modified;
330              
331 19         34 my(@module, @default);
332 19 50       43 if (@module = $obj->modopt(\@s)) {
333 0         0 @default = grep { @$_ } map { [ $_->default ] } @module;
  0         0  
  0         0  
334 0         0 debug_argv({color=>''}, $argv, \@default, \@s, \@follow);
335             }
336 19         53 push @$argv, @default, @s, @follow;
337              
338 19 50       70 redo ARGV if $i < @$argv;
339             }
340             }
341             }
342              
343             sub debug_argv {
344 39 50   39 0 87 $debug or return;
345 0 0         my $opt = ref $_[0] eq 'HASH' ? shift : {};
346 0           my($before, $default, $working, $follow) = @_;
347 0   0       my $color = $opt->{color} // '';
348             printf STDERR
349             "\@ARGV = %s\n",
350             array_to_str(
351             pairmap {
352 0 0   0     if (@$a > 0) {
353 0           colorize($b, array_to_str(@$a));
354             } else {
355 0           ();
356             }
357             }
358 0   0       $before // [], "L20",
      0        
      0        
      0        
359             $default // [], "$color;U",
360             $working // [], "$color;",
361             $follow // [], "N");
362             }
363              
364             sub array_to_str {
365             join ' ', map {
366 0 0   0 0   if (ref eq 'ARRAY') {
  0 0          
367 0           join ' ', '[', array_to_str(@$_), ']';
368             } elsif (length == 0) {
369 0           "''";
370             } else {
371 0           $_;
372             }
373             } @_;
374             }
375              
376             sub modules {
377 0     0 0   my $obj = shift;
378 0   0       my $class = $obj->baseclass // return ();
379 0 0         my @base = ref $class eq 'ARRAY' ? @$class : ($class);
380 0           for (@base) {
381 0           s/::/\//g;
382 0 0         $_ = "/$_" if $_ ne "";
383             }
384              
385             map {
386 0           my $base = $_;
  0            
387 0           grep { /^[a-z]/ }
388 0           map { /(\w+)\.pm$/ }
389 0           map { glob $_ . $base . "/*.pm" }
  0            
390             @INC;
391             } @base;
392             }
393              
394             1;
395              
396             =head1 NAME
397              
398             Getopt::EX::Loader - RC/Module loader
399              
400             =head1 SYNOPSIS
401              
402             use Getopt::EX::Loader;
403              
404             my $loader = Getopt::EX::Loader->new(
405             BASECLASS => 'App::example',
406             );
407              
408             $loader->load_file("$ENV{HOME}/.examplerc");
409              
410             $loader->deal_with(\@ARGV);
411              
412             my $parser = Getopt::Long::Parser->new;
413             $parser->getoptions(... , $loader->builtins);
414             or
415             $parser->getoptions(\%hash, ... , $loader->hashed_builtins(\%hash));
416              
417             =head1 DESCRIPTION
418              
419             This is the main interface to use L modules. You can
420             create a loader object, load user-defined rc files, load modules
421             specified by command arguments, substitute user-defined options and
422             insert default options defined in rc files or modules, and get module-defined
423             built-in option definitions for the option parser.
424              
425             Most of the work is done in the C method. It parses command
426             arguments and loads modules specified by the B<-M> option by default. Then
427             it scans options and substitutes them according to the definitions in the
428             rc file or modules. If the RC file and modules define default options, they
429             are inserted into the arguments.
430              
431             Modules can define built-in options which should be handled by the option
432             parser. They can be retrieved by the C method, so you should pass
433             them to the option parser.
434              
435             If option values are stored in a hash, use C with the
436             hash reference. Actually, C works even for hash storage in
437             the current version of the B module, but it is not
438             documented.
439              
440             If C is given as a C of the loader object, it
441             is prepended to all module names. So command line
442              
443             % example -Mfoo
444              
445             will load C module.
446              
447             In this case, if the module C exists, it is loaded
448             automatically without explicit indication. The default module can be used
449             just like a startup RC file.
450              
451              
452             =head1 METHODS
453              
454             =over 4
455              
456             =item B I => I, ...
457              
458             =over 4
459              
460             =item RCFILE
461              
462             Define the name of startup file.
463              
464             =item BASECLASS
465              
466             Define the base class for user-defined modules. Use an array reference to
467             specify multiple base classes; they are tried to be loaded in order.
468              
469             =item MODULE_OPT
470              
471             Define the module option string. String C<-M> is set by default.
472              
473             =item DEFAULT
474              
475             Define the default module name. The string C is set by default. Set to
476             C if you don't want to load any default module.
477              
478             =item PARSE_MODULE_OPT
479              
480             Defaults to true, and parses module options given to the C method.
481             When disabled, module options in command line arguments are not
482             processed, but module options given in rc or module files are still
483             effective.
484              
485             =item IGNORE_NO_MODULE
486              
487             Defaults to false, and the process dies when a given module was not found on the
488             system. When set to true, the program ignores non-existing modules and stops
489             parsing at that point, leaving the argument untouched.
490              
491             =item IGNORE_NO_FILE
492              
493             Defaults to true for historical reasons, and silently ignores when the
494             specified RC file does not exist. When set to false, the process dies
495             if the file is not found.
496              
497             =back
498              
499             =item B
500              
501             Returns the loaded L object list.
502              
503             =item B
504              
505             Loads the specified file.
506              
507             =item B
508              
509             Loads the specified module.
510              
511             =back