File Coverage

blib/lib/CLI/Driver.pm
Criterion Covered Total %
statement 217 303 71.6
branch 38 122 31.1
condition 5 18 27.7
subroutine 45 50 90.0
pod n/a
total 305 493 61.8


line stmt bran cond sub pod time code
1             package CLI::Driver;
2              
3             =head1 NAME
4              
5             CLI::Driver - Drive your cli tool with YAML
6              
7             =cut
8              
9 18     18   890429 use Modern::Perl;
  18         8336  
  18         141  
10 18     18   13561 use Moose;
  18         8736517  
  18         131  
11 18     18   148107 use namespace::autoclean;
  18         152523  
  18         74  
12 18     18   10496 use Kavorka 'method';
  18         253095  
  18         163  
13 18     18   3652843 use Data::Printer alias => 'pdump';
  18         38071  
  18         207  
14 18     18   12621 use CLI::Driver::Action;
  18         82  
  18         1017  
15 18     18   182 use Module::Load;
  18         42  
  18         178  
16              
17 18     18   1339 use Getopt::Long;
  18         43  
  18         216  
18             Getopt::Long::Configure('no_ignore_case');
19             Getopt::Long::Configure('pass_through');
20             Getopt::Long::Configure('no_auto_abbrev');
21              
22 18     18   3942 use YAML::Syck;
  18         52  
  18         1868  
23              
24             with 'CLI::Driver::CommonRole';
25              
26             our $VERSION = 0.77;
27              
28             =head1 SYNOPSIS
29              
30             use CLI::Driver;
31            
32             my $cli = CLI::Driver->new;
33             $cli->run;
34              
35             - or -
36            
37             my $cli = CLI::Driver->new(
38             path => './etc:/etc',
39             file => 'myconfig.yml'
40             );
41             $cli->run;
42            
43             - or -
44              
45             my $cli = CLI::Driver->new(
46             use_file_sharedir => 1,
47             file_sharedir_dist_name => 'CLI-Driver',
48             );
49             $cli->run;
50            
51             #################################
52             # cli-driver.yml example
53             #################################
54             do-something:
55             desc: "Action description"
56             deprecated:
57             status: false
58             replaced-by: na
59             class:
60             name: My::App
61             attr:
62             required:
63             hard:
64             f: foo
65             soft:
66             h: home
67             a: '@array_arg'
68             optional:
69             flags:
70             dry-run: dry_run_flag
71             method:
72             name: my_method
73             args:
74             required:
75             hard:
76             soft:
77             optional:
78             flags:
79             help:
80             args:
81             f: "Additional help info for argument 'f'"
82             examples:
83             - "-f foo -a val1 -a val2 --dry-run"
84            
85             =cut
86              
87             ##############################################################################
88             ### CONSTANTS
89             ##############################################################################
90              
91 18     18   121 use constant DEFAULT_CLI_DRIVER_PATH => ( '.', 'etc', '/etc' );
  18         50  
  18         1614  
92 18     18   134 use constant DEFAULT_CLI_DRIVER_FILE => 'cli-driver.yml';
  18         55  
  18         2507  
