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