| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# ABSTRACT: Specification for commandline app |
|
2
|
7
|
|
|
7
|
|
922053
|
use strict; |
|
|
7
|
|
|
|
|
51
|
|
|
|
7
|
|
|
|
|
257
|
|
|
3
|
7
|
|
|
7
|
|
32
|
use warnings; |
|
|
7
|
|
|
|
|
11
|
|
|
|
7
|
|
|
|
|
451
|
|
|
4
|
|
|
|
|
|
|
package App::Spec; |
|
5
|
7
|
|
|
7
|
|
114
|
use 5.010; |
|
|
7
|
|
|
|
|
24
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = 'v0.15.0'; # VERSION |
|
8
|
|
|
|
|
|
|
|
|
9
|
7
|
|
|
7
|
|
2864
|
use App::Spec::Subcommand; |
|
|
7
|
|
|
|
|
32
|
|
|
|
7
|
|
|
|
|
396
|
|
|
10
|
7
|
|
|
7
|
|
60
|
use App::Spec::Option; |
|
|
7
|
|
|
|
|
16
|
|
|
|
7
|
|
|
|
|
217
|
|
|
11
|
7
|
|
|
7
|
|
32
|
use App::Spec::Parameter; |
|
|
7
|
|
|
|
|
28
|
|
|
|
7
|
|
|
|
|
188
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
7
|
|
|
7
|
|
31
|
use Moo; |
|
|
7
|
|
|
|
|
13
|
|
|
|
7
|
|
|
|
|
35
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
with('App::Spec::Role::Command'); |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
has title => ( is => 'rw' ); |
|
18
|
|
|
|
|
|
|
has abstract => ( is => 'rw' ); |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
sub runner { |
|
24
|
33
|
|
|
33
|
1
|
309
|
my ($self, %args) = @_; |
|
25
|
33
|
|
|
|
|
158
|
my $class = $self->class; |
|
26
|
33
|
|
|
|
|
614
|
my $cmd = $class->new; |
|
27
|
33
|
|
|
|
|
2134
|
my $run = App::Spec::Run->new({ |
|
28
|
|
|
|
|
|
|
spec => $self, |
|
29
|
|
|
|
|
|
|
cmd => $cmd, |
|
30
|
|
|
|
|
|
|
%args, |
|
31
|
|
|
|
|
|
|
}); |
|
32
|
33
|
|
|
|
|
285
|
return $run; |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
sub usage { |
|
36
|
19
|
|
|
19
|
1
|
105
|
my ($self, %args) = @_; |
|
37
|
19
|
|
|
|
|
52
|
my $cmds = $args{commands}; |
|
38
|
19
|
100
|
|
|
|
45
|
my %highlights = %{ $args{highlights} || {} }; |
|
|
19
|
|
|
|
|
180
|
|
|
39
|
19
|
|
33
|
0
|
|
91
|
my $colored = $args{colored} || sub { $_[1] }; |
|
|
0
|
|
|
|
|
0
|
|
|
40
|
19
|
|
|
|
|
81
|
my $appname = $self->name; |
|
41
|
|
|
|
|
|
|
|
|
42
|
19
|
|
100
|
|
|
102
|
my $abstract = $self->abstract // ''; |
|
43
|
19
|
|
|
|
|
68
|
my $title = $self->title; |
|
44
|
19
|
|
|
|
|
98
|
my ($options, $parameters, $subcmds) = $self->_gather_options_parameters($cmds); |
|
45
|
19
|
|
|
|
|
134
|
my $header = $colored->(['bold'], "$appname - $title"); |
|
46
|
19
|
|
|
|
|
73
|
my $usage = <<"EOM"; |
|
47
|
|
|
|
|
|
|
$header |
|
48
|
|
|
|
|
|
|
$abstract |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
EOM |
|
51
|
|
|
|
|
|
|
|
|
52
|
19
|
|
|
|
|
56
|
my $body = ''; |
|
53
|
19
|
|
|
|
|
65
|
my $usage_header = $colored->([qw/ bold /], "Usage:"); |
|
54
|
19
|
|
|
|
|
67
|
$usage .= "$usage_header $appname"; |
|
55
|
19
|
100
|
|
|
|
621
|
$usage .= " @$cmds" if @$cmds; |
|
56
|
19
|
100
|
|
|
|
75
|
if (keys %$subcmds) { |
|
57
|
6
|
|
|
|
|
16
|
my $maxlength = 0; |
|
58
|
6
|
|
|
|
|
13
|
my @table; |
|
59
|
6
|
|
|
|
|
13
|
my $usage_string = ""; |
|
60
|
6
|
|
|
|
|
12
|
my $header = "Subcommands:"; |
|
61
|
6
|
100
|
|
|
|
131
|
if ($highlights{subcommands}) { |
|
62
|
3
|
|
|
|
|
15
|
$colored->([qw/ bold red /], $usage_string); |
|
63
|
3
|
|
|
|
|
12
|
$colored->([qw/ bold red /], $header); |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
else { |
|
66
|
3
|
|
|
|
|
12
|
$colored->([qw/ bold /], $header); |
|
67
|
|
|
|
|
|
|
} |
|
68
|
6
|
|
|
|
|
18
|
$usage .= " $usage_string"; |
|
69
|
6
|
|
|
|
|
15
|
$body .= "$header\n"; |
|
70
|
|
|
|
|
|
|
|
|
71
|
6
|
|
|
|
|
10
|
my %keys; |
|
72
|
6
|
|
|
|
|
60
|
@keys{ keys %$subcmds } = (); |
|
73
|
6
|
|
|
|
|
17
|
my @keys; |
|
74
|
6
|
50
|
|
|
|
20
|
if (@$cmds) { |
|
75
|
0
|
|
|
|
|
0
|
@keys = sort keys %keys; |
|
76
|
|
|
|
|
|
|
} |
|
77
|
|
|
|
|
|
|
else { |
|
78
|
6
|
|
|
|
|
17
|
for my $key (qw/ help _meta /) { |
|
79
|
12
|
100
|
|
|
|
60
|
if (exists $keys{ $key }) { |
|
80
|
10
|
|
|
|
|
19
|
push @keys, $key; |
|
81
|
10
|
|
|
|
|
22
|
delete $keys{ $key }; |
|
82
|
|
|
|
|
|
|
} |
|
83
|
|
|
|
|
|
|
} |
|
84
|
6
|
|
|
|
|
49
|
unshift @keys, sort keys %keys; |
|
85
|
|
|
|
|
|
|
} |
|
86
|
6
|
|
|
|
|
18
|
for my $name (@keys) { |
|
87
|
38
|
|
|
|
|
73
|
my $cmd_spec = $subcmds->{ $name }; |
|
88
|
38
|
|
|
|
|
122
|
my $summary = $cmd_spec->summary; |
|
89
|
38
|
|
|
|
|
104
|
my @lines = split m/\n/, $summary; |
|
90
|
38
|
|
50
|
|
|
117
|
push @table, [$name, $lines[0] // '']; |
|
91
|
38
|
|
|
|
|
4165
|
push @table, ['', $_] for map { s/^ +//; $_ } @lines[1 .. $#lines]; |
|
|
4
|
|
|
|
|
13
|
|
|
|
4
|
|
|
|
|
18
|
|
|
92
|
38
|
100
|
|
|
|
114
|
if (length $name > $maxlength) { |
|
93
|
16
|
|
|
|
|
39
|
$maxlength = length $name; |
|
94
|
|
|
|
|
|
|
} |
|
95
|
|
|
|
|
|
|
} |
|
96
|
6
|
|
|
|
|
36
|
$body .= $self->_output_table(\@table, [$maxlength]); |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
|
|
99
|
19
|
100
|
|
|
|
90
|
if (@$parameters) { |
|
100
|
9
|
|
|
|
|
26
|
my $maxlength = 0; |
|
101
|
9
|
|
|
|
|
25
|
my @table; |
|
102
|
|
|
|
|
|
|
my @highlights; |
|
103
|
9
|
|
|
|
|
22
|
for my $param (@$parameters) { |
|
104
|
25
|
|
|
|
|
91
|
my $name = $param->name; |
|
105
|
25
|
|
|
|
|
67
|
my $highlight = $highlights{parameters}->{ $name }; |
|
106
|
25
|
100
|
|
|
|
62
|
push @highlights, $highlight ? 1 : 0; |
|
107
|
25
|
|
|
|
|
86
|
my $summary = $param->summary; |
|
108
|
25
|
|
|
|
|
94
|
my $param_usage_header = $param->to_usage_header; |
|
109
|
25
|
100
|
|
|
|
108
|
if ($highlight) { |
|
110
|
8
|
|
|
|
|
34
|
$colored->([qw/ bold red /], $param_usage_header); |
|
111
|
|
|
|
|
|
|
} |
|
112
|
25
|
|
|
|
|
51
|
$usage .= " " . $param_usage_header; |
|
113
|
25
|
|
|
|
|
60
|
my ($req, $multi) = (' ', ' '); |
|
114
|
25
|
100
|
|
|
|
73
|
if ($param->required) { |
|
115
|
24
|
|
|
|
|
35
|
$req = "*"; |
|
116
|
|
|
|
|
|
|
} |
|
117
|
25
|
50
|
|
|
|
149
|
if ($param->mapping) { |
|
|
|
100
|
|
|
|
|
|
|
118
|
0
|
|
|
|
|
0
|
$multi = '{}'; |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
elsif ($param->multiple) { |
|
121
|
6
|
|
|
|
|
11
|
$multi = '[]'; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
|
|
124
|
25
|
|
|
|
|
77
|
my $flags = $self->_param_flags_string($param); |
|
125
|
|
|
|
|
|
|
|
|
126
|
25
|
|
|
|
|
103
|
my @lines = split m/\n/, $summary; |
|
127
|
25
|
|
50
|
|
|
143
|
push @table, [$name, $req, $multi, ($lines[0] // '') . $flags]; |
|
128
|
25
|
|
|
|
|
87
|
push @table, ['', ' ', ' ', $_] for map { s/^ +//; $_ } @lines[1 .. $#lines]; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
12
|
|
|
129
|
25
|
100
|
|
|
|
98
|
if (length $name > $maxlength) { |
|
130
|
14
|
|
|
|
|
48
|
$maxlength = length $name; |
|
131
|
|
|
|
|
|
|
} |
|
132
|
|
|
|
|
|
|
} |
|
133
|
9
|
|
|
|
|
54
|
my $parameters_string = $colored->([qw/ bold /], "Parameters:"); |
|
134
|
9
|
|
|
|
|
25
|
$body .= "$parameters_string\n"; |
|
135
|
9
|
|
|
|
|
47
|
my @lines = $self->_output_table(\@table, [$maxlength]); |
|
136
|
9
|
|
|
|
|
41
|
my $lines = $self->_colorize_lines(\@lines, \@highlights, $colored); |
|
137
|
9
|
|
|
|
|
55
|
$body .= $lines; |
|
138
|
|
|
|
|
|
|
} |
|
139
|
|
|
|
|
|
|
|
|
140
|
19
|
50
|
|
|
|
65
|
if (@$options) { |
|
141
|
19
|
|
|
|
|
44
|
my @highlights; |
|
142
|
19
|
|
|
|
|
45
|
$usage .= " [options]"; |
|
143
|
19
|
|
|
|
|
46
|
my $maxlength = 0; |
|
144
|
19
|
|
|
|
|
40
|
my @table; |
|
145
|
19
|
|
|
|
|
135
|
for my $opt (sort { $a->name cmp $b->name } @$options) { |
|
|
80
|
|
|
|
|
261
|
|
|
146
|
69
|
|
|
|
|
181
|
my $name = $opt->name; |
|
147
|
69
|
|
|
|
|
155
|
my $highlight = $highlights{options}->{ $name }; |
|
148
|
69
|
100
|
|
|
|
175
|
push @highlights, $highlight ? 1 : 0; |
|
149
|
69
|
|
|
|
|
179
|
my $aliases = $opt->aliases; |
|
150
|
69
|
|
|
|
|
215
|
my $summary = $opt->summary; |
|
151
|
|
|
|
|
|
|
my @names = map { |
|
152
|
69
|
100
|
|
|
|
149
|
length $_ > 1 ? "--$_" : "-$_" |
|
|
109
|
|
|
|
|
454
|
|
|
153
|
|
|
|
|
|
|
} ($name, @$aliases); |
|
154
|
69
|
|
|
|
|
182
|
my $string = "@names"; |
|
155
|
69
|
100
|
|
|
|
193
|
if (length $string > $maxlength) { |
|
156
|
55
|
|
|
|
|
116
|
$maxlength = length $string; |
|
157
|
|
|
|
|
|
|
} |
|
158
|
69
|
|
|
|
|
145
|
my ($req, $multi) = (' ', ' '); |
|
159
|
69
|
50
|
|
|
|
324
|
if ($opt->required) { |
|
160
|
0
|
|
|
|
|
0
|
$req = "*"; |
|
161
|
|
|
|
|
|
|
} |
|
162
|
69
|
100
|
|
|
|
290
|
if ($opt->mapping) { |
|
|
|
100
|
|
|
|
|
|
|
163
|
3
|
|
|
|
|
7
|
$multi = '{}'; |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
elsif ($opt->multiple) { |
|
166
|
16
|
|
|
|
|
31
|
$multi = '[]'; |
|
167
|
|
|
|
|
|
|
} |
|
168
|
|
|
|
|
|
|
|
|
169
|
69
|
|
|
|
|
195
|
my $flags = $self->_param_flags_string($opt); |
|
170
|
|
|
|
|
|
|
|
|
171
|
69
|
|
|
|
|
221
|
my @lines = split m/\n/, $summary; |
|
172
|
69
|
|
50
|
|
|
335
|
push @table, [$string, $req, $multi, ($lines[0] // '') . $flags]; |
|
173
|
69
|
|
|
|
|
308
|
push @table, ['', ' ', ' ', $_ ] for map { s/^ +//; $_ } @lines[1 .. $#lines]; |
|
|
4
|
|
|
|
|
18
|
|
|
|
4
|
|
|
|
|
24
|
|
|
174
|
|
|
|
|
|
|
} |
|
175
|
19
|
|
|
|
|
82
|
my $options_string = $colored->([qw/ bold /], "Options:"); |
|
176
|
19
|
|
|
|
|
55
|
$body .= "\n$options_string\n"; |
|
177
|
19
|
|
|
|
|
77
|
my @lines = $self->_output_table(\@table, [$maxlength]); |
|
178
|
19
|
|
|
|
|
107
|
my $lines = $self->_colorize_lines(\@lines, \@highlights, $colored); |
|
179
|
19
|
|
|
|
|
130
|
$body .= $lines; |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
|
|
182
|
19
|
|
|
|
|
179
|
return "$usage\n\n$body"; |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub _param_flags_string { |
|
186
|
149
|
|
|
149
|
|
321
|
my ($self, $param) = @_; |
|
187
|
149
|
|
|
|
|
228
|
my @flags; |
|
188
|
149
|
100
|
|
|
|
477
|
if ($param->type eq 'flag') { |
|
189
|
70
|
|
|
|
|
134
|
push @flags, "flag"; |
|
190
|
|
|
|
|
|
|
} |
|
191
|
149
|
100
|
|
|
|
351
|
if ($param->multiple) { |
|
192
|
31
|
|
|
|
|
61
|
push @flags, "multiple"; |
|
193
|
|
|
|
|
|
|
} |
|
194
|
149
|
100
|
|
|
|
390
|
if ($param->mapping) { |
|
195
|
4
|
|
|
|
|
11
|
push @flags, "mapping"; |
|
196
|
|
|
|
|
|
|
} |
|
197
|
149
|
100
|
|
|
|
417
|
my $flags = @flags ? " (" . join("; ", @flags) . ")" : ''; |
|
198
|
149
|
|
|
|
|
380
|
return $flags; |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub _colorize_lines { |
|
202
|
28
|
|
|
28
|
|
80
|
my ($self, $lines, $highlights, $colored) = @_; |
|
203
|
28
|
|
|
|
|
61
|
my $output = ''; |
|
204
|
28
|
|
|
|
|
121
|
for my $i (0 .. $#$lines) { |
|
205
|
100
|
|
|
|
|
160
|
my $line = $lines->[ $i ]; |
|
206
|
100
|
100
|
|
|
|
238
|
if ($highlights->[ $i ]) { |
|
207
|
12
|
|
|
|
|
45
|
$colored->([qw/ bold red /], $line); |
|
208
|
|
|
|
|
|
|
} |
|
209
|
100
|
|
|
|
|
239
|
$output .= $line; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
28
|
|
|
|
|
81
|
return $output; |
|
212
|
|
|
|
|
|
|
} |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
sub _output_table { |
|
215
|
34
|
|
|
34
|
|
92
|
my ($self, $table, $lengths) = @_; |
|
216
|
34
|
|
|
|
|
75
|
my @lines; |
|
217
|
|
|
|
|
|
|
my @lengths = map { |
|
218
|
124
|
100
|
|
|
|
396
|
defined $lengths->[$_] ? "%-$lengths->[$_]s" : "%s" |
|
219
|
34
|
|
|
|
|
69
|
} 0 .. @{ $table->[0] } - 1; |
|
|
34
|
|
|
|
|
111
|
|
|
220
|
34
|
|
|
|
|
141
|
for my $row (@$table) { |
|
221
|
7
|
|
|
7
|
|
20610
|
no warnings 'uninitialized'; |
|
|
7
|
|
|
|
|
25
|
|
|
|
7
|
|
|
|
|
5371
|
|
|
222
|
142
|
|
|
|
|
738
|
push @lines, sprintf join(' ', @lengths) . "\n", @$row; |
|
223
|
|
|
|
|
|
|
} |
|
224
|
34
|
100
|
|
|
|
240
|
return wantarray ? @lines : join '', @lines; |
|
225
|
|
|
|
|
|
|
} |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
sub _gather_options_parameters { |
|
229
|
19
|
|
|
19
|
|
52
|
my ($self, $cmds) = @_; |
|
230
|
19
|
|
|
|
|
46
|
my @options; |
|
231
|
|
|
|
|
|
|
my @parameters; |
|
232
|
19
|
|
|
|
|
54
|
my $global_options = $self->options; |
|
233
|
19
|
|
|
|
|
61
|
my $commands = $self->subcommands; |
|
234
|
19
|
|
|
|
|
67
|
push @options, @$global_options; |
|
235
|
|
|
|
|
|
|
|
|
236
|
19
|
|
|
|
|
61
|
for my $cmd (@$cmds) { |
|
237
|
13
|
|
|
|
|
39
|
my $cmd_spec = $commands->{ $cmd }; |
|
238
|
13
|
|
50
|
|
|
59
|
my $options = $cmd_spec->options || []; |
|
239
|
13
|
|
50
|
|
|
54
|
my $parameters = $cmd_spec->parameters || []; |
|
240
|
13
|
|
|
|
|
34
|
push @options, @$options; |
|
241
|
13
|
|
|
|
|
36
|
push @parameters, @$parameters; |
|
242
|
|
|
|
|
|
|
|
|
243
|
13
|
|
100
|
|
|
94
|
$commands = $cmd_spec->subcommands || {}; |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
} |
|
246
|
19
|
|
|
|
|
122
|
return \@options, \@parameters, $commands; |
|
247
|
|
|
|
|
|
|
} |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub generate_completion { |
|
250
|
0
|
|
|
0
|
1
|
0
|
my ($self, %args) = @_; |
|
251
|
0
|
|
|
|
|
0
|
my $shell = delete $args{shell}; |
|
252
|
|
|
|
|
|
|
|
|
253
|
0
|
0
|
|
|
|
0
|
if ($shell eq "zsh") { |
|
|
|
0
|
|
|
|
|
|
|
254
|
0
|
|
|
|
|
0
|
require App::Spec::Completion::Zsh; |
|
255
|
0
|
|
|
|
|
0
|
my $completer = App::Spec::Completion::Zsh->new( |
|
256
|
|
|
|
|
|
|
spec => $self, |
|
257
|
|
|
|
|
|
|
); |
|
258
|
0
|
|
|
|
|
0
|
return $completer->generate_completion(%args); |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
elsif ($shell eq "bash") { |
|
261
|
0
|
|
|
|
|
0
|
require App::Spec::Completion::Bash; |
|
262
|
0
|
|
|
|
|
0
|
my $completer = App::Spec::Completion::Bash->new( |
|
263
|
|
|
|
|
|
|
spec => $self, |
|
264
|
|
|
|
|
|
|
); |
|
265
|
0
|
|
|
|
|
0
|
return $completer->generate_completion(%args); |
|
266
|
|
|
|
|
|
|
} |
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub make_getopt { |
|
271
|
68
|
|
|
68
|
1
|
218
|
my ($self, $options, $result, $specs) = @_; |
|
272
|
68
|
|
|
|
|
125
|
my @getopt; |
|
273
|
68
|
|
|
|
|
190
|
for my $opt (@$options) { |
|
274
|
139
|
|
|
|
|
492
|
my $name = $opt->name; |
|
275
|
139
|
|
|
|
|
278
|
my $spec = $name; |
|
276
|
139
|
50
|
|
|
|
428
|
if (my $aliases = $opt->aliases) { |
|
277
|
139
|
|
|
|
|
439
|
$spec .= "|$_" for @$aliases; |
|
278
|
|
|
|
|
|
|
} |
|
279
|
139
|
100
|
|
|
|
493
|
unless ($opt->type eq 'flag') { |
|
280
|
49
|
|
|
|
|
100
|
$spec .= "=s"; |
|
281
|
|
|
|
|
|
|
} |
|
282
|
139
|
|
|
|
|
363
|
$specs->{ $name } = $opt; |
|
283
|
139
|
100
|
|
|
|
404
|
if ($opt->multiple) { |
|
284
|
34
|
100
|
|
|
|
155
|
if ($opt->type eq 'flag') { |
|
|
|
50
|
|
|
|
|
|
|
285
|
29
|
|
|
|
|
83
|
$spec .= '+'; |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
elsif ($opt->mapping) { |
|
288
|
5
|
|
|
|
|
17
|
$result->{ $name } = {}; |
|
289
|
5
|
|
|
|
|
15
|
$spec .= '%'; |
|
290
|
|
|
|
|
|
|
} |
|
291
|
|
|
|
|
|
|
else { |
|
292
|
0
|
|
|
|
|
0
|
$result->{ $name } = []; |
|
293
|
0
|
|
|
|
|
0
|
$spec .= '@'; |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
} |
|
296
|
139
|
|
|
|
|
508
|
push @getopt, $spec, \$result->{ $name }, |
|
297
|
|
|
|
|
|
|
} |
|
298
|
68
|
|
|
|
|
391
|
return @getopt; |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=pod |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=head1 NAME |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
App::Spec - Specification for commandline apps |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
WARNING: This is still experimental. The spec is subject to change. |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
This module represents a specification of a command line tool. |
|
312
|
|
|
|
|
|
|
Currently it can read the spec from a YAML file or directly from a data |
|
313
|
|
|
|
|
|
|
structure in perl. |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
It uses the role L. |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
The L module is the framework which will run the actual |
|
318
|
|
|
|
|
|
|
app. |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Have a look at the L for how to write an app. |
|
321
|
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
In the examples directory you will find the app C which is supposed |
|
323
|
|
|
|
|
|
|
to demonstrate everything that App::Spec supports right now. |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
Your script: |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
use App::Spec; |
|
328
|
|
|
|
|
|
|
my $spec = App::Spec->read("/path/to/myapp-spec.yaml"); |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
my $run = $spec->runner; |
|
331
|
|
|
|
|
|
|
$run->run; |
|
332
|
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# this is equivalent to |
|
334
|
|
|
|
|
|
|
#my $run = App::Spec::Run->new( |
|
335
|
|
|
|
|
|
|
# spec => $spec, |
|
336
|
|
|
|
|
|
|
# cmd => Your::App->new, |
|
337
|
|
|
|
|
|
|
#); |
|
338
|
|
|
|
|
|
|
#$run->run; |
|
339
|
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
Your App class: |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
package Your::App; |
|
343
|
|
|
|
|
|
|
use base 'App::Spec::Run::Cmd'; |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
sub command1 { |
|
346
|
|
|
|
|
|
|
my ($self, $run) = @_; |
|
347
|
|
|
|
|
|
|
my $options = $run->options; |
|
348
|
|
|
|
|
|
|
my $param = $run->parameters; |
|
349
|
|
|
|
|
|
|
# Do something |
|
350
|
|
|
|
|
|
|
$run->out("Hello world!"); |
|
351
|
|
|
|
|
|
|
$run->err("oops"); |
|
352
|
|
|
|
|
|
|
# you can also use print directly |
|
353
|
|
|
|
|
|
|
} |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
=head1 METHODS |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=over 4 |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=item read |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
my $spec = App::Spec->read("/path/to/myapp-spec.yaml"); |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=item load_data |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
Takes a file, hashref or glob and returns generated appspec hashref |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
my $hash = $class->load_data($file); |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=item build |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
Builds objects out of the hashref |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
my $appspec = App::Spec->build(%hash); |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=item runner |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
Returns an instance of the your app class |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
my $run = $spec->runner; |
|
381
|
|
|
|
|
|
|
$run->run; |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
# this is equivalent to |
|
384
|
|
|
|
|
|
|
my $run = App::Spec::Example::MyApp->new({ |
|
385
|
|
|
|
|
|
|
spec => $spec, |
|
386
|
|
|
|
|
|
|
}); |
|
387
|
|
|
|
|
|
|
$run->run; |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=item usage |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
Returns usage output for the specified subcommands: |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
my $usage = $spec->usage( |
|
394
|
|
|
|
|
|
|
commands => ["subcommand1","subcommand2"], |
|
395
|
|
|
|
|
|
|
); |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=item generate_completion |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
Generates shell completion script for the spec. |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
my $completion = $spec->generate_completion( |
|
402
|
|
|
|
|
|
|
shell => "zsh", |
|
403
|
|
|
|
|
|
|
); |
|
404
|
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
=item make_getopt |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
Returns options for Getopt::Long |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
my @getopt = $spec->make_getopt($global_options, \%options, $option_specs); |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
=item abstract, appspec, class, description, has_subcommands, markup, name, options, parameters, subcommands, title |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
Accessors for the things defined in the spec (file) |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
=back |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
L - Utilities for App::Spec authors |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
=head1 LICENSE |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
This library is free software and may be distributed under the same terms |
|
424
|
|
|
|
|
|
|
as perl itself. |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=cut |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
1; |
|
429
|
|
|
|
|
|
|
|