File Coverage

blib/lib/App/CLI.pm
Criterion Covered Total %
statement 90 93 96.7
branch 24 28 85.7
condition 3 6 50.0
subroutine 18 20 90.0
pod 4 10 40.0
total 139 157 88.5


line stmt bran cond sub pod time code
1             package App::CLI;
2              
3 2     2   2213 use strict;
  2         4  
  2         61  
4 2     2   10 use warnings;
  2         2  
  2         69  
5 2     2   35 use 5.006;
  2         7  
6 2     2   524 use Class::Load qw( load_class );
  2         20177  
  2         177  
7              
8             our $VERSION = '0.52';
9              
10             =encoding utf8
11              
12             =head1 NAME
13              
14             App::CLI - Dispatcher module for command line interface programs
15              
16             =head1 SYNOPSIS
17              
18             package MyApp;
19             use base 'App::CLI'; # the DISPATCHER of your App
20             # it's not necessary to put the dispatcher
21             # on the top level of your App
22              
23             package main;
24              
25             MyApp->dispatch; # call the dispatcher where you want
26              
27              
28             package MyApp::List;
29             use base qw(App::CLI::Command); # any (SUB)COMMAND of your App
30              
31             use constant options => (
32             "h|help" => "help",
33             "verbose" => "verbose",
34             'n|name=s' => 'name',
35             );
36              
37             use constant subcommands => qw(User Nickname type); # if you want subcommands
38             # automatically dispatch to subcommands
39             # when invoke $ myapp list [user|nickname|--type]
40             # note 'type' is not capitalized
41             # it is a deprecated subcommand
42              
43             sub run {
44             my ($self, @args) = @_;
45              
46             print "verbose" if $self->{verbose};
47             my $name = $self->{name}; # get arg following long option --name
48              
49             if ($self->{help}) {
50             # if $ myapp list --help or $ myapp list -h
51             # only output PODs
52             } else {
53             # do something when invoking $ myapp list
54             # without subcommand and --help
55             }
56             }
57              
58              
59             package MyApp::List::User;
60             use base qw(App::CLI::Command);
61             use constant options => (
62             "h|help" => "help",
63             );
64              
65             sub run {
66             my ($self,@args) = @_;
67             # code for listing user
68             }
69              
70              
71             pakcage MyApp::List::Nickname;
72             use base qw(App::CLI::Command);
73             use constant options => (
74             "sort=s" => "sort",
75             );
76              
77             sub run {
78             my ($self,@args) = @_;
79             # code for listing nickname
80             }
81              
82              
83             package MyApp::List::type; # old genre of subcommand could not cascade infinitely
84             use base qw(MyApp::List); # should inherit its parent's command
85              
86             sub run {
87             my ($self, @args);
88             # run to here when invoking $ myapp list --type
89             }
90              
91              
92             package MyApp::Help;
93             use base 'App::CLI::Command::Help';
94              
95             use constant options => (
96             'verbose' => 'verbose',
97             );
98              
99             sub run {
100             my ($self, @arg) = @_;
101             # do something
102             $self->SUPER(@_); # App::CLI::Command::Help would output POD of each command
103             }
104              
105             =head1 DESCRIPTION
106              
107             C dispatches CLI (command line interface) based commands
108             into command classes. It also supports subcommand and per-command
109             options.
110              
111             =head2 Methods
112              
113             =cut
114              
115 2     2   483 use App::CLI::Helper;
  2         4  
  2         12  
116 2     2   1558 use Getopt::Long ();
  2         21148  
  2         68  
117              
118 2     2   17 use constant alias => ();
  2         4  
  2         146  
119 2     2   14 use constant global_options => ();
  2         5  
  2         92  
120 2     2   12 use constant options => ();
  2         4  
  2         1719  
