line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Getopt::Compact::WithCmd; |
2
|
|
|
|
|
|
|
|
3
|
20
|
|
|
20
|
|
829104
|
use strict; |
|
20
|
|
|
|
|
50
|
|
|
20
|
|
|
|
|
868
|
|
4
|
20
|
|
|
20
|
|
109
|
use warnings; |
|
20
|
|
|
|
|
45
|
|
|
20
|
|
|
|
|
815
|
|
5
|
20
|
|
|
20
|
|
507
|
use 5.008_001; |
|
20
|
|
|
|
|
71
|
|
|
20
|
|
|
|
|
961
|
|
6
|
20
|
|
|
20
|
|
27749
|
use Data::Dumper (); |
|
20
|
|
|
|
|
325270
|
|
|
20
|
|
|
|
|
691
|
|
7
|
20
|
|
|
20
|
|
296
|
use List::Util qw(max); |
|
20
|
|
|
|
|
48
|
|
|
20
|
|
|
|
|
3990
|
|
8
|
20
|
|
|
20
|
|
38416
|
use Getopt::Long qw(GetOptionsFromArray); |
|
20
|
|
|
|
|
356446
|
|
|
20
|
|
|
|
|
153
|
|
9
|
20
|
|
|
20
|
|
4828
|
use Carp (); |
|
20
|
|
|
|
|
44
|
|
|
20
|
|
|
|
|
684
|
|
10
|
20
|
|
|
|
|
216490
|
use constant DEFAULT_CONFIG => ( |
11
|
|
|
|
|
|
|
no_auto_abbrev => 1, |
12
|
|
|
|
|
|
|
no_ignore_case => 1, |
13
|
|
|
|
|
|
|
bundling => 1, |
14
|
20
|
|
|
20
|
|
113
|
); |
|
20
|
|
|
|
|
41
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION = '0.22'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
my $TYPE_MAP = { |
19
|
|
|
|
|
|
|
'Bool' => '!', |
20
|
|
|
|
|
|
|
'Incr' => '+', |
21
|
|
|
|
|
|
|
'Str' => '=s', |
22
|
|
|
|
|
|
|
'Int' => '=i', |
23
|
|
|
|
|
|
|
'Num' => '=f', |
24
|
|
|
|
|
|
|
'ExNum' => '=o', |
25
|
|
|
|
|
|
|
}; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
my $TYPE_GEN = {}; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
sub new { |
30
|
199
|
|
|
199
|
1
|
980491
|
my ($class, %args) = @_; |
31
|
|
|
|
|
|
|
my $self = bless { |
32
|
199
|
100
|
66
|
|
|
1336
|
cmd => $args{cmd} || do { require File::Basename; File::Basename::basename($0) }, |
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
33
|
|
|
|
|
|
|
name => $args{name}, |
34
|
|
|
|
|
|
|
version => $args{version} || $::VERSION, |
35
|
|
|
|
|
|
|
modes => $args{modes}, |
36
|
|
|
|
|
|
|
opt => {}, |
37
|
|
|
|
|
|
|
usage => exists $args{usage} && !$args{usage} ? 0 : 1, |
38
|
|
|
|
|
|
|
args => $args{args} || '', |
39
|
|
|
|
|
|
|
_argv => \@ARGV, |
40
|
|
|
|
|
|
|
struct => [], |
41
|
|
|
|
|
|
|
summary => {}, |
42
|
|
|
|
|
|
|
requires => {}, |
43
|
|
|
|
|
|
|
ret => 0, |
44
|
|
|
|
|
|
|
error => undef, |
45
|
|
|
|
|
|
|
other_usage => undef, |
46
|
|
|
|
|
|
|
commands => [], |
47
|
|
|
|
|
|
|
_struct => $args{command_struct} || {}, |
48
|
|
|
|
|
|
|
}, $class; |
49
|
|
|
|
|
|
|
|
50
|
199
|
50
|
|
|
|
635
|
my %config = (DEFAULT_CONFIG, %{$args{configure} || {}}); |
|
199
|
|
|
|
|
2001
|
|
51
|
199
|
|
|
|
|
1138
|
my @gconf = grep $config{$_}, keys %config; |
52
|
199
|
50
|
|
|
|
1443
|
Getopt::Long::Configure(@gconf) if @gconf; |
53
|
|
|
|
|
|
|
|
54
|
199
|
|
|
|
|
13232
|
$self->_init_summary($args{command_struct}); |
55
|
|
|
|
|
|
|
|
56
|
199
|
|
100
|
|
|
1504
|
$self->_init_struct($args{global_struct} || []); |
57
|
199
|
|
100
|
|
|
906
|
my $opthash = $self->_parse_struct || return $self; |
58
|
195
|
100
|
|
|
|
570
|
if ($args{command_struct}) { |
59
|
79
|
100
|
|
|
|
287
|
if (my @gopts = $self->_parse_argv) { |
60
|
62
|
|
|
|
|
241
|
$self->{ret} = $self->_parse_option(\@gopts, $opthash); |
61
|
62
|
|
|
|
|
165
|
unshift @ARGV, @gopts; |
62
|
62
|
100
|
|
|
|
1304
|
return $self unless $self->{ret}; |
63
|
60
|
50
|
|
|
|
399
|
return $self if $self->_want_help; |
64
|
|
|
|
|
|
|
} |
65
|
77
|
|
|
|
|
508
|
$self->_check_requires; |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
else { |
68
|
116
|
|
|
|
|
532
|
$self->{ret} = $self->_parse_option(\@ARGV, $opthash); |
69
|
116
|
100
|
|
|
|
455
|
return $self unless $self->{ret}; |
70
|
110
|
100
|
|
|
|
416
|
return $self if $self->_want_help; |
71
|
103
|
|
|
|
|
356
|
$self->_check_requires; |
72
|
103
|
|
|
|
|
10174
|
return $self; |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
77
|
|
|
|
|
314
|
$self->_parse_command_struct($args{command_struct}); |
76
|
77
|
|
|
|
|
467
|
return $self; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub new_from_array { |
80
|
40
|
|
|
40
|
1
|
483136
|
my ($class, $args, %options) = @_; |
81
|
40
|
50
|
|
|
|
226
|
unless (ref $args eq 'ARRAY') { |
82
|
0
|
|
|
|
|
0
|
Carp::croak("Usage: $class->new_from_array(\\\@args, %options)"); |
83
|
|
|
|
|
|
|
} |
84
|
40
|
|
|
|
|
119
|
local *ARGV = $args; |
85
|
40
|
|
|
|
|
194
|
return $class->new(%options); |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub new_from_string { |
89
|
38
|
|
|
38
|
1
|
78270
|
my ($class, $str, %options) = @_; |
90
|
38
|
100
|
|
|
|
143
|
unless (defined $str) { |
91
|
1
|
|
|
|
|
186
|
Carp::croak("Usage: $class->new_from_string(\$str, %options)"); |
92
|
|
|
|
|
|
|
} |
93
|
37
|
|
|
|
|
2655
|
require Text::ParseWords; |
94
|
37
|
|
|
|
|
3502
|
my $args = [Text::ParseWords::shellwords($str)]; |
95
|
37
|
|
|
|
|
3875
|
local *ARGV = $args; |
96
|
37
|
|
|
|
|
196
|
return $class->new(%options); |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
31
|
|
|
31
|
1
|
204
|
sub args { $_[0]->{_argv} } |
100
|
3
|
50
|
|
3
|
1
|
101
|
sub error { $_[0]->{error}||'' } |
101
|
140
|
|
|
140
|
1
|
1345
|
sub command { $_[0]->{command} } |
102
|
38
|
|
|
38
|
1
|
152
|
sub commands { $_[0]->{commands} } |
103
|
54
|
|
|
54
|
1
|
298
|
sub status { $_[0]->{ret} } |
104
|
0
|
|
|
0
|
1
|
0
|
sub is_success { $_[0]->{ret} } |
105
|
0
|
|
|
0
|
1
|
0
|
sub pod2usage { Carp::carp('Not implemented') } |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub opts { |
108
|
51
|
|
|
51
|
1
|
15460
|
my($self) = @_; |
109
|
51
|
|
|
|
|
113
|
my $opt = $self->{opt}; |
110
|
51
|
100
|
100
|
|
|
375
|
if ($self->{usage} && ($opt->{help} || $self->status == 0)) { |
|
|
|
33
|
|
|
|
|
111
|
|
|
|
|
|
|
# display usage message & exit |
112
|
5
|
|
|
|
|
22
|
print $self->usage; |
113
|
5
|
|
|
|
|
30
|
exit !$self->status; |
114
|
|
|
|
|
|
|
} |
115
|
51
|
|
|
|
|
337
|
return $opt; |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub usage { |
119
|
37
|
|
|
37
|
1
|
22829
|
my($self, @targets) = @_; |
120
|
37
|
|
|
|
|
84
|
my $usage = ''; |
121
|
37
|
|
|
|
|
66
|
my(@help, @commands); |
122
|
|
|
|
|
|
|
|
123
|
37
|
100
|
100
|
|
|
111
|
if ((defined $self->command && $self->command eq 'help') || @targets) { |
|
|
|
100
|
|
|
|
|
124
|
9
|
|
|
|
|
23
|
delete $self->{command}; |
125
|
9
|
100
|
|
|
|
28
|
@targets = @{$self->{_argv}} unless @targets; |
|
7
|
|
|
|
|
25
|
|
126
|
9
|
|
|
|
|
38
|
for (my $i = 0; $i < @targets; $i++) { |
127
|
8
|
|
|
|
|
16
|
my $target = $targets[$i]; |
128
|
8
|
50
|
|
|
|
24
|
last unless defined $target; |
129
|
8
|
50
|
|
|
|
34
|
unless (ref $self->{_struct}{$target} eq 'HASH') { |
130
|
0
|
|
|
|
|
0
|
$self->{error} = "Unknown command: $target"; |
131
|
0
|
|
|
|
|
0
|
last; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
else { |
134
|
8
|
|
|
|
|
18
|
$self->{command} = $target; |
135
|
8
|
|
|
|
|
13
|
push @{$self->{commands}}, $target; |
|
8
|
|
|
|
|
22
|
|
136
|
8
|
|
|
|
|
38
|
$self->_init_struct($self->{_struct}{$target}{options}); |
137
|
8
|
|
|
|
|
36
|
$self->_extends_usage($self->{_struct}{$target}); |
138
|
|
|
|
|
|
|
|
139
|
8
|
100
|
|
|
|
34
|
if (ref $self->{_struct}{$target}{command_struct} eq 'HASH') { |
140
|
3
|
|
|
|
|
14
|
$self->{_struct} = $self->{_struct}{$target}{command_struct}; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
else { |
143
|
5
|
|
|
|
|
29
|
$self->{summary} = {}; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
37
|
|
100
|
|
|
832
|
my($name, $version, $cmd, $struct, $args, $summary, $error, $other_usage) = map |
150
|
|
|
|
|
|
|
$self->{$_} || '', qw/name version cmd struct args summary error other_usage/; |
151
|
|
|
|
|
|
|
|
152
|
37
|
100
|
|
|
|
138
|
$usage .= "$error\n" if $error; |
153
|
|
|
|
|
|
|
|
154
|
37
|
100
|
|
|
|
88
|
if ($name) { |
155
|
2
|
|
|
|
|
4
|
$usage .= $name; |
156
|
2
|
100
|
|
|
|
7
|
$usage .= " v$version" if $version; |
157
|
2
|
|
|
|
|
4
|
$usage .= "\n"; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
37
|
100
|
66
|
|
|
95
|
if ($self->command && $self->command ne 'help') { |
161
|
14
|
50
|
|
|
|
22
|
my $sub_command = join q{ }, @{$self->commands} ? @{$self->commands} : $self->command; |
|
14
|
|
|
|
|
42
|
|
|
14
|
|
|
|
|
28
|
|
162
|
14
|
|
|
|
|
60
|
$usage .= "usage: $cmd $sub_command [options]"; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
else { |
165
|
23
|
|
|
|
|
80
|
$usage .= "usage: $cmd [options]"; |
166
|
23
|
100
|
|
|
|
83
|
$usage .= ' COMMAND' if keys %$summary; |
167
|
|
|
|
|
|
|
} |
168
|
37
|
100
|
|
|
|
128
|
$usage .= ($args ? " $args" : '') . "\n\n"; |
169
|
|
|
|
|
|
|
|
170
|
37
|
|
|
|
|
84
|
for my $o (@$struct) { |
171
|
62
|
|
|
|
|
123
|
my ($name_spec, $desc, $arg_spec, $dist, $opts) = @$o; |
172
|
62
|
100
|
|
|
|
140
|
$desc = '' unless defined $desc; |
173
|
62
|
|
|
|
|
142
|
my @onames = $self->_option_names($name_spec); |
174
|
121
|
100
|
|
|
|
408
|
my $optname = join |
175
|
62
|
|
|
|
|
126
|
(', ', map { (length($_) > 1 ? '--' : '-').$_ } @onames); |
176
|
62
|
100
|
|
|
|
189
|
$optname = ' '.$optname unless length($onames[0]) == 1; |
177
|
62
|
|
|
|
|
74
|
my $info = do { |
178
|
62
|
|
|
|
|
98
|
local $Data::Dumper::Indent = 0; |
179
|
62
|
|
|
|
|
92
|
local $Data::Dumper::Terse = 1; |
180
|
62
|
|
|
|
|
106
|
my $info = []; |
181
|
62
|
|
100
|
|
|
215
|
push @$info, $self->_opt_spec2name($arg_spec) || $arg_spec || ''; |
182
|
62
|
100
|
|
|
|
177
|
push @$info, $opts->{required} ? "(required)" : ''; |
183
|
62
|
100
|
|
|
|
275
|
push @$info, defined $opts->{default} ? "(default: ".Data::Dumper::Dumper($opts->{default}).")" : ''; |
184
|
62
|
|
|
|
|
225
|
$info; |
185
|
|
|
|
|
|
|
}; |
186
|
62
|
|
|
|
|
366
|
push @help, [ $optname, $info, ucfirst($desc) ]; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
37
|
50
|
|
|
|
105
|
if (@help) { |
190
|
37
|
|
|
|
|
5339
|
require Text::Table; |
191
|
37
|
|
|
|
|
71394
|
my $sep = \' '; |
192
|
37
|
|
|
|
|
78
|
$usage .= "options:\n"; |
193
|
37
|
|
|
|
|
267
|
$usage .= Text::Table->new($sep, '', $sep, '', $sep, '')->load($self->_format_info(@help))->stringify."\n"; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
37
|
100
|
66
|
|
|
181335
|
if (defined $other_usage && length $other_usage > 0) { |
197
|
5
|
|
|
|
|
18
|
$other_usage =~ s/\n$//ms; |
198
|
5
|
|
|
|
|
26
|
$usage .= "$other_usage\n\n"; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
37
|
100
|
100
|
|
|
171
|
if (!$self->command || $self->{has_sub_command}) { |
202
|
26
|
|
|
|
|
137
|
for my $command (sort keys %$summary) { |
203
|
8
|
|
|
|
|
46
|
push @commands, [ $command, ucfirst $summary->{$command} ]; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
26
|
100
|
|
|
|
93
|
if (@commands) { |
207
|
8
|
|
|
|
|
79
|
require Text::Table; |
208
|
8
|
|
|
|
|
15
|
my $sep = \' '; |
209
|
8
|
|
|
|
|
35
|
$usage .= "Implemented commands are:\n"; |
210
|
8
|
|
|
|
|
43
|
$usage .= Text::Table->new($sep, '', $sep, '')->load(@commands)->stringify."\n"; |
211
|
8
|
|
|
|
|
25291
|
my $help_command = "$cmd help COMMAND"; |
212
|
8
|
100
|
|
|
|
21
|
if (@{$self->commands}) { |
|
8
|
|
|
|
|
35
|
|
213
|
2
|
|
|
|
|
4
|
my $sub_commands = join q{ }, @{$self->commands}; |
|
2
|
|
|
|
|
6
|
|
214
|
2
|
|
|
|
|
7
|
$help_command = "$cmd $sub_commands COMMAND --help"; |
215
|
|
|
|
|
|
|
} |
216
|
8
|
|
|
|
|
47
|
$usage .= "See '$help_command' for more information on a specific command.\n\n"; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
37
|
|
|
|
|
831
|
return $usage; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub show_usage { |
224
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
225
|
0
|
|
|
|
|
0
|
print $self->usage(@_); |
226
|
0
|
|
|
|
|
0
|
exit !$self->status; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub completion { |
230
|
30
|
|
|
30
|
1
|
17928
|
my($self, $shell) = @_; |
231
|
30
|
|
50
|
|
|
86
|
$shell ||= 'bash'; |
232
|
|
|
|
|
|
|
|
233
|
30
|
100
|
|
|
|
92
|
if ($shell eq 'bash') { |
234
|
29
|
|
|
|
|
74
|
return $self->_completion_bash; |
235
|
|
|
|
|
|
|
} else { |
236
|
1
|
|
|
|
|
499
|
Carp::carp("Not implemented: completion for $shell"); |
237
|
1
|
|
|
|
|
1034
|
return ""; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub show_completion { |
242
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
243
|
0
|
|
|
|
|
0
|
print $self->completion(@_); |
244
|
0
|
|
|
|
|
0
|
exit !$self->status; |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub _completion_bash { |
248
|
29
|
|
|
29
|
|
37
|
my $self = shift; |
249
|
29
|
|
|
|
|
47
|
my $comp = ''; |
250
|
|
|
|
|
|
|
|
251
|
29
|
|
66
|
|
|
214
|
my $prog = $self->{name} || substr($0, rindex($0, '/')+1); |
252
|
29
|
|
|
|
|
46
|
my $fname = $prog; |
253
|
29
|
|
|
|
|
148
|
$fname =~ s/[.-]/_/g; |
254
|
|
|
|
|
|
|
|
255
|
29
|
|
|
|
|
39
|
my @global_opts; |
256
|
|
|
|
|
|
|
my @commands; |
257
|
29
|
|
|
|
|
105
|
my $case = { |
258
|
|
|
|
|
|
|
word => '"$cmd"', |
259
|
|
|
|
|
|
|
cases => [], |
260
|
|
|
|
|
|
|
}; |
261
|
|
|
|
|
|
|
|
262
|
29
|
|
|
|
|
103
|
@global_opts = $self->_options2optarg($self->{struct}); |
263
|
|
|
|
|
|
|
|
264
|
29
|
|
|
|
|
44
|
for my $cmd (sort keys %{ $self->{_struct} }) { |
|
29
|
|
|
|
|
118
|
|
265
|
30
|
|
|
|
|
59
|
my $s = $self->{_struct}{$cmd}; |
266
|
|
|
|
|
|
|
|
267
|
30
|
|
|
|
|
95
|
my @opts = $self->_options2optarg($s->{options}); |
268
|
30
|
|
|
|
|
72
|
my @commands2; |
269
|
|
|
|
|
|
|
|
270
|
30
|
100
|
|
|
|
76
|
if (ref $s->{command_struct} eq 'HASH') { |
271
|
5
|
|
|
|
|
8
|
for my $cmd (sort keys %{ $s->{command_struct} }) { |
|
5
|
|
|
|
|
16
|
|
272
|
6
|
|
|
|
|
9
|
my $s = $s->{command_struct}{$cmd}; |
273
|
6
|
|
|
|
|
20
|
my @opts = $self->_options2optarg($s->{options}); |
274
|
|
|
|
|
|
|
|
275
|
6
|
|
|
|
|
33
|
push @commands2, { |
276
|
|
|
|
|
|
|
cmd => $cmd, |
277
|
|
|
|
|
|
|
opts => \@opts, |
278
|
|
|
|
|
|
|
}; |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
30
|
|
100
|
|
|
236
|
push @commands, { |
283
|
|
|
|
|
|
|
cmd => $cmd, |
284
|
|
|
|
|
|
|
opts => \@opts, |
285
|
|
|
|
|
|
|
subcmd => \@commands2, |
286
|
|
|
|
|
|
|
args => ($s->{args} || ''), |
287
|
|
|
|
|
|
|
}; |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
29
|
|
|
|
|
74
|
$comp .= "_$fname() {\n"; |
291
|
29
|
|
|
|
|
67
|
$comp .= <<'EOC'; |
292
|
|
|
|
|
|
|
COMPREPLY=() |
293
|
|
|
|
|
|
|
local cur=${COMP_WORDS[COMP_CWORD]} |
294
|
|
|
|
|
|
|
local prev=${COMP_WORDS[COMP_CWORD-1]} |
295
|
|
|
|
|
|
|
local cmd=() |
296
|
|
|
|
|
|
|
for ((i=1; i
|
297
|
|
|
|
|
|
|
# skip global opts and type to find cmd |
298
|
|
|
|
|
|
|
if [[ "${COMP_WORDS[$i]}" != -* && "${COMP_WORDS[$i]}" != [A-Z]* ]]; then |
299
|
|
|
|
|
|
|
cmd[${#cmd[@]}]=${COMP_WORDS[$i]} |
300
|
|
|
|
|
|
|
fi |
301
|
|
|
|
|
|
|
done |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
EOC |
304
|
|
|
|
|
|
|
|
305
|
50
|
|
|
|
|
259
|
$comp .= sprintf qq{ local global_opts="%s"\n}, |
306
|
29
|
|
|
|
|
52
|
join(" ", map { @{$_->{opt}} } @global_opts); |
|
50
|
|
|
|
|
47
|
|
307
|
30
|
|
|
|
|
79
|
$comp .= sprintf qq{ local cmds="%s"\n}, |
308
|
29
|
|
|
|
|
81
|
join(" ", map { $_->{cmd} } @commands); |
309
|
29
|
|
|
|
|
41
|
$comp .= "\n"; |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
### sub commands |
312
|
29
|
|
|
|
|
50
|
for my $command (@commands) { |
313
|
|
|
|
|
|
|
|
314
|
30
|
|
|
|
|
69
|
my $case_prev = { |
315
|
|
|
|
|
|
|
word => '"$prev"', |
316
|
|
|
|
|
|
|
cases => [ |
317
|
30
|
|
|
|
|
44
|
_opts2casecmd(@{ $command->{opts} }), |
318
|
|
|
|
|
|
|
{ |
319
|
|
|
|
|
|
|
pat => '*', |
320
|
|
|
|
|
|
|
cmd => ['COMPREPLY=($(compgen -W "'._gen_wordlist($command).'" -- "$cur"))'], |
321
|
|
|
|
|
|
|
}, |
322
|
|
|
|
|
|
|
], |
323
|
|
|
|
|
|
|
}; |
324
|
|
|
|
|
|
|
|
325
|
30
|
100
|
|
|
|
51
|
if (scalar(@{ $command->{subcmd} }) > 0) { |
|
30
|
|
|
|
|
66
|
|
326
|
5
|
|
|
|
|
7
|
my @cases; |
327
|
|
|
|
|
|
|
|
328
|
5
|
|
|
|
|
6
|
for my $subcommand (@{ $command->{subcmd} }) { |
|
5
|
|
|
|
|
13
|
|
329
|
6
|
100
|
|
|
|
9
|
next if (scalar(@{ $subcommand->{opts} }) <= 0); |
|
6
|
|
|
|
|
30
|
|
330
|
5
|
|
|
|
|
20
|
push @cases, { |
331
|
|
|
|
|
|
|
pat => $subcommand->{cmd}, |
332
|
|
|
|
|
|
|
cmd => [{ |
333
|
|
|
|
|
|
|
word => '"$prev"', |
334
|
|
|
|
|
|
|
cases => [ |
335
|
5
|
|
|
|
|
12
|
_opts2casecmd(@{ $subcommand->{opts} }), |
336
|
|
|
|
|
|
|
{ |
337
|
|
|
|
|
|
|
pat => '*', |
338
|
|
|
|
|
|
|
cmd => ['COMPREPLY=($(compgen -W "'._gen_wordlist($subcommand).'" -- "$cur"))'], |
339
|
|
|
|
|
|
|
}, |
340
|
|
|
|
|
|
|
], |
341
|
|
|
|
|
|
|
}], |
342
|
|
|
|
|
|
|
}; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
5
|
|
|
|
|
20
|
push @cases, { |
346
|
|
|
|
|
|
|
pat => '*', |
347
|
|
|
|
|
|
|
cmd => [ $case_prev ], |
348
|
|
|
|
|
|
|
}; |
349
|
|
|
|
|
|
|
|
350
|
5
|
|
|
|
|
8
|
push @{ $case->{cases} }, { |
|
5
|
|
|
|
|
39
|
|
351
|
|
|
|
|
|
|
pat => $command->{cmd}, |
352
|
|
|
|
|
|
|
cmd => [{ |
353
|
|
|
|
|
|
|
word => '"${cmd[1]}"', |
354
|
|
|
|
|
|
|
cases => [@cases], |
355
|
|
|
|
|
|
|
}], |
356
|
|
|
|
|
|
|
}; |
357
|
|
|
|
|
|
|
} else { |
358
|
25
|
|
|
|
|
31
|
push @{ $case->{cases} }, { |
|
25
|
|
|
|
|
133
|
|
359
|
|
|
|
|
|
|
pat => $command->{cmd}, |
360
|
|
|
|
|
|
|
cmd => [ $case_prev ], |
361
|
|
|
|
|
|
|
}; |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
### global opts |
366
|
29
|
|
|
|
|
44
|
push @{ $case->{cases} }, { |
|
29
|
|
|
|
|
92
|
|
367
|
|
|
|
|
|
|
pat => '*', |
368
|
|
|
|
|
|
|
cmd => [{ |
369
|
|
|
|
|
|
|
word => '"$prev"', |
370
|
|
|
|
|
|
|
cases => [ |
371
|
|
|
|
|
|
|
_opts2casecmd(@global_opts), |
372
|
|
|
|
|
|
|
{ |
373
|
|
|
|
|
|
|
pat => '*', |
374
|
|
|
|
|
|
|
cmd => ['COMPREPLY=($(compgen -W "$global_opts $cmds" -- "$cur"))'], |
375
|
|
|
|
|
|
|
}, |
376
|
|
|
|
|
|
|
], |
377
|
|
|
|
|
|
|
}], |
378
|
|
|
|
|
|
|
}; |
379
|
|
|
|
|
|
|
|
380
|
29
|
|
|
|
|
76
|
my @c = _generate_case_command($case); |
381
|
29
|
|
|
|
|
77
|
$comp .= join("\n", map {" ".$_} @c)."\n"; |
|
544
|
|
|
|
|
1024
|
|
382
|
|
|
|
|
|
|
|
383
|
29
|
|
|
|
|
116
|
$comp .= <<"EOC"; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
complete -F _$fname $prog |
387
|
|
|
|
|
|
|
EOC |
388
|
29
|
|
|
|
|
844
|
return $comp; |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# take following hashref and generate case command string |
392
|
|
|
|
|
|
|
# +{ |
393
|
|
|
|
|
|
|
# word => WORD, # case WORD in |
394
|
|
|
|
|
|
|
# cases => [ |
395
|
|
|
|
|
|
|
# { |
396
|
|
|
|
|
|
|
# pat => PATTERN, # PATTERN) |
397
|
|
|
|
|
|
|
# cmd => ['cmd1', 'cmd2', ...], # COMMANDS;; |
398
|
|
|
|
|
|
|
# }, |
399
|
|
|
|
|
|
|
# { |
400
|
|
|
|
|
|
|
# pat => PATTERN, # PATTERN) |
401
|
|
|
|
|
|
|
# cmd => [ # nested case command |
402
|
|
|
|
|
|
|
# { |
403
|
|
|
|
|
|
|
# word => WORD, |
404
|
|
|
|
|
|
|
# cases => [ ... ], |
405
|
|
|
|
|
|
|
# }, |
406
|
|
|
|
|
|
|
# ], |
407
|
|
|
|
|
|
|
# }, |
408
|
|
|
|
|
|
|
# ], |
409
|
|
|
|
|
|
|
# } |
410
|
|
|
|
|
|
|
sub _generate_case_command { |
411
|
98
|
|
|
98
|
|
131
|
my $case = shift; |
412
|
98
|
|
|
|
|
99
|
my @line; |
413
|
|
|
|
|
|
|
|
414
|
98
|
|
|
|
|
218
|
push @line, "case $case->{word} in"; |
415
|
98
|
|
|
|
|
100
|
for my $c (@{ $case->{cases} }) { |
|
98
|
|
|
|
|
185
|
|
416
|
139
|
|
|
|
|
267
|
push @line, " $c->{pat})"; |
417
|
139
|
|
|
|
|
136
|
for my $cmd (@{ $c->{cmd} }, ';;') { |
|
139
|
|
|
|
|
227
|
|
418
|
278
|
100
|
|
|
|
483
|
if (ref $cmd eq 'HASH') { |
419
|
69
|
|
|
|
|
151
|
push @line, map {" ".$_} _generate_case_command->($cmd); |
|
418
|
|
|
|
|
993
|
|
420
|
|
|
|
|
|
|
} else { |
421
|
209
|
|
|
|
|
621
|
push @line, " ".$cmd; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
} |
425
|
98
|
|
|
|
|
159
|
push @line, "esac"; |
426
|
|
|
|
|
|
|
|
427
|
98
|
|
|
|
|
449
|
return @line; |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
sub _options2optarg { |
431
|
65
|
|
|
65
|
|
104
|
my($self, $opts) = @_; |
432
|
65
|
|
|
|
|
104
|
my @optarg; |
433
|
|
|
|
|
|
|
|
434
|
65
|
|
|
|
|
74
|
for my $o (@{ $opts }) { |
|
65
|
|
|
|
|
132
|
|
435
|
62
|
|
|
|
|
111
|
my ($name_spec, $desc, $arg_spec, $dist, $opts) = @$o; |
436
|
62
|
100
|
|
|
|
161
|
my @onames = map { (length($_) > 1 ? '--' : '-').$_ } $self->_option_names($name_spec); |
|
124
|
|
|
|
|
437
|
|
437
|
62
|
|
100
|
|
|
161
|
my $arg = $self->_opt_spec2name($arg_spec) || $arg_spec || ''; |
438
|
62
|
50
|
|
|
|
133
|
$arg = '' if $arg eq 'Incr'; |
439
|
62
|
|
|
|
|
305
|
push @optarg, { |
440
|
|
|
|
|
|
|
opt => \@onames, |
441
|
|
|
|
|
|
|
arg => $arg, |
442
|
|
|
|
|
|
|
}; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
65
|
|
|
|
|
179
|
return @optarg; |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
sub _opts2casecmd { |
449
|
64
|
|
|
64
|
|
75
|
my @cases; |
450
|
64
|
|
|
|
|
134
|
for my $o (grep { $_->{arg} } @_) { |
|
62
|
|
|
|
|
163
|
|
451
|
6
|
|
|
|
|
49
|
push @cases, { |
452
|
6
|
|
|
|
|
14
|
pat => join("|", @{ $o->{opt} }), |
453
|
|
|
|
|
|
|
cmd => ['COMPREPLY=($(compgen -W "'.$o->{arg}.'" -- "$cur"))'], |
454
|
|
|
|
|
|
|
}; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
64
|
|
|
|
|
311
|
return @cases; |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
sub _gen_wordlist { |
461
|
35
|
|
|
35
|
|
45
|
my $command = shift; |
462
|
|
|
|
|
|
|
|
463
|
12
|
|
|
|
|
55
|
return join(" ", |
464
|
|
|
|
|
|
|
'-h', '--help', |
465
|
12
|
|
|
|
|
14
|
(map { @{$_->{opt}} } @{ $command->{opts} }), |
|
35
|
|
|
|
|
110
|
|
|
6
|
|
|
|
|
40
|
|
466
|
|
|
|
|
|
|
($command->{args}||''), |
467
|
35
|
|
100
|
|
|
50
|
(map { $_->{cmd} } @{ $command->{subcmd} }), |
|
35
|
|
|
|
|
243
|
|
468
|
|
|
|
|
|
|
); |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
sub _opt_spec2name { |
472
|
262
|
|
|
262
|
|
786
|
my ($self, $spec) = @_; |
473
|
262
|
|
|
|
|
393
|
my $name = ''; |
474
|
262
|
100
|
|
|
|
1192
|
return $name unless defined $spec; |
475
|
156
|
|
|
|
|
1015
|
my ($type, $dest) = $spec =~ /^[=:]?([!+isof])([@%])?/; |
476
|
156
|
100
|
|
|
|
519
|
if ($type) { |
477
|
110
|
50
|
|
|
|
666
|
$name = |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
478
|
|
|
|
|
|
|
$type eq '!' ? 'Bool' : |
479
|
|
|
|
|
|
|
$type eq '+' ? 'Incr' : |
480
|
|
|
|
|
|
|
$type eq 's' ? 'Str' : |
481
|
|
|
|
|
|
|
$type eq 'i' ? 'Int' : |
482
|
|
|
|
|
|
|
$type eq 'f' ? 'Num' : |
483
|
|
|
|
|
|
|
$type eq 'o' ? 'ExNum' : ''; |
484
|
|
|
|
|
|
|
} |
485
|
156
|
100
|
|
|
|
424
|
if ($dest) { |
486
|
11
|
50
|
|
|
|
67
|
$name = $dest eq '@' ? "Array[$name]" : $dest eq '%' ? "Hash[$name]" : $name; |
|
|
100
|
|
|
|
|
|
487
|
|
|
|
|
|
|
} |
488
|
156
|
|
|
|
|
838
|
return $name; |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
sub _format_info { |
492
|
37
|
|
|
37
|
|
52289
|
my ($self, @help) = @_; |
493
|
|
|
|
|
|
|
|
494
|
37
|
|
|
|
|
73
|
my $type_max = 0; |
495
|
37
|
|
|
|
|
67
|
my $required_max = 0; |
496
|
37
|
|
|
|
|
55
|
my $default_max = 0; |
497
|
37
|
|
|
|
|
88
|
for my $row (@help) { |
498
|
62
|
|
|
|
|
79
|
my ($type, $required, $default) = @{$row->[1]}; |
|
62
|
|
|
|
|
154
|
|
499
|
62
|
|
|
|
|
181
|
$type_max = max $type_max, length($type); |
500
|
62
|
|
|
|
|
127
|
$required_max = max $required_max, length($required); |
501
|
62
|
|
|
|
|
200
|
$default_max = max $default_max, length($default); |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
|
504
|
37
|
|
|
|
|
79
|
for my $row (@help) { |
505
|
62
|
|
|
|
|
81
|
my ($type, $required, $default) = @{$row->[1]}; |
|
62
|
|
|
|
|
141
|
|
506
|
62
|
|
|
|
|
113
|
my $parts = []; |
507
|
62
|
|
|
|
|
224
|
for my $stuff ([$type_max, $type], [$required_max, $required], [$default_max, $default]) { |
508
|
186
|
100
|
|
|
|
562
|
push @$parts, sprintf '%-*s', @$stuff if $stuff->[0] > 0; |
509
|
|
|
|
|
|
|
} |
510
|
62
|
|
|
|
|
309
|
$row->[1] = join ' ', @$parts; |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
37
|
|
|
|
|
381
|
return @help; |
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
sub _parse_command_struct { |
517
|
95
|
|
|
95
|
|
37058
|
my ($self, $command_struct) = @_; |
518
|
95
|
|
50
|
|
|
283
|
$command_struct ||= {}; |
519
|
|
|
|
|
|
|
|
520
|
95
|
|
|
|
|
246
|
my $command_map = { map { $_ => 1 } keys %$command_struct }; |
|
94
|
|
|
|
|
348
|
|
521
|
95
|
|
|
|
|
6436
|
my $command = shift @ARGV; |
522
|
95
|
100
|
|
|
|
269
|
unless (defined $command) { |
523
|
21
|
|
|
|
|
55
|
$self->{ret} = $self->_check_requires; |
524
|
21
|
|
|
|
|
57
|
return $self; |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
74
|
50
|
|
|
|
226
|
unless ($command_map->{help}) { |
528
|
74
|
|
|
|
|
130
|
$command_map->{help} = 1; |
529
|
74
|
|
|
|
|
1450
|
$command_struct->{help} = { |
530
|
|
|
|
|
|
|
args => '[COMMAND]', |
531
|
|
|
|
|
|
|
desc => 'show help message', |
532
|
|
|
|
|
|
|
}; |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
74
|
100
|
|
|
|
242
|
unless (exists $command_map->{$command}) { |
536
|
4
|
|
|
|
|
57
|
$self->{error} = "Unknown command: $command"; |
537
|
4
|
|
|
|
|
9
|
$self->{ret} = 0; |
538
|
4
|
|
|
|
|
14
|
return $self; |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
|
541
|
70
|
|
66
|
|
|
355
|
$self->{command} ||= $command; |
542
|
|
|
|
|
|
|
|
543
|
70
|
100
|
|
|
|
186
|
if ($command eq 'help') { |
544
|
14
|
|
|
|
|
23
|
$self->{ret} = 0; |
545
|
14
|
|
|
|
|
29
|
delete $self->{error}; |
546
|
14
|
100
|
66
|
|
|
76
|
if (defined $ARGV[0] && exists $command_struct->{$ARGV[0]}) { |
547
|
8
|
|
|
|
|
19
|
my $nested_struct = $command_struct->{$ARGV[0]}{command_struct}; |
548
|
8
|
100
|
|
|
|
38
|
$self->_init_nested_struct($nested_struct) if $nested_struct; |
549
|
|
|
|
|
|
|
} |
550
|
14
|
|
|
|
|
37
|
return $self; |
551
|
|
|
|
|
|
|
} |
552
|
|
|
|
|
|
|
|
553
|
56
|
|
100
|
|
|
81
|
push @{$self->{commands} ||= []}, $command; |
|
56
|
|
|
|
|
309
|
|
554
|
56
|
|
|
|
|
223
|
$self->_init_struct($command_struct->{$command}{options}); |
555
|
56
|
|
|
|
|
208
|
$self->_extends_usage($command_struct->{$command}); |
556
|
56
|
|
50
|
|
|
150
|
my $opthash = $self->_parse_struct || return $self; |
557
|
|
|
|
|
|
|
|
558
|
56
|
100
|
|
|
|
193
|
if (my $nested_struct = $command_struct->{$command}{command_struct}) { |
559
|
10
|
|
|
|
|
43
|
$self->_init_nested_struct($nested_struct); |
560
|
|
|
|
|
|
|
|
561
|
10
|
|
|
|
|
32
|
my @opts = $self->_parse_argv($nested_struct); |
562
|
10
|
|
|
|
|
48
|
$self->{ret} = $self->_parse_option(\@opts, $opthash); |
563
|
10
|
|
|
|
|
22
|
unshift @ARGV, @opts; |
564
|
10
|
|
|
|
|
33
|
$self->_check_requires; |
565
|
10
|
100
|
|
|
|
32
|
if ($self->_want_help) { |
566
|
1
|
|
|
|
|
2
|
delete $self->{error}; |
567
|
1
|
|
|
|
|
3
|
$self->{ret} = 0; |
568
|
|
|
|
|
|
|
} |
569
|
10
|
100
|
|
|
|
37
|
return $self unless $self->{ret}; |
570
|
9
|
|
|
|
|
40
|
$self->_parse_command_struct($nested_struct); |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
else { |
573
|
46
|
|
|
|
|
149
|
$self->{ret} = $self->_parse_option(\@ARGV, $opthash); |
574
|
46
|
|
|
|
|
159
|
$self->_check_requires; |
575
|
46
|
|
|
|
|
100
|
$self->{has_sub_command} = 0; |
576
|
46
|
100
|
|
|
|
108
|
if ($self->_want_help) { |
577
|
2
|
|
|
|
|
4
|
delete $self->{error}; |
578
|
2
|
|
|
|
|
6
|
$self->{ret} = 0; |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
} |
581
|
|
|
|
|
|
|
|
582
|
55
|
|
|
|
|
159
|
return $self; |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
sub _want_help { |
586
|
226
|
100
|
100
|
226
|
|
3074
|
exists $_[0]->{opt}{help} && $_[0]->{opt}{help} ? 1 : 0; |
587
|
|
|
|
|
|
|
} |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
sub _init_nested_struct { |
590
|
15
|
|
|
15
|
|
79
|
my ($self, $nested_struct) = @_; |
591
|
15
|
|
|
|
|
35
|
$self->{summary} = {}; # reset |
592
|
15
|
|
|
|
|
55
|
$self->_init_summary($nested_struct); |
593
|
15
|
|
|
|
|
45
|
$self->{has_sub_command} = 1; |
594
|
|
|
|
|
|
|
} |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
sub _parse_option { |
597
|
255
|
|
|
255
|
|
2668
|
my ($self, $argv, $opthash) = @_; |
598
|
|
|
|
|
|
|
local $SIG{__WARN__} = sub { |
599
|
16
|
|
|
16
|
|
4851
|
$self->{error} = join '', @_; |
600
|
16
|
|
|
|
|
120
|
chomp $self->{error}; |
601
|
255
|
|
|
|
|
2098
|
}; |
602
|
255
|
100
|
|
|
|
1439
|
my $ret = GetOptionsFromArray($argv, %$opthash) ? 1 : 0; |
603
|
|
|
|
|
|
|
|
604
|
255
|
|
|
|
|
53694
|
$self->{parsed_opthash} = $opthash; |
605
|
|
|
|
|
|
|
|
606
|
255
|
|
|
|
|
1967
|
return $ret; |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
sub _parse_argv { |
610
|
94
|
|
|
94
|
|
19648
|
my ($self, $struct) = @_; |
611
|
94
|
|
66
|
|
|
421
|
$struct ||= $self->{_struct}; |
612
|
|
|
|
|
|
|
|
613
|
94
|
|
|
|
|
137
|
my @opts; |
614
|
94
|
|
|
|
|
295
|
while (@ARGV) { |
615
|
90
|
|
|
|
|
155
|
my $argv = shift @ARGV; |
616
|
90
|
|
|
|
|
152
|
push @opts, $argv; |
617
|
90
|
100
|
|
|
|
318
|
last if exists $struct->{$argv}; |
618
|
|
|
|
|
|
|
} |
619
|
94
|
|
|
|
|
409
|
return @opts; |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
sub _parse_struct { |
623
|
272
|
|
|
272
|
|
119045
|
my ($self) = @_; |
624
|
272
|
|
|
|
|
969
|
my $struct = $self->{struct}; |
625
|
|
|
|
|
|
|
|
626
|
272
|
|
|
|
|
469
|
my $opthash = {}; |
627
|
272
|
|
|
|
|
463
|
my $default_opthash = {}; |
628
|
272
|
|
|
|
|
484
|
my $default_args = []; |
629
|
272
|
|
|
|
|
586
|
for my $s (@$struct) { |
630
|
414
|
|
|
|
|
843
|
my($m, $descr, $spec, $ref, $opts) = @$s; |
631
|
414
|
|
|
|
|
1359
|
my @onames = $self->_option_names($m); |
632
|
414
|
|
|
|
|
1331
|
my($longname) = grep length($_) > 1, @onames; |
633
|
414
|
|
|
|
|
1142
|
my ($type, $cb) = $self->_compile_spec($spec); |
634
|
414
|
|
100
|
|
|
5965
|
my $o = join('|', @onames).($type||''); |
635
|
414
|
50
|
|
|
|
917
|
my $dest = $longname ? $longname : $onames[0]; |
636
|
414
|
|
100
|
|
|
1468
|
$opts ||= {}; |
637
|
414
|
|
|
|
|
782
|
my $destination; |
638
|
414
|
100
|
|
|
|
1260
|
if (ref $cb eq 'CODE') { |
639
|
27
|
100
|
|
|
|
112
|
my $t = |
|
|
100
|
|
|
|
|
|
640
|
|
|
|
|
|
|
substr($type, -1, 1) eq '@' ? 'Array' : |
641
|
|
|
|
|
|
|
substr($type, -1, 1) eq '%' ? 'Hash' : ''; |
642
|
27
|
50
|
|
|
|
103
|
if (ref $ref eq 'CODE') { |
|
|
100
|
|
|
|
|
|
643
|
0
|
|
|
0
|
|
0
|
$destination = sub { $ref->($_[0], $cb->($_[1])) }; |
|
0
|
|
|
|
|
0
|
|
644
|
|
|
|
|
|
|
} |
645
|
|
|
|
|
|
|
elsif (ref $ref) { |
646
|
20
|
100
|
66
|
|
|
121
|
if (ref $ref eq 'SCALAR' || ref $ref eq 'REF') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
647
|
6
|
100
|
|
|
|
23
|
$$ref = $t eq 'Array' ? [] : $t eq 'Hash' ? {} : undef; |
|
|
100
|
|
|
|
|
|
648
|
|
|
|
|
|
|
} |
649
|
|
|
|
|
|
|
elsif (ref $ref eq 'ARRAY') { |
650
|
6
|
|
|
|
|
14
|
@$ref = (); |
651
|
|
|
|
|
|
|
} |
652
|
|
|
|
|
|
|
elsif (ref $ref eq 'HASH') { |
653
|
8
|
|
|
|
|
16
|
%$ref = (); |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
$destination = sub { |
656
|
20
|
100
|
|
20
|
|
4986
|
if ($t eq 'Array') { |
|
|
100
|
|
|
|
|
|
657
|
7
|
100
|
66
|
|
|
61
|
if (ref $ref eq 'SCALAR' || ref $ref eq 'REF') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
658
|
2
|
|
|
|
|
3
|
push @{$$ref}, scalar $cb->($_[1]); |
|
2
|
|
|
|
|
9
|
|
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
elsif (ref $ref eq 'ARRAY') { |
661
|
2
|
|
|
|
|
8
|
push @$ref, scalar $cb->($_[1]); |
662
|
|
|
|
|
|
|
} |
663
|
|
|
|
|
|
|
elsif (ref $ref eq 'HASH') { |
664
|
3
|
|
|
|
|
10
|
my @kv = split '=', $_[1], 2; |
665
|
3
|
100
|
|
|
|
20
|
die qq(Option $_[0], key "$_[1]", requires a value\n) |
666
|
|
|
|
|
|
|
unless @kv == 2; |
667
|
2
|
|
|
|
|
7
|
$ref->{$kv[0]} = scalar $cb->($kv[1]); |
668
|
|
|
|
|
|
|
} |
669
|
|
|
|
|
|
|
} |
670
|
|
|
|
|
|
|
elsif ($t eq 'Hash') { |
671
|
6
|
100
|
66
|
|
|
44
|
if (ref $ref eq 'SCALAR' || ref $ref eq 'REF') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
672
|
2
|
|
|
|
|
7
|
$$ref->{$_[1]} = scalar $cb->($_[2]); |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
elsif (ref $ref eq 'ARRAY') { |
675
|
|
|
|
|
|
|
# XXX but Getopt::Long is $ret = join '=', $_[1], $_[2]; |
676
|
2
|
|
|
|
|
8
|
push @$ref, $_[1], scalar $cb->($_[2]); |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
elsif (ref $ref eq 'HASH') { |
679
|
2
|
|
|
|
|
7
|
$ref->{$_[1]} = scalar $cb->($_[2]); |
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
else { |
683
|
7
|
100
|
66
|
|
|
55
|
if (ref $ref eq 'SCALAR' || ref $ref eq 'REF') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
684
|
2
|
|
|
|
|
19
|
$$ref = $cb->($_[1]); |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
elsif (ref $ref eq 'ARRAY') { |
687
|
2
|
|
|
|
|
8
|
@$ref = (scalar $cb->($_[1])); |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
elsif (ref $ref eq 'HASH') { |
690
|
3
|
|
|
|
|
10
|
my @kv = split '=', $_[1], 2; |
691
|
3
|
100
|
|
|
|
53
|
die qq(Option $_[0], key "$_[1]", requires a value\n) |
692
|
|
|
|
|
|
|
unless @kv == 2; |
693
|
2
|
|
|
|
|
8
|
%$ref = ($kv[0] => scalar $cb->($kv[1])); |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
} |
696
|
20
|
|
|
|
|
131
|
}; |
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
else { |
699
|
|
|
|
|
|
|
$destination = sub { |
700
|
5
|
100
|
|
5
|
|
1422
|
if ($t eq 'Array') { |
|
|
100
|
|
|
|
|
|
701
|
2
|
|
100
|
|
|
12
|
$self->{opt}{$dest} ||= []; |
702
|
2
|
|
|
|
|
3
|
push @{$self->{opt}{$dest}}, scalar $cb->($_[1]); |
|
2
|
|
|
|
|
9
|
|
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
elsif ($t eq 'Hash') { |
705
|
2
|
|
100
|
|
|
9
|
$self->{opt}{$dest} ||= {}; |
706
|
2
|
|
|
|
|
6
|
$self->{opt}{$dest}{$_[1]} = $cb->($_[2]); |
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
else { |
709
|
1
|
|
|
|
|
12
|
$self->{opt}{$dest} = $cb->($_[1]); |
710
|
|
|
|
|
|
|
} |
711
|
7
|
|
|
|
|
46
|
}; |
712
|
|
|
|
|
|
|
} |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
else { |
715
|
387
|
100
|
|
|
|
1612
|
$destination = ref $ref ? $ref : \$self->{opt}{$dest}; |
716
|
|
|
|
|
|
|
} |
717
|
414
|
100
|
|
|
|
1231
|
if (exists $opts->{default}) { |
718
|
26
|
|
|
|
|
74
|
my $value = $opts->{default}; |
719
|
26
|
100
|
|
|
|
367
|
if (ref $value eq 'ARRAY') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
720
|
4
|
|
|
|
|
18
|
push @$default_args, map { |
721
|
4
|
|
|
|
|
1666
|
("--$dest", $_) |
722
|
3
|
|
|
|
|
8
|
} grep { defined $_ } @$value; |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
elsif (ref $value eq 'HASH') { |
725
|
6
|
|
|
|
|
20
|
push @$default_args, map { |
726
|
6
|
|
|
|
|
21
|
(my $key = $_) =~ s/=/\\=/g; |
727
|
6
|
|
|
|
|
32
|
("--$dest" => "$key=$value->{$_}") |
728
|
|
|
|
|
|
|
} grep { |
729
|
6
|
|
|
|
|
29
|
defined $value->{$_} |
730
|
|
|
|
|
|
|
} keys %$value; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
elsif (not ref $value) { |
733
|
12
|
100
|
66
|
|
|
126
|
if (!$spec || ($TYPE_MAP->{$spec} || $spec) eq '!') { |
|
|
|
66
|
|
|
|
|
734
|
3
|
50
|
|
|
|
33
|
push @$default_args, "--$dest" if $value; |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
else { |
737
|
9
|
50
|
|
|
|
48
|
push @$default_args, "--$dest", $value if defined $value; |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
} |
740
|
|
|
|
|
|
|
else { |
741
|
5
|
|
|
|
|
23
|
$self->{error} = "Invalid default option for $dest"; |
742
|
5
|
|
|
|
|
16
|
$self->{ret} = 0; |
743
|
|
|
|
|
|
|
} |
744
|
26
|
|
|
|
|
71
|
$default_opthash->{$o} = $destination; |
745
|
|
|
|
|
|
|
} |
746
|
414
|
|
|
|
|
1073
|
$opthash->{$o} = $destination; |
747
|
414
|
100
|
|
|
|
2497
|
$self->{requires}{$dest} = $o if $opts->{required}; |
748
|
|
|
|
|
|
|
} |
749
|
|
|
|
|
|
|
|
750
|
272
|
100
|
|
|
|
973
|
return if $self->{error}; |
751
|
267
|
100
|
|
|
|
731
|
if (@$default_args) { |
752
|
19
|
|
|
|
|
80
|
$self->{ret} = $self->_parse_option($default_args, $default_opthash); |
753
|
19
|
|
|
|
|
48
|
unshift @ARGV, @$default_args; |
754
|
19
|
100
|
|
|
|
67
|
return unless $self->{ret}; |
755
|
|
|
|
|
|
|
} |
756
|
|
|
|
|
|
|
|
757
|
266
|
|
|
|
|
1004
|
return $opthash; |
758
|
|
|
|
|
|
|
} |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
sub _init_struct { |
761
|
270
|
|
|
270
|
|
17621
|
my ($self, $struct) = @_; |
762
|
270
|
100
|
|
|
|
1139
|
$self->{struct} = ref $struct eq 'ARRAY' ? $struct : ref $struct eq 'HASH' ? $self->_normalize_struct($struct) : []; |
|
|
100
|
|
|
|
|
|
763
|
|
|
|
|
|
|
|
764
|
270
|
100
|
|
|
|
1061
|
if (ref $self->{modes} eq 'ARRAY') { |
765
|
4
|
|
|
|
|
8
|
my @modeopt; |
766
|
4
|
|
|
|
|
10
|
for my $m (@{$self->{modes}}) { |
|
4
|
|
|
|
|
12
|
|
767
|
8
|
|
|
|
|
32
|
my($mc) = $m =~ /^(\w)/; |
768
|
8
|
|
|
|
|
38
|
push @modeopt, [[$mc, $m], qq($m mode)]; |
769
|
|
|
|
|
|
|
} |
770
|
4
|
|
|
|
|
14
|
unshift @$struct, @modeopt; |
771
|
|
|
|
|
|
|
} |
772
|
|
|
|
|
|
|
|
773
|
270
|
100
|
100
|
|
|
1374
|
unshift @{$self->{struct}}, [[qw(h help)], qq(this help message)] |
|
220
|
|
|
|
|
1025
|
|
774
|
|
|
|
|
|
|
if $self->{usage} && !$self->_has_option('help'); |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
sub _normalize_struct { |
778
|
41
|
|
|
41
|
|
69
|
my ($self, $struct) = @_; |
779
|
|
|
|
|
|
|
|
780
|
41
|
|
|
|
|
76
|
my $result = []; |
781
|
41
|
|
|
|
|
135
|
for my $option (keys %$struct) { |
782
|
41
|
|
50
|
|
|
117
|
my $data = $struct->{$option} || {}; |
783
|
41
|
50
|
|
|
|
2986
|
$data = ref $data eq 'HASH' ? $data : {}; |
784
|
41
|
|
|
|
|
244
|
my $row = []; |
785
|
3
|
|
|
|
|
9
|
push @$row, [ |
786
|
|
|
|
|
|
|
$option, |
787
|
41
|
100
|
|
|
|
324
|
ref $data->{alias} eq 'ARRAY' ? @{$data->{alias}} : |
|
|
100
|
|
|
|
|
|
788
|
|
|
|
|
|
|
defined $data->{alias} ? $data->{alias} : (), |
789
|
|
|
|
|
|
|
]; |
790
|
41
|
|
|
|
|
89
|
push @$row, $data->{desc}; |
791
|
41
|
|
|
|
|
80
|
push @$row, $data->{type}; |
792
|
41
|
|
|
|
|
123
|
push @$row, $data->{dest}; |
793
|
41
|
|
|
|
|
244
|
push @$row, $data->{opts}; |
794
|
41
|
|
|
|
|
134
|
push @$result, $row; |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
|
797
|
41
|
|
|
|
|
290
|
return $result; |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
sub _compile_spec { |
801
|
427
|
|
|
427
|
|
8188
|
my ($self, $spec) = @_; |
802
|
427
|
100
|
100
|
|
|
2143
|
return if !defined $spec or $spec eq ''; |
803
|
138
|
100
|
|
|
|
521
|
return $spec if $self->_opt_spec2name($spec); |
804
|
43
|
|
|
|
|
62
|
my ($type, $cb); |
805
|
43
|
100
|
|
|
|
234
|
if ($spec =~ /^(Array|Hash)\[(\w+)\]$/) { |
|
|
50
|
|
|
|
|
|
806
|
20
|
|
33
|
|
|
99
|
$type = $TYPE_MAP->{$2} || Carp::croak("Can't find type constraint '$2'"); |
807
|
20
|
100
|
|
|
|
66
|
$type .= $1 eq 'Array' ? '@' : '%'; |
808
|
20
|
|
|
|
|
51
|
$cb = $TYPE_GEN->{$2}; |
809
|
|
|
|
|
|
|
} |
810
|
|
|
|
|
|
|
elsif ($type = $TYPE_MAP->{$spec}) { |
811
|
23
|
|
|
|
|
41
|
$cb = $TYPE_GEN->{$spec}; |
812
|
|
|
|
|
|
|
} |
813
|
|
|
|
|
|
|
else { |
814
|
0
|
|
|
|
|
0
|
Carp::croak("Can't find type constraint '$spec'"); |
815
|
|
|
|
|
|
|
} |
816
|
43
|
|
|
|
|
118
|
return $type, $cb; |
817
|
|
|
|
|
|
|
} |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
sub add_type { |
820
|
2
|
|
|
2
|
1
|
1491
|
my ($class, $name, $src_type, $cb) = @_; |
821
|
2
|
50
|
33
|
|
|
33
|
unless (defined $name && $src_type && ref $cb eq 'CODE') { |
|
|
|
33
|
|
|
|
|
822
|
0
|
|
|
|
|
0
|
Carp::croak("Usage: $class->add_type(\$name, \$src_type, \$cb)"); |
823
|
|
|
|
|
|
|
} |
824
|
2
|
50
|
|
|
|
11
|
unless ($TYPE_MAP->{$src_type}) { |
825
|
0
|
|
|
|
|
0
|
Carp::croak("$src_type is not defined src type"); |
826
|
|
|
|
|
|
|
} |
827
|
2
|
|
|
|
|
9
|
$TYPE_MAP->{$name} = $TYPE_MAP->{$src_type}; |
828
|
2
|
|
|
|
|
8
|
$TYPE_GEN->{$name} = $cb; |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
sub _init_summary { |
832
|
216
|
|
|
216
|
|
4214
|
my ($self, $command_struct) = @_; |
833
|
216
|
100
|
|
|
|
559
|
if ($command_struct) { |
834
|
95
|
|
|
|
|
332
|
for my $key (keys %$command_struct) { |
835
|
95
|
|
100
|
|
|
647
|
$self->{summary}{$key} = $command_struct->{$key}->{desc} || ''; |
836
|
|
|
|
|
|
|
} |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
else { |
839
|
121
|
|
|
|
|
444
|
$self->{summary} = {}; |
840
|
|
|
|
|
|
|
} |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
sub _extends_usage { |
844
|
66
|
|
|
66
|
|
3178
|
my ($self, $command_option) = @_; |
845
|
66
|
|
|
|
|
132
|
for my $key (qw/args other_usage/) { |
846
|
132
|
100
|
|
|
|
824
|
$self->{$key} = $command_option->{$key} if exists $command_option->{$key}; |
847
|
|
|
|
|
|
|
} |
848
|
|
|
|
|
|
|
} |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
sub _check_requires { |
851
|
260
|
|
|
260
|
|
5285
|
my ($self) = @_; |
852
|
260
|
|
|
|
|
400
|
for my $dest (sort keys %{$self->{requires}}) { |
|
260
|
|
|
|
|
1297
|
|
853
|
34
|
100
|
|
|
|
138
|
unless (defined $self->{opt}{$dest}) { |
854
|
19
|
100
|
|
|
|
35
|
unless (defined ${$self->{parsed_opthash}{$self->{requires}{$dest}}}) { |
|
19
|
|
|
|
|
85
|
|
855
|
17
|
|
|
|
|
34
|
$self->{ret} = 0; |
856
|
17
|
|
|
|
|
52
|
$self->{error} = "`--$dest` option must be specified"; |
857
|
17
|
|
|
|
|
42
|
return 0; |
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
} |
860
|
|
|
|
|
|
|
} |
861
|
243
|
|
|
|
|
548
|
return 1; |
862
|
|
|
|
|
|
|
} |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
sub _option_names { |
865
|
695
|
|
|
695
|
|
8963
|
my($self, $m) = @_; |
866
|
600
|
|
|
|
|
1144
|
my @sorted = sort { |
867
|
695
|
100
|
|
|
|
3307
|
my ($la, $lb) = (length($a), length($b)); |
868
|
600
|
50
|
66
|
|
|
3946
|
return $la <=> $lb if $la < 2 or $lb < 2; |
869
|
0
|
|
|
|
|
0
|
return 0; |
870
|
|
|
|
|
|
|
} ref $m eq 'ARRAY' ? @$m : $m; |
871
|
695
|
|
|
|
|
4247
|
return @sorted; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
sub _has_option { |
875
|
223
|
|
|
223
|
|
2982
|
my($self, $option) = @_; |
876
|
223
|
100
|
|
|
|
338
|
return 1 if grep { $_ eq $option } map { $self->_option_names($_->[0]) } @{$self->{struct}}; |
|
262
|
|
|
|
|
913
|
|
|
153
|
|
|
|
|
521
|
|
|
223
|
|
|
|
|
807
|
|
877
|
221
|
|
|
|
|
1224
|
return 0; |
878
|
|
|
|
|
|
|
} |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
1; |
881
|
|
|
|
|
|
|
__END__ |