File Coverage

blib/lib/MooX/Cmd/Role.pm
Criterion Covered Total %
statement 106 107 99.0
branch 36 42 85.7
condition n/a
subroutine 25 26 96.1
pod 2 2 100.0
total 169 177 95.4


line stmt bran cond sub pod time code
1             package MooX::Cmd::Role;
2              
3 6     6   30573 use strict;
  6         54  
  6         168  
4 6     6   34 use warnings;
  6         9  
  6         218  
5              
6             our $VERSION = "0.016_001";
7              
8 6     6   41 use Moo::Role;
  6         22  
  6         30  
9              
10 6     6   1828 use Carp;
  6         16  
  6         306  
11 6     6   27 use Module::Runtime qw/ use_module /;
  6         8  
  6         52  
12 6     6   2753 use Regexp::Common;
  6         14181  
  6         19  
13 6     6   756854 use Text::ParseWords 'shellwords';
  6         6031  
  6         319  
14 6     6   1580 use Module::Pluggable::Object;
  6         23566  
  6         190  
15              
16 6     6   1874 use List::MoreUtils qw/first_index first_result/;
  6         39915  
  6         87  
17 6     6   4624 use Scalar::Util qw/blessed/;
  6         14  
  6         311  
18 6     6   2612 use Params::Util qw/_ARRAY/;
  6         12380  
  6         8047  
