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   139903 use v5.24;
  22         95  
3 22     22   109 use warnings;
  22         50  
  22         1200  
4 22     22   731 use experimental qw< signatures >;
  22         5394  
  22         132  
5 22     22   3420 no warnings qw< experimental::signatures >;
  22         42  
  22         1909  
6             { our $VERSION = '2.014' }
7              
8 22     22   2425 use parent 'Exporter';
  22         1485  
  22         158  
9             our @EXPORT_OK = qw< d run appeaser_api >;
10              
11 103     103 1 289 sub add_auto_commands ($application) {
  103         175  
  103         175  
12 103         269 my $commands = $application->{commands};
13             $commands->{help} //= {
14 103   100     788 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     553 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         275 return $application;
32             } ## end sub add_auto_commands ($application)
33              
34 3     3 1 693617 sub appeaser_api { return 'V1' }
35              
36 183     183 1 281 sub collect ($self, $spec, $args) {
  183         323  
  183         280  
  183         389  
  183         276  
37 183         322 my @sequence;
38 183         343 my $config = {};
39 183         307 my @residual_args;
40              
41 183         565 my $merger = merger($self, $spec);
42              
43 183         668 for my $source_spec (sources($self, $spec, $args)) {
44 734 50       3210 my ($src, $src_cnf) =
45             'ARRAY' eq ref $source_spec
46             ? $source_spec->@*
47             : ($source_spec, {});
48 734         1895 $src = $self->{factory}->($src, 'collect'); # "resolve"
49 734         5650 $src_cnf = {$spec->%*, $src_cnf->%*, config => $config};
50 734         2412 my ($slice, $residual_args) = $src->($self, $src_cnf, $args);
51 734 100       2377 push @residual_args, $residual_args->@* if defined $residual_args;
52 734         1500 push @sequence, $slice;
53 734         2041 $config = $merger->(@sequence);
54             } ## end for my $source_spec (sources...)
55              
56 183         1138 return ($config, \@residual_args);
57             } ## end sub collect
58              
59 184     184 1 318 sub collect_options ($self, $spec, $args) {
  184         325  
  184         402  
  184         261  
  184         284  
60 184         357 my $factory = $self->{factory};
61             my $collect = $spec->{collect}
62 184   66     1382 // $self->{application}{configuration}{collect} // \&collect;
      100        
63 184         434 my $collector = $factory->($collect, 'collect'); # "resolve"
64 184         569 (my $config, $args) = $collector->($self, $spec, $args);
65 184         556 push $self->{configs}->@*, $config;
66 184         470 return $args;
67             } ## end sub collect_options
68              
69 25     25 1 44 sub commandline_help ($getopt) {
  25         52  
  25         41  
70 25         47 my @retval;
71              
72 25         74 my ($mode, $type, $desttype, $min, $max, $default);
73 25 100       327 if (substr($getopt, -1, 1) eq '!') {
    50          
    50          
    0          
    0          
74 10         52 $type = 'bool';
75 10         75 substr $getopt, -1, 1, '';
76 10         40 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       91 $mode = $1 eq '=' ? 'mandatory' : 'optional';
99 15         62 $type = $2;
100 15         63 $desttype = $3;
101 15         65 $min = $4;
102 15         51 $max = $5;
103 15 50       67 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         196 }->{$type};
112 15         71 my $line = "$mode $type option";
113 15 50 33     86 $line .= ", at least $min times" if defined($min) && $min > 1;
114 15 50 33     103 $line .= ", no more than $max times"
115             if defined($max) && length($max);
116 15 50 33     76 $line .= ", list valued" if defined($desttype) && $desttype eq '@';
117 15         49 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         107 my @alternatives = split /\|/, $getopt;
139 25 100       106 if ($type eq 'bool') {
    50          
140             push @retval, map {
141 10 100       29 if (length($_) == 1) { "-$_" }
  18         51  
  8         77  
142 10         41 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       44 if (length($_) == 1) { "-$_ " }
  30         102  
  15         58  
154 15         52 else { "--$_ " }
155             } @alternatives;
156             } ## end else [ if ($type eq 'bool') ]
157              
158 25         121 return @retval;
159             } ## end sub commandline_help ($getopt)
160              
161 184     184 1 286 sub commit_configuration ($self, $spec, $args) {
  184         316  
  184         274  
  184         275  
  184         279  
162 184   50     576 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   41355 no warnings;
  22         39  
  22         183848  
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 352 sub default_getopt_config ($self, $spec) {
  183         325  
  183         358  
  183         261  
174 183         1670 my @r = qw< gnu_getopt >;
175 183 100       526 push @r, qw< require_order pass_through >
176             if has_children($self, $spec);
177 183 50       506 push @r, qw< pass_through > if $spec->{'allow-residual-options'};
178 183         627 return \@r;
179             }
180              
181 93     93 1 215 sub execute ($self, $args) {
  93         156  
  93         158  
  93         191  
182 93         248 my $command = $self->{trail}[-1][0];
183             my $executable = fetch_spec_for($self, $command)->{execute}
184 93 50       435 or die "no executable for '$command'\n";
185 93         296 $executable = $self->{factory}->($executable, 'execute'); # "resolve"
186 93   50     352 my $config = $self->{configs}[-1] // {};
187 93         423 return $executable->($self, $config, $args);
188             } ## end sub execute
189              
190 37     37 1 67 sub fetch_subcommand_default ($self, $spec) {
  37         85  
  37         69  
  37         61  
191 37         88 my $acfg = $self->{application}{configuration};
192             my $child = exists($spec->{'default-child'}) ? $spec->{'default-child'}
193 37 0       125 : exists($acfg->{'default-child'}) ? $acfg->{'default-child'}
    50          
194             : get_child($self, $spec, 'help'); # help is last resort
195 37 100 100     252 return ($child, $child) if defined $child && length $child;
196 23         151 return;
197             }
198              
199 184     184 1 288 sub fetch_subcommand ($self, $spec, $args) {
  184         272  
  184         282  
  184         290  
  184         279  
200 184 100       508 my ($subc, $alias) = fetch_subcommand_wh($self, $spec, $args)
201             or return;
202 85         258 my $r = ref $subc;
203 85 100       318 if ($r eq 'HASH') {
204             $subc = $spec->{children}[$subc->{index}]
205 3 100 66     19 if scalar(keys $subc->%*) == 1 && defined $subc->{index};
206 3         6 $r = ref $subc;
207 3 100       16 return ($subc, $subc->{supports}[0]) if $r eq 'HASH';
208 1         3 $alias = $subc;
209             }
210 83 50       336 die "invalid sub-command (ref to $r)" if $r;
211 83         377 return ($subc, $alias);
212             }
213              
214 184     184 1 282 sub fetch_subcommand_wh ($self, $spec, $args) {
  184         326  
  184         332  
  184         330  
  184         308  
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         638 for my $cfg ($spec, $self->{application}{configuration}) {
218 366 100       946 next unless exists $cfg->{dispatch};
219 2         8 my $sub = $self->{factory}->($cfg->{dispatch}, 'dispatch');
220 2 50       11 defined(my $child = $sub->($self, $spec, $args)) or return;
221 2         20 return ($child, $child);
222             }
223              
224             # regular course here, no point in going forth without children
225 182 100       447 return unless has_children($self, $spec);
226              
227             # use defaults if there's no argument to investigate
228 112 100       381 return fetch_subcommand_default($self, $spec) unless $args->@*;
229              
230             # try to get a child from the first argument
231 77 100       279 if (my $child = get_child($self, $spec, $args->[0])) {
232 65         354 return ($child, shift $args->@*); # consumed arg name
233             }
234              
235             # the first argument didn't help, but we might want to fallback
236 12         45 for my $cfg ($spec, $self->{application}{configuration}) {
237 18 100       60 if (exists $cfg->{fallback}) { # executable
238 2 50       10 defined(my $fb = $cfg->{fallback}) or return;
239 2         6 my $sub = $self->{factory}->($fb, 'fallback'); # "resolve"
240 2 50       13 defined(my $child = $sub->($self, $spec, $args)) or return;
241 2         20 return ($child, $child);
242             }
243 16 100       48 if (exists $spec->{'fallback-to'}) {
244 2 50       11 defined(my $fbto = $spec->{'fallback-to'}) or return;
245 2         12 return ($fbto, $fbto);
246             }
247             return fetch_subcommand_default($self, $spec)
248 14 100       49 if $cfg->{'fallback-to-default'};
249             }
250              
251             # no fallback at this point... it's an error, build a message and die!
252 6         63 my @names = map { $_->[1] } $self->{trail}->@*;
  6         29  
253 6         13 shift @names; # remove first one
254 6         31 my $path = join '/', @names, $args->[0]; # $args->[0] was the candidate
255 6         117 die "cannot find sub-command '$path'\n";
256             } ## end sub fetch_subcommand_wh
257              
258 104     104 1 203139 sub generate_factory ($c) {
  104         244  
  104         173  
259 104         272 my $w = \&stock_factory; # default factory
260 104 50       471 $w = stock_factory($c->{create}, 'factory', $c) if defined $c->{create};
261 104     2166   1846 return sub ($e, $d = '') { $w->($e, $d, $c) };
  2166         4585  
  2166         23118  
  2166         3679  
  2166         3394  
  2166         3053  
262             }
263              
264 88     88 1 161 sub get_child ($self, $spec, $name) {
  88         150  
  88         137  
  88         335  
  88         234  
265 88         220 for my $child (get_children($self, $spec)) {
266 157         360 my $command = fetch_spec_for($self, $child);
267             next
268 199         712 unless grep { $_ eq $name }
269 157 100 100     683 ($command->{supports} //= [$child])->@*;
270 76         375 return $child;
271             } ## end for my $child (get_children...)
272 12         107 return;
273             } ## end sub get_child
274              
275 10     10 1 14 sub stock_ChildrenByPrefix ($self, $spec, @prefixes) {
  10         16  
  10         14  
  10         18  
  10         13  
276 10         69 require File::Spec;
277             my @expanded_inc = map {
278 10         24 my ($v, $dirs) = File::Spec->splitpath($_, 'no-file');
  70         396  
279 70         465 [$v, File::Spec->splitdir($dirs)];
280             } @INC;
281 10         18 my %seen;
282             return map {
283 10         21 my @parts = split m{::}mxs, $_ . 'x';
  10         38  
284 10         29 substr(my $bprefix = pop @parts, -1, 1, '');
285             map {
286 10         20 my ($v, @dirs) = $_->@*;
  70         260  
287 70         569 my $dirs = File::Spec->catdir(@dirs, @parts);
288 70 100       2316 if (opendir my $dh, File::Spec->catpath($v, $dirs, '')) {
289 18         300 grep { ! $seen{$_}++ }
290             map {
291 18         45 substr(my $lastpart = $_, -3, 3, '');
292 18         60 join '::', @parts, $lastpart;
293             } grep {
294 10         334 my $path = File::Spec->catpath($v, $dirs, $_);
  46         386  
295 46 100 66     1163 (-e $path && ! -d $path)
      66        
296             && substr($_, 0, length($bprefix)) eq $bprefix
297             && substr($_, -3, 3) eq '.pm'
298             } readdir $dh;
299             }
300 60         375 else { () }
301             } @expanded_inc;
302             } @prefixes;
303             }
304              
305 126     126 1 180 sub expand_children ($self, $spec, $child_spec) {
  126         195  
  126         181  
  126         186  
  126         172  
306 126 100       578 return $child_spec unless ref($child_spec) eq 'ARRAY';
307 10         31 my ($exe, @args) = $child_spec->@*;
308 10         25 return $self->{factory}->($exe, 'children')->($self, $spec, @args);
309             }
310              
311 482     482 1 692 sub get_children ($self, $spec, $expand = 1) {
  482         705  
  482         726  
  482         905  
  482         695  
312 482 100       1591 return if $spec->{leaf};
313 411 100 100     1688 return if exists($spec->{children}) && !$spec->{children};
314 388   100     1580 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       1233 unless exists $self->{application}{configuration}{'auto-leaves'};
319              
320             return
321 388 100 100     1942 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       845 @children = map { expand_children($self, $spec, $_) } @children
  126         317  
326             if $expand;
327              
328             my @auto =
329             exists $self->{application}{configuration}{'auto-children'}
330 333 50 0     1236 ? (($self->{application}{configuration}{'auto-children'} // [])->@*)
331             : (qw< help commands >);
332 333 50       819 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         1750 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 50 sub get_descendant ($self, $start, $list) {
  27         46  
  27         51  
  27         49  
  27         43  
350 27         53 my $target = $start;
351 27         46 my $path;
352 27         79 for my $desc ($list->@*) {
353 11 50       51 $path = defined($path) ? "$path/$desc" : $desc;
354 11 50       36 my $command = fetch_spec_for($self, $target)
355             or die "cannot find sub-command '$path'\n";
356 11 50       38 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       77 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 755 sub has_children ($self, $spec) { get_children($self, $spec, 0) ? 1 : 0 }
  365         567  
  365         566  
  365         571  
  365         1598  
366              
367             sub hash_merge {
368 737     737 1 1407 my (%retval, %is_overridable);
369 737         1631 for my $href (@_) {
370 1850         4030 for my $src_key (keys $href->%*) {
371 638         1089 my $dst_key = $src_key;
372 638         874 my $this_overridable;
373 638 100       2198 if ($dst_key =~ m{\A //= (.*) \z}mxs) { # overridable
374 354         1144 $dst_key = $1;
375 354 50       1194 $is_overridable{$dst_key} = 1 unless exists $retval{$dst_key};
376 354         606 $this_overridable = 1;
377             }
378             $retval{$dst_key} = $href->{$src_key}
379 638 100 100     3118 if $is_overridable{$dst_key} || ! exists($retval{$dst_key});
380 638 100       2345 $is_overridable{$dst_key} = 0 unless $this_overridable;
381             }
382             }
383 737         3942 return \%retval;
384             # was a simple: return {map { $_->%* } reverse @_};
385             }
386              
387 19     19 1 37 sub list_commands ($self, $children) {
  19         31  
  19         34  
  19         47  
388 19         37 my $retval = '';
389 19         400 open my $fh, '>', \$retval;
390 19         81 for my $child ($children->@*) {
391 49         119 my $command = fetch_spec_for($self, $child);
392 49         121 my $help = $command->{help};
393 49   50     193 my @aliases = ($command->{supports} // [$child])->@*;
394 49 50       141 next unless @aliases;
395 49         75 printf {$fh} "%15s: %s\n", shift(@aliases), $help;
  49         279  
396 49 100       172 printf {$fh} "%15s (also as: %s)\n", '', join ', ', @aliases
  2         14  
397             if @aliases;
398             } ## end for my $child ($children...)
399 19         62 close $fh;
400 19         949 return $retval;
401             } ## end sub list_commands
402              
403 109     109 1 212649 sub load_application ($application) {
  109         234  
  109         182  
404 109 100       924 return $application if 'HASH' eq ref $application;
405              
406 5         9 my $text;
407 5 100       18 if ('SCALAR' eq ref $application) {
408 2         5 $text = $$application;
409             }
410             else {
411             my $fh =
412             'GLOB' eq ref $application
413             ? $application
414 3 100       9 : do {
415 2 50       140 open my $fh, '<:encoding(UTF-8)', $application
416             or die "cannot open '$application'\n";
417 2         1435 $fh;
418             };
419 3         16 local $/; # slurp mode
420 3         134 $text = <$fh>;
421 3         90 close $fh;
422             } ## end else [ if ('SCALAR' eq ref $application)]
423              
424             return eval {
425 5         37 require JSON::PP;
426 5         49 JSON::PP::decode_json($text);
427 5   66     12 } // eval { eval $text; } // die "cannot load application\n";
  3   50     2619  
428             } ## end sub load_application ($application)
429              
430 186     186 1 825 sub merger ($self, $spec = {}) {
  186         337  
  186         324  
  186         283  
431             my $merger = $spec->{merge}
432 186   33     1370 // $self->{application}{configuration}{merge} // \&hash_merge;
      50        
433 186         558 return $self->{factory}->($merger, 'merge'); # "resolve"
434             }
435              
436 204     204 1 340 sub env_namer ($self, $cspec) {
  204         307  
  204         305  
  204         302  
437             my $namenv = $cspec->{namenv}
438 204   33     1698 // $self->{application}{configuration}{namenv} // \&stock_NamEnv;
      50        
439 204         543 $namenv = $self->{factory}->($namenv, 'namenv'); # "resolve"
440 204     263   1709 return sub ($ospec) { $namenv->($self, $cspec, $ospec) };
  263         667  
  263         409  
  263         425  
  263         357  
441             } ## end sub name_for_option ($o)
442              
443 182     182 1 310 sub name_for_option ($o) {
  182         289  
  182         265  
444 182 50       750 return $o->{name} if defined $o->{name};
445 182 50 33     3156 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 272 sub params_validate ($self, $spec, $args) {
  184         322  
  184         271  
  184         348  
  184         457  
452             my $validator = $spec->{validate}
453 184   33     1120 // $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 61 sub print_commands ($self, $target) {
  8         16  
  8         32  
  8         16  
459 8         27 my $command = fetch_spec_for($self, $target);
460             my $fh =
461 8 50       63 $self->{application}{configuration}{'help-on-stderr'}
462             ? \*STDERR
463             : \*STDOUT;
464 8 100       71 if (my @children = get_children($self, $command)) {
465 7         16 print {$fh} list_commands($self, \@children);
  7         42  
466             }
467             else {
468 1         2 print {$fh} "no sub-commands\n";
  1         109  
469             }
470             }
471              
472 21     21 1 81 sub print_help ($self, $target) {
  21         43  
  21         40  
  21         37  
473 21         98 my $command = fetch_spec_for($self, $target);
474 21         66 my $enamr = env_namer($self, $command);
475             my $fh =
476 21 50       127 $self->{application}{configuration}{'help-on-stderr'}
477             ? \*STDERR
478             : \*STDOUT;
479              
480 21         47 print {$fh} $command->{help}, "\n\n";
  21         1711  
481              
482 21 100       194 if (defined(my $description = $command->{description})) {
483 20         255 $description =~ s{\A\s+|\s+\z}{}gmxs; # trim
484 20         145 $description =~ s{^}{ }gmxs; # add some indentation
485 20         42 print {$fh} "Description:\n$description\n\n";
  20         863  
486             }
487              
488 13         331 printf {$fh} "Can be called as: %s\n\n", join ', ',
489             $command->{supports}->@*
490 21 100       183 if $command->{supports};
491              
492 21   100     107 my $options = $command->{options} // [];
493 21 100       82 if ($options->@*) {
494 15         54 print {$fh} "Options:\n";
  15         262  
495 15         43 my $n = 0; # count the option
496 15         65 for my $option ($options->@*) {
497 25 100       120 print {$fh} "\n" if $n++;
  10         145  
498              
499 25         95 printf {$fh} "%15s: %s\n", name_for_option($option),
500 25   50     47 $option->{help} // '';
501              
502 25 50       126 if (exists $option->{getopt}) {
503 25         331 my @lines = commandline_help($option->{getopt});
504 25         61 printf {$fh} "%15s command-line: %s\n", '', shift(@lines);
  25         713  
505 25         110 printf {$fh} "%15s %s\n", '', $_ for @lines;
  48         947  
506             }
507              
508 25 100       88 if (defined(my $env_name = $enamr->($option))) {
509 16         43 printf {$fh} "%15s environment : %s\n", '', $env_name;
  16         445  
510             }
511              
512 8         167 printf {$fh} "%15s default : %s\n", '',
513             $option->{default} // '*undef*'
514 25 100 50     124 if exists $option->{default};
515             } ## end for my $option ($options...)
516 15         40 print {$fh} "\n";
  15         280  
517             } ## end if ($options->@*)
518             else {
519 6         16 print {$fh} "This command has no options.\n\n";
  6         100  
520             }
521              
522 21 100       96 if (my @children = get_children($self, $command)) {
523 12         25 print {$fh} "Sub commands:\n", list_commands($self, \@children),
  12         75  
524             "\n";
525             }
526             else {
527 9         21 print {$fh} "no sub-commands\n\n";
  9         276  
528             }
529             }
530              
531 403     403 1 618 sub stock_SpecFromHash ($s, $cmd) {
  403         615  
  403         608  
  403         552  
532 403 100       933 return $cmd if ref($cmd) eq 'HASH';
533 390   100     2064 return $s->{application}{commands}{$cmd} // undef;
534             }
535              
536 151     151 1 246 sub stock_SpecFromHashOrModule ($s, $cmd) {
  151         273  
  151         283  
  151         207  
537 151 50       420 return $cmd if ref($cmd) eq 'HASH';
538             return $s->{application}{commands}{$cmd}
539 151   66     831 //= $s->{factory}->($cmd, 'spec')->();
540             }
541              
542 554     554 1 830 sub fetch_spec_for ($self, $command) {
  554         829  
  554         835  
  554         796  
543             my $fetcher = $self->{application}{configuration}{specfetch}
544 554   100     2200 // \&stock_SpecFromHash;
545 554         1457 return $self->{factory}->($fetcher, 'specfetch')->($self, $command);
546             }
547              
548 103     103 1 4040422 sub run ($application, $args) {
  103         385  
  103         295  
  103         319  
549 103         459 $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     997 trail => [['MAIN', $application->{commands}{MAIN}{name}]],
559             };
560              
561 103         261 while ('necessary') {
562 188         440 my $command = $self->{trail}[-1][0];
563 188 100       507 my $spec = fetch_spec_for($self, $command)
564             or die "no definition for '$command'\n";
565              
566 184         555 $args = collect_options($self, $spec, $args);
567 184         647 validate_configuration($self, $spec, $args);
568 184         645 commit_configuration($self, $spec, $args);
569              
570 184 100       528 my ($subc, $alias) = fetch_subcommand($self, $spec, $args) or last;
571 85         416 push $self->{trail}->@*, [$subc, $alias];
572             } ## end while ('necessary')
573              
574 93   50     300 return execute($self, $args) // 0;
575             } ## end sub run
576              
577 2     2 1 3 sub slurp ($file, $mode = '<:encoding(UTF-8)') {
  2         5  
  2         6  
  2         3  
578 2 50   2   118 open my $fh, $mode, $file or die "open('$file'): $!\n";
  2         871  
  2         23  
  2         16  
579 2         2215 local $/;
580 2         88 return <$fh>;
581             }
582              
583 183     183 1 396 sub sources ($self, $spec, $args) {
  183         364  
  183         290  
  183         298  
  183         267  
584             my $s = $spec->{sources}
585             // $self->{application}{configuration}{sources}
586 183   100     1239 // \&stock_DefaultSources;
      100        
587 183 100       973 $s = $self->{factory}->($s, 'sources')->() if 'ARRAY' ne ref $s;
588 183         897 return $s->@*;
589             } ## end sub sources
590              
591 183     183 1 315 sub stock_CmdLine ($self, $spec, $args) {
  183         314  
  183         366  
  183         299  
  183         307  
592 183         533 my @args = $args->@*;
593 183   33     961 my $goc = $spec->{'getopt-config'}
594             // default_getopt_config($self, $spec);
595 183         16974 require Getopt::Long;
596 183         262901 Getopt::Long::Configure('default', $goc->@*);
597              
598 183         15045 my %option_for;
599             my @specs = map {
600 238         489 my $go = $_->{getopt};
601             ref($go) eq 'ARRAY'
602 0     0   0 ? ($go->[0] => sub { $go->[1]->(\%option_for, @_) })
603 238 50       1533 : $go;
604             }
605 183   100     876 grep { exists $_->{getopt} } ($spec->{options} // [])->@*;
  238         831  
606 183 50       754 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         66461 my $strict = !$spec->{'allow-residual-options'};
611 183 50 66     1318 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         856 return (\%option_for, \@args);
618             } ## end sub stock_CmdLine
619              
620 3     3 1 6 sub stock_JsonFileFromConfig ($self, $spec, $args) {
  3         5  
  3         25  
  3         6  
  3         8  
621 3   50     13 my $key = $spec->{'config-option'} // 'config';
622 3 100       14 return {} if !exists($spec->{config}{$key});
623 1         8 require JSON::PP;
624 1         5 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         4  
628             return merger($self, $spec)->(
629             map {
630 1         8 require JSON::PP;
631 1         6 JSON::PP::decode_json(slurp($_));
632             }
633 3   100     22 grep { -e $_ } ($spec->{'config-files'} // [])->@*
  2         200  
634             );
635             } ## end sub stock_JsonFiles
636              
637 181     181 1 323 sub stock_Default ($self, $spec, @ignore) {
  181         302  
  181         327  
  181         380  
  181         300  
638             return {
639 87         315 map { '//=' . name_for_option($_) => $_->{default} }
640 181   100     987 grep { exists $_->{default} } ($spec->{options} // [])->@*
  230         929  
641             };
642             } ## end sub stock_Default
643              
644 183     183 1 349 sub stock_Environment ($self, $spec, @ignore) {
  183         315  
  183         331  
  183         333  
  183         291  
645 183         510 my $enamr = env_namer($self, $spec);
646             return {
647             map {
648 238         522 my $en = $enamr->($_); # name of environment variable
649             defined($en) && exists($ENV{$en})
650 238 100 100     1987 ? (name_for_option($_) => $ENV{$en}) : ();
651 183   100     1185 } ($spec->{options} // [])->@*
652             };
653             } ## end sub stock_Environment
654              
655 263     263 1 388 sub stock_NamEnv ($self, $cspec, $ospec) {
  263         405  
  263         397  
  263         392  
  263         499  
656 263         430 my $aek = 'auto-environment';
657             my $autoenv = exists $cspec->{$aek} ? $cspec->{$aek}
658 263 100 100     1441 : $self->{application}{configuration}{$aek} // undef;
659             my $env = exists $ospec->{environment} ? $ospec->{environment}
660 263 100       828 : $autoenv ? 1 : undef;
    100          
661 263 100 100     1320 return $env unless ($env // '') eq '1';
662 36   50     121 my $appname = $self->{application}{configuration}{name} // '';
663 36         95 my $optname = name_for_option($ospec);
664 36         207 return uc(join '_', $appname, $optname);
665             }
666              
667 181   100 181 1 341 sub stock_Parent ($self, $spec, @ignore) { $self->{configs}[-1] // {} }
  181         285  
  181         301  
  181         317  
  181         278  
  181         888  
668              
669 7     7 1 20 sub stock_commands ($self, $config, $args) {
  7         17  
  7         16  
  7         30  
  7         12  
670 7 50       29 die "this command does not support arguments\n" if $args->@*;
671 7         32 my $target = get_descendant($self, $self->{trail}[-2][0], $args);
672 7         36 print_commands($self, $target);
673 7         139 return 0;
674             } ## end sub stock_commands
675              
676 2166     2166 1 3258 sub stock_factory ($executable, $default_subname = '', $opts = {}) {
  2166         3247  
  2166         3242  
  2166         3160  
  2166         3057  
677 90     90   226 state $factory = sub ($executable, $default_subname) {
  90         167  
  90         183  
  90         171  
678             my @prefixes =
679             !defined $opts->{prefixes} ? ()
680             : 'ARRAY' eq ref $opts->{prefixes} ? ($opts->{prefixes}->@*)
681 90 50       353 : ($opts->{prefixes});
    100          
682 90         348 push @prefixes, {'+' => 'App::Easer::V1#stock_'};
683             SEARCH:
684 90         259 for my $expansion_for (@prefixes) {
685 96         307 for my $p (keys $expansion_for->%*) {
686 111 100       469 next if $p ne substr $executable, 0, length $p;
687 74         257 substr $executable, 0, length $p, $expansion_for->{$p};
688 74         355 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       649 return eval $executable if $executable =~ s{\A \s* = \s* }{}mxs;
694              
695 89         408 my ($package, $sname) = split m{\#}mxs, $executable;
696 89 100 66     469 $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       1132 if (my $s = $package->can($sname)) { return $s }
  83         484  
700              
701             # otherwise force loading of $package and retry
702 6         32 (my $path = "$package.pm") =~ s{::}{/}gmxs;
703 6         3837 require $path;
704 4 50       136 if (my $s = $package->can($sname)) { return $s }
  4         33  
705              
706 0         0 die "no '$sname' in '$package'\n";
707 2166         3178 };
708 2166         3132 state $cache = {};
709              
710 2166         3186 my $args;
711 2166 100       5395 ($executable, $args) = ($executable->{executable}, $executable)
712             if 'HASH' eq ref $executable;
713 2166 100 100     7016 $executable = $cache->{$executable . ' ' . $default_subname} //=
714             $factory->($executable, $default_subname)
715             if 'CODE' ne ref $executable;
716 2164 100       7754 return $executable unless $args;
717 1     1   11 return sub { $executable->($args, @_) };
  1         5  
718             } ## end sub stock_factory
719              
720 20     20 1 116 sub stock_help ($self, $config, $args) {
  20         67  
  20         39  
  20         75  
  20         37  
721 20         115 print_help($self, get_descendant($self, $self->{trail}[-2][0], $args));
722 20         401 return 0;
723             } ## end sub stock_help
724              
725 178     178 1 751 sub stock_DefaultSources { [qw< +Default +CmdLine +Environment +Parent >] }
726              
727             sub stock_SourcesWithFiles {
728             [
729 3     3 1 10 qw< +Default +CmdLine +Environment +Parent
730             +JsonFileFromConfig +JsonFiles
731             >
732             ]
733             } ## end sub stock_SourcesWithFiles
734              
735 184     184 1 305 sub validate_configuration ($self, $spec, $args) {
  184         293  
  184         389  
  184         269  
  184         265  
736 184         372 my $from_spec = $spec->{validate};
737 184         445 my $from_self = $self->{application}{configuration}{validate};
738 184         301 my $validator;
739 184 50 33     931 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         383 $validator = \¶ms_validate;
747             }
748 184         502 $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;