File Coverage

blib/lib/App/Spec/Run.pm
Criterion Covered Total %
statement 222 244 90.9
branch 61 80 76.2
condition 29 44 65.9
subroutine 25 28 89.2
pod 17 17 100.0
total 354 413 85.7


line stmt bran cond sub pod time code
1             # ABSTRACT: App::Spec framework to run your app
2 6     6   197470 use strict;
  6         11  
  6         216  
3 6     6   29 use warnings;
  6         9  
  6         328  
4             package App::Spec::Run;
5 6     6   128 use 5.010;
  6         21  
6             our $VERSION = 'v0.15.0'; # VERSION
7              
8 6     6   2772 use App::Spec::Run::Validator;
  6         58  
  6         315  
9 6     6   10092 use App::Spec::Run::Response;
  6         21  
  6         242  
10 6     6   4664 use Getopt::Long qw/ :config pass_through bundling /;
  6         74247  
  6         31  
11 6     6   1524 use Ref::Util qw/ is_arrayref /;
  6         9  
  6         280  
12 6     6   24 use Moo;
  6         10  
  6         47  
13              
14             has spec => ( is => 'ro' );
15             has options => ( is => 'rw' );
16             has parameters => ( is => 'rw', default => sub { +{} } );
17             has commands => ( is => 'rw' );
18             has argv => ( is => 'rw' );
19             has argv_orig => ( is => 'rw' );
20             #has runmode => ( is => 'rw', default => 'normal' );
21             has validation_errors => ( is => 'rw' );
22             has op => ( is => 'rw' );
23             has cmd => ( is => 'rw' );
24             has response => ( is => 'rw', default => sub { App::Spec::Run::Response->new } );
25             has subscribers => ( is => 'rw', default => sub { +{} } );
26              
27             my %EVENTS = (
28             print_output => 1,
29             global_options => 1,
30             );
31              
32             sub process {
33 33     33 1 57268 my ($self) = @_;
34              
35 33   50     405 my $plugins = $self->spec->plugins || [];
36 33         117 for my $plugin (@$plugins) {
37 89         551 $plugin->init_run($self);
38             }
39 33         65 my @callbacks;
40 33         107 my $subscriber_events = $self->subscribers;
41 33         106 for my $key (qw/ global_options print_output /) {
42 66         173 my $subscribers = $subscriber_events->{ $key };
43 66         150 for my $sub (@$subscribers) {
44 60         161 my $plugin = $sub->{plugin};
45 60         131 my $method = $sub->{method};
46             my $callback = sub {
47 4     4   22 $plugin->$method( run => $self, @_);
48 60         525 };
49 60         164 push @callbacks, $callback;
50             }
51 66         337 $self->response->add_callbacks($key => \@callbacks);
52             }
53              
54 33         160 my $argv = $self->argv;
55 33 100       113 unless ($argv) {
56 32         73 $argv = \@ARGV;
57 32         126 $self->argv($argv);
58 32         181 $self->argv_orig([ @$argv ]);
59             }
60              
61 33         131 my $completion_parameter = $ENV{PERL5_APPSPECRUN_COMPLETION_PARAMETER};
62              
63 33         87 my %option_specs;
64             my %param_specs;
65 33 50       197 unless ($self->op) {
66 33         230 $self->process_input(
67             option_specs => \%option_specs,
68             param_specs => \%param_specs,
69             );
70             }
71              
72 33 100       169 unless ($self->response->halted) {
73 30         1546 my $opt = App::Spec::Run::Validator->new({
74             options => $self->options,
75             option_specs => \%option_specs,
76             parameters => $self->parameters,
77             param_specs => \%param_specs,
78             });
79 30         7020 my %errs;
80 30         197 my $ok = $opt->process( $self, \%errs );
81 30 100       143 unless ($ok) {
82 14         65 $self->validation_errors(\%errs);
83             # if we are in completion mode, some errors might be ok
84 14 100       57 if (not $completion_parameter) {
85 9         55 $self->error_output;
86             }
87             }
88             }
89              
90 33 100       824 unless ($self->response->halted) {
91              
92 21         66 my $op = $self->op;
93              
94 21 100       84 if ($completion_parameter) {
95 5         26 $self->completion_output(
96             param_specs => \%param_specs,
97             option_specs => \%option_specs,
98             completion_parameter => $completion_parameter,
99             );
100             }
101             else {
102 16         94 $self->run_op($op);
103             }
104             }
105              
106             }
107              
108             sub run {
109 0     0 1 0 my ($self) = @_;
110              
111 0         0 $self->process;
112              
113             # $self->event_processed;
114 0         0 $self->finish;
115              
116             }
117              
118             sub run_op {
119 20     20 1 64 my ($self, $op, $args) = @_;
120 20         182 $self->cmd->$op($self, $args);
121             }
122              
123             sub out {
124 25     25 1 534 my ($self, $text) = @_;
125 25 100 100     221 $text .= "\n" if (not ref $text and $text !~ m/\n\z/);
126 25         156 $self->response->add_output($text);
127             }
128              
129             sub err {
130 28     28 1 81 my ($self, $text) = @_;
131 28 100 66     282 $text .= "\n" if (not ref $text and $text !~ m/\n\z/);
132 28         193 $self->response->add_error($text);
133             }
134              
135             sub halt {
136 13     13 1 45 my ($self, $exit) = @_;
137 13         59 $self->response->halted(1);
138 13   50     120 $self->response->exit($exit || 0);
139             }
140              
141             sub finish {
142 0     0 1 0 my ($self) = @_;
143 0         0 my $res = $self->response;
144 0         0 $res->print_output;
145 0         0 $res->finished(1);
146 0 0       0 if (my $exit = $res->exit) {
147 0         0 exit $exit;
148             }
149             }
150              
151             sub completion_output {
152 5     5 1 81 my ($self, %args) = @_;
153 5         15 my $completion_parameter = $args{completion_parameter};
154 5         11 my $param_specs = $args{param_specs};
155 5         41 my $option_specs = $args{option_specs};
156 5 100       110 my $shell = $ENV{PERL5_APPSPECRUN_SHELL} or return;
157 4         11 my $param = $param_specs->{ $completion_parameter };
158 4   33     11 $param ||= $option_specs->{ $completion_parameter };
159 4         32 my $unique = $param->{unique};
160 4 50       21 my $completion = $param->completion or return;
161 4         8 my $op;
162 4 100       11 if (ref $completion) {
163 2 50       9 $op = $completion->{op} or return;
164             }
165             else {
166 2 50       12 my $possible_values = $param->values or return;
167 2 50       13 $op = $possible_values->{op} or return;
168             }
169 4         16 my $args = {
170             runmode => "completion",
171             parameter => $completion_parameter,
172             };
173 4         16 my $result = $self->run_op($op, $args);
174              
175 4         254 my $string = '';
176 4         8 my %seen;
177 4 100       11 if ($unique) {
178 2         7 my $params = $self->parameters;
179 2         6 my $value = $params->{ $completion_parameter };
180 2 50       9 $value = [$value] unless is_arrayref $value;
181             # cmd param1 param2 results in
182             # @ARGV = ["param1", "param2"]
183             # cmd param1 param2 results in
184             # @ARGV = ["param1", "param2", ""]
185             # so we know that there is a new value to be completed
186 2         5 my $last = pop @$value;
187 2         9 @seen{ @$value } = (1) x @$value;
188             }
189 4         11 for my $item (@$result) {
190 9 100       21 if (ref $item eq 'HASH') {
191 2         3 my $name = $item->{name};
192 2 50 33     14 $unique and $seen{ $name }++ and next;
193 2         4 my $desc = $item->{description};
194 2         6 $string .= "$name\t$desc\n";
195             }
196             else {
197 7 100 100     32 $unique and $seen{ $item }++ and next;
198 6         18 $string .= "$item\n";
199             }
200             }
201              
202 4         14 $self->out($string);
203 4         216 return;
204             }
205              
206             sub error_output {
207 9     9 1 26 my ($self) = @_;
208 9         58 my $errs = $self->validation_errors;
209 9         21 my @error_output;
210 9         38 for my $key (sort keys %$errs) {
211 9         26 my $errors = $errs->{ $key };
212 9 50 66     51 if ($key eq "parameters" or $key eq "options") {
213 9         66 for my $name (sort keys %$errors) {
214 12         33 my $error = $errors->{ $name };
215 12         66 $key =~ s/s$//;
216 12         71 push @error_output, "Error: $key '$name': $error";
217             }
218             }
219             else {
220 0         0 require Data::Dumper;
221 0         0 my $err = Data::Dumper->Dump([$errs], ['errs']);
222 0         0 push @error_output, $err;
223             }
224             }
225 9         64 my $help = $self->spec->usage(
226             commands => $self->commands,
227             highlights => $errs,
228             colored => $self->colorize_code('err'),
229             );
230 9         130 $self->err($help);
231 9         28 for my $msg (@error_output) {
232 12         59 $msg = $self->colored('err', [qw/ error /], $msg);
233 12         59 $self->err("$msg\n");
234             }
235 9         38 $self->halt(1);
236             }
237              
238             sub colorize_code {
239 19     19 1 62 my ($self, $out) = @_;
240             $self->colorize($out)
241             ? sub {
242 0     0   0 my $colored = $self->colored($out, $_[0], $_[1]);
243 0 0       0 unless (defined wantarray) {
244 0         0 $_[1] = $colored;
245             }
246 0         0 return $colored;
247             }
248 95     95   279 : sub { $_[1] },
249 19 50       87 }
250              
251             sub colorize {
252 34     34 1 88 my ($self, $out) = @_;
253 34   100     178 $out ||= 'out';
254 34 50 100     196 if (($ENV{PERL5_APPSPECRUN_COLOR} // '') eq 'always') {
255 0         0 return 1;
256             }
257 34 100 100     189 if (($ENV{PERL5_APPSPECRUN_COLOR} // '') eq 'never') {
258 32         252 return 0;
259             }
260 2 50 33     31 if ($out eq 'out' and -t STDOUT or $out eq 'err' and -t STDERR) {
      33        
      33        
261 0         0 return 1;
262             }
263 2         19 return 0;
264             }
265              
266             sub process_parameters {
267 68     68 1 662 my ($self, %args) = @_;
268 68         152 my $param_list = $args{parameter_list};
269 68         189 my $parameters = $self->parameters;
270 68         134 my $param_specs = $args{param_specs};
271 68         262 for my $p (@$param_list) {
272 42         1531 my $name = $p->name;
273 42         126 my $type = $p->type;
274 42         122 my $multiple = $p->multiple;
275 42         138 my $required = $p->required;
276 42         96 my $value;
277 42 100       135 if ($multiple) {
278 10         19 $value = [@{ $self->argv }];
  10         34  
279 10         23 @{ $self->argv } = ();
  10         25  
280             }
281             else {
282 32         51 $value = shift @{ $self->argv };
  32         92  
283             }
284 42         148 $parameters->{ $name } = $value;
285 42         187 $param_specs->{ $name } = $p;
286             }
287             }
288              
289             sub process_input {
290 33     33 1 195 my ($self, %args) = @_;
291 33         79 my %options;
292 33         146 $self->options(\%options);
293 33         66 my @cmds;
294 33         101 my $spec = $self->spec;
295 33         164 my $option_specs = $args{option_specs};
296 33         73 my $param_specs = $args{param_specs};
297 33         125 my $global_options = $spec->options;
298 33         118 my $global_parameters = $spec->parameters;
299 33         223 my @getopt = $spec->make_getopt($global_options, \%options, $option_specs);
300 33         292 GetOptions(@getopt);
301 33         20104 $self->event_globaloptions;
302 33         100 my $op = $self->op;
303              
304 33         174 $self->process_parameters(
305             parameter_list => $global_parameters,
306             param_specs => $param_specs,
307             );
308              
309              
310              
311 33         97 my $commands = $spec->subcommands;
312 33         143 my $opclass = $self->spec->class;
313 33         70 my $cmd_spec;
314 33         88 my $subcommand_required = 1;
315 33         209 while (keys %$commands) {
316 41         69 my $cmd = shift @{ $self->argv };
  41         158  
317 41 100       134 if (not defined $cmd) {
318 4 100 66     32 if (not $op or $subcommand_required) {
319 1         4 $self->err($spec->usage(
320             commands => \@cmds,
321             colored => $self->colorize_code('err'),
322             highlights => {
323             subcommands => 1,
324             },
325             ));
326 1         7 $self->err( $self->colorize_error("Missing subcommand(s)") );
327 1         4 $self->halt(1);
328             }
329 4         12 last;
330             }
331 37 100       158 $cmd_spec = $commands->{ $cmd } or do {
332 2         13 $self->err($spec->usage(
333             commands => \@cmds,
334             colored => $self->colorize_code('err'),
335             highlights => {
336             subcommands => 1,
337             },
338             ));
339 2         21 $self->err( $self->colorize_error("Unknown subcommand '$cmd'") );
340 2         13 $self->halt(1);
341 2         6 last;
342             };
343 35   100     173 $subcommand_required = $cmd_spec->{subcommand_required} // 1;
344 35         182 my $cmd_options = $cmd_spec->options;
345 35         162 my @getopt = $spec->make_getopt($cmd_options, \%options, $option_specs);
346 35         194 GetOptions(@getopt);
347 35         7620 push @cmds, $cmd;
348 35   100     263 $commands = $cmd_spec->subcommands || {};
349 35 100       211 $op = '::' . $cmd_spec->op if $cmd_spec->op;
350 35 100       141 $opclass = $cmd_spec->class if $cmd_spec->class;
351              
352 35         191 $self->process_parameters(
353             parameter_list => $cmd_spec->parameters,
354             param_specs => $param_specs,
355             );
356             }
357              
358 33 100       247 unless ($self->response->halted) {
359 30 100       152 unless ($op) {
360 1 50       6 if ($spec->has_subcommands) {
361 0         0 $self->err( "Missing op for commands (@cmds)\n" );
362 0         0 my $help = $spec->usage(
363             commands => \@cmds,
364             colored => $self->colorize_code('err'),
365             );
366 0         0 $self->err( $help );
367 0         0 $self->halt(1);
368             }
369             else {
370 1         3 $op = "::execute";
371             }
372             }
373 30         141 $self->commands(\@cmds);
374 30         120 $self->options(\%options);
375 30 100       175 if ($op =~ m/^::/) {
376 29         115 $op = $opclass . $op;
377             }
378 30         108 $self->op($op);
379 30         221 return $op;
380             }
381              
382 3         21 return;
383             }
384              
385             sub colorize_error {
386 3     3 1 8 my ($self, $msg) = @_;
387 3         16 $msg = $self->colored('err', [qw/ error /], $msg) . "\n";
388             }
389              
390             sub colored {
391 15     15 1 49 my ($self, $out, $colors, $msg) = @_;
392 15 50       42 $colors = [ map { $_ eq 'error' ? qw/ bold red / : $_ } @$colors ];
  15         89  
393 15         1982 require Term::ANSIColor;
394 15 50       35656 $self->colorize($out)
395             and $msg = Term::ANSIColor::colored($colors, $msg);
396 15         95 return $msg;
397             }
398              
399             sub subscribe {
400 60     60 1 213 my ($self, %args) = @_;
401              
402 60         265 for my $event (sort keys %args) {
403 60 50       207 next unless exists $EVENTS{ $event };
404 60         105 my $info = $args{ $event };
405 60         115 push @{ $self->subscribers->{ $event } }, $info;
  60         572  
406             }
407              
408             }
409              
410             sub event_globaloptions {
411 33     33 1 123 my ($self) = @_;
412              
413 33         176 my $subscribers = $self->subscribers->{global_options};
414 33         101 for my $sub (@$subscribers) {
415 33         91 my $plugin = $sub->{plugin};
416 33         79 my $method = $sub->{method};
417 33         229 $plugin->$method( run => $self);
418             }
419             }
420              
421             #sub event_processed {
422             # my ($self) = @_;
423             # my $plugins = $self->spec->plugins_by_type->{GlobalOptions};
424             # for my $plugin (@$plugins) {
425             # next unless $plugin->can("event_processed");
426             # $plugin->event_processed(
427             # run => $self,
428             # );
429             # }
430             #}
431              
432             1;
433              
434             __END__