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   102086 use strict;
  2         16  
  2         61  
3 2     2   11 use warnings;
  2         2  
  2         64  
4 2     2   1120 use Locale::Maketext::Simple;
  2         3464  
  2         13  
5 2     2   500 use Carp ();
  2         4  
  2         35  
6 2     2   455 use App::CLI::Helper;
  2         4  
  2         12  
7 2     2   457 use Class::Load qw( load_class );
  2         19196  
  2         136  
8 2     2   16 use Scalar::Util qw( weaken );
  2         4  
  2         106  
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         5  
  2         98  
42 2     2   13 use constant options => ();
  2         3  
  2         517  
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 141 ( ( map { $_ => $_ } $_[0]->subcommands ), $_[0]->options );
  12         65  
51             }
52              
53             sub run_command {
54 20     20 0 30 my $self = shift;
55 20         56 $self->run(@_);
56             }
57              
58             sub run {
59 1     1 0 5 my $class = shift;
60 1         181 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         78 my @cmd = $self->subcommands;
72 20 50 66     68 @cmd = values %{ { $self->options } } if @cmd && $cmd[0] eq '*';
  0         0  
73 20         32 my $subcmd = undef;
74 20         46 for ( grep { $self->{$_} } @cmd ) {
  12         29  
75 2     2   19 no strict 'refs'; ## no critic
  2         4  
  2         489  
76 1 50       3 if ( exists ${ ref($self) . '::' }{ $_ . '::' } ) {
  1         9  
77 1         3 my %data = %{$self};
  1         7  
78 1         8 $subcmd = bless( {%data}, ( ref($self) . "::$_" ) );
79 1         4 last;
80             }
81             }
82 20 100       69 $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 9 my $self = shift;
94 6 50       13 if ( my $subcmd = $self->cascadable ) {
95 6         10 shift @ARGV;
96 6         9 my %data = %{$self};
  6         38  
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 49 my $self = shift;
113 32   33     88 my $class = ref $self || $self;
114 32         117 for ( $self->subcommands ) {
115 2     2   15 no strict 'refs'; ## no critic
  2         4  
  2         2034  
116 30         66 my $package_name = $class . '::' . $_;
117 30         68 load_class $package_name;
118 30 100 100     4214 if ( $ARGV[0]
      66        
119             && (ucfirst( $ARGV[0] ) eq $_)
120 12         50 && exists ${ $class . '::' }{ $_ . '::' } )
121             {
122 12         37 return $package_name;
123             }
124             }
125 20         96 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 117 my $self = shift;
136              
137 70 100       147 if (@_) {
138 44         91 $self->{app} = shift;
139 44         137 weaken( $self->{app} );
140             }
141              
142 70         282 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 37 my ( $self, $file ) = @_;
154 6 50 66     171 open my ($podfh), '<', ( $file || $self->filename ) or return;
155 6         37 local $/ = undef;
156 6         172 my $buf = <$podfh>;
157 6         28 my $base = ref $self->app;
158 6         46 my $indent = " ";
159 6 100       98 if ( $buf =~ /^=head1\s+NAME\s*\Q$base\E::(\w+)( - .+)$/m ) {
160 4         31 print $indent, loc( lc($1) . $2 ), "\n";
161             }
162             else {
163 2   33     11 my $cmd = $file || $self->filename;
164 2         19 $cmd =~ s/^(?:.*)\/(.*?).pm$/$1/;
165 2         13 print $indent, lc($cmd), " - ", loc("undocumented") . "\n";
166             }
167 6         476 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 31 my ( $self, $want_detail ) = @_;
179 3         11 my $fname = $self->filename;
180 3         20 my ($cmd) = $fname =~ m{\W(\w+)\.pm$};
181 3         21 require Pod::Simple::Text;
182 3         22 my $parser = Pod::Simple::Text->new;
183 3         273 my $buf;
184 3         16 $parser->output_string( \$buf );
185 3         1224 $parser->parse_file($fname);
186              
187 3         5845 my $base = ref $self->app;
188 3         52 $buf =~ s/\Q$base\E::(\w+)/\l$1/g;
189 3         15 $buf =~ s/^AUTHORS.*//sm;
190 3 100       10 $buf =~ s/^DESCRIPTION.*//sm unless $want_detail;
191 3         31 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 13 my $self = shift;
203 4         6 my $buf = shift;
204              
205 4         8 my $out = "";
206 4         25 foreach my $line ( split( /\n\n+/, $buf, -1 ) ) {
207 11 100       158 if ( my @lines = $line =~ /^( {4}\s+.+\s*)$/mg ) {
    100          
    100          
208 1         3 foreach my $chunk (@lines) {
209 1 50       15 $chunk =~ /^(\s*)(.+?)( *)(: .+?)?(\s*)$/ or next;
210 1         4 my $spaces = $3;
211 1   50     8 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         3 $out .= "\n";
216             }
217             elsif ( $line =~ /^(\s+)(\w+ - .*)$/ ) {
218 2         6 $out .= $1 . loc($2) . "\n\n";
219             }
220             elsif ( length $line ) {
221 5         18 $out .= loc($line) . "\n\n";
222             }
223             }
224 4         184 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 13 my $self = shift;
235 7         12 my $fname = ref($self);
236 7         19 $fname =~ s{::[a-z]+$}{}; # subcommand
237 7         16 $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;