File Coverage

blib/lib/App/Base/Script/Common.pm
Criterion Covered Total %
statement 115 115 100.0
branch 12 12 100.0
condition 6 9 66.6
subroutine 25 25 100.0
pod 10 10 100.0
total 168 171 98.2


line stmt bran cond sub pod time code
1             package App::Base::Script::Common;
2 12     12   68375 use 5.010;
  12         24  
3 12     12   584 use Moose::Role;
  12         304428  
  12         58  
4              
5             =head1 NAME
6              
7             App::Base::Script::Common - Behaviors common to App::Base::Script and App::Base::Daemon
8              
9             =head1 VERSION
10              
11             This document describes App::Base version 0.05
12              
13             =head1 DESCRIPTION
14              
15             App::Base::Script::Common provides infrastructure that is common to the
16             App::Base::Script and App::Base::Daemon classes, including options parsing.
17              
18             =cut
19              
20 12     12   44823 use App::Base::Script::Option;
  12         32  
  12         62  
21              
22 12     12   5916 use Cwd qw( abs_path );
  12         17  
  12         659  
23 12     12   9330 use Getopt::Long;
  12         87133  
  12         44  
24 12     12   3235 use IO::Handle;
  12         13968  
  12         410  
25 12     12   46 use List::Util qw( max );
  12         23  
  12         580  
26 12     12   735 use Path::Tiny;
  12         7190  
  12         430  
27 12     12   1655 use POSIX qw( strftime );
  12         16148  
  12         68  
28 12     12   14714 use Text::Reform qw( form break_wrap );
  12         55564  
  12         61  
29 12     12   1743 use Try::Tiny;
  12         18  
  12         838  
30              
31 12     12   56 use MooseX::Types::Moose qw( Str Bool );
  12         14  
  12         124  
