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 4     4   1217 use strict;
  4         12  
  4         108  
3 4     4   19 use warnings;
  4         10  
  4         116  
4             package App::Spec::Run;
5 4     4   78 use 5.010;
  4         14  
6             our $VERSION = '0.011'; # VERSION
7              
8 4     4   1637 use App::Spec::Run::Validator;
  4         12  
  4         138  
9 4     4   1781 use App::Spec::Run::Response;
  4         12  
  4         138  
10 4     4   2757 use Getopt::Long qw/ :config pass_through bundling /;
  4         41619  
  4         14  
11 4     4   843 use Ref::Util qw/ is_arrayref /;
  4         6  
  4         176  
12 4     4   24 use Moo;
  4         9  
  4         23  
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 30     30 1 32430 my ($self) = @_;
34              
35 30   50     223 my $plugins = $self->spec->plugins || [];
36 30         89 for my $plugin (@$plugins) {
37 84         416 $plugin->init_run($self);
38             }
39 30         74 my @callbacks;
40 30         71 my $subscriber_events = $self->subscribers;
41 30         73 for my $key (qw/ global_options print_output /) {
42 60         163 my $subscribers = $subscriber_events->{ $key };
43 60         110 for my $sub (@$subscribers) {
44 56         108 my $plugin = $sub->{plugin};
45 56         103 my $method = $sub->{method};
46             my $callback = sub {
47 4     4   19 $plugin->$method( run => $self, @_);
48 56         327 };
49 56         143 push @callbacks, $callback;
50             }
51 60         266 $self->response->add_callbacks($key => \@callbacks);
52             }
53              
54 30         101 my $argv = $self->argv;
55 30 100       93 unless ($argv) {
56 29         88 $argv = \@ARGV;
57 29         114 $self->argv($argv);
58 29         113 $self->argv_orig([ @$argv ]);
59             }
60              
61 30         72 my $completion_parameter = $ENV{PERL5_APPSPECRUN_COMPLETION_PARAMETER};
62              
63 30         57 my %option_specs;
64             my %param_specs;
65 30 50       118 unless ($self->op) {
66 30         137 $self->process_input(
67             option_specs => \%option_specs,
68             param_specs => \%param_specs,
69             );
70             }
71              
72 30 100       146 unless ($self->response->halted) {
73 27         932 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 27         5013 my %errs;
80 27         177 my $ok = $opt->process( $self, \%errs );
81 27 100       127 unless ($ok) {
82 14         55 $self->validation_errors(\%errs);
83             # if we are in completion mode, some errors might be ok
84 14 100       52 if (not $completion_parameter) {
85 9         42 $self->error_output;
86             }
87             }
88             }
89              
90 30 100       530 unless ($self->response->halted) {
91              
92 18         52 my $op = $self->op;
93              
94 18 100       53 if ($completion_parameter) {
95 5         21 $self->completion_output(
96             param_specs => \%param_specs,
97             option_specs => \%option_specs,
98             completion_parameter => $completion_parameter,
99             );
100             }
101             else {
102 13         54 $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 17     17 1 51 my ($self, $op, $args) = @_;
120 17         147 $self->cmd->$op($self, $args);
121             }
122              
123             sub out {
124 22     22 1 425 my ($self, $text) = @_;
125 22 100 100     164 $text .= "\n" if (not ref $text and $text !~ m/\n\z/);
126 22         118 $self->response->add_output($text);
127             }
128              
129             sub err {
130 28     28 1 79 my ($self, $text) = @_;
131 28 100 66     252 $text .= "\n" if (not ref $text and $text !~ m/\n\z/);
132 28         136 $self->response->add_error($text);
133             }
134              
135             sub halt {
136 13     13 1 40 my ($self, $exit) = @_;
137 13         51 $self->response->halted(1);
138 13   50     98 $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 24 my ($self, %args) = @_;
153 5         11 my $completion_parameter = $args{completion_parameter};
154 5         10 my $param_specs = $args{param_specs};
155 5         10 my $option_specs = $args{option_specs};
156 5 100       52 my $shell = $ENV{PERL5_APPSPECRUN_SHELL} or return;
157 4         13 my $param = $param_specs->{ $completion_parameter };
158 4   33     11 $param ||= $option_specs->{ $completion_parameter };
159 4         8 my $unique = $param->{unique};
160 4 50       34 my $completion = $param->completion or return;
161 4         10 my $op;
162 4 100       10 if (ref $completion) {
163 2 50       9 $op = $completion->{op} or return;
164             }
165             else {
166 2 50       10 my $possible_values = $param->values or return;
167 2 50       8 $op = $possible_values->{op} or return;
168             }
169 4         16 my $args = {
170             runmode => "completion",
171             parameter => $completion_parameter,
172             };
173 4         18 my $result = $self->run_op($op, $args);
174              
175 4         244 my $string = '';
176 4         8 my %seen;
177 4 100       12 if ($unique) {
178 2         8 my $params = $self->parameters;
179 2         5 my $value = $params->{ $completion_parameter };
180 2 50       6 $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         6 my $last = pop @$value;
187 2         9 @seen{ @$value } = (1) x @$value;
188             }
189 4         12 for my $item (@$result) {
190 9 100       22 if (ref $item eq 'HASH') {
191 2         5 my $name = $item->{name};
192 2 50 33     6 $unique and $seen{ $name }++ and next;
193 2         5 my $desc = $item->{description};
194 2         10 $string .= "$name\t$desc\n";
195             }
196             else {
197 7 100 100     33 $unique and $seen{ $item }++ and next;
198 6         15 $string .= "$item\n";
199             }
200             }
201              
202 4         14 $self->out($string);
203 4         158 return;
204             }
205              
206             sub error_output {
207 9     9 1 28 my ($self) = @_;
208 9         28 my $errs = $self->validation_errors;
209 9         19 my @error_output;
210 9         38 for my $key (sort keys %$errs) {
211 9         20 my $errors = $errs->{ $key };
212 9 50 66     50 if ($key eq "parameters" or $key eq "options") {
213 9         39 for my $name (sort keys %$errors) {
214 12         26 my $error = $errors->{ $name };
215 12         55 $key =~ s/s$//;
216 12         69 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         60 my $help = $self->spec->usage(
226             commands => $self->commands,
227             highlights => $errs,
228             colored => $self->colorize_code('err'),
229             );
230 9         55 $self->err($help);
231 9         29 for my $msg (@error_output) {
232 12         47 $msg = $self->colored('err', [qw/ error /], $msg);
233 12         51 $self->err("$msg\n");
234             }
235 9         41 $self->halt(1);
236             }
237              
238             sub colorize_code {
239 16     16 1 50 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 83     83   184 : sub { $_[1] },
249 16 50       62 }
250              
251             sub colorize {
252 31     31 1 75 my ($self, $out) = @_;
253 31   100     103 $out ||= 'out';
254 31 50 100     149 if (($ENV{PERL5_APPSPECRUN_COLOR} // '') eq 'always') {
255 0         0 return 1;
256             }
257 31 100 100     115 if (($ENV{PERL5_APPSPECRUN_COLOR} // '') eq 'never') {
258 29         202 return 0;
259             }
260 2 50 33     33 if ($out eq 'out' and -t STDOUT or $out eq 'err' and -t STDERR) {
      33        
      33        
261 0         0 return 1;
262             }
263 2         21 return 0;
264             }
265              
266             sub process_parameters {
267 61     61 1 199 my ($self, %args) = @_;
268 61         113 my $param_list = $args{parameter_list};
269 61         135 my $parameters = $self->parameters;
270 61         121 my $param_specs = $args{param_specs};
271 61         239 for my $p (@$param_list) {
272 42         125 my $name = $p->name;
273 42         95 my $type = $p->type;
274 42         100 my $multiple = $p->multiple;
275 42         105 my $required = $p->required;
276 42         66 my $value;
277 42 100       89 if ($multiple) {
278 10         16 $value = [@{ $self->argv }];
  10         27  
279 10         21 @{ $self->argv } = ();
  10         20  
280             }
281             else {
282 32         47 $value = shift @{ $self->argv };
  32         75  
283             }
284 42         117 $parameters->{ $name } = $value;
285 42         144 $param_specs->{ $name } = $p;
286             }
287             }
288              
289             sub process_input {
290 30     30 1 143 my ($self, %args) = @_;
291 30         81 my %options;
292 30         150 $self->options(\%options);
293 30         64 my @cmds;
294 30         79 my $spec = $self->spec;
295 30         63 my $option_specs = $args{option_specs};
296 30         68 my $param_specs = $args{param_specs};
297 30         92 my $global_options = $spec->options;
298 30         86 my $global_parameters = $spec->parameters;
299 30         147 my @getopt = $spec->make_getopt($global_options, \%options, $option_specs);
300 30         193 GetOptions(@getopt);
301 30         12981 $self->event_globaloptions;
302 30         78 my $op = $self->op;
303              
304 30         136 $self->process_parameters(
305             parameter_list => $global_parameters,
306             param_specs => $param_specs,
307             );
308              
309              
310              
311 30         81 my $commands = $spec->subcommands;
312 30         94 my $opclass = $self->spec->class;
313 30         73 my $cmd_spec;
314 30         60 my $subcommand_required = 1;
315 30         107 while (keys %$commands) {
316 35         55 my $cmd = shift @{ $self->argv };
  35         107  
317 35 100       112 if (not defined $cmd) {
318 2 100 66     10 if (not $op or $subcommand_required) {
319 1         6 $self->err($spec->usage(
320             commands => \@cmds,
321             colored => $self->colorize_code('err'),
322             highlights => {
323             subcommands => 1,
324             },
325             ));
326 1         8 $self->err( $self->colorize_error("Missing subcommand(s)") );
327 1         6 $self->halt(1);
328             }
329 2         6 last;
330             }
331 33 100       126 $cmd_spec = $commands->{ $cmd } or do {
332 2         14 $self->err($spec->usage(
333             commands => \@cmds,
334             colored => $self->colorize_code('err'),
335             highlights => {
336             subcommands => 1,
337             },
338             ));
339 2         18 $self->err( $self->colorize_error("Unknown subcommand '$cmd'") );
340 2         11 $self->halt(1);
341 2         7 last;
342             };
343 31   100     142 $subcommand_required = $cmd_spec->{subcommand_required} // 1;
344 31         103 my $cmd_options = $cmd_spec->options;
345 31         116 my @getopt = $spec->make_getopt($cmd_options, \%options, $option_specs);
346 31         144 GetOptions(@getopt);
347 31         5329 push @cmds, $cmd;
348 31   100     210 $commands = $cmd_spec->subcommands || {};
349 31 100       151 $op = '::' . $cmd_spec->op if $cmd_spec->op;
350 31 100       130 $opclass = $cmd_spec->class if $cmd_spec->class;
351              
352 31         139 $self->process_parameters(
353             parameter_list => $cmd_spec->parameters,
354             param_specs => $param_specs,
355             );
356             }
357              
358 30 100       153 unless ($self->response->halted) {
359 27 100       84 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 27         92 $self->commands(\@cmds);
374 27         81 $self->options(\%options);
375 27 100       147 if ($op =~ m/^::/) {
376 26         80 $op = $opclass . $op;
377             }
378 27         92 $self->op($op);
379 27         121 return $op;
380             }
381              
382 3         15 return;
383             }
384              
385             sub colorize_error {
386 3     3 1 11 my ($self, $msg) = @_;
387 3         12 $msg = $self->colored('err', [qw/ error /], $msg) . "\n";
388             }
389              
390             sub colored {
391 15     15 1 46 my ($self, $out, $colors, $msg) = @_;
392 15 50       33 $colors = [ map { $_ eq 'error' ? qw/ bold red / : $_ } @$colors ];
  15         72  
393 15         1660 require Term::ANSIColor;
394 15 50       17798 $self->colorize($out)
395             and $msg = Term::ANSIColor::colored($colors, $msg);
396 15         53 return $msg;
397             }
398              
399             sub subscribe {
400 56     56 1 201 my ($self, %args) = @_;
401              
402 56         167 for my $event (sort keys %args) {
403 56 50       179 next unless exists $EVENTS{ $event };
404 56         96 my $info = $args{ $event };
405 56         88 push @{ $self->subscribers->{ $event } }, $info;
  56         288  
406             }
407              
408             }
409              
410             sub event_globaloptions {
411 30     30 1 90 my ($self) = @_;
412              
413 30         100 my $subscribers = $self->subscribers->{global_options};
414 30         86 for my $sub (@$subscribers) {
415 30         61 my $plugin = $sub->{plugin};
416 30         68 my $method = $sub->{method};
417 30         160 $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__