93              
94             ##############################################################################
95             ### ATTRIBUTES
96             ##############################################################################
97              
98             =head1 ATTRIBUTES
99              
100             =head2 path
101              
102             Directory where your cli-driver.yml file is located. You can specify
103             multiple directories by separating them with ':'. For example,
104             "etc:/etc".
105              
106             isa: Str
107              
108             defaults: .:etc:/etc
109              
110             =cut
111              
112             has path => (
113             is => 'rw',
114             isa => 'Str',
115             );
116              
117             =head2 file
118              
119             Name of your YAML driver file.
120              
121             isa: Str
122              
123             default: cli-driver.yml
124              
125             =cut
126              
127             has file => (
128             is => 'ro',
129             isa => 'Str',
130             lazy => 1,
131             builder => '_build_file'
132             );
133              
134             =head2 use_file_sharedir
135              
136             Flag indicating you want to use File::ShareDir to locate the driver file.
137             Requires the attribute 'file_sharedir_dist_name' to be provided. Is mutually
138             exclusive with the 'path' attribute.
139              
140             isa: Bool
141              
142             default: 0
143              
144             =cut
145              
146             has use_file_sharedir => (
147             is => 'ro',
148             isa => 'Bool',
149             default => 0,
150             );
151              
152             =head2 file_sharedir_dist_name
153              
154             Your distro name. For example: 'CLI-Driver'.
155              
156             isa: Str
157              
158             default: undef
159              
160             =cut
161              
162             has file_sharedir_dist_name => (
163             is => 'ro',
164             isa => 'Str',
165             );
166              
167             =head2 argv_map
168              
169             A set of command line overrides for retrieving arguments. This can be used
170             in-place of @ARGV args.
171              
172             Example:
173              
174             {
175             classAttrName1 => 'abc',
176             classAttrName2 => 'def',
177             methodArgName1 => 'ghi'
178             }
179              
180             isa: HashRef
181              
182             default: undef
183              
184             =cut
185              
186             # notice the cli switches are not part of the map.
187             has argv_map => (
188             is => 'rw',
189             isa => 'HashRef',
190             predicate => 'has_argv_map',
191             writer => '_set_argv_map',
192             );
193              
194             =head2 actions
195              
196             A list of actions parsed from the driver file.
197              
198             isa: ArrayRef[CLI::Driver::Action]
199              
200             =cut
201              
202             has actions => (
203             is => 'rw',
204             isa => 'ArrayRef[CLI::Driver::Action]',
205             lazy => 1,
206             builder => '_build_actions',
207             );
208              
209             ##############################################################################
210             ### PUBLIC METHODS
211             ##############################################################################
212              
213 18 50   18   39046 method BUILD (@argv) {
  18 50   17   57  
  18         2947  
  17         87  
  17         155  
  17         47  
214              
215 17 100       647 if ( $self->has_argv_map ) {
216 1         27 $self->_build_global_argv_map( $self->argv_map );
217             }
218             }
219              
220 18 0   18   46249 method set_argv_map (HashRef $argv_map) {
  18 0   18   80  
  18 0   0   3411  
  18 0       145  
  18 0       47  
  18         2072  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
221              
222 0         0 $self->_set_argv_map( {%$argv_map} );
223 0         0 $self->_build_global_argv_map( $self->argv_map );
224             }
225              
226 18 50 33 18   42891 method get_action (Str :$name!) {
  18 50 33 18   46  
  18 50   18   2313  
  18 50   18   180  
  18 50   18   74  
  18 50   21   1196  
  18         137  
  18         46  
  18         209  
  18         2027  
  18         54  
  18         7342  
  18         143  
  18         49  
  18         4046  
  21         10061  
  21         60  
  21         43  
  21         122  
  0         0  
  21         46  
  21         67  
  21         92  
  0         0  
  21         95  
  21         65  
  21         97  
  21         88  
  21         41  
  21         96  
  21         180  
  21         109  
  21         80  
  21         43  
  21         113  
  21         44  
227              
228 21         96 my $actions = $self->get_actions;
229              
230 21         69 foreach my $action (@$actions) {
231 167 100       4127 if ( $action->name eq $name ) {
232 21         170 return $action;
233             }
234             }
235             }
236              
237 18 50 33 18   53915 method get_actions (Bool :$want_hashref = 0) {
  18 50 0 18   76  
  18 50   18   2254  
  18 50   18   137  
  18 50   18   45  
  18     21   919  
  18         122  
  18         47  
  18         124  
  18         1618  
  18         54  
  18         6973  
  18         143  
  18         40  
  18         5194  
  21         92  
  21         45  
  21         38  
  21         114  
  0         0  
  21         53  
  21         60  
  21         84  
  0         0  
  21         88  
  0         0  
  0         0  
  0         0  
  21         42  
  21         87  
  21         100  
  21         175  
  21         48  
  21         137  
  21         54  
238              
239 21         49 my @ret = @{ $self->actions };
  21         661  
240              
241 21 50       123 if ($want_hashref) {
242              
243 0         0 my %actions;
244 0         0 foreach my $action (@ret) {
245 0         0 my $name = $action->name;
246 0 0       0 next if $name =~ /dummy/i;
247 0         0 $actions{$name} = $action;
248             }
249              
250 0         0 return \%actions;
251             }
252              
253 21         95 return \@ret;
254             }
255              
256 18 0   18   21996 method run {
  18     0   53  
  18         2793  
  0         0  
  0         0  
257              
258 0         0 my $action = $self->parse_cmd_line();
259 0 0       0 if ($action) {
260 0         0 $action->do;
261             }
262             else {
263 0         0 $self->fatal("failed to find action in config file");
264             }
265             }
266              
267 18 0   18   21864 method parse_cmd_line {
  18     0   54  
  18         6779  
  0         0  
  0         0  
268              
269 0         0 my $help;
270             my $action_name;
271 0         0 my $dump;
272              
273 0         0 GetOptions( #
274             "dump" => \$dump,
275             "help|?" => \$help
276             );
277            
278 0 0       0 if ( !@ARGV ) {
    0          
279 0         0 $self->usage;
280             }
281             elsif (@ARGV) {
282 0         0 $action_name = shift @ARGV;
283             }
284              
285 0         0 my $action;
286 0 0       0 if ($action_name) {
287 0         0 $action = $self->get_action( name => $action_name );
288            
289 0 0       0 if ($dump) {
290 0         0 say $action->to_yaml;
291 0         0 exit;
292             }
293             }
294              
295 0 0       0 if ($help) {
296 0 0       0 if ($action) {
297 0         0 $action->usage;
298             }
299             else {
300 0         0 $self->usage;
301             }
302             }
303              
304 0         0 return $action;
305             }
306              
307 18 0   18   48163 method usage (Str $errmsg?) {
  18 0   18   76  
  18 0   0   3759  
  18 0       152  
  18 0       58  
  18         7861  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
308              
309 0 0       0 print STDERR "$errmsg\n" if $errmsg;
310 0         0 print "\nusage: $0 <action> [opts] [-?] [--dump]\n\n";
311              
312 0         0 my @list;
313 0         0 my $actions = $self->get_actions;
314              
315 0         0 foreach my $action (@$actions) {
316              
317 0 0       0 next if $action->name =~ /dummy/i;
318              
319 0         0 my @display;
320 0         0 push @display, $action->name;
321              
322 0 0       0 if ( $action->is_deprecated ) {
323 0         0 my $depr = $action->deprecated;
324 0         0 push @display, sprintf '(%s)', $depr->get_usage_modifier;
325             }
326              
327 0         0 push @list, join( ' ', @display );
328             }
329              
330 0         0 say "\tACTIONS:";
331              
332 0         0 foreach my $action ( sort @list ) {
333 0         0 print "\t\t$action\n";
334             }
335              
336 0         0 print "\n";
337 0         0 exit 1;
338             }
339              
340             ##############################################################################
341             ### PRIVATE METHODS
342             ##############################################################################
343              
344 18 50   18   22029 method _find_file {
  18     17   46  
  18         8808  
  17         85  
  17         41  
345              
346 17         47 my @search_dirs;
347              
348 17 50       612 if ( $self->use_file_sharedir ) {
349              
350 0         0 my $dist_name = $self->file_sharedir_dist_name;
351 0 0       0 if ( !$dist_name ) {
352 0         0 confess "must provide file_sharedir_dist_name "
353             . "when use_file_sharedir is true";
354             }
355              
356 0         0 load 'File::ShareDir';
357              
358 0         0 @search_dirs = ('./share');
359 0         0 push @search_dirs, File::ShareDir::dist_dir($dist_name);
360             }
361             else {
362              
363 17 50       474 if ( $self->path ) {
364 17         393 push @search_dirs, split( /:/, $self->path );
365             }
366              
367 17         93 push @search_dirs, DEFAULT_CLI_DRIVER_PATH;
368             }
369              
370 17         71 foreach my $path (@search_dirs) {
371 17         535 my $fullpath = sprintf "%s/%s", $path, $self->file;
372 17 50       498 if ( -f $fullpath ) {
373 17         124 return $fullpath;
374             }
375             }
376              
377 0         0 my $msg = sprintf "unable to find %s in: %s", $self->file,
378             join( ', ', @search_dirs );
379 0         0 confess $msg;
380             }
381              
382 18 50   18   21926 method _build_actions {
  18     17   51  
  18         5131  
  17         85  
  17         59  
383              
384 17         63 my @actions;
385              
386 17         98 my $driver_file = $self->_find_file;
387 17         99 my $actions = $self->_parse_yaml( path => $driver_file );
388              
389 17         123 foreach my $action_name ( keys %$actions ) {
390              
391             my $action = CLI::Driver::Action->new(
392 272 100       8937 href => $actions->{$action_name},
393             name => $action_name,
394             use_argv_map => $self->has_argv_map ? 1 : 0
395             );
396              
397 272         959 my $success = $action->parse;
398 272 50       657 if ($success) {
399 272         806 push @actions, $action;
400             }
401             }
402              
403 17         539 return \@actions;
404             }
405              
406 18 50 33 18   44514 method _parse_yaml (Str :$path!) {
  18 50 33 18   84  
  18 50   18   2283  
  18 50   18   138  
  18 50   18   39  
  18 50   17   985  
  18         142  
  18         60  
  18         164  
  18         1707  
  18         62  
  18         7290  
  18         142  
  18         40  
  18         3394  
  17         120  
  17         44  
  17         42  
  17         112  
  0         0  
  17         55  
  17         63  
  17         134  
  0         0  
  17         70  
  17         82  
  17         80  
  17         78  
  17         38  
  17         87  
  17         152  
  17         82  
  17         69  
  17         48  
  17         95  
  17         36  
407              
408 17         45 my $href;
409 17         39 eval {
410 17         105 $href = YAML::Syck::LoadFile($path);
411             };
412 17 50       16837 confess $@ if $@;
413            
414 17         83 return $href;
415             }
416              
417 18 0   18   22575 method _build_file {
  18     0   46  
  18         2865  
  0         0  
  0         0  
418              
419 0 0       0 if ( $ENV{CLI_DRIVER_FILE} ) {
420 0         0 return $ENV{CLI_DRIVER_FILE};
421             }
422              
423 0         0 return DEFAULT_CLI_DRIVER_FILE;
424             }
425              
426 18 50   18   43243 method _build_global_argv_map (HashRef $argv_map) {
  18 50   18   45  
  18 50   1   3556  
  18 50       145  
  18 50       45  
  18         4323  
  1         4  
  1         5  
  1         5  
  1         5  
  1         2  
  1         5  
  1         2  
427              
428 1         3 %ARGV = ();
429              
430 1         5 foreach my $key ( keys %$argv_map ) {
431 4         32 $ARGV{$key} = $argv_map->{$key};
432             }
433             }
434              
435             __PACKAGE__->meta->make_immutable;
436              
437             1;