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   163690 use version; our $VERSION = version->declare("2.1.2");
  6         4232  
  6         37  
3              
4 6     6   673 use v5.14;
  6         30  
5 6     6   46 use warnings;
  6         11  
  6         169  
6 6     6   31 use utf8;
  6         17  
  6         45  
7 6     6   145 use Carp;
  6         9  
  6         369  
8              
9 6     6   31 use Exporter 'import';
  6         12  
  6         532  
10             our @EXPORT = qw();
11             our %EXPORT_TAGS = ( );
12             our @EXPORT_OK = qw();
13              
14 6     6   39 use Data::Dumper;
  6         13  
  6         341  
15 6     6   40 use List::Util qw(pairmap);
  6         22  
  6         610  
16              
17 6     6   2515 use Getopt::EX::Module;
  6         14  
  6         278  
18 6     6   42 use Getopt::EX::Func qw(parse_func);
  6         15  
  6         279  
19 6     6   2500 use Getopt::EX::Colormap qw(colorize);
  6         20  
  6         7965  
20              
21             our $debug = 0;
22              
23             sub new {
24 9     9 0 4235 my $class = shift;
25              
26 9         131 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       59 configure $obj @_ if @_;
36              
37 9         35 $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 18 my $obj = shift;
51 9         30 my %opt = @_;
52              
53 9         25 for my $opt (@OPTIONS) {
54 54 100       116 next if $opt eq 'RCFILE';
55 45 100       104 if (exists $opt{$opt}) {
56 9         53 $obj->{$opt} = delete $opt{$opt};
57             }
58             }
59              
60 9 100       36 if (my $rc = delete $opt{RCFILE}) {
61 1 50       6 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       35 warn "Unknown option: ", Dumper \%opt if %opt;
68              
69 9         24 $obj;
70             }
71              
72             sub baseclass {
73 22     22 0 33 my $obj = shift;
74             @_ ? $obj->{BASECLASS} = shift
75 22 50       141 : $obj->{BASECLASS};
76             }
77              
78             sub buckets {
79 95     95 1 143 my $obj = shift;
80 95         116 @{ $obj->{BUCKETS} };
  95         214  
81             }
82              
83             sub append {
84 13     13 0 36 my $obj = shift;
85 13         23 push @{ $obj->{BUCKETS} }, @_;
  13         55  
86             }
87              
88             sub load {
89 22     22 0 35 my $obj = shift;
90 22         59 my $bucket =
91             Getopt::EX::Module->new(@_, BASECLASS => $obj->baseclass);
92 13         80 $obj->append($bucket);
93 13         77 $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 48 my $obj = shift;
103 21         51 $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 8 my $obj = shift;
118 3         27 map { $_->builtin } $obj->buckets;
  4         15  
119             }
120              
121             sub hashed_builtins {
122 2     2 0 5 my $obj = shift;
123 2         3 my $hash = shift;
124             pairmap {
125 8 50   8   36 my($key) = $a =~ /^([-\w]+)/ or die;
126 8         28 $hash->{$key} = $b;
127 8         33 $a;
128 2         14 } $obj->builtins;
129             }
130              
131             sub deal_with {
132 9     9 0 1756 my $obj = shift;
133 9         16 my $argv = shift;
134              
135 9 50       35 if (my $default = $obj->{DEFAULT}) {
136 9 50       17 if (my $bucket = eval { $obj->load_module($default) }) {
  9         29  
137 0         0 $bucket->run_inits($argv);
138             } else {
139 9 50   5   101 $!{ENOENT} or die $@;
  5         2465  
  5         7174  
  5         45  
140             }
141             }
142 9 50       211 $obj->modopt($argv) if $obj->{PARSE_MODULE_OPT};
143 9         35 $obj->expand($argv);
144 9         26 $obj;
145             }
146              
147             sub modopt {
148 32     32 0 61 my $obj = shift;
149 32         49 my $argv = shift;
150              
151 32   50     91 my $start = $obj->{MODULE_OPT} // return ();
152 32 50       148 $start eq '' and return ();
153 32         185 my $start_re = qr/\Q$start\E/;
154 32         57 my @modules;
155 32         81 while (@$argv) {
156 38 100       295 if (my($modpart) = ($argv->[0] =~ /^$start_re(.+)/)) {
157 9         36 debug_argv($argv);
158 9 50       33 if (my $mod = $obj->parseopt($modpart, $argv)) {
159 9         33 push @modules, $mod;
160             } else {
161 0         0 last;
162             }
163 9         32 next;
164             }
165 29         62 last;
166             }
167 32         129 @modules;
168             }
169              
170             sub parseopt {
171 9     9 0 17 my $obj = shift;
172 9         39 my($mod, $argv) = @_;
173 9         14 my $call;
174              
175             ##
176             ## Check -Mmod::func(arg) or -Mmod::func=arg
177             ##
178 9 50       207 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         47 $call = $+{call};
192             }
193              
194 9 50       30 my $bucket = eval { $obj->load_module($mod) } or do {
  9         60  
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         21 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         41 $bucket->run_inits($argv);
216              
217 9         32 $bucket;
218             }
219              
220             sub expand {
221 9     9 0 29 my $obj = shift;
222 9         19 my $argv = shift;
223              
224             ##
225             ## Insert module defaults.
226             ##
227             unshift @$argv, map {
228 9 100       113 if (my @s = $_->default()) {
  16         72  
229 5         28 my @modules = $obj->modopt(\@s);
230 5         38 [ @s, map { $_->default } @modules ];
  0         0  
231             } else {
232 11         21 ();
233             }
234             } $obj->buckets;
235              
236             ##
237             ## Expand user defined option.
238             ##
239             ARGV:
240 9         41 for (my $i = 0; $i < @$argv; $i++) {
241              
242 83 50       185 last if $argv->[$i] eq '--';
243 83         172 my $current = $argv->[$i];
244              
245 83         155 for my $bucket ($obj->buckets) {
246              
247 96         126 my @s;
248 96 100       196 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         198 @s = $bucket->getopt($current);
260 91 100       188 if (not @s) {
261 78 100       288 $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         93 s/\$<(-?\d+)>/$follow[$1]/ge foreach @s;
  0         0  
273              
274 18         29 shift @follow;
275              
276 18         93 debug_argv({color=>'R'}, $argv, undef, \@s, \@follow);
277              
278             ##
279             ## $, $, $, $, $
280             ##
281 18         36 my $modified;
282             @s = map sub {
283 18 50   18   49 $modified += s/\$/@follow ? shift @follow : ''/ge;
  4         19  
284 18 100       162 m{\A \$ < # $<
285             (? move|remove|copy|ignore ) # command
286             (?: \( (? -?\d+ ) ? # (off
287             (?: ,(? -?\d+ ))? \) )? # ,len)
288             > \z # >
289             }x or return $_;
290 5         9 $modified++;
291 5 100       33 return () if $+{cmd} eq 'ignore';
292             my $p = ($+{cmd} eq 'copy')
293 4 100       19 ? do { my @new = @follow; \@new }
  1         3  
  1         6  
294             : \@follow;
295             my @arg = @$p == 0 ? ()
296             : defined $+{len} ? splice @$p, $+{off}//0, $+{len}
297 4 50 50     36 : splice @$p, $+{off}//0;
    50 0        
298 4 100       65 ($+{cmd} eq 'remove') ? () : @arg;
299 18         94 }->(), @s;
300              
301 18         69 @s = $bucket->expand_args(@s);
302 18 100       78 debug_argv({color=>'B'}, $argv, undef, \@s, \@follow) if $modified;
303              
304 18         41 my(@module, @default);
305 18 50       55 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         60 push @$argv, @default, @s, @follow;
310              
311 18 50       79 redo ARGV if $i < @$argv;
312             }
313             }
314             }
315              
316             sub debug_argv {
317 36 50   36 0 87 $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