File Coverage

blib/lib/App/Easer/V1.pm
Criterion Covered Total %
statement 561 609 92.1
branch 164 228 71.9
condition 94 147 63.9
subroutine 57 59 96.6
pod 47 47 100.0
total 923 1090 84.6


line stmt bran cond sub pod time code
1             package App::Easer::V1;
2 22     22   67046 use v5.24;
  22         91  
3 22     22   109 use warnings;
  22         54  
  22         604  
4 22     22   554 use experimental qw< signatures >;
  22         3406  
  22         127  
5 22     22   2517 no warnings qw< experimental::signatures >;
  22         53  
  22         1432  
6             { our $VERSION = '2.007001' }
7              
8 22     22   2240 use parent 'Exporter';
  22         1445  
  22         134  
9             our @EXPORT_OK = qw< d run appeaser_api >;
10              
11 103     103 1 175 sub add_auto_commands ($application) {
  103         169  
  103         139  
12 103         200 my $commands = $application->{commands};
13             $commands->{help} //= {
14 103   100     483 name => 'help',
15             supports => ['help'],
16             help => 'print a help message',
17             description => 'print help for (sub)command',
18             'allow-residual-options' => 0,
19             leaf => 1,
20             execute => \&stock_help,
21             };
22             $commands->{commands} //= {
23 103   100     385 name => 'commands',
24             supports => ['commands'],
25             help => 'list sub-commands',
26             description => 'Print list of supported sub-commands',
27             'allow-residual-options' => 0,
28             leaf => 1,
29             execute => \&stock_commands,
30             };
31 103         195 return $application;
32             } ## end sub add_auto_commands ($application)
33              
34 3     3 1 3770 sub appeaser_api { return 'V1' }
35              
36 183     183 1 248 sub collect ($self, $spec, $args) {
  183         244  
  183         250  
  183         226  
  183         230  
37 183         240 my @sequence;
38 183         295 my $config = {};
39 183         234 my @residual_args;
40              
41 183         371 my $merger = merger($self, $spec);
42              
43 183         415 for my $source_spec (sources($self, $spec, $args)) {
44 734 50       2033 my ($src, $src_cnf) =
45             'ARRAY' eq ref $source_spec
46             ? $source_spec->@*
47             : ($source_spec, {});
48 734         1528 $src = $self->{factory}->($src, 'collect'); # "resolve"
49 734         3574 $src_cnf = {$spec->%*, $src_cnf->%*, config => $config};
50 734         2105 my ($slice, $residual_args) = $src->($self, $src_cnf, $args);
51 734 100       2149 push @residual_args, $residual_args->@* if defined $residual_args;
52 734         1149 push @sequence, $slice;
53 734         1359 $config = $merger->(@sequence);
54             } ## end for my $source_spec (sources...)
55              
56 183         674 return ($config, \@residual_args);
57             } ## end sub collect
58              
59 184     184 1 261 sub collect_options ($self, $spec, $args) {
  184         267  
  184         258  
  184         243  
  184         223  
60 184         285 my $factory = $self->{factory};
61             my $collect = $spec->{collect}
62 184   66     960 // $self->{application}{configuration}{collect} // \&collect;
      100        
63 184         357 my $collector = $factory->($collect, 'collect'); # "resolve"
64 184         407 (my $config, $args) = $collector->($self, $spec, $args);
65 184         405 push $self->{configs}->@*, $config;
66 184         366 return $args;
67             } ## end sub collect_options
68              
69 25     25 1 48 sub commandline_help ($getopt) {
  25         51  
  25         36  
70 25         45 my @retval;
71              
72 25         47 my ($mode, $type, $desttype, $min, $max, $default);
73 25 100       243 if (substr($getopt, -1, 1) eq '!') {
    50          
    50          
    0          
    0          
74 10         24 $type = 'bool';
75 10         26 substr $getopt, -1, 1, '';
76 10         25 push @retval, 'boolean option';
77             }
78             elsif (substr($getopt, -1, 1) eq '+') {
79 0         0 $mode = 'increment';
80 0         0 substr $getopt, -1, 1, '';
81 0         0 push @retval,
82             'incremental option (adds 1 every time it is provided)';
83             } ## end elsif (substr($getopt, -1...))
84             elsif (
85             $getopt =~ s<(
86             [:=]) # 1 mode
87             ([siof]) # 2 type
88             ([@%])? # 3 desttype
89             (?:
90             \{
91             (\d*)? # 4 min
92             ,?
93             (\d*)? # 5 max
94             \}
95             )? \z><>mxs
96             )
97             {
98 15 50       78 $mode = $1 eq '=' ? 'mandatory' : 'optional';
99 15         60 $type = $2;
100 15         46 $desttype = $3;
101 15         28 $min = $4;
102 15         28 $max = $5;
103 15 50       46 if (defined $min) {
104 0 0       0 $mode = $min ? 'optional' : 'required';
105             }
106             $type = {
107             s => 'string',
108             i => 'integer',
109             o => 'perl-extended-integer',
110             f => 'float',
111 15         206 }->{$type};
112 15         74 my $line = "$mode $type option";
113 15 50 33     110 $line .= ", at least $min times" if defined($min) && $min > 1;
114 15 50 33     97 $line .= ", no more than $max times"
115             if defined($max) && length($max);
116 15 50 33     205 $line .= ", list valued" if defined($desttype) && $desttype eq '@';
117 15         48 push @retval, $line;
118             } ## end elsif ($getopt =~ s<( )? \z><>mxs)
119             elsif ($getopt =~ s<: (\d+) ([@%])? \z><>mxs) {
120 0         0 $mode = 'optional';
121 0         0 $type = 'i';
122 0         0 $default = $1;
123 0         0 $desttype = $2;
124 0         0 my $line = "optional integer, defaults to $default";
125 0 0 0     0 $line .= ", list valued" if defined($desttype) && $desttype eq '@';
126 0         0 push @retval, $line;
127             } ## end elsif ($getopt =~ s<: (\d+) ([@%])? \z><>mxs)
128             elsif ($getopt =~ s<:+ ([@%])? \z><>mxs) {
129 0         0 $mode = 'optional';
130 0         0 $type = 'i';
131 0         0 $default = 'increment';
132 0         0 $desttype = $1;
133 0         0 my $line = "optional integer, current value incremented if omitted";
134 0 0 0     0 $line .= ", list valued" if defined($desttype) && $desttype eq '@';
135 0         0 push @retval, $line;
136             } ## end elsif ($getopt =~ s<:+ ([@%])? \z><>mxs)
137              
138 25         94 my @alternatives = split /\|/, $getopt;
139 25 100       130 if ($type eq 'bool') {
    50          
140             push @retval, map {
141 10 100       36 if (length($_) == 1) { "-$_" }
  18         70  
  8         39  
142 10         50 else { "--$_ | --no-$_" }
143             } @alternatives;
144             } ## end if ($type eq 'bool')
145             elsif ($mode eq 'optional') {
146             push @retval, map {
147 0 0       0 if (length($_) == 1) { "-$_ []" }
  0         0  
  0         0  
148 0         0 else { "--$_ []" }
149             } @alternatives;
150             } ## end elsif ($mode eq 'optional')
151             else {
152             push @retval, map {
153 15 100       42 if (length($_) == 1) { "-$_ " }
  30         128  
  15         56  
154 15         117 else { "--$_ " }
155             } @alternatives;
156             } ## end else [ if ($type eq 'bool') ]
157              
158 25         104 return @retval;
159             } ## end sub commandline_help ($getopt)
160              
161 184     184 1 240 sub commit_configuration ($self, $spec, $args) {
  184         244  
  184         239  
  184         226  
  184         225  
162 184   50     376 my $commit = $spec->{commit} // return;
163 0         0 $self->{factory}->($commit, 'commit')->($self, $spec, $args);
164             }
165              
166 0     0 1 0 sub d (@stuff) {
  0         0  
  0         0  
167 22     22   32491 no warnings;
  22         50  
  22         147359  
168 0         0 require Data::Dumper;
169 0         0 local $Data::Dumper::Indent = 1;
170 0 0       0 warn Data::Dumper::Dumper(@stuff % 2 ? \@stuff : {@stuff});
171             } ## end sub d (@stuff)
172              
173 183     183 1 268 sub default_getopt_config ($self, $spec) {
  183         241  
  183         236  
  183         222  
174 183         355 my @r = qw< gnu_getopt >;
175 183 100       370 push @r, qw< require_order pass_through >
176             if has_children($self, $spec);
177 183 50       433 push @r, qw< pass_through > if $spec->{'allow-residual-options'};
178 183         485 return \@r;
179             }
180              
181 93     93 1 162 sub execute ($self, $args) {
  93         146  
  93         126  
  93         115  
182 93         178 my $command = $self->{trail}[-1][0];
183             my $executable = fetch_spec_for($self, $command)->{execute}
184 93 50       221 or die "no executable for '$command'\n";
185 93         205 $executable = $self->{factory}->($executable, 'execute'); # "resolve"
186 93   50     298 my $config = $self->{configs}[-1] // {};
187 93         299 return $executable->($self, $config, $args);
188             } ## end sub execute
189              
190 37     37 1 61 sub fetch_subcommand_default ($self, $spec) {
  37         57  
  37         50  
  37         54  
191 37         71 my $acfg = $self->{application}{configuration};
192             my $child = exists($spec->{'default-child'}) ? $spec->{'default-child'}
193 37 0       99 : exists($acfg->{'default-child'}) ? $acfg->{'default-child'}
    50          
194             : get_child($self, $spec, 'help'); # help is last resort
195 37 100 100     264 return ($child, $child) if defined $child && length $child;
196 23         115 return;
197             }
198              
199 184     184 1 240 sub fetch_subcommand ($self, $spec, $args) {
  184         233  
  184         227  
  184         239  
  184         228  
200 184 100       373 my ($subc, $alias) = fetch_subcommand_wh($self, $spec, $args)
201             or return;
202 85         185 my $r = ref $subc;
203 85 100       192 if ($r eq 'HASH') {
204             $subc = $spec->{children}[$subc->{index}]
205 3 100 66     18 if scalar(keys $subc->%*) == 1 && defined $subc->{index};
206 3         7 $r = ref $subc;
207 3 100       13 return ($subc, $subc->{supports}[0]) if $r eq 'HASH';
208 1         3 $alias = $subc;
209             }
210 83 50       198 die "invalid sub-command (ref to $r)" if $r;
211 83         291 return ($subc, $alias);
212             }
213              
214 184     184 1 255 sub fetch_subcommand_wh ($self, $spec, $args) {
  184         238  
  184         228  
  184         222  
  184         220  
215             # if there's a dispatch, use that to figure out where to go next
216             # **this** might even overcome having children at all!
217 184         402 for my $cfg ($spec, $self->{application}{configuration}) {
218 366 100       806 next unless exists $cfg->{dispatch};
219 2         7 my $sub = $self->{factory}->($cfg->{dispatch}, 'dispatch');
220 2 50       7 defined(my $child = $sub->($self, $spec, $args)) or return;
221 2         15 return ($child, $child);
222             }
223              
224             # regular course here, no point in going forth without children
225 182 100       352 return unless has_children($self, $spec);
226              
227             # use defaults if there's no argument to investigate
228 112 100       290 return fetch_subcommand_default($self, $spec) unless $args->@*;
229              
230             # try to get a child from the first argument
231 77 100       200 if (my $child = get_child($self, $spec, $args->[0])) {
232 65         246 return ($child, shift $args->@*); # consumed arg name
233             }
234              
235             # the first argument didn't help, but we might want to fallback
236 12         32 for my $cfg ($spec, $self->{application}{configuration}) {
237 18 100       48 if (exists $cfg->{fallback}) { # executable
238 2 50       8 defined(my $fb = $cfg->{fallback}) or return;
239 2         6 my $sub = $self->{factory}->($fb, 'fallback'); # "resolve"
240 2 50       8 defined(my $child = $sub->($self, $spec, $args)) or return;
241 2         14 return ($child, $child);
242             }
243 16 100       42 if (exists $spec->{'fallback-to'}) {
244 2 50       6 defined(my $fbto = $spec->{'fallback-to'}) or return;
245 2         11 return ($fbto, $fbto);
246             }
247             return fetch_subcommand_default($self, $spec)
248 14 100       38 if $cfg->{'fallback-to-default'};
249             }
250              
251             # no fallback at this point... it's an error, build a message and die!
252 6         18 my @names = map { $_->[1] } $self->{trail}->@*;
  6         23  
253 6         12 shift @names; # remove first one
254 6         25 my $path = join '/', @names, $args->[0]; # $args->[0] was the candidate
255 6         87 die "cannot find sub-command '$path'\n";
256             } ## end sub fetch_subcommand_wh
257              
258 104     104 1 256 sub generate_factory ($c) {
  104         166  
  104         150  
259 104         212 my $w = \&stock_factory; # default factory
260 104 50       302 $w = stock_factory($c->{create}, 'factory', $c) if defined $c->{create};
261 104     2166   1094 return sub ($e, $d = '') { $w->($e, $d, $c) };
  2166         2525  
  2166         3524  
  2166         8670  
  2166         2771  
  2166         2836  
262             }
263              
264 88     88 1 129 sub get_child ($self, $spec, $name) {
  88         119  
  88         110  
  88         141  
  88         117  
265 88         162 for my $child (get_children($self, $spec)) {
266 157         325 my $command = fetch_spec_for($self, $child);
267             next
268 199         637 unless grep { $_ eq $name }
269 157 100 100     489 ($command->{supports} //= [$child])->@*;
270 76         284 return $child;
271             } ## end for my $child (get_children...)
272 12         40 return;
273             } ## end sub get_child
274              
275 10     10 1 13 sub stock_ChildrenByPrefix ($self, $spec, @prefixes) {
  10         14  
  10         12  
  10         32  
  10         12  
276 10         53 require File::Spec;
277             my @expanded_inc = map {
278 10         21 my ($v, $dirs) = File::Spec->splitpath($_, 'no-file');
  90         336  
279 90         469 [$v, File::Spec->splitdir($dirs)];
280             } @INC;
281 10         17 my %seen;
282             return map {
283 10         16 my @parts = split m{::}mxs, $_ . 'x';
  10         32  
284 10         35 substr(my $bprefix = pop @parts, -1, 1, '');
285             map {
286 10         17 my ($v, @dirs) = $_->@*;
  90         308  
287 90         631 my $dirs = File::Spec->catdir(@dirs, @parts);
288 90 100       1817 if (opendir my $dh, File::Spec->catpath($v, $dirs, '')) {
289 18         188 grep { ! $seen{$_}++ }
290             map {
291 18         41 substr(my $lastpart = $_, -3, 3, '');
292 18         50 join '::', @parts, $lastpart;
293             } grep {
294 10         229 my $path = File::Spec->catpath($v, $dirs, $_);
  46         315  
295 46 100 66     1112 (-e $path && ! -d $path)
      66        
296             && substr($_, 0, length($bprefix)) eq $bprefix
297             && substr($_, -3, 3) eq '.pm'
298             } readdir $dh;
299             }
300 80         474 else { () }
301             } @expanded_inc;
302             } @prefixes;
303             }
304              
305 126     126 1 158 sub expand_children ($self, $spec, $child_spec) {
  126         156  
  126         160  
  126         161  
  126         144  
306 126 100       407 return $child_spec unless ref($child_spec) eq 'ARRAY';
307 10         59 my ($exe, @args) = $child_spec->@*;
308 10         28 return $self->{factory}->($exe, 'children')->($self, $spec, @args);
309             }
310              
311 482     482 1 637 sub get_children ($self, $spec, $expand = 1) {
  482         621  
  482         575  
  482         666  
  482         658  
312 482 100       1189 return if $spec->{leaf};
313 411 100 100     1323 return if exists($spec->{children}) && !$spec->{children};
314 388   100     1194 my @children = ($spec->{children} // [])->@*;
315              
316             # set auto-leaves as 1 by default, new in 0.007002
317             $self->{application}{configuration}{'auto-leaves'} = 1
318 388 100       895 unless exists $self->{application}{configuration}{'auto-leaves'};
319              
320             return
321 388 100 100     1406 if $self->{application}{configuration}{'auto-leaves'}
322             && @children == 0; # no auto-children for leaves under auto-leaves
323              
324             # skip expansion if $expand is false (default is expand)
325 333 100       712 @children = map { expand_children($self, $spec, $_) } @children
  126         250  
326             if $expand;
327              
328             my @auto =
329             exists $self->{application}{configuration}{'auto-children'}
330 333 50 0     868 ? (($self->{application}{configuration}{'auto-children'} // [])->@*)
331             : (qw< help commands >);
332 333 50       643 if (exists $spec->{'no-auto'}) {
333 0 0       0 if (ref $spec->{'no-auto'}) {
    0          
334 0         0 my %no = map { $_ => 1 } $spec->{'no-auto'}->@*;
  0         0  
335 0         0 @auto = grep { !$no{$_} } @auto;
  0         0  
336             }
337             elsif ($spec->{'no-auto'} eq '*') {
338 0         0 @auto = ();
339             }
340             else {
341 0         0 die "invalid no-auto, array or '*' are allowed\n";
342             }
343             } ## end if (exists $spec->{'no-auto'...})
344 333         1218 return (@children, @auto);
345             } ## end sub get_children
346              
347             # traverse a whole @$list of sub-commands from $start. This is used to
348             # list "commands" at a certain sub-level or show help
349 27     27 1 57 sub get_descendant ($self, $start, $list) {
  27         48  
  27         42  
  27         59  
  27         79  
350 27         51 my $target = $start;
351 27         42 my $path;
352 27         62 for my $desc ($list->@*) {
353 11 50       35 $path = defined($path) ? "$path/$desc" : $desc;
354 11 50       27 my $command = fetch_spec_for($self, $target)
355             or die "cannot find sub-command '$path'\n";
356 11 50       47 defined($target = get_child($self, $command, $desc))
357             or die "cannot find sub-command '$path'\n";
358             } ## end for my $desc ($list->@*)
359              
360             # check that this last is associated to a real command
361 27 50       63 return $target if fetch_spec_for($self, $target);
362 0         0 die "cannot find sub-command '$path'\n";
363             } ## end sub get_descendant
364              
365 365 100   365 1 476 sub has_children ($self, $spec) { get_children($self, $spec, 0) ? 1 : 0 }
  365         560  
  365         438  
  365         451  
  365         703  
366              
367             sub hash_merge {
368 737     737 1 1396 my (%retval, %is_overridable);
369 737         1268 for my $href (@_) {
370 1850         3477 for my $src_key (keys $href->%*) {
371 638         948 my $dst_key = $src_key;
372 638         773 my $this_overridable;
373 638 100       1846 if ($dst_key =~ m{\A //= (.*) \z}mxs) { # overridable
374 354         794 $dst_key = $1;
375 354 50       874 $is_overridable{$dst_key} = 1 unless exists $retval{$dst_key};
376 354         505 $this_overridable = 1;
377             }
378             $retval{$dst_key} = $href->{$src_key}
379 638 100 100     2049 if $is_overridable{$dst_key} || ! exists($retval{$dst_key});
380 638 100       1654 $is_overridable{$dst_key} = 0 unless $this_overridable;
381             }
382             }
383 737         2540 return \%retval;
384             # was a simple: return {map { $_->%* } reverse @_};
385             }
386              
387 19     19 1 73 sub list_commands ($self, $children) {
  19         40  
  19         36  
  19         33  
388 19         35 my $retval = '';
389 19     10   543 open my $fh, '>', \$retval;
  10         72  
  10         21  
  10         77  
390 19         6900 for my $child ($children->@*) {
391 49         98 my $command = fetch_spec_for($self, $child);
392 49         97 my $help = $command->{help};
393 49   50     139 my @aliases = ($command->{supports} // [$child])->@*;
394 49 50       112 next unless @aliases;
395 49         72 printf {$fh} "%15s: %s\n", shift(@aliases), $help;
  49         253  
396 49 100       151 printf {$fh} "%15s (also as: %s)\n", '', join ', ', @aliases
  2         11  
397             if @aliases;
398             } ## end for my $child ($children...)
399 19         63 close $fh;
400 19         641 return $retval;
401             } ## end sub list_commands
402              
403 109     109 1 406 sub load_application ($application) {
  109         159  
  109         151  
404 109 100       497 return $application if 'HASH' eq ref $application;
405              
406 5         9 my $text;
407 5 100       18 if ('SCALAR' eq ref $application) {
408 2         4 $text = $$application;
409             }
410             else {
411             my $fh =
412             'GLOB' eq ref $application
413             ? $application
414 3 100       9 : do {
415 2 50       80 open my $fh, '<:encoding(UTF-8)', $application
416             or die "cannot open '$application'\n";
417 2         11269 $fh;
418             };
419 3         14 local $/; # slurp mode
420 3         104 $text = <$fh>;
421 3         90 close $fh;
422             } ## end else [ if ('SCALAR' eq ref $application)]
423              
424             return eval {
425 5         716 require JSON::PP;
426 5         14424 JSON::PP::decode_json($text);
427 5   66     14 } // eval { eval $text; } // die "cannot load application\n";
  3   50     2370  
428             } ## end sub load_application ($application)
429              
430 186     186 1 545 sub merger ($self, $spec = {}) {
  186         258  
  186         268  
  186         250  
431             my $merger = $spec->{merge}
432 186   33     912 // $self->{application}{configuration}{merge} // \&hash_merge;
      50        
433 186         401 return $self->{factory}->($merger, 'merge'); # "resolve"
434             }
435              
436 204     204 1 274 sub env_namer ($self, $cspec) {
  204         270  
  204         267  
  204         260  
437             my $namenv = $cspec->{namenv}
438 204   33     1084 // $self->{application}{configuration}{namenv} // \&stock_NamEnv;
      50        
439 204         411 $namenv = $self->{factory}->($namenv, 'namenv'); # "resolve"
440 204     263   947 return sub ($ospec) { $namenv->($self, $cspec, $ospec) };
  263         325  
  263         498  
  263         374  
  263         354  
441             } ## end sub name_for_option ($o)
442              
443 182     182 1 238 sub name_for_option ($o) {
  182         403  
  182         477  
444 182 50       489 return $o->{name} if defined $o->{name};
445 182 50 33     2181 return $1 if defined $o->{getopt} && $o->{getopt} =~ m{\A(\w+)}mxs;
446             return lc $o->{environment}
447 0 0 0     0 if defined $o->{environment} && $o->{environment} ne '1';
448 0         0 return '~~~';
449             } ## end sub name_for_option ($o)
450              
451 184     184 1 267 sub params_validate ($self, $spec, $args) {
  184         249  
  184         227  
  184         234  
  184         216  
452             my $validator = $spec->{validate}
453 184   33     817 // $self->{application}{configuration}{validate} // return;
      50        
454 0         0 require Params::Validate;
455 0         0 Params::Validate::validate($self->{configs}[-1]->%*, $validator);
456             } ## end sub params_validate
457              
458 8     8 1 43 sub print_commands ($self, $target) {
  8         15  
  8         13  
  8         16  
459 8         23 my $command = fetch_spec_for($self, $target);
460             my $fh =
461 8 50       42 $self->{application}{configuration}{'help-on-stderr'}
462             ? \*STDERR
463             : \*STDOUT;
464 8 100       26 if (my @children = get_children($self, $command)) {
465 7         15 print {$fh} list_commands($self, \@children);
  7         29  
466             }
467             else {
468 1         2 print {$fh} "no sub-commands\n";
  1         47  
469             }
470             }
471              
472 21     21 1 71 sub print_help ($self, $target) {
  21         36  
  21         38  
  21         34  
473 21         68 my $command = fetch_spec_for($self, $target);
474 21         78 my $enamr = env_namer($self, $command);
475             my $fh =
476 21 50       94 $self->{application}{configuration}{'help-on-stderr'}
477             ? \*STDERR
478             : \*STDOUT;
479              
480 21         54 print {$fh} $command->{help}, "\n\n";
  21         1070  
481              
482 21 100       158 if (defined(my $description = $command->{description})) {
483 20         161 $description =~ s{\A\s+|\s+\z}{}gmxs; # trim
484 20         126 $description =~ s{^}{ }gmxs; # add some indentation
485 20         43 print {$fh} "Description:\n$description\n\n";
  20         317  
486             }
487              
488 13         201 printf {$fh} "Can be called as: %s\n\n", join ', ',
489             $command->{supports}->@*
490 21 100       104 if $command->{supports};
491              
492 21   100     119 my $options = $command->{options} // [];
493 21 100       93 if ($options->@*) {
494 15         29 print {$fh} "Options:\n";
  15         163  
495 15         58 my $n = 0; # count the option
496 15         53 for my $option ($options->@*) {
497 25 100       83 print {$fh} "\n" if $n++;
  10         107  
498              
499 25         82 printf {$fh} "%15s: %s\n", name_for_option($option),
500 25   50     63 $option->{help} // '';
501              
502 25 50       135 if (exists $option->{getopt}) {
503 25         105 my @lines = commandline_help($option->{getopt});
504 25         46 printf {$fh} "%15s command-line: %s\n", '', shift(@lines);
  25         437  
505 25         158 printf {$fh} "%15s %s\n", '', $_ for @lines;
  48         679  
506             }
507              
508 25 100       112 if (defined(my $env_name = $enamr->($option))) {
509 16         29 printf {$fh} "%15s environment : %s\n", '', $env_name;
  16         240  
510             }
511              
512 8         133 printf {$fh} "%15s default : %s\n", '',
513             $option->{default} // '*undef*'
514 25 100 50     121 if exists $option->{default};
515             } ## end for my $option ($options...)
516 15         38 print {$fh} "\n";
  15         204  
517             } ## end if ($options->@*)
518             else {
519 6         14 print {$fh} "This command has no options.\n\n";
  6         67  
520             }
521              
522 21 100       100 if (my @children = get_children($self, $command)) {
523 12         23 print {$fh} "Sub commands:\n", list_commands($self, \@children),
  12         50  
524             "\n";
525             }
526             else {
527 9         19 print {$fh} "no sub-commands\n\n";
  9         138  
528             }
529             }
530              
531 403     403 1 507 sub stock_SpecFromHash ($s, $cmd) {
  403         504  
  403         517  
  403         501  
532 403 100       774 return $cmd if ref($cmd) eq 'HASH';
533 390   100     1398 return $s->{application}{commands}{$cmd} // undef;
534             }
535              
536 151     151 1 206 sub stock_SpecFromHashOrModule ($s, $cmd) {
  151         177  
  151         191  
  151         181  
537 151 50       269 return $cmd if ref($cmd) eq 'HASH';
538             return $s->{application}{commands}{$cmd}
539 151   66     549 //= $s->{factory}->($cmd, 'spec')->();
540             }
541              
542 554     554 1 718 sub fetch_spec_for ($self, $command) {
  554         728  
  554         713  
  554         700  
543             my $fetcher = $self->{application}{configuration}{specfetch}
544 554   100     1620 // \&stock_SpecFromHash;
545 554         1133 return $self->{factory}->($fetcher, 'specfetch')->($self, $command);
546             }
547              
548 103     103 1 432837 sub run ($application, $args) {
  103         204  
  103         162  
  103         147  
549 103         279 $application = add_auto_commands(load_application($application));
550             my $self = {
551             application => $application,
552             configs => [],
553             factory => generate_factory($application->{factory} // {}),
554             helpers => {
555             'print-commands' => \&print_commands,
556             'print-help' => \&print_help,
557             },
558 103   50     632 trail => [['MAIN', $application->{commands}{MAIN}{name}]],
559             };
560              
561 103         219 while ('necessary') {
562 188         393 my $command = $self->{trail}[-1][0];
563 188 100       394 my $spec = fetch_spec_for($self, $command)
564             or die "no definition for '$command'\n";
565              
566 184         453 $args = collect_options($self, $spec, $args);
567 184         505 validate_configuration($self, $spec, $args);
568 184         458 commit_configuration($self, $spec, $args);
569              
570 184 100       382 my ($subc, $alias) = fetch_subcommand($self, $spec, $args) or last;
571 85         312 push $self->{trail}->@*, [$subc, $alias];
572             } ## end while ('necessary')
573              
574 93   50     223 return execute($self, $args) // 0;
575             } ## end sub run
576              
577 2     2 1 5 sub slurp ($file, $mode = '<:encoding(UTF-8)') {
  2         4  
  2         3  
  2         3  
578 2 50       72 open my $fh, $mode, $file or die "open('$file'): $!\n";
579 2         11134 local $/;
580 2         83 return <$fh>;
581             }
582              
583 183     183 1 257 sub sources ($self, $spec, $args) {
  183         256  
  183         239  
  183         254  
  183         234  
584             my $s = $spec->{sources}
585             // $self->{application}{configuration}{sources}
586 183   100     857 // \&stock_DefaultSources;
      100        
587 183 100       539 $s = $self->{factory}->($s, 'sources')->() if 'ARRAY' ne ref $s;
588 183         1242 return $s->@*;
589             } ## end sub sources
590              
591 183     183 1 252 sub stock_CmdLine ($self, $spec, $args) {
  183         257  
  183         228  
  183         238  
  183         226  
592 183         385 my @args = $args->@*;
593 183   33     636 my $goc = $spec->{'getopt-config'}
594             // default_getopt_config($self, $spec);
595 183         12462 require Getopt::Long;
596 183         168186 Getopt::Long::Configure('default', $goc->@*);
597              
598 183         11463 my %option_for;
599             my @specs = map {
600 238         464 my $go = $_->{getopt};
601             ref($go) eq 'ARRAY'
602 0     0   0 ? ($go->[0] => sub { $go->[1]->(\%option_for, @_) })
603 238 50       671 : $go;
604             }
605 183   100     642 grep { exists $_->{getopt} } ($spec->{options} // [])->@*;
  238         603  
606 183 50       561 Getopt::Long::GetOptionsFromArray(\@args, \%option_for, @specs)
607             or die "bailing out\n";
608              
609             # Check if we want to forbid the residual @args to start with a '-'
610 183         33984 my $strict = !$spec->{'allow-residual-options'};
611 183 50 66     1106 if ($strict && @args && $args[0] =~ m{\A -}mxs) {
      66        
612 0         0 Getopt::Long::Configure('default', 'gnu_getopt');
613 0         0 Getopt::Long::GetOptionsFromArray(\@args, {});
614 0         0 die "bailing out\n";
615             }
616              
617 183         658 return (\%option_for, \@args);
618             } ## end sub stock_CmdLine
619              
620 3     3 1 5 sub stock_JsonFileFromConfig ($self, $spec, $args) {
  3         5  
  3         5  
  3         4  
  3         5  
621 3   50     14 my $key = $spec->{'config-option'} // 'config';
622 3 100       11 return {} if !exists($spec->{config}{$key});
623 1         751 require JSON::PP;
624 1         13980 return JSON::PP::decode_json(slurp($spec->{config}{$key}));
625             } ## end sub stock_JsonFileFromConfig
626              
627 3     3 1 6 sub stock_JsonFiles ($self, $spec, @ignore) {
  3         4  
  3         4  
  3         6  
  3         3  
628             return merger($self, $spec)->(
629             map {
630 1         8 require JSON::PP;
631 1         5 JSON::PP::decode_json(slurp($_));
632             }
633 3   100     21 grep { -e $_ } ($spec->{'config-files'} // [])->@*
  2         54  
634             );
635             } ## end sub stock_JsonFiles
636              
637 181     181 1 263 sub stock_Default ($self, $spec, @ignore) {
  181         258  
  181         236  
  181         286  
  181         230  
638             return {
639 87         226 map { '//=' . name_for_option($_) => $_->{default} }
640 181   100     664 grep { exists $_->{default} } ($spec->{options} // [])->@*
  230         700  
641             };
642             } ## end sub stock_Default
643              
644 183     183 1 270 sub stock_Environment ($self, $spec, @ignore) {
  183         246  
  183         239  
  183         272  
  183         238  
645 183         401 my $enamr = env_namer($self, $spec);
646             return {
647             map {
648 238         445 my $en = $enamr->($_); # name of environment variable
649             defined($en) && exists($ENV{$en})
650 238 100 100     1353 ? (name_for_option($_) => $ENV{$en}) : ();
651 183   100     779 } ($spec->{options} // [])->@*
652             };
653             } ## end sub stock_Environment
654              
655 263     263 1 333 sub stock_NamEnv ($self, $cspec, $ospec) {
  263         331  
  263         327  
  263         355  
  263         347  
656 263         373 my $aek = 'auto-environment';
657             my $autoenv = exists $cspec->{$aek} ? $cspec->{$aek}
658 263 100 100     996 : $self->{application}{configuration}{$aek} // undef;
659             my $env = exists $ospec->{environment} ? $ospec->{environment}
660 263 100       599 : $autoenv ? 1 : undef;
    100          
661 263 100 100     935 return $env unless ($env // '') eq '1';
662 36   50     82 my $appname = $self->{application}{configuration}{name} // '';
663 36         66 my $optname = name_for_option($ospec);
664 36         152 return uc(join '_', $appname, $optname);
665             }
666              
667 181   100 181 1 256 sub stock_Parent ($self, $spec, @ignore) { $self->{configs}[-1] // {} }
  181         249  
  181         581  
  181         265  
  181         224  
  181         674  
668              
669 7     7 1 20 sub stock_commands ($self, $config, $args) {
  7         15  
  7         11  
  7         15  
  7         14  
670 7 50       29 die "this command does not support arguments\n" if $args->@*;
671 7         27 my $target = get_descendant($self, $self->{trail}[-2][0], $args);
672 7         33 print_commands($self, $target);
673 7         121 return 0;
674             } ## end sub stock_commands
675              
676 2166     2166 1 2619 sub stock_factory ($executable, $default_subname = '', $opts = {}) {
  2166         2814  
  2166         2754  
  2166         2614  
  2166         2568  
677 90     90   144 state $factory = sub ($executable, $default_subname) {
  90         138  
  90         152  
  90         135  
678             my @prefixes =
679             !defined $opts->{prefixes} ? ()
680             : 'ARRAY' eq ref $opts->{prefixes} ? ($opts->{prefixes}->@*)
681 90 50       277 : ($opts->{prefixes});
    100          
682 90         261 push @prefixes, {'+' => 'App::Easer::V1#stock_'};
683             SEARCH:
684 90         205 for my $expansion_for (@prefixes) {
685 96         275 for my $p (keys $expansion_for->%*) {
686 111 100       353 next if $p ne substr $executable, 0, length $p;
687 74         202 substr $executable, 0, length $p, $expansion_for->{$p};
688 74         173 last SEARCH;
689             }
690             } ## end SEARCH: for my $expansion_for (...)
691              
692             # if it *still* "starts" with '=', it's "inline" Perl code
693 90 100       406 return eval $executable if $executable =~ s{\A \s* = \s* }{}mxs;
694              
695 89         336 my ($package, $sname) = split m{\#}mxs, $executable;
696 89 100 66     441 $sname = $default_subname unless defined $sname && length $sname;
697              
698             # first try to see if the sub is already available in $package
699 89 100       859 if (my $s = $package->can($sname)) { return $s }
  83         373  
700              
701             # otherwise force loading of $package and retry
702 6         28 (my $path = "$package.pm") =~ s{::}{/}gmxs;
703 6         2003 require $path;
704 4 50       86 if (my $s = $package->can($sname)) { return $s }
  4         25  
705              
706 0         0 die "no '$sname' in '$package'\n";
707 2166         2777 };
708 2166         2531 state $cache = {};
709              
710 2166         2588 my $args;
711 2166 100       4176 ($executable, $args) = ($executable->{executable}, $executable)
712             if 'HASH' eq ref $executable;
713 2166 100 100     5629 $executable = $cache->{$executable . ' ' . $default_subname} //=
714             $factory->($executable, $default_subname)
715             if 'CODE' ne ref $executable;
716 2164 100       5496 return $executable unless $args;
717 1     1   7 return sub { $executable->($args, @_) };
  1         3  
718             } ## end sub stock_factory
719              
720 20     20 1 36 sub stock_help ($self, $config, $args) {
  20         45  
  20         38  
  20         54  
  20         33  
721 20         71 print_help($self, get_descendant($self, $self->{trail}[-2][0], $args));
722 20         298 return 0;
723             } ## end sub stock_help
724              
725 178     178 1 467 sub stock_DefaultSources { [qw< +Default +CmdLine +Environment +Parent >] }
726              
727             sub stock_SourcesWithFiles {
728             [
729 3     3 1 11 qw< +Default +CmdLine +Environment +Parent
730             +JsonFileFromConfig +JsonFiles
731             >
732             ]
733             } ## end sub stock_SourcesWithFiles
734              
735 184     184 1 276 sub validate_configuration ($self, $spec, $args) {
  184         270  
  184         241  
  184         231  
  184         237  
736 184         305 my $from_spec = $spec->{validate};
737 184         303 my $from_self = $self->{application}{configuration}{validate};
738 184         250 my $validator;
739 184 50 33     681 if (defined $from_spec && 'HASH' ne ref $from_spec) {
    50 33        
740 0         0 $validator = $self->{factory}->($from_spec, 'validate');
741             }
742             elsif (defined $from_self && 'HASH' ne ref $from_self) {
743 0         0 $validator = $self->{factory}->($from_self, 'validate');
744             }
745             else { # use stock one
746 184         319 $validator = \¶ms_validate;
747             }
748 184         405 $validator->($self, $spec, $args);
749             } ## end sub validate_configuration
750              
751             exit run(
752             $ENV{APPEASER} // {
753             commands => {
754             MAIN => {
755             name => 'main app',
756             help => 'this is the main app',
757             description => 'Yes, this really is the main app',
758             options => [
759             {
760             name => 'foo',
761             description => 'option foo!',
762             getopt => 'foo|f=s',
763             environment => 'FOO',
764             default => 'bar',
765             },
766             ],
767             execute => sub ($global, $conf, $args) {
768             my $foo = $conf->{foo};
769             say "Hello, $foo!";
770             return 0;
771             },
772             'default-child' => '', # run execute by default
773             },
774             },
775             },
776             [@ARGV]
777             ) unless caller;
778              
779             1;