19              
20             =head1 NAME
21              
22             MooX::Cmd::Role - MooX cli app commands do this
23              
24             =head1 SYNOPSIS
25              
26             =head2 using role and want behavior as MooX::Cmd
27              
28             package MyFoo;
29            
30             with MooX::Cmd::Role;
31            
32             sub _build_command_execute_from_new { 1 }
33              
34             package main;
35              
36             my $cmd = MyFoo->new_with_cmd;
37              
38             =head2 using role and don't execute immediately
39              
40             package MyFoo;
41              
42             with MooX::Cmd::Role;
43             use List::MoreUtils qw/ first_idx /;
44              
45             sub _build_command_base { "MyFoo::Command" }
46              
47             sub _build_command_execute_from_new { 0 }
48              
49             sub execute {
50             my $self = shift;
51             my $chain_idx = first_idx { $self == $_ } @{$self->command_chain};
52             my $next_cmd = $self->command_chain->{$chain_idx+1};
53             $next_cmd->owner($self);
54             $next_cmd->execute;
55             }
56              
57             package main;
58              
59             my $cmd = MyFoo->new_with_cmd;
60             $cmd->command_chain->[-1]->run();
61              
62             =head2 explicit expression of some implicit stuff
63              
64             package MyFoo;
65              
66             with MooX::Cmd::Role;
67              
68             sub _build_command_base { "MyFoo::Command" }
69              
70             sub _build_command_execute_method_name { "run" }
71              
72             sub _build_command_execute_from_new { 0 }
73              
74             package main;
75              
76             my $cmd = MyFoo->new_with_cmd;
77             $cmd->command_chain->[-1]->run();
78              
79             =head1 DESCRIPTION
80              
81             MooX::Cmd::Role is made for modern, flexible Moo style to tailor cli commands.
82              
83             =head1 ATTRIBUTES
84              
85             =head2 command_args
86              
87             ARRAY-REF of args on command line
88              
89             =cut
90              
91             has 'command_args' => (is => "ro");
92              
93             =head2 command_chain
94              
95             ARRAY-REF of commands lead to this instance
96              
97             =cut
98              
99             has 'command_chain' => (is => "ro");
100              
101             =head2 command_chain_end
102              
103             COMMAND accesses the finally detected command in chain
104              
105             =cut
106              
107             has 'command_chain_end' => (is => "lazy");
108              
109 22     22   685 sub _build_command_chain_end { $_[0]->command_chain->[-1] }
110              
111             =head2 command_name
112              
113             ARRAY-REF the name of the command lead to this command
114              
115             =cut
116              
117             has 'command_name' => (is => "ro");
118              
119             =head2 command_commands
120              
121             HASH-REF names of other commands
122              
123             =cut
124              
125             has 'command_commands' => (is => "lazy");
126              
127             sub _build_command_commands
128             {
129 44     44   140 my ($class, $params) = @_;
130 44 100       383 defined $params->{command_base} or $params->{command_base} = $class->_build_command_base($params);
131 44         103 my $base = $params->{command_base};
132              
133             # I have no clue why 'only' and 'except' seems to not fulfill what I need or are bugged in M::P - Getty
134             my @cmd_plugins = grep {
135 44         870 my $plug_class = _mkcommand($_, $base);
  63         57049  
136 63         206 index($plug_class, ":") == -1;
137             } Module::Pluggable::Object->new(
138             search_path => $base,
139             require => 0,
140             )->plugins;
141              
142 44         3458 my %cmds = map { _mkcommand($_, $base) => $_ } @cmd_plugins;
  55         128  
143 44 50       187 scalar keys %cmds == scalar @cmd_plugins
144             or croak "Can't compute unambiguous list of commands from '" . join("', '", @cmd_plugins) . "'";
145              
146 44         219 \%cmds;
147             }
148              
149             =head2 command_base
150              
151             STRING base of command plugins
152              
153             =cut
154              
155             has command_base => (is => "lazy");
156              
157 42     42   330 sub _build_command_base { $_[0] . '::Cmd'; }
158              
159             =head2 command_execute_method_name
160              
161             STRING name of the method to invoke to execute a command, default "execute"
162              
163             =cut
164              
165             has command_execute_method_name => (is => "lazy");
166              
167 17     17   139 sub _build_command_execute_method_name { "execute" }
168              
169             =head2 command_execute_return_method_name
170              
171             STRING I have no clue what that is good for ...
172              
173             =cut
174              
175             has command_execute_return_method_name => (is => "lazy");
176              
177 29     29   578 sub _build_command_execute_return_method_name { "execute_return" }
178              
179             =head2 command_creation_method_name
180              
181             STRING name of constructor
182              
183             =cut
184              
185             has command_creation_method_name => (is => "lazy");
186              
187 2     2   18 sub _build_command_creation_method_name { "new_with_cmd" }
188              
189             =head2 command_creation_chain_methods
190              
191             ARRAY-REF names of methods to chain for creating object (from L)
192              
193             =cut
194              
195             has command_creation_chain_methods => (is => "lazy");
196              
197 39     39   191 sub _build_command_creation_chain_methods { ['new_with_options', 'new'] }
198              
199             =head2 command_execute_from_new
200              
201             BOOL true when constructor shall invoke L, false otherwise
202              
203             =cut
204              
205             has command_execute_from_new => (is => "lazy");
206              
207 0     0   0 sub _build_command_execute_from_new { 0 }
208              
209             =head1 METHODS
210              
211             =head2 new_with_cmd
212              
213             initializes by searching command line args for commands and invoke them
214              
215             =cut
216              
217 2     2 1 17 sub new_with_cmd { goto &_initialize_from_cmd; }
218              
219             sub _mkcommand
220             {
221 141     141   249 my ($package, $base) = @_;
222 141         238 my $bwc = "${base}::";
223 141         174 my $len_bwc = length($bwc);
224 141 50       419 index($package, $bwc) == 0 and substr($package, 0, $len_bwc, "");
225 141         605 lc($package);
226             }
227              
228             my @private_init_params =
229             qw(command_base command_execute_method_name command_execute_return_method_name command_creation_chain_methods command_execute_method_name);
230              
231             my $required_method = sub {
232             my ($tgt, $method) = @_;
233             $tgt->can($method) or croak("You need an '$method' in " . (blessed $tgt || $tgt));
234             };
235              
236             my $call_required_method = sub {
237             my ($tgt, $method, @args) = @_;
238             my $m = $required_method->($tgt, $method);
239             return $m->($tgt, @args);
240             };
241              
242             my $call_optional_method = sub {
243             my ($tgt, $method, @args) = @_;
244             my $m = $tgt->can($method) or return;
245             return $m->($tgt, @args);
246             };
247              
248             my $call_indirect_method = sub {
249             my ($tgt, $name_getter, @args) = @_;
250             my $g = $call_required_method->($tgt, $name_getter);
251             my $m = $required_method->($tgt, $g);
252             return $m->($tgt, @args);
253             };
254              
255             ## no critic qw(ProhibitExcessComplexity)
256             sub _initialize_from_cmd
257             {
258 45     45   300 my ($class, %params) = @_;
259              
260 45         379 my @args = shellwords(join ' ', map { quotemeta } @ARGV);
  34         523  
261              
262 45         3684 my (@used_args, $cmd, $cmd_name, $cmd_name_index);
263              
264 45         171 my %cmd_create_params = %params;
265 45         315 delete @cmd_create_params{qw(command_commands), @private_init_params};
266              
267 45 100       905 defined $params{command_commands} or $params{command_commands} = $class->_build_command_commands(\%params);
268 45 100   26   681 if (($cmd_name_index = first_index { $cmd = $params{command_commands}->{$_} } @args) >= 0)
  26         181  
269             {
270 23         81 @used_args = splice @args, 0, $cmd_name_index;
271 23         63 shift @args; # be careful about relics
272 23         93 $cmd_name = _mkcommand($cmd, $params{command_base});
273              
274 23         292 use_module($cmd);
275             defined $cmd_create_params{command_execute_method_name}
276             or $cmd_create_params{command_execute_method_name} =
277 23 50       25902 $call_optional_method->($cmd, "_build_command_execute_method_name", \%cmd_create_params);
278             defined $cmd_create_params{command_execute_method_name}
279 23 100       189 or $cmd_create_params{command_execute_method_name} = "execute";
280 23         163 $required_method->($cmd, $cmd_create_params{command_execute_method_name});
281             }
282             else
283             {
284 22         53 @used_args = @args;
285 22         38 @args = ();
286             }
287              
288             defined $params{command_creation_chain_methods}
289 44 100       370 or $params{command_creation_chain_methods} = $class->_build_command_creation_chain_methods(\%params);
290             my @creation_chain =
291             _ARRAY($params{command_creation_chain_methods})
292 39         99 ? @{$params{command_creation_chain_methods}}
293 44 100       250 : ($params{command_creation_chain_methods});
294 44 100   83   327 (my $creation_method = first_result { defined $_ and $class->can($_) } @creation_chain)
  83 100       1462  
295             or croak "Can't find a creation method on $class";
296              
297             ## no critic qw(RequireLocalizedPunctuationVars)
298 41         164 @ARGV = @used_args;
299 41         125 $params{command_args} = [@args];
300 41         107 $params{command_name} = $cmd_name;
301 41 100       156 defined $params{command_chain} or $params{command_chain} = [];
302 41         913 my $self = $creation_method->($class, %params);
303 41         202600 push @{$self->command_chain}, $self;
  41         188  
304              
305 41 100       107 if ($cmd)
306             {
307 22         67 @ARGV = @args;
308 22         48 my ($creation_method, $creation_method_name, $cmd_plugin);
309 22 100       442 $cmd->can("_build_command_creation_method_name")
310             and $creation_method_name = $cmd->_build_command_creation_method_name(\%params);
311 22 100       120 $creation_method_name and $creation_method = $cmd->can($creation_method_name);
312 22 100       94 if ($creation_method)
313             {
314 16         58 @cmd_create_params{qw(command_chain)} = @$self{qw(command_chain)};
315 16         77 $cmd_plugin = $creation_method->($cmd, %cmd_create_params);
316             $self->{$self->command_execute_return_method_name} =
317 14         29 [@{$call_indirect_method->($cmd_plugin, "command_execute_return_method_name")}];
  14         31  
318             }
319             else
320             {
321 6 50   12   94 ($creation_method = first_result { defined $_ and $cmd->can($_) } @creation_chain)
  12 50       123  
322             or croak "Can't find a creation method on " . $cmd;
323 6         89 $cmd_plugin = $creation_method->($cmd);
324 6         202 push @{$self->command_chain}, $cmd_plugin;
  6         30  
325              
326 6         31 my $cemn = $cmd_plugin->can("command_execute_method_name");
327 6 50       186 my $exec_fun = $cemn ? $cemn->() : $self->command_execute_method_name();
328             $self->command_execute_from_new
329 6 100       286 and $self->{$self->command_execute_return_method_name} =
330             [$call_required_method->($cmd_plugin, $exec_fun, \@ARGV, $self->command_chain)];
331             }
332             }
333             else
334             {
335             $self->command_execute_from_new
336 19 100       353 and $self->{$self->command_execute_return_method_name} =
337             [$call_indirect_method->($self, "command_execute_method_name", \@ARGV, $self->command_chain)];
338             }
339              
340 39         343 return $self;
341             }
342              
343             =head2 execute_return
344              
345             returns the content of $self->{execute_return}
346              
347             =cut
348              
349             # XXX should be an r/w attribute - can be renamed on loading ...
350 38     38 1 340 sub execute_return { $_[0]->{execute_return} }
351              
352             =head1 LICENSE AND COPYRIGHT
353              
354             Copyright 2012-2013 Torsten Raudssus, Copyright 2013-2017 Jens Rehsack.
355              
356             This program is free software; you can redistribute it and/or modify it
357             under the terms of either: the GNU General Public License as published
358             by the Free Software Foundation; or the Artistic License.
359              
360             See L for more information.
361              
362             =cut
363              
364             1;