File Coverage

blib/lib/MooseX/App.pm
Criterion Covered Total %
statement 65 73 89.0
branch 16 18 88.8
condition 15 24 62.5
subroutine 15 17 88.2
pod 5 6 83.3
total 116 138 84.0


line stmt bran cond sub pod time code
1             # ============================================================================«
2             package MooseX::App;
3             # ============================================================================«
4              
5 10     10   3169278 use 5.010;
  10         115  
6 10     10   6436 use utf8;
  10         155  
  10         59  
7 10     10   369 use strict;
  10         20  
  10         208  
8 10     10   53 use warnings;
  10         20  
  10         582  
9              
10             our $AUTHORITY = 'cpan:MAROS';
11             our $VERSION = '1.42';
12              
13 10     10   5658 use MooseX::App::Meta::Role::Attribute::Option;
  10         46  
  10         705  
14 10     10   6111 use MooseX::App::Exporter qw(app_usage app_description app_base app_fuzzy app_strict app_prefer_commandline app_permute option parameter);
  10         40  
  10         107  
15 10     10   6604 use MooseX::App::Message::Envelope;
  10         52  
  10         455  
16 10     10   93 use Moose::Exporter;
  10         22  
  10         70  
17 10     10   545 use Scalar::Util qw(blessed);
  10         22  
  10         10391  
18              
19             my ($IMPORT,$UNIMPORT,$INIT_META) = Moose::Exporter->build_import_methods(
20             with_meta => [ qw(app_usage app_description app_namespace app_exclude app_base app_fuzzy app_command_name app_command_register app_strict app_prefer_commandline option parameter app_permute) ],
21             also => [ 'Moose' ],
22             as_is => [ 'new_with_command' ],
23             install => [ 'unimport','init_meta' ],
24             );
25              
26             sub import {
27 11     11   652 my ( $class, @plugins ) = @_;
28              
29             # Get caller
30 11         51 my ($caller_class) = caller();
31              
32             # Process plugins
33 11         314 MooseX::App::Exporter->process_plugins($caller_class,@plugins);
34              
35             # Call Moose-Exporter generated importer
36 11         62 return $class->$IMPORT( { into => $caller_class } );
37             }
38              
39             sub init_meta {
40 11     11 0 1285 my ($class,%args) = @_;
41              
42             # Get required roles and metaroles
43 11         46 $args{roles} = ['MooseX::App::Role::Base'];
44             $args{metaroles} = {
45 11         67 class => [
46             'MooseX::App::Meta::Role::Class::Base',
47             'MooseX::App::Meta::Role::Class::Documentation'
48             ],
49             attribute => [
50             'MooseX::App::Meta::Role::Attribute::Option'
51             ],
52             };
53              
54 11         112 return MooseX::App::Exporter->process_init_meta(%args);
55             }
56              
57             sub app_command_name(&) {
58 3     3 1 61 my ( $meta, $namesub ) = @_;
59 3         112 return $meta->app_command_name($namesub);
60             }
61              
62             sub app_command_register(%) {
63 0     0 1 0 my ( $meta, %commands ) = @_;
64              
65 0         0 foreach my $command (keys %commands) {
66 0         0 $meta->command_register($command,$commands{$command});
67             }
68 0         0 return;
69             }
70              
71             sub app_namespace(@) {
72 0     0 1 0 my ( $meta, @namespaces ) = @_;
73 0         0 return $meta->app_namespace( \@namespaces );
74             }
75              
76             sub app_exclude(@) {
77 3     3 1 51 my ( $meta, @namespaces ) = @_;
78 3         109 return $meta->app_exclude( \@namespaces );
79             }
80              
81             sub new_with_command {
82 70     70 1 64939 my ($class,@args) = @_;
83              
84 70 100 66     569 Moose->throw_error('new_with_command is a class method')
85             if ! defined $class || blessed($class);
86              
87 69         490 my $meta = $class->meta;
88 69         2373 my $metameta = $meta->meta;
89              
90             # Sanity check
91 69 100 66     1574 Moose->throw_error('new_with_command may only be called from the application base package:'.$class)
92             if $metameta->does_role('MooseX::App::Meta::Role::Class::Command')
93             || ! $metameta->does_role('MooseX::App::Meta::Role::Class::Base');
94              
95             $meta->command_check()
96 68 50 33     55909 if $ENV{APP_DEVELOPER} || $ENV{HARNESS_ACTIVE};
97              
98             # Extra args
99 68         143 my %args;
100 68 50 33     488 if (scalar @args == 1
    100          
101             && ref($args[0]) eq 'HASH' ) {
102 0         0 %args = %{$args[0]};
  0         0  
103             } elsif (scalar @args % 2 == 0) {
104 67         235 %args = @args;
105             } else {
106 1         6 Moose->throw_error('new_with_command got invalid extra arguments');
107             }
108              
109             # Get ARGV
110 67         272 my $argv = delete $args{ARGV};
111 67         129 my $parsed_argv;
112 67 100       181 if (defined $argv) {
113 7         245 $parsed_argv = MooseX::App::ParsedArgv->new( argv => $argv );
114             } else {
115 60         324 $parsed_argv = MooseX::App::ParsedArgv->instance();
116             }
117              
118 67         287 my $first_argv = $parsed_argv->first_argv;
119              
120             # Requested help
121 67 100 100     1240 if (defined $first_argv
    100 66        
      66        
122             && lc($first_argv) =~ m/^(help|h|\?|usage|-h|--help|-\?|--usage)$/) {
123 2         46 return MooseX::App::Message::Envelope->new(
124             $meta->command_usage_global(),
125             );
126             # No args
127             } elsif (! defined $first_argv
128             || $first_argv =~ m/^\s*$/
129             || $first_argv =~ m/^-{1,2}\w/) {
130 2         18 return MooseX::App::Message::Envelope->new(
131             $meta->command_message(
132             header => "Missing command", # LOCALIZE
133             type => "error",
134             ),
135             $meta->command_usage_global(),
136             127, # exitcode
137             );
138             # Looks like a command
139             } else {
140 63         368 my $return = $meta->command_find();
141              
142             # Nothing found
143 63 100 66     383 if (blessed $return
144             && $return->isa('MooseX::App::Message::Block')) {
145 2         12 return MooseX::App::Message::Envelope->new(
146             $return,
147             $meta->command_usage_global(),
148             127, # exitcode
149             );
150             # One command found
151             } else {
152 61         2706 my $command_class = $meta->command_get($return);
153 61         442 return $class->initialize_command_class($command_class,%args);
154             }
155             }
156             }
157              
158              
159 10     10   106 no Moose;
  10         30  
  10         87  
