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; |