File Coverage

blib/lib/App/CLI/Command.pm
Criterion Covered Total %
statement 119 121 98.3
branch 22 28 78.5
condition 12 20 60.0
subroutine 23 23 100.0
pod 8 12 66.6
total 184 204 90.2


line stmt bran cond sub pod time code
1             package App::CLI::Command;
2 2     2   102437 use strict;
  2         15  
  2         61  
3 2     2   10 use warnings;
  2         5  
  2         55  
4 2     2   1084 use Locale::Maketext::Simple;
  2         3495  
  2         14  
5 2     2   484 use Carp ();
  2         5  
  2         34  
6 2     2   425 use App::CLI::Helper;
  2         4  
  2         12  
7 2     2   462 use Class::Load qw( load_class );
  2         19194  
  2         108  
8 2     2   14 use Scalar::Util qw( weaken );
  2         3  
  2         95  
9              
10             =head1 NAME
11              
12             App::CLI::Command - Base class for App::CLI commands
13              
14             =head1 SYNOPSIS
15              
16             package MyApp::List;
17             use base qw(App::CLI::Command);
18              
19             use constant options => (
20             'verbose' => 'verbose',
21             'n|name=s' => 'name',
22             );
23              
24             sub run {
25             my ( $self, $arg ) = @_;
26              
27             print "verbose" if $self->{verbose};
28              
29             my $name = $self->{name}; # get arg following long option --name
30              
31             # anything you want this command do
32             }
33              
34             # See App::CLI for information of how to invoke (sub)command.
35              
36             =head1 DESCRIPTION
37              
38              
39             =cut
40              
41 2     2   11 use constant subcommands => ();
  2         4  
  2         114  
42 2     2   11 use constant options => ();
  2         5  
  2         520  