160             1;
161              
162             __END__
163              
164             =encoding utf8
165              
166             =head1 NAME
167              
168             MooseX::App - Write user-friendly command line apps with even less suffering
169              
170             =head1 SYNOPSIS
171              
172             In your base class:
173              
174             package MyApp;
175             use MooseX::App qw(Color);
176            
177             option 'global_option' => (
178             is => 'rw',
179             isa => 'Bool',
180             documentation => q[Enable this to do fancy stuff],
181             ); # Global option
182            
183             has 'private' => (
184             is => 'rw',
185             ); # not exposed
186              
187             Write multiple command classes (If you have only a single command class
188             you should use L<MooseX::App::Simple> instead). Packackes in the namespace may be
189             deeply nested.
190              
191             package MyApp::SomeCommand;
192             use MooseX::App::Command; # important (also imports Moose)
193             extends qw(MyApp); # optional, only if you want to use global options from base class
194            
195             # Positional parameter
196             parameter 'some_parameter' => (
197             is => 'rw',
198             isa => 'Str',
199             required => 1,
200             documentation => q[Some parameter that you need to supply],
201             );
202            
203             option 'some_option' => (
204             is => 'rw',
205             isa => 'Int',
206             required => 1,
207             documentation => q[Very important option!],
208             ); # Option
209            
210             sub run {
211             my ($self) = @_;
212             # Do something
213             }
214              
215             And then you need a simple wrapper script (called eg. myapp):
216              
217             #!/usr/bin/env perl
218             use MyApp;
219             MyApp->new_with_command->run;
220              
221             On the command line:
222              
223             bash$ myapp help
224             usage:
225             myapp <command> [long options...]
226             myapp help
227            
228             global options:
229             --global_option Enable this to do fancy stuff [Flag]
230             --help --usage -? Prints this usage information. [Flag]
231            
232             available commands:
233             some_command Description of some command
234             another_command Description of another command
235             help Prints this usage information
236              
237             or
238              
239             bash$ myapp some_command --help
240             usage:
241             myapp some_command <SOME_PARAMETER> [long options...]
242             myapp help
243             myapp some_command --help
244            
245             parameters:
246             some_parameter Some parameter that you need to supply [Required]
247            
248             options:
249             --global_option Enable this to do fancy stuff [Flag]
250             --some_option Very important option! [Int,Required]
251             --help --usage -? Prints this usage information. [Flag]
252              
253             =head1 DESCRIPTION
254              
255             MooseX-App is a highly customisable helper to write user-friendly
256             command line applications without having to worry about most of the annoying
257             things usually involved. Just take any existing L<Moose> class, add a single
258             line (C<use MooseX-App qw(PluginA PluginB ...);>) and create one class
259             for each command in an underlying namespace. Options and positional parameters
260             can be defined as simple L<Moose> accessors using the C<option> and
261             C<parameter> keywords respectively.
262              
263             MooseX-App will then
264              
265             =over
266              
267             =item * Find, load and initialise the command classes (see
268             L<MooseX::App::Simple> for single class/command applications)
269              
270             =item * Create automated help and documentation from modules POD as well as
271             attributes metadata and type constraints
272              
273             =item * Read, encode and validate the command line options and positional
274             parameters entered by the user from @ARGV and %ENV (and possibly prompt
275             the user for additional parameters see L<MooseX::App::Plugin::Term>)
276              
277             =item * Provide helpful error messages if user input cannot be validated
278             (either missing or wrong attributes or Moose type constraints not satisfied)
279             or if the user requests help.
280              
281             =back
282              
283             Commandline options are defined using the 'option' keyword which accepts
284             the same attributes as Moose' 'has' keyword.
285              
286             option 'some_option' => (
287             is => 'rw',
288             isa => 'Str',
289             );
290              
291             This is equivalent to
292              
293             has 'some_option' => (
294             is => 'rw',
295             isa => 'Str',
296             traits => ['AppOption'], # Load extra metaclass
297             cmd_type => 'option', # Set attribute type
298             );
299              
300             Single letter options are treated as flags and may be combined with each other.
301             However such options must have a Boolean type constraint.
302              
303             option 'verbose' => (
304             is => 'rw',
305             isa => 'Bool',
306             cmd_flag => 'v',
307             );
308              
309             Positional parameters are defined with the 'parameter' keyword
310              
311             parameter 'some_option' => (
312             is => 'rw',
313             isa => 'Str',
314             );
315              
316             This is equivalent to
317              
318             has 'some_option' => (
319             is => 'rw',
320             isa => 'Str',
321             traits => ['AppOption'],
322             cmd_type => 'parameter',
323             );
324              
325             All keywords are imported by L<Moosex::App> (in the app base class) and
326             L<MooseX::App::Command> (in the command class) or L<MooseX::App::Simple>
327             (single class application).
328              
329             Furthermore, all options and parameters can also be supplied via %ENV
330              
331             option 'some_option' => (
332             is => 'rw',
333             isa => 'Str',
334             cmd_env => 'SOME_OPTION', # sets the env key
335             );
336              
337             Moose type constraints help MooseX::App to construct helpful error messages
338             and parse @ARGV in a meaningful way. The following type constraints are
339             supported:
340              
341             =over
342              
343             =item * ArrayRef: Specify multiple values ('--opt value1 --opt value2',
344             also see L<app_permute> and L<cmd_split>)
345              
346             =item * HashRef: Specify multiple key value pairs ('--opt key=value --opt
347             key2=value2', also see L<app_permute>)
348              
349             =item * Enum: Display all possibilities
350              
351             =item * Bool: Flags that do not require values
352              
353             =item * Int, Num: Used for proper error messages
354              
355             =back
356              
357             Read the L<Tutorial|MooseX::App::Tutorial> for getting started with a simple
358             MooseX::App command line application.
359              
360             =head1 METHODS
361              
362             =head2 new_with_command
363              
364             my $myapp_command = MyApp->new_with_command();
365              
366             This constructor reads the command line arguments and tries to create a
367             command class instance. If it fails it returns a
368             L<MooseX::App::Message::Envelope> object holding an error message.
369              
370             You can pass a hash of default/fallback params to new_with_command
371              
372             my $obj = MyApp->new_with_command(%default);
373              
374             Optionally you can pass a custom ARGV to this constructor
375              
376             my $obj = MyApp->new_with_command( ARGV => \@myARGV );
377              
378             However, if you do so you must take care of propper @ARGV encoding yourself.
379              
380             =head2 initialize_command_class
381              
382             my $obj = MyApp->initialize_command_class($command_name,%default);
383              
384             Helper method to instantiate the command class for the given command.
385              
386             =head1 GLOBAL OPTIONS
387              
388             These options may be used to alter the default behaviour of MooseX-App.
389              
390             =head2 app_base
391              
392             app_base 'my_script'; # Defaults to $0
393              
394             Usually MooseX::App will take the name of the calling wrapper script to
395             construct the program name in various help messages. This name can
396             be changed via the app_base function.
397              
398             =head2 app_fuzzy
399              
400             app_fuzzy 1; # default
401             OR
402             app_fuzzy 0;
403              
404             Enables fuzzy matching of commands and attributes. Is turned on by default.
405              
406             =head2 app_strict
407              
408             app_strict 0; # default
409             OR
410             app_strict 1;
411              
412             If strict is enabled the program will terminate with an error message if
413             superfluous/unknown positional parameters are supplied. If disabled all
414             extra parameters will be copied to the L<extra_argv> attribute. Unknown
415             options (with leading dashes) will always yield an error message.
416              
417             The command_strict config in the command classes allows one to set this option
418             individually for each command in the respective command class.
419              
420             =head2 app_prefer_commandline
421              
422             app_prefer_commandline 0; # default
423             or
424             app_prefer_commandline 1;
425              
426             Specifies if parameters/options supplied via @ARGV,%ENV should take precedence
427             over arguments passed directly to new_with_command.
428              
429             =head2 app_namespace
430              
431             app_namespace 'MyApp::Commands', 'YourApp::MoreCommands';
432             OR
433             app_namespace();
434              
435             Usually MooseX::App will take the package name of the base class as the
436             namespace for commands. This namespace can be changed and you can add
437             multiple extra namespaces.
438              
439             If app_namespace is called with no arguments then autoloading of command
440             classes will be disabled entirely.
441              
442             =head2 app_exclude
443              
444             app_exclude 'MyApp::Commands::Roles','MyApp::Commands::Utils';
445              
446             A sub namespace included via L<app_namespace> (or the default behaviour) can
447             be excluded using app_exclude.
448              
449             =head2 app_command_name
450              
451             app_command_name {
452             my ($package_short,$package_full) = @_;
453             # munge package name;
454             return $command_name;
455             };
456              
457             This coderef can be used to control how autoloaded package names should be
458             translated to command names. If this command returns nothing the respective
459             command class will be skipped and not loaded.
460              
461             =head2 app_command_register
462              
463             app_command_register
464             do => 'MyApp::Commands::DoSomething',
465             undo => 'MyApp::Commands::UndoSomething';
466              
467             This keyword can be used to register additional commands. Especially
468             useful in conjunction with L<app_namespace> and disabled autoloading.
469              
470             =head2 app_description
471              
472             app_description qq[Description text];
473              
474             Set the app description text. If not set this information will be taken from
475             the Pod DESCRIPTION or OVERVIEW sections. (see command_description to set
476             usage per command)
477              
478             =head2 app_usage
479              
480             app_usage qq[myapp --option ...];
481              
482             Set a custom usage text. If not set this will be taken from the Pod SYNOPSIS
483             or USAGE section. If both sections are not available, the usage information
484             will be autogenerated. (see command_usage to set usage per command)
485              
486             =head2 app_permute
487              
488             app_permute 0; # default
489             OR
490             app_permute 1;
491              
492             Allows one to specify multiple values with one key. So instead of writing
493             C<--list element1 --list element2 --list element3> one might write
494             C<--list element1 element2 element3> for ArrayRef elements. HashRef elements
495             may be expressed as C<--hash key=value key2=value2>.
496              
497             =head1 GLOBAL ATTRIBUTES
498              
499             All MooseX::App classes will have two extra attributes
500              
501             =head2 extra_argv
502              
503             Carries all parameters from @ARGV that were not consumed (only if app_strict
504             is turned off, otherwise superfluous parameters will raise an exception).
505              
506             =head2 help_flag
507              
508             Help flag that is set when help was requested.
509              
510             =head1 ATTRIBUTE OPTIONS
511              
512             Options and parameters accept extra attributes for customisation:
513              
514             =over
515              
516             =item * cmd_tags - Extra tags (as used by the help)
517              
518             =item * cmd_flag - Override option/parameter name
519              
520             =item * cmd_aliases - Additional option/parameter name aliases
521              
522             =item * cmd_split - Split values into ArrayRefs on this token or RegEx
523              
524             =item * cmd_position - Specify option/parameter order in help
525              
526             =item * cmd_env - Read options/parameters from %ENV
527              
528             =item * cmd_count - Value of option equals to number of occurrences in @ARGV
529              
530             =item * cmd_negate - Adds an option to negate boolean flags
531              
532             =back
533              
534             Refer to L<MooseX::App::Meta::Role::Attribute::Option> for detailed
535             documentation.
536              
537             =head1 METADATA
538              
539             MooseX::App will use your class metadata and POD to construct the commands and
540             helpful error- or usage-messages. These bits of information are utilised
541             and should be provided if possible:
542              
543             =over
544              
545             =item * Package names
546              
547             =item * L<required|Moose/"required-=E<gt>-(1|0)"> options for Moose attributes
548              
549             =item * L<documentation|Moose/"documentation =E<gt> $string"> options for Moose attributes
550              
551             =item * Moose type constraints (Bool, ArrayRef, HashRef, Int, Num, and Enum)
552              
553             =item * Documentation set via app_description, app_usage,
554             command_short_description, command_long_description and command_usage
555              
556             =item * POD (NAME, ABSTRACT, DESCRIPTION, USAGE, SYNOPSIS, OVERVIEW,
557             COPYRIGHT, LICENSE, COPYRIGHT AND LICENSE, AUTHOR and AUTHORS sections)
558              
559             =item * L<Dist::Zilla> ABSTRACT tag if no POD is available yet
560              
561             =back
562              
563             =head1 PLUGINS
564              
565             The behaviour of MooseX-App can be customised with plugins. To load a
566             plugin just pass a list of plugin names after the C<use MooseX-App> statement.
567             (Attention: order sometimes matters)
568              
569             use MooseX::App qw(PluginA PluginB);
570              
571             Currently the following plugins are shipped with MooseX::App
572              
573             =over
574              
575             =item * L<MooseX::App::Plugin::BashCompletion>
576              
577             Adds a command that generates a bash completion script for your application.
578             See third party L<MooseX::App::Plugin::ZshCompletion> for Z shell completion.
579              
580             =item * L<MooseX::App::Plugin::Color>
581              
582             Colorful output for your MooseX::App applications.
583              
584             =item * L<MooseX::App::Plugin::Config>
585              
586             Config files for MooseX::App applications.
587              
588             =item * L<MooseX::App::Plugin::ConfigHome>
589              
590             Try to find config files in users home directory.
591              
592             =item * L<MooseX::App::Plugin::Term>
593              
594             Prompt user for options and parameters that were not provided via options or
595             params. Prompt offers basic editing capabilities and non-persistent history.
596              
597             =item * L<MooseX::App::Plugin::Typo>
598              
599             Handle typos in command names and provide suggestions.
600              
601             =item * L<MooseX::App::Plugin::Version>
602              
603             Adds a command to display the version and license of your application.
604              
605             =item * L<MooseX::App::Plugin::Man>
606              
607             Display full manpage of application and commands.
608              
609             =item * L<MooseX::App::Plugin::MutexGroup>
610              
611             Allow for mutally exclusive options.
612              
613             =item * L<MooseX::App::Plugin::Depends>
614              
615             Adds dependent options.
616              
617             =back
618              
619             Refer to L<Writing MooseX-App Plugins|MooseX::App::WritingPlugins>
620             for documentation on how to create your own plugins.
621              
622             =head1 DEVELOPMENT
623              
624             Make sure to invoke your script with APP_DEVELOPER=1 ser during development. This
625             will come with a starup penalty but perform additional checks for detecting wrong
626             attribute/type constraint combinations, name clashes, ...
627              
628             =head1 CAVEATS & KNOWN BUGS
629              
630             Startup time may be an issue - escpecially if you load many plugins. If you do
631             not require the functionality of plugins and ability for fine grained
632             customisation (or Moose for that matter) then you should probably
633             use L<MooX::Options> or L<MooX::Cmd>.
634              
635             In some cases - especially when using non-standard class inheritance - you may
636             end up with command classes lacking the help attribute. In this case you need
637             to include the following line in your base class or command classes.
638              
639             with qw(MooseX::App::Role::Common);
640              
641             When manually registering command classes (eg. via app_command_register) in
642             multiple base classes with different sets of plugins (why would you ever want
643             to do that?), then meta attributes may lack some attribute metaclasses. In
644             this case you need to load the missing attribute traits explicitly:
645              
646             option 'argument' => (
647             depends => 'otherargument',
648             trait => ['MooseX::App::Plugin::Depends::Meta::Attribute'], # load trait
649             );
650              
651             =head1 SEE ALSO
652              
653             Read the L<Tutorial|MooseX::App::Tutorial> for getting started with a simple
654             MooseX::App command line application.
655              
656             For alternatives you can check out
657              
658             L<MooseX::App::Cmd>, L<MooseX::Getopt>, L<MooX::Options>, L<MooX::Cmd> and L<App::Cmd>
659              
660             =head1 SUPPORT
661              
662             Please report any bugs or feature requests via
663             L<https://github.com/maros/MooseX-App/issues/new>. I will be notified, and
664             then you'll automatically be notified of progress on your report as I make
665             changes.
666              
667             =head1 AUTHOR
668              
669             Maroš Kollár
670             CPAN ID: MAROS
671             maros [at] k-1.com
672            
673             http://www.k-1.com
674              
675             =head1 CONTRIBUTORS
676              
677             Special thanks to all contributors.
678              
679             In no particular order: Andrew Jones, George Hartzell, Steve Nolte,
680             Michael G, Thomas Klausner, Yanick Champoux, Edward Baudrez, David Golden,
681             J.R. Mash, Thilo Fester, Gregor Herrmann, Sergey Romanov, Sawyer X, Roman F.,
682             Hunter McMillen, Maik Hentsche, Alexander Stoddard, Marc Logghe, Tina Müller,
683             Lisa Hare, Jose Luis Martinez, Frank Schreiner
684              
685             You are more than welcome to contribute to MooseX-App. Please have a look
686             at the L<https://github.com/maros/MooseX-App/issues?q=is%3Aissue+is%3Aopen+label%3AWishlist>
687             list of open wishlist issues for ideas.
688              
689             =head1 COPYRIGHT
690              
691             MooseX::App is Copyright (c) 2012-21 Maroš Kollár.
692              
693             This library is free software and may be distributed under the same terms as
694             perl itself. The full text of the licence can be found in the LICENCE file
695             included with this module.
696              
697             =cut