32              
33             has 'return_value' => (
34             is => 'rw',
35             default => 0
36             );
37              
38             =head1 REQUIRED SUBCLASS METHODS
39              
40             =head2 documentation
41              
42             Returns a scalar (string) containing the documentation portion
43             of the script's usage statement.
44              
45             =cut
46              
47             requires 'documentation'; # Seriously, it does.
48              
49             # For our own subclasses like App::Base::Script and App::Base::Daemon
50              
51             =head2 __run
52              
53             For INTERNAL USE ONLY: Used by subclasses such as App::Base::Script and
54             App::Base::Daemon to redefine dispatch rules to their own required
55             subclass methods such as script_run() and daemon_run().
56              
57             =cut
58              
59             requires '__run';
60              
61             =head2 error
62              
63             All App::Base::Script::Common-implementing classes must have an
64             error() method that handles exceptional cases which also
65             require a shutdown of the running script/daemon/whatever.
66              
67             =cut
68              
69             requires 'error';
70              
71             =head1 OPTIONAL SUBCLASS METHODS
72              
73             =head2 options
74              
75             Concrete subclasses can specify their own options list by defining a method
76             called options() which returns an arrayref of hashes with the parameters
77             required to create L<App::Base::Script::Option> objects. Alternatively, your
78             script/daemon can simply get by with the standard --help option provided by its
79             role.
80              
81             =cut
82              
83             sub options {
84 24     24 1 39 my $self = shift;
85 24         60 return [];
86             }
87              
88             =head1 ATTRIBUTES
89              
90             =head2 _option_values
91              
92             The (parsed) option values, including defaults values if none were
93             specified, for all of the options declared by $self. This accessor
94             should not be called directly; use getOption() instead.
95              
96             =cut
97              
98             has '_option_values' => (
99             is => 'rw',
100             isa => 'HashRef',
101             );
102              
103             =head2 orig_args
104              
105             An arrayref of arguments as they existed prior to option parsing.
106              
107             =cut
108              
109             has 'orig_args' => (
110             is => 'rw',
111             isa => 'ArrayRef[Str]',
112             );
113              
114             =head2 parsed_args
115              
116             An arrayref of the arguments which remained after option parsing.
117              
118             =cut
119              
120             has 'parsed_args' => (
121             is => 'rw',
122             isa => 'ArrayRef[Str]',
123             );
124              
125             =head2 script_name
126              
127             The name of the running script, computed from $0.
128              
129             =cut
130              
131             has 'script_name' => (
132             is => 'ro',
133             default => sub { path($0)->basename; },
134             );
135              
136             =head1 METHODS
137              
138             =head2 BUILDARGS
139              
140             Combines the results of base_options() and options() and then parses the
141             command-line arguments of the script. Exits with a readable error message
142             if the script was invoked in a nonsensical or invalid manner.
143              
144             =cut
145              
146             sub BUILDARGS {
147 43     43 1 6783895 my $class = shift;
148 43         75 my $arg_ref = shift;
149              
150             ## no critic (RequireLocalizedPunctuationVars)
151 43         2004 $ENV{APP_BASE_SCRIPT_EXE} = abs_path($0);
152 43         189 $arg_ref->{orig_args} = [@ARGV];
153              
154 43         339 my $results = $class->_parse_arguments( \@ARGV );
155 43 100       164 if ( $results->{parse_result} ) {
156 41         98 $arg_ref->{_option_values} = $results->{option_values};
157 41         71 $arg_ref->{parsed_args} = $results->{parsed_args};
158              
159             # This exits.
160 41 100       138 $class->usage(0) if ( $results->{option_values}->{'help'} );
161             }
162             else {
163             # This exits.
164 2         6 $class->usage(1);
165             }
166              
167 38         692 return $arg_ref;
168             }
169              
170             =head2 all_options
171              
172             Returns the composition of options() and base_options() as list of L<App::Base::Script::Option> objects.
173              
174             =cut
175              
176             sub all_options {
177 61     61 1 76 my $self = shift;
178 61         75 state $cache;
179 61   66     315 my $class = ref($self) || $self;
180             $cache->{$class} //=
181 61   100     210 [ map { App::Base::Script::Option->new($_) } @{ $self->options }, @{ $self->base_options } ];
  130         3487  
  24         303  
  24         154  
182 61         184 $cache->{$class};
183             }
184              
185             =head2 base_options
186              
187             The options provided for every classes which implements App::Base::Script::Common.
188             See BUILT-IN OPTIONS
189              
190             =cut
191              
192             sub base_options {
193             return [
194             {
195 24     24 1 336 name => 'help',
196             documentation => 'Show this help information',
197             },
198             ];
199             }
200              
201             =head2 switch_name_width
202              
203             Computes the maximum width of any of the switch (option) names.
204              
205             =cut
206              
207             sub switch_name_width {
208 9     9 1 10 my $self = shift;
209 9         13 return max( map { length( $_->display_name ) } @{ $self->all_options } );
  29         63  
  9         18  
210             }
211              
212             =head2 switches
213              
214             Generates the switch table output of the usage statement.
215              
216             =cut
217              
218             sub switches {
219 9     9 1 421 my $self = shift;
220              
221 9   50     48 my $col_width = $ENV{COLUMNS} || 76;
222              
223 9         24 my $max_option_length = $self->switch_name_width;
224 9         25 my $sw = '[' x ( $max_option_length + 2 );
225 9         23 my $doc = '[' x ( $col_width - $max_option_length - 1 );
226              
227             my @lines = map {
228 29         7108 form { break => break_wrap }, "$sw $doc", '--' . $_->display_name, $_->show_documentation;
229 9         14 } ( sort { $a->name cmp $b->name } ( @{ $self->all_options } ) );
  40         791  
  9         20  
230              
231 9         4542 return join( '', @lines );
232             }
233              
234             =head2 cli_template
235              
236             The template usage form that should be shown to the user in the usage
237             statement when --help or an invalid invocation is provided.
238              
239             Defaults to "(program name) [options]", which is pretty standard Unix.
240              
241             =cut
242              
243             sub cli_template {
244 6     6 1 70 return "$0 [options] "; # Override this if your script has a more complex command-line
245             # invocation template such as "$0[options] company_id [list1 [, list2 [, ...]]] "
246             }
247              
248             =head2 usage
249              
250             Outputs a statement explaining the usage of the script, then exits.
251              
252             =cut
253              
254             sub usage {
255 6     6 1 719 my $self = shift;
256              
257 6   50     32 my $col_width = $ENV{COLUMNS} || 76;
258              
259 6         17 my $format = '[' x $col_width;
260              
261 6         26 my $message = join(
262             '', "\n",
263             form(
264             { break => break_wrap },
265             $format, [ "Usage: " . $self->cli_template, split( /[\r\n]/, $self->documentation ) ]
266             )
267             );
268              
269 6         3122 $message .= "\nOptions:\n\n";
270              
271 6         25 $message .= $self->switches . "\n\n";
272              
273 6         31 print STDERR $message;
274              
275 6         23 exit(1);
276              
277             }
278              
279             =head2 getOption
280              
281             Returns the value of a specified option. For example, getOption('help') returns
282             1 or 0 depending on whether the --help option was specified. For option types
283             which are non-boolean (see App::Base::Script::Option) the return value is the actual
284             string/integer/float provided on the common line - or undef if none was provided.
285              
286             =cut
287              
288             sub getOption {
289 83     83 1 769 my $self = shift;
290 83         129 my $option = shift;
291              
292 83 100       2475 if ( exists( $self->_option_values->{$option} ) ) {
293 82         1727 return $self->_option_values->{$option};
294             }
295             else {
296 1         9 die "Unknown option $option";
297             }
298              
299             }
300              
301             =head2 run
302              
303             Runs the script, returning the return value of __run
304              
305             =cut
306              
307             sub run {
308 31     31 1 6487 my $self = shift;
309              
310             # This is implemented by subclasses of App::Base::Script::Common
311 31         178 $self->__run;
312 17         1397 return $self->return_value;
313             }
314              
315             =head2 _parse_arguments
316              
317             Parses the arguments in @ARGV, returning a hashref containing:
318              
319             =over 4
320              
321             =item -
322              
323             The parsed arguments (that is, those that should remain in @ARGV)
324              
325             =item -
326              
327             The option values, as a hashref, including default values
328              
329             =item -
330              
331             Whether the parsing encountered any errors
332              
333             =back
334              
335             =cut
336              
337             sub _parse_arguments {
338 43     43   66 my $self = shift;
339 43         69 my $args = shift;
340              
341 43         119 local @ARGV = (@$args);
342              
343             # Build the hash of options to pass to Getopt::Long
344 43         143 my $options = $self->all_options;
345 43         104 my %options_hash = ();
346 43         60 my %getopt_args = ();
347              
348 43         102 foreach my $option (@$options) {
349 233         5244 my $id = $option->name;
350 233         5099 my $type = $option->option_type;
351 233 100       574 if ( $type eq 'string' ) {
    100          
    100          
352 33         41 $id .= '=s';
353             }
354             elsif ( $type eq 'integer' ) {
355 4         4 $id .= '=i';
356             }
357             elsif ( $type eq 'float' ) {
358 4         5 $id .= '=f';
359             }
360              
361 233         4998 my $scalar = $option->default;
362 233         4828 $getopt_args{ $option->name } = \$scalar;
363 233         447 $options_hash{$id} = \$scalar;
364             }
365              
366 43         270 my $result = GetOptions(%options_hash);
367 43         12941 my %option_values = map { $_ => ${ $getopt_args{$_} } } ( keys %getopt_args );
  233         192  
  233         364  
368             return {
369 43         341 parse_result => $result,
370             option_values => \%option_values,
371             parsed_args => \@ARGV
372             };
373              
374             }
375              
376             =head2 __error
377              
378             Dispatches its arguments to the subclass-provided error() method (see REQUIRED
379             SUBCLASS METHODS), then exits.
380              
381             =cut
382              
383             sub __error {
384 6     6   390 my $self = shift;
385 6         138 warn( join " ", @_ );
386 6         363 exit(-1);
387             }
388              
389 12     12   47616 no Moose::Role;
  12         17  
  12         101  
