File Coverage

blib/lib/Getopt/EX/Loader.pm
Criterion Covered Total %
statement 150 194 77.3
branch 48 84 57.1
condition 2 13 15.3
subroutine 28 34 82.3
pod 4 19 21.0
total 232 344 67.4


line stmt bran cond sub pod time code
1             package Getopt::EX::Loader;
2 6     6   162306 use version; our $VERSION = version->declare("2.1.4");
  6         4257  
  6         35  
3              
4 6     6   582 use v5.14;
  6         28  
5 6     6   32 use warnings;
  6         10  
  6         180  
6 6     6   36 use utf8;
  6         9  
  6         64  
7 6     6   151 use Carp;
  6         11  
  6         351  
8              
9 6     6   37 use Exporter 'import';
  6         9  
  6         474  
10             our @EXPORT = qw();
11             our %EXPORT_TAGS = ( );
12             our @EXPORT_OK = qw();
13              
14 6     6   37 use Data::Dumper;
  6         20  
  6         385  
15 6     6   42 use List::Util qw(pairmap);
  6         10  
  6         553  
16              
17 6     6   2523 use Getopt::EX::Module;
  6         13  
  6         274  
18 6     6   36 use Getopt::EX::Func qw(parse_func);
  6         23  
  6         269  
19 6     6   2439 use Getopt::EX::Colormap qw(colorize);
  6         13  
  6         7222  