121              
122             sub new {
123 30     30 0 33028 my ( $class, @args ) = @_;
124 30         74 my $self = bless {@args}, $class;
125 30         85 $self->{'app_argv'} = undef;
126              
127 30         74 return $self;
128             }
129              
130             sub app_argv {
131 45     45 0 63 my $self = shift;
132              
133 45 100       95 if (@_) {
134 42         81 $self->{'app_argv'} = shift;
135             }
136              
137 45         75 return $self->{'app_argv'};
138             }
139              
140             sub prepare {
141 22     22 0 40 my $self = shift;
142 22         34 my $data = {};
143              
144 22         110 $self->get_opt(
145             [qw(no_ignore_case bundling pass_through)],
146             opt_map( $data, $self->global_options )
147             );
148              
149 22         5841 my $command_name = shift @ARGV;
150 22         69 my $cmd = $self->get_cmd( $command_name, @_, $data );
151              
152 20         62 while ( $cmd->cascadable ) {
153 6         23 $cmd = $cmd->cascading;
154             }
155              
156 20         95 $self->get_opt( [qw(no_ignore_case bundling)],
157             opt_map( $cmd, $cmd->command_options ) );
158              
159 20         3488 $cmd = $cmd->subcommand;
160              
161 20         59 return $cmd;
162             }
163              
164             =head3 get_opt([@config], %opt_map)
165              
166             Give options map, processed by L.
167              
168             =cut
169              
170             sub get_opt {
171 42     42 1 86 my ( $self, $config, @app_options ) = @_;
172 42         172 my $p = Getopt::Long::Parser->new;
173 42         786 $p->configure(@$config);
174 42         2834 my $err = '';
175             local $SIG{__WARN__} = sub {
176 0     0   0 my $msg = shift;
177 0         0 $err .= "$msg";
178 42         296 };
179 42         112 my @current_argv = @ARGV;
180 42         121 $self->app_argv( \@current_argv );
181 42 50       102 die $self->error_opt($err) unless $p->getoptions(@app_options);
182             }
183              
184             sub opt_map {
185 42     42 0 149 my ( $self, %opt ) = @_;
186             return
187 42 50       135 map { $_ => ref( $opt{$_} ) ? $opt{$_} : \$self->{ $opt{$_} } } keys %opt;
  55         241  
188             }
189              
190             =head3 dispatch(@args)
191              
192             Interface of dispatcher
193              
194             =cut
195              
196             sub dispatch {
197 22     22 1 7678 my ( $self, @args ) = @_;
198 22 100       82 $self = $self->new unless ref $self;
199              
200 22 50       136 $self->app($self) if $self->can('app');
201              
202 22         59 my $cmd = $self->prepare(@args);
203 20         76 $cmd->run_command(@ARGV);
204             }
205              
206             =head3 cmd_map($cmd)
207              
208             Find the name of the package implementing the requested command.
209              
210             The command is first searched for in C. If the alias exists and points
211             to a package name starting with the C<+> sign, then that package name (minus
212             the C<+> sign) is returned. This makes it possible to map commands to arbitrary
213             packages.
214              
215             Otherwise, the package is searched for in the result of calling C,
216             and a package name is constructed by upper-casing the first character of the
217             command name, and appending it to the package name of the app itself.
218              
219             If both of these fail, and the command does not map to any package name,
220             C is returned instead.
221              
222             =cut
223              
224             sub cmd_map {
225 24     24 1 47 my ( $self, $cmd ) = @_;
226              
227 24         111 my %alias = $self->alias;
228              
229 24 100       64 if ( exists $alias{$cmd} ) {
230 4         10 $cmd = $alias{$cmd};
231              
232             # Alias points to package name, return immediately
233 4 100       27 return $cmd if $cmd =~ s/^\+//;
234             }
235              
236 21         65 ($cmd) = grep { $_ eq $cmd } $self->commands;
  31         78  
237              
238             # No such command
239 21 100       55 return unless $cmd;
240              
241 19         33 my $base = ref $self;
242 19         92 return join '::', $base, ucfirst $cmd;
243             }
244              
245             sub error_cmd {
246 4     4 0 13 my ( $self, $pkg ) = @_;
247              
248 4         7 my $cmd;
249 4 100       12 if ( defined($pkg) ) {
    100          
250 2   33     12 $cmd = ref($pkg) || $pkg;
251             }
252 2         4 elsif ( ${ $self->app_argv }[0] ) {
253 1         2 $cmd = ${ $self->app_argv }[0];
  1         6  
254             }
255             else {
256 1         2 $cmd = '';
257             }
258              
259 4         60 return "Command '$cmd' not recognized, try $0 --help.\n";
260             }
261              
262 0     0 0 0 sub error_opt { $_[1] }
263              
264             =head3 get_cmd($cmd, @arg)
265              
266             Return subcommand of first level via C<$ARGV[0]>.
267              
268             =cut
269              
270             sub get_cmd {
271 26     26 1 58 my ( $self, $cmd, $data ) = @_;
272 26 100 66     135 die $self->error_cmd($cmd) unless $cmd && $cmd eq lc($cmd);
273              
274 24         43 my $base = ref $self;
275 24         57 my $pkg = $self->cmd_map($cmd);
276              
277 24 100       80 die $self->error_cmd($cmd) unless $pkg;
278              
279 22         79 load_class $pkg;
280              
281 22 50       3824 die $self->error_cmd($cmd) unless $pkg->can('run');
282              
283 22 100       96 my @arg = defined $data ? %$data : ();
284 22         81 $cmd = $pkg->new(@arg);
285 22         79 $cmd->app($self);
286 22         64 return $cmd;
287             }
288              
289             =head1 SEE ALSO
290              
291             =over 4
292              
293             =item *
294              
295             L
296              
297             =item *
298              
299             L
300              
301             =back
302              
303             =head1 AUTHORS
304              
305             =over 4
306              
307             =item *
308              
309             Chia-liang Kao Eclkao@clkao.orgE
310              
311             =item *
312              
313             Alex Vandiver Ealexmv@bestpractical.comE
314              
315             =item *
316              
317             Yo-An Lin Ecornelius.howl@gmail.comE
318              
319             =item *
320              
321             Shelling Enavyblueshellingford@gmail.comE
322              
323             =item *
324              
325             Paul Cochrane Epaul@liekut.deE (current maintainer)
326              
327             =back
328              
329             =head1 CONTRIBUTORS
330              
331             The following people have contributed patches to the project:
332              
333             =over 4
334              
335             =item *
336              
337             José Joaquín Atria Ejjatria@gmail.comE
338              
339             =item *
340              
341             sunnavy Esunnavy@gmail.comE
342              
343             =item *
344              
345              
346             Ildar Shaimordanov Eildar.shaimordanov@gmail.comE
347              
348             =back
349              
350             =head1 COPYRIGHT
351              
352             Copyright 2005-2010 by Chia-liang Kao Eclkao@clkao.orgE.
353             Copyright 2010 by Yo-An Lin Ecornelius.howl@gmail.comE
354             and Shelling Enavyblueshellingford@gmail.comE.
355             Copyright 2017-2018 by Paul Cochrane Epaul@liekut.deE
356              
357             This program is free software; you can redistribute it and/or modify it
358             under the same terms as Perl itself.
359              
360             See L
361              
362             =cut
363              
364             1;