43              
44             sub new {
45 15     15 0 1215 my $class = shift;
46 15         48 bless {@_}, $class;
47             }
48              
49             sub command_options {
50 20     20 0 96 ( ( map { $_ => $_ } $_[0]->subcommands ), $_[0]->options );
  12         53  
51             }
52              
53             sub run_command {
54 20     20 0 35 my $self = shift;
55 20         54 $self->run(@_);
56             }
57              
58             sub run {
59 1     1 0 6 my $class = shift;
60 1         230 Carp::croak ref($class) . " does not implement mandatory method 'run'\n";
61             }
62              
63             =head3 subcommand()
64              
65             return old genre subcommand of $self;
66              
67             =cut
68              
69             sub subcommand {
70 20     20 1 38 my $self = shift;
71 20         69 my @cmd = $self->subcommands;
72 20 50 66     79 @cmd = values %{ { $self->options } } if @cmd && $cmd[0] eq '*';
  0         0  
73 20         35 my $subcmd = undef;
74 20         41 for ( grep { $self->{$_} } @cmd ) {
  12         30  
75 2     2   16 no strict 'refs'; ## no critic
  2         4  
  2         505  
76 1 50       6 if ( exists ${ ref($self) . '::' }{ $_ . '::' } ) {
  1         8  
77 1         2 my %data = %{$self};
  1         7  
78 1         6 $subcmd = bless( {%data}, ( ref($self) . "::$_" ) );
79 1         4 last;
80             }
81             }
82 20 100       65 $subcmd ? $subcmd : $self;
83             }
84              
85             =head3 cascading()
86              
87             Return instance of cascading subcommand invoked if it was listed in your
88             constant subcommands.
89              
90             =cut
91              
92             sub cascading {
93 6     6 1 12 my $self = shift;
94 6 50       9 if ( my $subcmd = $self->cascadable ) {
95 6         9 shift @ARGV;
96 6         7 my %data = %{$self};
  6         39  
97 6         52 return bless {%data}, $subcmd;
98             }
99             else {
100 0         0 die $self->error_cmd( $ARGV[0] );
101             }
102             }
103              
104             =head3 cascadable()
105              
106             Return package name of subcommand if the subcommand invoked is in your
107             constant subcommands, otherwise, return C.
108              
109             =cut
110              
111             sub cascadable {
112 32     32 1 50 my $self = shift;
113 32   33     74 my $class = ref $self || $self;
114 32         113 for ( $self->subcommands ) {
115 2     2   20 no strict 'refs'; ## no critic
  2         4  
  2         2036  
116 30         66 my $package_name = $class . '::' . $_;
117 30         67 load_class $package_name;
118 30 100 100     4252 if ( $ARGV[0]
      66        
119             && (ucfirst( $ARGV[0] ) eq $_)
120 12         49 && exists ${ $class . '::' }{ $_ . '::' } )
121             {
122 12         36 return $package_name;
123             }
124             }
125 20         88 return undef;
126             }
127              
128             =head3 app
129              
130             Return the object referring to the current app.
131              
132             =cut
133              
134             sub app {
135 70     70 1 124 my $self = shift;
136              
137 70 100       146 if (@_) {
138 44         93 $self->{app} = shift;
139 44         131 weaken( $self->{app} );
140             }
141              
142 70         297 return $self->{app};
143             }
144              
145             =head3 brief_usage ($file)
146              
147             Display a one-line brief usage of the command object. Optionally, a file
148             could be given to extract the usage from the POD.
149              
150             =cut
151              
152             sub brief_usage {
153 6     6 1 39 my ( $self, $file ) = @_;
154 6 50 66     170 open my ($podfh), '<', ( $file || $self->filename ) or return;
155 6         40 local $/ = undef;
156 6         154 my $buf = <$podfh>;
157 6         27 my $base = ref $self->app;
158 6         14 my $indent = " ";
159 6 100       101 if ( $buf =~ /^=head1\s+NAME\s*\Q$base\E::(\w+)( - .+)$/m ) {
160 4         33 print $indent, loc( lc($1) . $2 ), "\n";
161             }
162             else {
163 2   33     10 my $cmd = $file || $self->filename;
164 2         21 $cmd =~ s/^(?:.*)\/(.*?).pm$/$1/;
165 2         14 print $indent, lc($cmd), " - ", loc("undocumented") . "\n";
166             }
167 6         487 close $podfh;
168             }
169              
170             =head3 usage ($want_detail)
171              
172             Display usage. If C<$want_detail> is true, the C
173             section is displayed as well.
174              
175             =cut
176              
177             sub usage {
178 3     3 1 32 my ( $self, $want_detail ) = @_;
179 3         12 my $fname = $self->filename;
180 3         22 my ($cmd) = $fname =~ m{\W(\w+)\.pm$};
181 3         21 require Pod::Simple::Text;
182 3         21 my $parser = Pod::Simple::Text->new;
183 3         275 my $buf;
184 3         16 $parser->output_string( \$buf );
185 3         1164 $parser->parse_file($fname);
186              
187 3         5799 my $base = ref $self->app;
188 3         48 $buf =~ s/\Q$base\E::(\w+)/\l$1/g;
189 3         13 $buf =~ s/^AUTHORS.*//sm;
190 3 100       10 $buf =~ s/^DESCRIPTION.*//sm unless $want_detail;
191 3         28 print $self->loc_text($buf);
192             }
193              
194             =head3 loc_text $text
195              
196             Localizes the body of (formatted) text in C<$text> and returns the
197             localized version.
198              
199             =cut
200              
201             sub loc_text {
202 4     4 1 9 my $self = shift;
203 4         8 my $buf = shift;
204              
205 4         6 my $out = "";
206 4         27 foreach my $line ( split( /\n\n+/, $buf, -1 ) ) {
207 11 100       161 if ( my @lines = $line =~ /^( {4}\s+.+\s*)$/mg ) {
    100          
    100          
208 1         3 foreach my $chunk (@lines) {
209 1 50       14 $chunk =~ /^(\s*)(.+?)( *)(: .+?)?(\s*)$/ or next;
210 1         3 my $spaces = $3;
211 1   50     9 my $loc = $1 . loc( $2 . ( $4 || '' ) ) . $5;
212 1 50       15 $loc =~ s/: /$spaces: / if $spaces;
213 1         4 $out .= $loc . "\n";
214             }
215 1         2 $out .= "\n";
216             }
217             elsif ( $line =~ /^(\s+)(\w+ - .*)$/ ) {
218 2         6 $out .= $1 . loc($2) . "\n\n";
219             }
220             elsif ( length $line ) {
221 5         17 $out .= loc($line) . "\n\n";
222             }
223             }
224 4         193 return $out;
225             }
226              
227             =head3 filename
228              
229             Return the filename for the command module.
230              
231             =cut
232              
233             sub filename {
234 7     7 1 15 my $self = shift;
235 7         14 my $fname = ref($self);
236 7         18 $fname =~ s{::[a-z]+$}{}; # subcommand
237 7         13 $fname =~ s{::}{/}g;
238 7         101 return $INC{"$fname.pm"};
239             }
240              
241             =head1 SEE ALSO
242              
243             L, L
244              
245             =head1 AUTHORS
246              
247             Chia-liang Kao Eclkao@clkao.orgE
248              
249             Cornelius Lin Ecornelius.howl@gmail.comE
250              
251             Shelling Enavyblueshellingford@gmail.comE
252              
253             Paul Cochrane Epaul@liekut.deE (current maintainer)
254              
255             =head1 COPYRIGHT
256              
257             Copyright 2005-2006 by Chia-liang Kao Eclkao@clkao.orgE.
258              
259             This program is free software; you can redistribute it and/or modify it
260             under the same terms as Perl itself.
261              
262             See L
263              
264             =cut
265              
266             1;