20              
21             our $debug = 0;
22              
23             sub new {
24 9     9 0 3665 my $class = shift;
25              
26 9         72 my $obj = bless {
27             BUCKETS => [],
28             BASECLASS => undef,
29             MODULE_OPT => '-M',
30             DEFAULT => 'default',
31             PARSE_MODULE_OPT => 1,
32             IGNORE_NO_MODULE => 0,
33             }, $class;
34              
35 9 50       53 configure $obj @_ if @_;
36              
37 9         26 $obj;
38             }
39              
40             our @OPTIONS = qw(
41             RCFILE
42             BASECLASS
43             MODULE_OPT
44             DEFAULT
45             PARSE_MODULE_OPT
46             IGNORE_NO_MODULE
47             );
48              
49             sub configure {
50 9     9 1 20 my $obj = shift;
51 9         30 my %opt = @_;
52              
53 9         25 for my $opt (@OPTIONS) {
54 54 100       109 next if $opt eq 'RCFILE';
55 45 100       108 if (exists $opt{$opt}) {
56 9         49 $obj->{$opt} = delete $opt{$opt};
57             }
58             }
59              
60 9 100       34 if (my $rc = delete $opt{RCFILE}) {
61 1 50       5 my @rc = ref $rc eq 'ARRAY' ? @$rc : $rc;
62 1         3 for (@rc) {
63 1         4 $obj->load(FILE => $_);
64             }
65             }
66              
67 9 50       22 warn "Unknown option: ", Dumper \%opt if %opt;
68              
69 9         21 $obj;
70             }
71              
72             sub baseclass {
73 22     22 0 64 my $obj = shift;
74             @_ ? $obj->{BASECLASS} = shift
75 22 50       127 : $obj->{BASECLASS};
76             }
77              
78             sub buckets {
79 95     95 1 145 my $obj = shift;
80 95         127 @{ $obj->{BUCKETS} };
  95         216  
81             }
82              
83             sub append {
84 13     13 0 28 my $obj = shift;
85 13         18 push @{ $obj->{BUCKETS} }, @_;
  13         50  
86             }
87              
88             sub load {
89 22     22 0 33 my $obj = shift;
90 22         52 my $bucket =
91             Getopt::EX::Module->new(@_, BASECLASS => $obj->baseclass);
92 13         72 $obj->append($bucket);
93 13         57 $bucket;
94             }
95              
96             sub load_file {
97 0     0 1 0 my $obj = shift;
98 0         0 $obj->load(FILE => shift);
99             }
100              
101             sub load_module {
102 21     21 1 37 my $obj = shift;
103 21         88 $obj->load(MODULE => shift);
104             }
105              
106             sub defaults {
107 0     0 0 0 my $obj = shift;
108 0         0 map { $_->default } $obj->buckets;
  0         0  
109             }
110              
111             sub calls {
112 0     0 0 0 my $obj = shift;
113 0         0 map { $_->call } $obj->buckets;
  0         0  
114             }
115              
116             sub builtins {
117 3     3 0 7 my $obj = shift;
118 3         8 map { $_->builtin } $obj->buckets;
  4         20  
119             }
120              
121             sub hashed_builtins {
122 2     2 0 14 my $obj = shift;
123 2         3 my $hash = shift;
124             pairmap {
125 8 50   8   45 my($key) = $a =~ /^([-\w]+)/ or die;
126 8         25 $hash->{$key} = $b;
127 8         49 $a;
128 2         12 } $obj->builtins;
129             }
130              
131             sub deal_with {
132 9     9 0 1759 my $obj = shift;
133 9         18 my $argv = shift;
134              
135 9 50       31 if (my $default = $obj->{DEFAULT}) {
136 9 50       22 if (my $bucket = eval { $obj->load_module($default) }) {
  9         25  
137 0         0 $bucket->run_inits($argv);
138             } else {
139 9 50   5   99 $!{ENOENT} or die $@;
  5         2518  
  5         7652  
  5         59  
140             }
141             }
142 9 50       200 $obj->modopt($argv) if $obj->{PARSE_MODULE_OPT};
143 9         44 $obj->expand($argv);
144 9         39 $obj;
145             }
146              
147             sub modopt {
148 32     32 0 53 my $obj = shift;
149 32         45 my $argv = shift;
150              
151 32   50     137 my $start = $obj->{MODULE_OPT} // return ();
152 32 50       75 $start eq '' and return ();
153 32         268 my $start_re = qr/\Q$start\E/;
154 32         56 my @modules;
155 32         79 while (@$argv) {
156 38 100       282 if (my($modpart) = ($argv->[0] =~ /^$start_re(.+)/)) {
157 9         34 debug_argv($argv);
158 9 50       39 if (my $mod = $obj->parseopt($modpart, $argv)) {
159 9         26 push @modules, $mod;
160             } else {
161 0         0 last;
162             }
163 9         32 next;
164             }
165 29         66 last;
166             }
167 32         118 @modules;
168             }
169              
170             sub parseopt {
171 9     9 0 19 my $obj = shift;
172 9         44 my($mod, $argv) = @_;
173 9         18 my $call;
174              
175             ##
176             ## Check -Mmod::func(arg) or -Mmod::func=arg
177             ##
178 9 50       175 if ($mod =~ s{
179             ^ (? \w+ (?: :: \w+)* )
180             (?:
181             ::
182             (?
183             \w+
184             (?: (?

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

185             (? [^)]* ) ## optional arg list
186             (?(

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

187             )
188             )?
189             $
190             }{$+{name}}x) {
191 9         54 $call = $+{call};
192             }
193              
194 9 50       32 my $bucket = eval { $obj->load_module($mod) } or do {
  9         30  
195 0 0       0 if ($!{ENOENT}) {
196 0 0 0     0 if ($obj->{IGNORE_NO_MODULE} and $@ =~ /need to install the (\w+::)*$mod/) {
197 0         0 return undef;
198             } else {
199 0         0 die "Can't load module \"$mod\".\n";
200             }
201             } else {
202 0         0 die $@;
203             }
204             };
205              
206 9         19 shift @$argv;
207              
208 9 50       32 if ($call) {
209 0         0 $bucket->call(join '::', $bucket->module, $call);
210             }
211              
212             ##
213             ## If &getopt is defined in module, call it and replace @ARGV.
214             ##
215 9         36 $bucket->run_inits($argv);
216              
217 9         37 $bucket;
218             }
219              
220             sub expand {
221 9     9 0 25 my $obj = shift;
222 9         20 my $argv = shift;
223              
224             ##
225             ## Insert module defaults.
226             ##
227             unshift @$argv, map {
228 9 100       105 if (my @s = $_->default()) {
  16         70  
229 5         27 my @modules = $obj->modopt(\@s);
230 5         36 [ @s, map { $_->default } @modules ];
  0         0  
231             } else {
232 11         19 ();
233             }
234             } $obj->buckets;
235              
236             ##
237             ## Expand user defined option.
238             ##
239             ARGV:
240 9         51 for (my $i = 0; $i < @$argv; $i++) {
241              
242 83 50       190 last if $argv->[$i] eq '--';
243 83         117 my $current = $argv->[$i];
244              
245 83         156 for my $bucket ($obj->buckets) {
246              
247 96         125 my @s;
248 96 100       194 if (ref $current eq 'ARRAY') {
249             ##
250             ## Expand defaults.
251             ##
252 5         18 @s = @$current;
253 5         10 $current = 'DEFAULT';
254             }
255             else {
256             ##
257             ## Try entire string match, and check --option=value.
258             ##
259 91         189 @s = $bucket->getopt($current);
260 91 100       206 if (not @s) {
261 78 100       280 $current =~ /^(.+?)=(.*)/ or next;
262 6 50       17 @s = $bucket->getopt($1) or next;
263 0         0 splice @$argv, $i, 1, ($1, $2);
264             }
265             }
266              
267 18         51 my @follow = splice @$argv, $i;
268              
269             ##
270             ## $
271             ##
272 18         85 s/\$<(-?\d+)>/$follow[$1]/ge foreach @s;
  0         0  
273              
274 18         29 shift @follow;
275              
276 18         96 debug_argv({color=>'R'}, $argv, undef, \@s, \@follow);
277              
278             ##
279             ## $, $, $, $, $
280             ##
281 18         37 my $modified;
282             @s = map sub {
283 18 50   18   54 $modified += s/\$/@follow ? shift @follow : ''/ge;
  4         19  
284 18 100       213 m{\A \$ < # $<
285             (? move|remove|copy|ignore ) # command
286             (?: \( (? -?\d+ ) ? # (off
287             (?: ,(? -?\d+ ))? \) )? # ,len)
288             > \z # >
289             }x or return $_;
290 5         8 $modified++;
291 5 100       31 return () if $+{cmd} eq 'ignore';
292             my $p = ($+{cmd} eq 'copy')
293 4 100       18 ? do { my @new = @follow; \@new }
  1         3  
  1         2  
294             : \@follow;
295             my @arg = @$p == 0 ? ()
296             : defined $+{len} ? splice @$p, $+{off}//0, $+{len}
297 4 50 50     35 : splice @$p, $+{off}//0;
    50 0        
298 4 100       34 ($+{cmd} eq 'remove') ? () : @arg;
299 18         105 }->(), @s;
300              
301 18         78 @s = $bucket->expand_args(@s);
302 18 100       63 debug_argv({color=>'B'}, $argv, undef, \@s, \@follow) if $modified;
303              
304 18         36 my(@module, @default);
305 18 50       66 if (@module = $obj->modopt(\@s)) {
306 0         0 @default = grep { @$_ } map { [ $_->default ] } @module;
  0         0  
  0         0  
307 0         0 debug_argv({color=>'Y'}, $argv, \@default, \@s, \@follow);
308             }
309 18         98 push @$argv, @default, @s, @follow;
310              
311 18 50       92 redo ARGV if $i < @$argv;
312             }
313             }
314             }
315              
316             sub debug_argv {
317 36 50   36 0 112 $debug or return;
318 0 0       0 my $opt = ref $_[0] eq 'HASH' ? shift : {};
319 0         0 my($before, $default, $working, $follow) = @_;
320 0   0     0 my $color = $opt->{color} // 'R';
321             printf STDERR
322             "\@ARGV = %s\n",
323 0 0   0   0 array_to_str(pairmap { $a ? colorize($b, array_to_str(@$a)) : () }
324 0         0 $before, "L10",
325             $default, "$color;DI",
326             $working, "$color;D",
327             $follow, "M");
328             }
329              
330             sub array_to_str {
331             join ' ', map {
332 0 0   0 0 0 if (ref eq 'ARRAY') {
  0         0  
333 0         0 join ' ', '[', array_to_str(@$_), ']';
334             } else {
335 0         0 $_;
336             }
337             } @_;
338             }
339              
340             sub modules {
341 0     0 0 0 my $obj = shift;
342 0   0     0 my $class = $obj->baseclass // return ();
343 0 0       0 my @base = ref $class eq 'ARRAY' ? @$class : ($class);
344 0         0 for (@base) {
345 0         0 s/::/\//g;
346 0 0       0 $_ = "/$_" if $_ ne "";
347             }
348              
349             map {
350 0         0 my $base = $_;
  0         0  
351 0         0 grep { /^[a-z]/ }
352 0         0 map { /(\w+)\.pm$/ }
353 0         0 map { glob $_ . $base . "/*.pm" }
  0         0  
354             @INC;
355             } @base;
356             }
357              
358             1;
359              
360             =head1 NAME
361              
362             Getopt::EX::Loader - RC/Module loader
363              
364             =head1 SYNOPSIS
365              
366             use Getopt::EX::Loader;
367              
368             my $loader = Getopt::EX::Loader->new(
369             BASECLASS => 'App::example',
370             );
371              
372             $loader->load_file("$ENV{HOME}/.examplerc");
373              
374             $loader->deal_with(\@ARGV);
375              
376             my $parser = Getopt::Long::Parser->new;
377             $parser->getoptions(... , $loader->builtins);
378             or
379             $parser->getoptions(\%hash, ... , $loader->hashed_builtins(\%hash));
380              
381             =head1 DESCRIPTION
382              
383             This is the main interface to use L modules. You can
384             create loader object, load user defined rc file, load modules
385             specified by command arguments, substitute user defined option and
386             insert default options defined in rc file or modules, get module
387             defined built-in option definition for option parser.
388              
389             Most of work is done in C method. It parses command
390             arguments and load modules specified by B<-M> option by default. Then
391             it scans options and substitute them according to the definitions in
392             rc file or modules. If RC and modules defines default options, they
393             are inserted to the arguments.
394              
395             Module can define built-in options which should be handled option
396             parser. They can be taken by C method, so you should give
397             them to option parser.
398              
399             If option values are stored in a hash, use C with the
400             hash reference. Actually, C works even for hash storage in
401             the current version of B module, but it is not
402             documented.
403              
404             If C is given as a C of the loader object, it
405             is prepended to all module names. So command line
406              
407             % example -Mfoo
408              
409             will load C module.
410              
411             In this case, if module C exists, it is loaded
412             automatically without explicit indication. Default module can be used
413             just like a startup RC file.
414              
415              
416             =head1 METHODS
417              
418             =over 4
419              
420             =item B I => I, ...
421              
422             =over 4
423              
424             =item RCFILE
425              
426             Define the name of startup file.
427              
428             =item BASECLASS
429              
430             Define the base class for user defined module. Use array reference to
431             specify multiple base classes; they are tried to be loaded in order.
432              
433             =item MODULE_OPT
434              
435             Define the module option string. String C<-M> is set by default.
436              
437             =item DEFAULT
438              
439             Define default module name. String C is set by default. Set
440             C if you don't want load any default module.
441              
442             =item PARSE_MODULE_OPT
443              
444             Default true, and parse module options given to C method.
445             When disabled, module option in command line argument is not
446             processed, but module option given in rc or module files are still
447             effective.
448              
449             =item IGNORE_NO_MODULE
450              
451             Default false, and process dies when given module was not found on the
452             system. When set true, program ignores not-existing module and stop
453             parsing at the point leaving the argument untouched.
454              
455             =back
456              
457             =item B
458              
459             Return loaded L object list.
460              
461             =item B
462              
463             Load specified file.
464              
465             =item B
466              
467             Load specified module.
468              
469             =back