390             1;
391              
392             __END__
393              
394             =head1 USAGE
395              
396             Invocation of a App::Base::Script::Common-based program is accomplished as follows:
397              
398             =over 4
399              
400             =item -
401              
402             Define a class that derives (via 'use Moose' and 'with') from App::Base::Script::Common
403              
404             =item -
405              
406             Instantiate an object of that class via new( )
407              
408             =item -
409              
410             Run the program by calling run( ). The return value of run( ) is the exit
411             status of the script, and should typically be passed back to the calling
412             program via exit()
413              
414             =back
415              
416             =head2 The new() method
417              
418             A Moose-style constructor for the App::Base::Script::Common-derived class.
419             Every such class has one important attribute: options -- an array ref of hashes
420             describing options to be added to the command-line processing for the script.
421             See L<App::Base::Script::Option> for more information.
422              
423             =head2 Options handling
424              
425             One of the most useful parts of App::Base::Script::Common is the simplified access to
426             options processing. The getOption() method allows your script to determine the
427             value of a given option, determined as follows:
428              
429             =over 4
430              
431             =item 1
432              
433             If given as a command line option (registered via options hashref)
434              
435             =item 2
436              
437             The default value specified in the App::Base::Script::Option object that
438             was passed to the options() attribute at construction time.
439              
440             =back
441              
442             For example, if your script registers an option 'foo' by saying
443              
444             my $object = MyScript->new(
445             options => [
446             App::Base::Script::Option->new(
447             name => "foo",
448             documentation => "The foo option",
449             option_type => "integer",
450             default => 7,
451             ),
452             ]
453             );
454              
455             Then in script_run() you can say
456              
457             my $foo = $self->getOption("foo")
458              
459             And C<$foo> will be resolved as follows:
460              
461             =over 4
462              
463             =item 1
464              
465             A --foo value specified as a command-line switch
466              
467             =item 2
468              
469             The default value specified at registration time ("bar")
470              
471             =back
472              
473             =head1 BUILT-IN OPTIONS
474              
475             =head2 --help
476              
477             Print a usage statement
478              
479             =head1 LICENSE AND COPYRIGHT
480              
481             Copyright (C) 2010-2014 Binary.com
482              
483             This program is free software; you can redistribute it and/or modify it
484             under the terms of either: the GNU General Public License as published
485             by the Free Software Foundation; or the Artistic License.
486              
487             See http://dev.perl.org/licenses/ for more information.
488              
489             =cut