line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
package Term::Shell::MultiCmd; |
3
|
|
|
|
|
|
|
|
4
|
4
|
|
|
4
|
|
84242
|
use warnings; |
|
4
|
|
|
|
|
10
|
|
|
4
|
|
|
|
|
123
|
|
5
|
4
|
|
|
4
|
|
21
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
81
|
|
6
|
4
|
|
|
4
|
|
19
|
use Carp ; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
1662
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 NAME |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
Term::Shell::MultiCmd - Nested Commands Tree in Shell Interface |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=cut |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION = '2.03'; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 SYNOPSIS |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# Examples are available with the distribution, under directory 'examples/' |
19
|
|
|
|
|
|
|
# This one is named examples/synopsis.pl |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
use Term::Shell::MultiCmd; |
22
|
|
|
|
|
|
|
my @command_tree = |
23
|
|
|
|
|
|
|
( 'multi word command' => |
24
|
|
|
|
|
|
|
{ help => "Help title.", |
25
|
|
|
|
|
|
|
opts => 'force repeat=i', |
26
|
|
|
|
|
|
|
exec => sub { |
27
|
|
|
|
|
|
|
my ($o, %p) = @_ ; |
28
|
|
|
|
|
|
|
print "$p{ARG0} was called with force=$p{force} and repeat=$p{repeat}\n" |
29
|
|
|
|
|
|
|
}, |
30
|
|
|
|
|
|
|
}, |
31
|
|
|
|
|
|
|
'multi word another command' => |
32
|
|
|
|
|
|
|
{ help => 'Another help title. |
33
|
|
|
|
|
|
|
Help my have multi lines, the top one |
34
|
|
|
|
|
|
|
would be used when one linear needed.', |
35
|
|
|
|
|
|
|
comp => sub { |
36
|
|
|
|
|
|
|
# this function would be called when use hits tab completion at arguments |
37
|
|
|
|
|
|
|
my ($o, $word, $line, $start, $op, $opts) = @_ ; |
38
|
|
|
|
|
|
|
# .. do something, then |
39
|
|
|
|
|
|
|
return qw/a list of completion words/ ; |
40
|
|
|
|
|
|
|
}, |
41
|
|
|
|
|
|
|
exec => sub { my ($o, %p) = @_ ; print "$p{ARG0} was called\n"}, |
42
|
|
|
|
|
|
|
}, |
43
|
|
|
|
|
|
|
'multi word third command' => |
44
|
|
|
|
|
|
|
{ help => 'same idea', |
45
|
|
|
|
|
|
|
comp => [qw/a list of words/], # this is also possible |
46
|
|
|
|
|
|
|
exec => sub { my ($o, %p) = @_ ; print "$p{ARG0} was called. Isn't that fun?\n"}, |
47
|
|
|
|
|
|
|
}, |
48
|
|
|
|
|
|
|
'multi word' => 'You can add general help title to a path', |
49
|
|
|
|
|
|
|
) ; |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
Term::Shell::MultiCmd |
52
|
|
|
|
|
|
|
-> new() |
53
|
|
|
|
|
|
|
-> populate( @command_tree ) |
54
|
|
|
|
|
|
|
-> loop ; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
print "All done, see you later\n" ; |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=head1 NOTE |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
To get the most from a command line, it might be a good idea to get the latest versions of |
61
|
|
|
|
|
|
|
Term::ReadLine and Term::ReadKey. |
62
|
|
|
|
|
|
|
There are numberless ways of doing it, one of them is running 'cpan update Bundle::CPAN' (with a proper write permission). |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=cut |
65
|
|
|
|
|
|
|
# some of my common utility functions: |
66
|
|
|
|
|
|
|
sub _params($@) { |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# convert parameter to hash table, at this point, |
69
|
|
|
|
|
|
|
# I wish perl would have followed python's function |
70
|
|
|
|
|
|
|
# parameters scheme, or made Params::Smart standard. |
71
|
|
|
|
|
|
|
# (Had anybody mentioned perl6?) |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# Note 1: this parameter processing takes time, and wouldn't |
74
|
|
|
|
|
|
|
# be a good choise for frequently called functions. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
# Note 2: as parameters are suplied by developer, a bad |
77
|
|
|
|
|
|
|
# would terminate the program - this is not a sandbox. |
78
|
|
|
|
|
|
|
|
79
|
41
|
|
|
41
|
|
50
|
my %ret ; |
80
|
41
|
|
|
|
|
59
|
my $str = shift ; |
81
|
41
|
|
|
|
|
163
|
for (split ' ', $str) { |
82
|
250
|
50
|
|
|
|
887
|
/(\w+)([\=\:](.*))?/ or confess "_params can only take simple instructions |
83
|
|
|
|
|
|
|
like key (must be provided), or key=value (value becomes default), or key= (default empty string) |
84
|
|
|
|
|
|
|
" ; |
85
|
250
|
100
|
|
|
|
911
|
$ret{$1} = $2 ? $3 : undef ; |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
# when called as OO, itemize self |
88
|
|
|
|
|
|
|
# Note: this one wouldn't work with classes (as in Term::Shell::MultiCmd -> new ) |
89
|
41
|
50
|
66
|
|
|
237
|
$ret{self} = shift if $_[0] and ref $_[0] ; |
90
|
41
|
|
|
|
|
104
|
while (@_) { |
91
|
123
|
|
|
|
|
201
|
my ($k, $v) = (shift, shift) ; |
92
|
123
|
50
|
|
|
|
437
|
$k =~ s/^\-?\-?// unless ref $k ; |
93
|
123
|
50
|
|
|
|
280
|
croak "unknown parameter: '$k'\n expected params: $str\n" unless exists $ret{$k} ; |
94
|
123
|
|
|
|
|
374
|
$ret{$k} = $v ; |
95
|
|
|
|
|
|
|
} ; |
96
|
41
|
|
|
|
|
135
|
while (my ($k, $v) = each %ret) { |
97
|
250
|
50
|
|
|
|
931
|
croak "missing parameter: '$k'\n expected params: $str\n" unless defined $v ; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
%ret |
100
|
41
|
|
|
|
|
284
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub _options { |
103
|
|
|
|
|
|
|
# Parsing user's options, this function is more forgiving than _params |
104
|
7
|
|
|
7
|
|
9
|
my $p = shift ; |
105
|
7
|
50
|
|
|
|
23
|
my @p = ref $p ? @$p : split ' ', $p ; |
106
|
7
|
|
|
|
|
8
|
my %p ; # now we have a complete set |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# use Getopt::Long 'GetOptionsFromArray' ; -- didn't work as I expected .. |
109
|
4
|
|
|
4
|
|
4720
|
use Getopt::Long ; |
|
4
|
|
|
|
|
56277
|
|
|
4
|
|
|
|
|
23
|
|
110
|
7
|
|
|
|
|
17
|
local @ARGV = @_ ; |
111
|
7
|
50
|
33
|
|
|
32
|
if (@p and not eval { GetOptions( \%p, @p ) }) { |
|
0
|
|
|
|
|
0
|
|
112
|
0
|
|
0
|
|
|
0
|
$p{_ERR_} = "$@ Expected " . join ', ', map {/(\w+)/ ; '-' . ($1 || $_)} sort @p ; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
113
|
0
|
|
|
|
|
0
|
$p{_ERR_} .= "\n" ; |
114
|
|
|
|
|
|
|
} |
115
|
7
|
|
50
|
|
|
41
|
$p{ARGV} ||= [@ARGV] ; # all the leftover, in order |
116
|
7
|
|
|
|
|
28
|
%p |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
# we can't limit ourselves by 'use :5.10', not yet. |
120
|
0
|
|
|
0
|
|
0
|
sub _say(@) { print join ('', @_) =~ /^\n*(.*?)\s*$/s, "\n" } |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# module specific functions |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# Important Note: |
126
|
|
|
|
|
|
|
# Do manipulate $o->{delimiter} and $o->{delimiterRE} ONLY if you know what you're doing ... |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub _split($$) { |
129
|
41
|
|
|
41
|
|
59
|
my ($o, $l) = @_ ; |
130
|
4
|
|
|
4
|
|
4531
|
use Text::ParseWords 'quotewords'; |
|
4
|
|
|
|
|
5237
|
|
|
4
|
|
|
|
|
2886
|
|
131
|
|
|
|
|
|
|
# grep {defined $_ and $_ ne ''} quotewords $o->{delimiterRE} || '\s+', 0, $l |
132
|
41
|
50
|
50
|
|
|
168
|
grep {defined and length } quotewords $o->{delimiterRE} || '\s+', 0, $l |
|
63
|
|
|
|
|
2651
|
|
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub _join($@) { |
136
|
8
|
|
|
8
|
|
15
|
my $o = shift ; |
137
|
8
|
|
50
|
|
|
40
|
join $o->{delimiter} || ' ', @_ |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub _travela($@) { # explicit array |
141
|
8
|
|
|
8
|
|
16
|
my ($o) = shift ; |
142
|
8
|
|
33
|
|
|
56
|
my ($c, $d, @w, @path) = ($o->{root} || $o->{cmds}, $o->{delimiter} || ' ', @_ ); |
|
|
|
50
|
|
|
|
|
143
|
8
|
|
66
|
|
|
49
|
while ( @w and 'HASH' eq ref $c ) { |
144
|
15
|
|
|
|
|
19
|
my $w = shift @w ; |
145
|
15
|
50
|
|
|
|
45
|
if (exists $c->{$w}) { |
146
|
15
|
|
|
|
|
33
|
$c = $c->{$w} ; |
147
|
15
|
|
|
|
|
26
|
push @path , $w ;# $path .= "$w "; |
148
|
15
|
|
|
|
|
64
|
next ; |
149
|
|
|
|
|
|
|
} |
150
|
0
|
|
|
|
|
0
|
my @c = grep /^$w/, keys %$c ; |
151
|
0
|
0
|
|
|
|
0
|
if(@c == 1) { |
152
|
0
|
|
|
|
|
0
|
$c = $c->{$c[0]} ; |
153
|
0
|
|
|
|
|
0
|
push @path, $c[0] ; # $path .= "$c[0] " ; |
154
|
0
|
|
|
|
|
0
|
next ; |
155
|
|
|
|
|
|
|
} |
156
|
0
|
0
|
|
|
|
0
|
if (@c > 1 ) { |
157
|
0
|
|
|
|
|
0
|
my $cmd = join $d, @path, $w ; |
158
|
0
|
|
|
|
|
0
|
return "Ambiguous command: '$cmd'\n $w could mean: @c\n" ; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# if @c == 0 : should I state the obvious? well, not with perl |
162
|
0
|
|
|
|
|
0
|
unshift @w, $w ; |
163
|
0
|
|
|
|
|
0
|
last ; |
164
|
|
|
|
|
|
|
} |
165
|
8
|
|
|
|
|
46
|
($c, join ($d, @path), @w) |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub _travel($$) { |
169
|
8
|
|
|
8
|
|
22
|
my ($o, $c) = &_check_pager ; # clear $c pager sign, let cmd know about it. |
170
|
8
|
|
|
|
|
22
|
$c = _check_silent_aliases($o, $c); |
171
|
8
|
|
|
|
|
21
|
_travela( $o, _split $o, $c ) |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub _expect_param_comp { |
175
|
0
|
|
|
0
|
|
0
|
my($o, $word, $line, $pos, $op, $opt) = @_; |
176
|
|
|
|
|
|
|
# This is ugly, Getopt::Long has many options, and |
177
|
|
|
|
|
|
|
# caller can use any of them. However, my parsing would |
178
|
|
|
|
|
|
|
# be limited. |
179
|
|
|
|
|
|
|
# print "$opt\n" ; |
180
|
0
|
|
|
|
|
0
|
my ($eq, $t) = $opt =~ /([\=\:])(\w)\W*$/ ; |
181
|
0
|
0
|
|
|
|
0
|
my $type = ($t ? |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
182
|
|
|
|
|
|
|
$t eq 'i' ? 'Integer': |
183
|
|
|
|
|
|
|
$t eq 'o' ? 'Extended Integer': |
184
|
|
|
|
|
|
|
$t eq 's' ? 'String' : |
185
|
|
|
|
|
|
|
$t eq 'f' ? 'Real Number' : |
186
|
|
|
|
|
|
|
$t : $t ) ; |
187
|
0
|
0
|
|
|
|
0
|
$type = "(optional) $type" if $eq eq ':' ; |
188
|
0
|
|
|
|
|
0
|
("$opt\nParameter Expected for -$op, type '$type'", $word) |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
my $dlm = $; ; # cache this value, in case the developer changes it on the fly. |
192
|
|
|
|
|
|
|
# Should I make it explicit '\034' value? |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub _filter($@) { |
195
|
0
|
|
|
0
|
|
0
|
my $w = shift ; |
196
|
0
|
|
|
|
|
0
|
my $qr = qr/^\Q$w/ ; |
197
|
0
|
|
|
|
|
0
|
grep /$qr/, sort grep {$_ ne $dlm} |
198
|
0
|
|
|
|
|
0
|
'ARRAY' eq ref $_[0] ? @{$_[0]} : |
199
|
0
|
0
|
|
|
|
0
|
'HASH' eq ref $_[0] ? (keys %{$_[0]}) : |
|
0
|
0
|
|
|
|
0
|
|
200
|
|
|
|
|
|
|
@_ ; |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head2 new |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ; |
208
|
|
|
|
|
|
|
- or - |
209
|
|
|
|
|
|
|
my $cli = Term::Shell::MultiCmd->new( [optional parameters ...] ) ; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
The parameters to the constructor are passed in hash form, preceding dash is optional. |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
Optional Parameters for the new command: |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=over 4 |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=item * -prompt |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -prompt => 'myprompt') ; |
220
|
|
|
|
|
|
|
- or - |
221
|
|
|
|
|
|
|
my $cli = mew Term::Shell::MultiCmd ( -prompt => \&myprompt) ; |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
Overwrite the default prompt 'shell'. |
224
|
|
|
|
|
|
|
Rules are: |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
If prompt is a CODE reference, call it in each loop cycle and display the results. |
227
|
|
|
|
|
|
|
if it ends with a non-word character, display it as is. |
228
|
|
|
|
|
|
|
Else, display it with the root-path (if exists) and '> ' characters. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=item * -help_cmd |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
Overwrite the default 'help' command, empty string would disable this command. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=item * -quit_cmd |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Overwrite the default 'quit' command, empty string would disable this command. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=item * -root_cmd |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -root_cmd => 'root' ) ; |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
This would enable the root command and set it to root. |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
Unlike 'quit' and 'help', the 'root' command is a little unexpected. Therefore it is disabled by default. I |
245
|
|
|
|
|
|
|
strongly recommend enabling this command when implementing a big, deep command tree. This allows the user rooting |
246
|
|
|
|
|
|
|
in a node, then referring to this node thereafter. After enabling, use 'help root' (or whatever names you've chosen) |
247
|
|
|
|
|
|
|
for usage manual. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=item * -history_file |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -history_file => "$ENV{HOME}/.my_progarms_data" ) ; |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
This is the history file name. If present, try to load history from this file just |
254
|
|
|
|
|
|
|
before the loop command, and try saving history in this file after the loop command. |
255
|
|
|
|
|
|
|
Default is an empty string (i.e. no history preserved between sessions). Please note that |
256
|
|
|
|
|
|
|
things might get tricky if that if multiple sessions are running at the same time. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=item * -history_size |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Overwrite the default 100 history entries to save in hisotry_file (if exists). |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=item * -history_more |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
If the history_file exists, try to load this data from the file during initialization, and save it at loop end. |
265
|
|
|
|
|
|
|
For Example: |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
my %user_defaults ; |
268
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -history_file => "$ENV{HOME}/.my_saved_data", |
269
|
|
|
|
|
|
|
-history_size => 200, |
270
|
|
|
|
|
|
|
-history_more => \%user_defaults, |
271
|
|
|
|
|
|
|
) ; |
272
|
|
|
|
|
|
|
# .... |
273
|
|
|
|
|
|
|
$cli -> loop ; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
This would load shell's history and %user_defaults from the file .my_saved_data before the loop, and |
276
|
|
|
|
|
|
|
store 200 history entries and %user_defaults in the file after the loop. |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
Note that the value of history_more must be a reference for HASH, ARRAY, or SCALAR. And |
279
|
|
|
|
|
|
|
no warnings would be provided if any of the operations fail. It wouldn't be a good idea |
280
|
|
|
|
|
|
|
to use it for sensitive data. |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=item * -pager |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
As pager's value, this module would expect a string or a sub that returns a FileHandle. If the value is a string, |
285
|
|
|
|
|
|
|
it would be converted to: |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
sub { use FileHandle ; new FileHandle "| $value_of_pager" } |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
When appropriate, the returned file handle would be selected before user's command execution, the old |
290
|
|
|
|
|
|
|
one would be restored afterward. The next example should work on most posix system: |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -pager => 'less -rX', |
293
|
|
|
|
|
|
|
... |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
The default pager's value is empty string, which means no pager manipulations. |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=item * -pager_re |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
Taking after perldb, the default value is '^\|' (i.e. a regular expression that matches '|' prefix, as in |
300
|
|
|
|
|
|
|
the user's command "| help"). If the value is set to an empty string, every command would trigger |
301
|
|
|
|
|
|
|
the pager. |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
The next example would print any output to a given filehandle: |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
my $ret_value ; |
306
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -pager => sub { |
307
|
|
|
|
|
|
|
open my $fh, '>', \$ret_value or die "can't open FileHandle to string (no PerlIO?)\n" ; |
308
|
|
|
|
|
|
|
$fh |
309
|
|
|
|
|
|
|
}, |
310
|
|
|
|
|
|
|
-pager_re => '', |
311
|
|
|
|
|
|
|
) ; |
312
|
|
|
|
|
|
|
# ... |
313
|
|
|
|
|
|
|
$cli -> cmd ('help -t') ; |
314
|
|
|
|
|
|
|
print "ret_value is:\n $ret_value" ; |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=item * -record_cmd |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
If it's a function ref, call it with an echo of the user's command |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -record_cmd => sub { |
322
|
|
|
|
|
|
|
my $user_cmd = shift; |
323
|
|
|
|
|
|
|
system "echo '$user_cmd' >> /tmp/history" |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
) ; |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=item * -empty_cmd |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
Function ref only, call it when user hits 'Return' with no command or args (not even spaces) |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
my $cli = new Term::Shell::MultiCmd ( -empty_cmd => sub { |
333
|
|
|
|
|
|
|
# Assuming some commands are recorded in $last_repeatable_cmd |
334
|
|
|
|
|
|
|
if ( $last_repeatable_cmd ) { |
335
|
|
|
|
|
|
|
# repeat it |
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
) ; |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=back |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=cut |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
sub _new_readline($) { |
346
|
0
|
|
|
0
|
|
0
|
my $o = shift ; |
347
|
4
|
|
|
4
|
|
3469
|
use Term::ReadLine; |
|
4
|
|
|
|
|
12982
|
|
|
4
|
|
|
|
|
5578
|
|
348
|
0
|
|
|
|
|
0
|
my $t = eval { local $SIG{__WARN__} = 'IGNORE' ; |
|
0
|
|
|
|
|
0
|
|
349
|
0
|
|
|
|
|
0
|
Term::ReadLine->new($o->prompt)} ; |
350
|
0
|
0
|
|
|
|
0
|
if (not $t ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
351
|
0
|
0
|
|
|
|
0
|
die "Can't create Term::ReadLine: $@\n" if -t select ; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
elsif (defined $readline::rl_completion_function) { |
354
|
|
|
|
|
|
|
$readline::rl_completion_function = |
355
|
0
|
|
|
0
|
|
0
|
sub { $o -> _complete_cli(@_)} ; |
|
0
|
|
|
|
|
0
|
|
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
elsif ( defined (my $attr = $t -> Attribs())) { |
358
|
|
|
|
|
|
|
$attr->{attempted_completion_function} = |
359
|
|
|
|
|
|
|
$attr->{completion_function} = |
360
|
0
|
|
|
0
|
|
0
|
sub { $o -> _complete_gnu(@_) } ; |
|
0
|
|
|
|
|
0
|
|
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
else { |
363
|
0
|
|
|
|
|
0
|
warn __PACKAGE__ . ": no tab completion support for this system. Sorry.\n" ; |
364
|
|
|
|
|
|
|
} |
365
|
0
|
|
|
|
|
0
|
$t |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
sub new { |
369
|
8
|
|
|
8
|
1
|
54
|
my $class = shift ; |
370
|
8
|
|
|
|
|
25
|
my %p = _params 'help_cmd=help quit_cmd=quit root_cmd= prompt=shell> |
371
|
|
|
|
|
|
|
history_file= history_size=100 history_more= pager= pager_re=^\| |
372
|
|
|
|
|
|
|
record_cmd= empty_cmd= |
373
|
|
|
|
|
|
|
', @_ ; |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# structure rules: |
376
|
|
|
|
|
|
|
# hash ref is a path, keys are items (commands or paths) special item $dlm is one liner help |
377
|
|
|
|
|
|
|
# array ref is command's data as [help, command, options, completion] |
378
|
|
|
|
|
|
|
# where: first help line is the one liner, default completion might be good enough |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
my $o = bless { cmds => { }, |
381
|
8
|
|
33
|
|
|
35
|
map {($_, $p{$_})} qw/prompt pager history_file history_size history_more |
|
96
|
|
|
|
|
278
|
|
382
|
|
|
|
|
|
|
help_cmd quit_cmd root_cmd pager pager_re record_cmd empty_cmd |
383
|
|
|
|
|
|
|
/ |
384
|
|
|
|
|
|
|
}, ref ( $class ) || $class ; |
385
|
|
|
|
|
|
|
|
386
|
8
|
|
|
|
|
40
|
$o -> {delimiter } = ' ' ; # now, programmers can manipulate the next two values after creating the object, |
387
|
8
|
|
|
|
|
18
|
$o -> {delimiterRE} = '\s+' ; # but they must be smart enough to read this code. - jezra |
388
|
8
|
|
|
|
|
26
|
$o -> _root_cmds_set() ; |
389
|
|
|
|
|
|
|
# _new_readline $o unless $DB::VERSION ; # Should I add parameter to prevent it? |
390
|
|
|
|
|
|
|
# # it could be useful when coder doesn't plan to use the loop |
391
|
|
|
|
|
|
|
# - on second thought, create it when you have to. |
392
|
8
|
|
|
|
|
27
|
_last_setting_load $o ; |
393
|
8
|
|
|
|
|
81
|
$o |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
sub _root_cmds_clr($) { |
397
|
0
|
|
|
0
|
|
0
|
my $o = shift ; |
398
|
0
|
|
|
|
|
0
|
my $root = $o->{root}; |
399
|
0
|
0
|
0
|
|
|
0
|
return unless $root and $o->{cmds} != $root ; |
400
|
0
|
|
|
|
|
0
|
for ([$o->{help_cmd}, \&_help_command], |
401
|
|
|
|
|
|
|
[$o->{quit_cmd}, \&_quit_command], |
402
|
|
|
|
|
|
|
[$o->{root_cmd}, \&_root_command], |
403
|
|
|
|
|
|
|
) { |
404
|
0
|
0
|
0
|
|
|
0
|
delete $root->{$_->[0]} if exists $root->{$_->[0]} and $root->{$_->[0]}[1] eq $_->[1] |
405
|
|
|
|
|
|
|
} |
406
|
0
|
|
|
|
|
0
|
delete $o->{root} ; |
407
|
0
|
|
|
|
|
0
|
delete $o->{root_path} ; |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
sub _root_cmds_set($;$$) { |
411
|
8
|
|
|
8
|
|
16
|
my ($o, $root, $path) = @_ ; |
412
|
8
|
50
|
|
|
|
21
|
($root, $o->{cmds}) = ($o->{cmds}, $root) if $root ; |
413
|
|
|
|
|
|
|
$o -> add_exec ( path => $o->{help_cmd}, |
414
|
8
|
|
|
|
|
37
|
exec => \&_help_command, |
415
|
|
|
|
|
|
|
comp => \&_help_command_comp, |
416
|
|
|
|
|
|
|
opts => 'recursive tree', |
417
|
|
|
|
|
|
|
help => 'help [command or prefix] |
418
|
|
|
|
|
|
|
Options: |
419
|
|
|
|
|
|
|
$PATH -t --tree : Show commands tree |
420
|
|
|
|
|
|
|
$PATH -r --recursive : Show full help instead of title, recursively' |
421
|
|
|
|
|
|
|
) ; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
$o -> add_exec ( path => $o->{quit_cmd}, |
424
|
8
|
|
|
|
|
33
|
exec => \&_quit_command, |
425
|
|
|
|
|
|
|
help => 'Exit this shell', |
426
|
|
|
|
|
|
|
) ; |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
$o -> add_exec ( path => $o->{root_cmd}, |
429
|
8
|
|
|
|
|
32
|
exec => \&_root_command, |
430
|
|
|
|
|
|
|
comp => \&_root_command_comp, |
431
|
|
|
|
|
|
|
# opts => 'set display clear', - use its own completion |
432
|
|
|
|
|
|
|
help => 'Execute from, or Set, the root node |
433
|
|
|
|
|
|
|
Usage: |
434
|
|
|
|
|
|
|
$PATH -set a path to node: set the current root at \'a path to node\' |
435
|
|
|
|
|
|
|
$PATH -clear : set the root to real root (alias to -set without parameters) |
436
|
|
|
|
|
|
|
$PATH -display : display the current root (if any) |
437
|
|
|
|
|
|
|
$PATH a path to command -with options |
438
|
|
|
|
|
|
|
: execute command from real root, options would be forwarded |
439
|
|
|
|
|
|
|
: to the command. |
440
|
|
|
|
|
|
|
' |
441
|
|
|
|
|
|
|
) ; |
442
|
8
|
50
|
|
|
|
35
|
($o->{root}, $o->{cmds}, $o->{root_path}) = ($o->{cmds}, $root, $path) if $root ; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=head2 add_exec |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
$cli -> add_exec ( -path => 'full command path', |
448
|
|
|
|
|
|
|
-exec => \&my_command, |
449
|
|
|
|
|
|
|
-help => 'some help', |
450
|
|
|
|
|
|
|
-opts => 'options', |
451
|
|
|
|
|
|
|
-comp => \&my_completion_function, |
452
|
|
|
|
|
|
|
) ; |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
This function adds an command item to the command tree. It is a little complicated, but useful (or so I hope). |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=over |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=item * -path |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
B |
461
|
|
|
|
|
|
|
This string would be parsed as multi-words command. |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Note: by default, this module expects whitespaces delimiter. If you'll read the module's code, you can find |
464
|
|
|
|
|
|
|
an easy way to change it - in unlikely case you'll find it useful. |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=item * -exec |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
B |
469
|
|
|
|
|
|
|
This code would be called when the user types a unique path for this command (with optional |
470
|
|
|
|
|
|
|
options and arguments). Parameters sent to this code are: |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
my ($cli, %p) = @_ ; |
473
|
|
|
|
|
|
|
# where: |
474
|
|
|
|
|
|
|
# $cli - self object. |
475
|
|
|
|
|
|
|
# $p{ARG0} - the command's full path (user might have used partial but unique path. This is the explicit path) |
476
|
|
|
|
|
|
|
# $p{ARGV} - all user arguments, in order (ARRAY ref) |
477
|
|
|
|
|
|
|
# %p - contains other options (see 'opts' below) |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=item * -help |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
B |
482
|
|
|
|
|
|
|
The top line would be presented when a one line title is needed (for example, when 'help -tree' |
483
|
|
|
|
|
|
|
is called), the whole string would be presented as the full help for this item. |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=item * -comp |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
B |
488
|
|
|
|
|
|
|
If Array, when the user hits tab completion for this command, try to complete his input with words |
489
|
|
|
|
|
|
|
from this list. |
490
|
|
|
|
|
|
|
If Hash, using the hash keys as array, following the rule above. |
491
|
|
|
|
|
|
|
If Code, call this function with the next parameters: |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
my ($cli, $word, $line, $start) = @_ ; |
494
|
|
|
|
|
|
|
# where: |
495
|
|
|
|
|
|
|
# $cli is the Term::Shell::MultiCmd object. |
496
|
|
|
|
|
|
|
# $word is the curent word |
497
|
|
|
|
|
|
|
# $line is the whole line |
498
|
|
|
|
|
|
|
# $start is the current location |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
This code should return a list of strings. Term::ReadLine would complete user's line to the longest |
501
|
|
|
|
|
|
|
common part, and display the list (unless unique). In other words - it would do what you expect. |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
For more information, see Term::ReadLine. |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=item * -opts |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
B |
508
|
|
|
|
|
|
|
If a string, split it to words by whitespaces. Those words are parsed as |
509
|
|
|
|
|
|
|
standard Getopt::Long options. For example: |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
-opts => 'force name=s flag=i@' |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
This would populating the previously described %p hash, correspond to user command: |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
shell> user command -name="Some String" -flag 2 -flag 3 -flag 4 -force |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
For more information, see Getopt::Long. Also see examples/multi_option.pl in distribution. |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
As ARRAY ref, caller can also add a complete 'instruction' after each non-flag option (i.e. an option that |
521
|
|
|
|
|
|
|
expects parameters). Like the 'comp' above, this 'instruction' must be an ARRAY or CODE ref, and follow |
522
|
|
|
|
|
|
|
the same roles. When omitted, a default function would be called and ask the user for input. |
523
|
|
|
|
|
|
|
For example: |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
-opts => [ 'verbose' => |
526
|
|
|
|
|
|
|
'file=s' => \&my_filename_completion, |
527
|
|
|
|
|
|
|
'level=i' => [qw/1 2 3 4/], |
528
|
|
|
|
|
|
|
'type=s' => \%my_hash_of_types, |
529
|
|
|
|
|
|
|
], |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=back |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
=cut |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
sub add_exec { |
536
|
32
|
|
|
32
|
1
|
42
|
my $o = shift ; |
537
|
32
|
|
|
|
|
83
|
my %p = _params 'path exec help= comp= opts=', @_ ; |
538
|
32
|
100
|
|
|
|
111
|
return unless $p{path}; # let user's empty string prevent this command |
539
|
25
|
|
|
|
|
37
|
my $r = $o ->{cmds} ; |
540
|
25
|
|
|
|
|
99
|
my $p = '' ; |
541
|
25
|
50
|
|
|
|
71
|
die "command must be CODE refferance\n" unless 'CODE' eq ref $p{exec} ; |
542
|
25
|
|
|
|
|
65
|
my @w = _split $o, $p{path} ; |
543
|
25
|
50
|
|
|
|
71
|
my $new = pop @w or return ; |
544
|
25
|
|
|
|
|
51
|
for my $w (@w) { |
545
|
8
|
|
|
|
|
23
|
$p .= _join $o, $p, $w ; |
546
|
8
|
50
|
|
|
|
27
|
if ('ARRAY' eq ref $r ->{$w} ) { |
547
|
0
|
|
|
|
|
0
|
carp "Overwrite command '$p'\n" ; |
548
|
0
|
|
|
|
|
0
|
delete $r -> {$w} ; |
549
|
|
|
|
|
|
|
} |
550
|
8
|
|
50
|
|
|
57
|
$r = ($r->{$w} ||= {}) ; |
551
|
|
|
|
|
|
|
} |
552
|
25
|
|
|
|
|
46
|
my ($opts, %opts) = '' ; # now calculate options |
553
|
25
|
100
|
|
|
|
58
|
if ($p{opts}) { |
554
|
8
|
50
|
|
|
|
41
|
my @opts = ref $p{opts} ? @{$p{opts}} : split ' ', $p{opts} ; |
|
0
|
|
|
|
|
0
|
|
555
|
|
|
|
|
|
|
# croak "options -opts must be ARRAY ref\n" unless 'ARRAY' eq ref $p{opts} ; |
556
|
8
|
|
|
|
|
23
|
while (@opts) { |
557
|
16
|
|
|
|
|
25
|
my $op = shift @opts ; |
558
|
16
|
50
|
|
|
|
37
|
croak "unexpected option completion\n" if ref $op ; |
559
|
16
|
|
|
|
|
34
|
$opts .= "$op " ; |
560
|
16
|
|
|
|
|
33
|
my $expecting = $op =~ s/[\=\:].*$// ; |
561
|
16
|
0
|
|
|
|
67
|
$opts{$op} = ( $expecting ? |
|
|
50
|
|
|
|
|
|
562
|
|
|
|
|
|
|
ref $opts[0] ? |
563
|
|
|
|
|
|
|
shift @opts : |
564
|
|
|
|
|
|
|
\&_expect_param_comp : |
565
|
|
|
|
|
|
|
'' ) ; |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
# 0 1 2 3 4.. |
569
|
25
|
|
|
|
|
180
|
$r->{$new} = [@p{qw/help exec comp/}, $opts, %opts] |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=head2 add_help |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
Although help string can set in add_exec, this command is useful when he wishes to |
576
|
|
|
|
|
|
|
add title (or hint) to a part of the command path. For example: |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
# assume $cli with commands 'feature set', 'feature get', etc. |
579
|
|
|
|
|
|
|
$cli -> add_help ( -path => 'feature' , |
580
|
|
|
|
|
|
|
-help => 'This feature is about something') ; |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=cut |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
sub add_help { |
585
|
1
|
|
|
1
|
1
|
2
|
my $o = shift ; |
586
|
1
|
|
|
|
|
3
|
my %p = _params "path help", @_ ; |
587
|
1
|
|
|
|
|
4
|
my ($cmd, $path, @args, $ret) = _travel $o, $p{path} ; # _split $o, $p{path} ; |
588
|
1
|
50
|
|
|
|
4
|
if ('HASH' eq ref $cmd) { |
589
|
1
|
|
|
|
|
3
|
for my $w (@args) { |
590
|
0
|
|
|
|
|
0
|
$cmd = ($cmd->{$w} = {}); |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
($ret, $cmd->{$dlm}) = ($cmd->{$dlm}, $p{help}) |
593
|
1
|
|
|
|
|
4
|
} |
594
|
|
|
|
|
|
|
else { |
595
|
0
|
0
|
|
|
|
0
|
croak "command '$p{path}' does not exists.\n For sanity reasons, will not add help to non-existing commands\n" if @args; |
596
|
|
|
|
|
|
|
($ret, $cmd->[0 ]) = ($cmd->[0 ], $p{help}) |
597
|
0
|
|
|
|
|
0
|
} |
598
|
1
|
|
|
|
|
6
|
$ret # Was it worth the trouble? |
599
|
|
|
|
|
|
|
} |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
=head2 populate |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
A convenient way to define a chain of add_exec and add_help commands. This function expects hash, where |
604
|
|
|
|
|
|
|
the key is the command path and the value might be HASH ref (calling add_exec), or a string (calling add_help). |
605
|
|
|
|
|
|
|
For example: |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
$cli -> populate |
608
|
|
|
|
|
|
|
( 'feature' => 'This feature is a secret', |
609
|
|
|
|
|
|
|
'feature set' => { help => 'help for feature set', |
610
|
|
|
|
|
|
|
exec => \&my_feature_set, |
611
|
|
|
|
|
|
|
opts => 'level=i', |
612
|
|
|
|
|
|
|
comp => \&my_feature_set_completion_function, |
613
|
|
|
|
|
|
|
}, |
614
|
|
|
|
|
|
|
'feature get' => { help => 'help for feature get', |
615
|
|
|
|
|
|
|
exec => \&my_feature_get |
616
|
|
|
|
|
|
|
}, |
617
|
|
|
|
|
|
|
) ; |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
# Note: |
620
|
|
|
|
|
|
|
# - Since the key is the path, '-path' is omitted from parameters. |
621
|
|
|
|
|
|
|
# - This function returns the self object, for easy chaining (as the synopsis demonstrates). |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
=cut |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
sub populate { |
626
|
8
|
|
|
8
|
1
|
22
|
my ($o, %p) = @_ ; |
627
|
8
|
|
|
|
|
36
|
while (my ($k, $v) = each %p) { |
628
|
9
|
100
|
|
|
|
38
|
if (not ref $v) { |
|
|
50
|
|
|
|
|
|
629
|
1
|
|
|
|
|
6
|
$o->add_help(-path => $k, -help => $v) ; |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
elsif ('HASH' eq ref $v) { |
632
|
8
|
|
|
|
|
36
|
$o->add_exec(-path => $k, %$v) |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
else { |
635
|
0
|
|
|
|
|
0
|
croak "unknow item for '$k': $v\n" ; |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
} |
638
|
|
|
|
|
|
|
$o |
639
|
8
|
|
|
|
|
35
|
} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
sub _last_setting_load($) { |
642
|
8
|
|
|
8
|
|
11
|
my $o = shift ; |
643
|
8
|
100
|
|
|
|
32
|
my $f = $o->{history_file} or return ; |
644
|
1
|
50
|
|
|
|
203
|
return unless -f $f ; |
645
|
0
|
|
|
|
|
0
|
my $d = $o->{history_more} ; |
646
|
0
|
|
|
|
|
0
|
eval { |
647
|
4
|
|
|
4
|
|
3971
|
my $setting = eval { use Storable ; retrieve $f } ; |
|
4
|
|
|
|
|
16295
|
|
|
4
|
|
|
|
|
1052
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
648
|
0
|
0
|
|
|
|
0
|
return print "Failed to load configuration from $f: $@\n" if $@ ; |
649
|
0
|
|
|
|
|
0
|
my ($hist, $more) = @$setting ; |
650
|
0
|
0
|
0
|
|
|
0
|
$o->{history_data} = $hist if 'ARRAY' eq ref $hist and @$hist ; |
651
|
0
|
0
|
0
|
|
|
0
|
return unless ref $d and ref $more and ref($d) eq ref($more) ; |
|
|
|
0
|
|
|
|
|
652
|
0
|
0
|
|
|
|
0
|
%$d = %$more if 'HASH' eq ref $d ; |
653
|
0
|
0
|
|
|
|
0
|
@$d = @$more if 'ARRAY' eq ref $d ; |
654
|
0
|
0
|
|
|
|
0
|
$$d = $$more if 'SCALAR' eq ref $d ; |
655
|
|
|
|
|
|
|
} ; |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
sub _last_setting_save($) { |
659
|
0
|
|
|
0
|
|
0
|
my $o = shift ; |
660
|
0
|
0
|
|
|
|
0
|
my $f = $o->{history_file} or return ; |
661
|
0
|
|
|
|
|
0
|
my @his = $o -> history(); |
662
|
0
|
|
|
|
|
0
|
splice @his, 0, @his - $o->{history_size} ; |
663
|
|
|
|
|
|
|
print |
664
|
4
|
0
|
|
4
|
|
27
|
eval {use Storable ; store ([[@his], $o->{history_more}], $f)} ? # Note: For backward compatibly, this array can only grow |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
9084
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
665
|
|
|
|
|
|
|
"Configuration saved in $f\n" : |
666
|
|
|
|
|
|
|
"Failed to save configuration in $f: $@\n" ; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
=head2 loop |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
$cli -> loop ; |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
Prompt, parse, and invoke in an endless loop |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
('endless loop' should never be taken literally. Users quit, systems crash, universes collapse - |
676
|
|
|
|
|
|
|
and the loop reaches its last cycle) |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=cut |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
sub loop { |
681
|
0
|
|
|
0
|
1
|
0
|
local $| = 1 ; |
682
|
0
|
|
|
|
|
0
|
my $o = shift ; |
683
|
|
|
|
|
|
|
|
684
|
0
|
|
0
|
|
|
0
|
$o-> {term} ||= _new_readline $o ; |
685
|
0
|
0
|
|
|
|
0
|
$o-> history($o->{history_data}) if $o->{history_data}; |
686
|
0
|
|
0
|
|
|
0
|
while ( not $o -> {stop} and |
687
|
|
|
|
|
|
|
defined (my $line = $o->{term}->readline($o->prompt)) ) { |
688
|
0
|
|
|
|
|
0
|
$o->cmd( $line ) ; |
689
|
|
|
|
|
|
|
} |
690
|
0
|
|
|
|
|
0
|
_last_setting_save $o ; |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
sub _complete_gnu { |
694
|
0
|
|
|
0
|
|
0
|
my($o, $text, $line, $start, $end) = @_; |
695
|
0
|
|
|
|
|
0
|
$text, &_complete_cli # apparently, this should work |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
sub _complete_cli { |
699
|
0
|
|
|
0
|
|
0
|
my($o, $word, $line, $start) = @_; |
700
|
|
|
|
|
|
|
# 1. complete command |
701
|
|
|
|
|
|
|
# 2. if current word starts with '-', complete option |
702
|
|
|
|
|
|
|
# 3. if previous word starts with '-', try arg completion |
703
|
|
|
|
|
|
|
# 4. try cmd completion (should it overwrite 3 for default _expect_param_comp?) |
704
|
|
|
|
|
|
|
# 5. show help, keep the line |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
# my @w = _split $o , # should I ignore the rest of the line? |
707
|
|
|
|
|
|
|
# substr $line, 0, $start ; # well, Term::ReadLine expects words list. |
708
|
|
|
|
|
|
|
|
709
|
0
|
|
|
|
|
0
|
my ($cmd, $path, @args) = _travel $o, substr $line, 0, $start ; # @w ; |
710
|
0
|
0
|
|
|
|
0
|
return ($cmd, $word) unless ref $cmd ; |
711
|
0
|
0
|
|
|
|
0
|
return (@args ? "\a" : _filter $word, $cmd) if 'HASH' eq ref $cmd ; |
|
|
0
|
|
|
|
|
|
712
|
|
|
|
|
|
|
|
713
|
0
|
|
|
|
|
0
|
my ($help, $exec, $comp, $opts, %opts) = @{ $cmd } ; # avoid confusion |
|
0
|
|
|
|
|
0
|
|
714
|
0
|
0
|
0
|
|
|
0
|
return &_root_command_comp if $comp and $comp == \&_root_command_comp ; # very special case: root 'imports' its options. |
715
|
0
|
0
|
|
|
|
0
|
return map {"$1$_"} _filter $2,\%opts if $word =~ /^(\-\-?)(.*)/ ; |
|
0
|
|
|
|
|
0
|
|
716
|
0
|
0
|
0
|
|
|
0
|
if ( @args and $args[-1] =~ /^\-\-?(.*)/) { |
717
|
0
|
|
|
|
|
0
|
my ($op, @op) = _filter $1, \%opts ; |
718
|
0
|
0
|
|
|
|
0
|
return ("Option $args[-1] is ambiguous: $op @op?", $word) if @op ; |
719
|
0
|
0
|
|
|
|
0
|
return ("Option $args[-1] is unknown", $word) unless $op ; |
720
|
0
|
|
|
|
|
0
|
my $cb = $opts{$op} ; |
721
|
0
|
0
|
0
|
|
|
0
|
return _filter $word, $cb if 'ARRAY' eq ref $cb or 'HASH' eq ref $cb ; |
722
|
0
|
0
|
|
|
|
0
|
return $cb->($o, $word, $line, $start, $op, $opts =~ /$op(\S*)/ ) if 'CODE' eq ref $cb ; |
723
|
|
|
|
|
|
|
} |
724
|
0
|
0
|
0
|
|
|
0
|
return _filter $word, $comp if 'ARRAY' eq ref $comp or 'HASH' eq ref $comp ; |
725
|
0
|
0
|
|
|
|
0
|
return $comp->($o, $word, $line, $start) if 'CODE' eq ref $comp ; |
726
|
0
|
|
|
|
|
0
|
return ($help, $word) # so be it |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
sub _help_message_tree { # inspired by Unix 'tree' command |
730
|
|
|
|
|
|
|
# Should I add ANSI colors? |
731
|
0
|
|
|
0
|
|
0
|
my ($h, $cmd, $pre, $last) = @_ ; |
732
|
0
|
0
|
|
|
|
0
|
print $pre . ($last ? '`' : '|') if $pre ; |
|
|
0
|
|
|
|
|
|
733
|
0
|
0
|
|
|
|
0
|
return _say "- $cmd : ", $h->[0] =~ /^(.*)/m if 'ARRAY' eq ref $h ; |
734
|
0
|
|
|
|
|
0
|
_say "-- $cmd" ; |
735
|
0
|
|
|
|
|
0
|
my @c = sort keys %$h ; |
736
|
0
|
|
|
|
|
0
|
for my $c (@c) { |
737
|
0
|
0
|
0
|
|
|
0
|
_help_message_tree( $h->{$c}, |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
738
|
|
|
|
|
|
|
$c, |
739
|
|
|
|
|
|
|
$pre ? $pre . ($last ? ' ' : '| ') : ' ' , |
740
|
|
|
|
|
|
|
$c eq ($c[-1]||'') |
741
|
|
|
|
|
|
|
) unless $c eq $dlm ; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
sub _help_message { |
746
|
0
|
|
|
0
|
|
0
|
my $o = shift ; |
747
|
0
|
|
|
|
|
0
|
my %p = _params "node path full= recursive= tree= ARGV= ARG0=", @_ ; |
748
|
0
|
|
|
|
|
0
|
my ($h, $p) = @p{'node', 'path'} ; |
749
|
0
|
|
|
|
|
0
|
$p =~ s/^\s*(.*?)\s*$/$1/ ; |
750
|
|
|
|
|
|
|
sub _align2($$) { |
751
|
0
|
|
|
0
|
|
0
|
my ($a, $b) = @_; |
752
|
0
|
|
|
|
|
0
|
_say $a, (' ' x (20 - length $a)), ': ', $b |
753
|
|
|
|
|
|
|
} |
754
|
|
|
|
|
|
|
|
755
|
0
|
0
|
|
|
|
0
|
if ('ARRAY' eq ref $h) { # simple command, full help |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
756
|
0
|
|
|
|
|
0
|
my $help = $h->[0] ; |
757
|
0
|
|
|
|
|
0
|
$help =~ s/\$PATH/$p{path}/g ; |
758
|
0
|
|
|
|
|
0
|
_say "$p:\n $help" ; |
759
|
0
|
|
|
|
|
0
|
$help |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
elsif ('HASH' ne ref $h) { # this one shouldn't happen |
762
|
0
|
|
|
|
|
0
|
confess "bad item in help message: $h" |
763
|
|
|
|
|
|
|
} |
764
|
|
|
|
|
|
|
elsif ($p{recursive}) { # show everything |
765
|
0
|
|
|
|
|
0
|
my $xxx = "----------------------\n" ; |
766
|
0
|
0
|
|
|
|
0
|
_say $xxx, $p, ":\t", $h->{$dlm} if exists $h->{$dlm}; |
767
|
|
|
|
|
|
|
|
768
|
0
|
|
|
|
|
0
|
for my $k (sort keys %$h) { |
769
|
0
|
0
|
|
|
|
0
|
next if $k eq $dlm ; |
770
|
0
|
|
|
|
|
0
|
_say $xxx ; |
771
|
0
|
|
|
|
|
0
|
_help_message( $o, %p, -node => $h->{$k}, -path => _join $o, $p, $k) ; |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
} |
774
|
|
|
|
|
|
|
elsif ($p{tree}) { # tree - one linear for each one |
775
|
0
|
|
|
|
|
0
|
_help_message_tree ($h, $p) |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
elsif ($p{full}) { # prefix, full list |
778
|
|
|
|
|
|
|
|
779
|
0
|
0
|
|
|
|
0
|
_say "$p:\t", $h->{$dlm} if exists $h->{$dlm} ; |
780
|
|
|
|
|
|
|
|
781
|
0
|
|
|
|
|
0
|
for my $k (sort keys %$h) { |
782
|
0
|
0
|
|
|
|
0
|
next if $k eq $dlm ; |
783
|
|
|
|
|
|
|
my ($l) = (('ARRAY' eq ref $h->{$k}) ? |
784
|
|
|
|
|
|
|
($h->{$k}[0] || 'a command') : |
785
|
0
|
0
|
0
|
|
|
0
|
($h->{$k}{$dlm} || 'a prefix' ) ) =~ /^(.*)$/m ; |
|
|
|
0
|
|
|
|
|
786
|
0
|
|
|
|
|
0
|
_align2 _join($o, $p, $k), $l; |
787
|
|
|
|
|
|
|
} |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
else { # just show the prefix with optional help |
790
|
0
|
|
0
|
|
|
0
|
_say "$p: \t", $h->{$dlm} || 'A command prefix' ; |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
sub _help_command { |
795
|
0
|
|
|
0
|
|
0
|
my ($o, %p) = @_ ; |
796
|
0
|
|
|
|
|
0
|
my ($cmd, $path, @args) = _travela $o, @{$p{ARGV}} ; |
|
0
|
|
|
|
|
0
|
|
797
|
0
|
0
|
|
|
|
0
|
return _say $cmd unless ref $cmd ; |
798
|
0
|
0
|
|
|
|
0
|
return _say "No such command or prefix: " . _join $o, $path, @args if @args ; |
799
|
0
|
|
|
|
|
0
|
return _help_message($o, -node => $cmd, -path => $path, -full => 1, %p) ; |
800
|
|
|
|
|
|
|
} |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
sub _help_command_comp { |
803
|
0
|
|
|
0
|
|
0
|
my($o, $word, $line, $start) = @_; |
804
|
0
|
|
|
|
|
0
|
my @w = _split $o , substr $line, 0, $start ; |
805
|
0
|
|
|
|
|
0
|
shift @w ; |
806
|
0
|
|
|
|
|
0
|
my ($cmd, $path, @args) = _travela $o, grep {!/\-\-?r(?:ecursive)?|\-\-?t(?:ree)?/} @w ; |
|
0
|
|
|
|
|
0
|
|
807
|
|
|
|
|
|
|
# potential issue: 'help -r some path' wouldn't be a valid path, is DWIM the solution? |
808
|
0
|
0
|
|
|
|
0
|
return ($cmd, $word) unless ref $cmd ; |
809
|
0
|
0
|
|
|
|
0
|
return _filter $word, $cmd if 'HASH' eq ref $cmd ; |
810
|
0
|
|
|
|
|
0
|
('', $word) |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
|
813
|
0
|
|
|
0
|
|
0
|
sub _quit_command { $_[0]->{stop} = 1 } |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
sub _root_command_comp { |
816
|
0
|
|
|
0
|
|
0
|
my($o, $word, $line, $start) = @_; |
817
|
0
|
|
|
|
|
0
|
$line =~ s/^(\s*\S+\s*(?:(\-\-?)(\w*))?)// ; # todo: delimiterRE |
818
|
0
|
|
|
|
|
0
|
my ($prolog, $par, $param) = ($1, $2, $3) ; |
819
|
0
|
0
|
|
|
|
0
|
return unless $prolog ; # error, avoid recursion |
820
|
0
|
0
|
0
|
|
|
0
|
return map {"$par$_"} _filter $param, qw/clear set display/ if $par and not $line ; |
|
0
|
|
|
|
|
0
|
|
821
|
0
|
|
|
|
|
0
|
$line =~ s/^(\s*)// ; |
822
|
0
|
|
|
|
|
0
|
$prolog .= $1 ; |
823
|
0
|
|
|
|
|
0
|
my $root = delete $o -> {root} ; |
824
|
0
|
|
|
|
|
0
|
my @res = _complete_cli($o, $word, $line, $start - length $prolog) ; |
825
|
0
|
0
|
|
|
|
0
|
$o->{root} = $root if $root ; |
826
|
|
|
|
|
|
|
@res |
827
|
0
|
|
|
|
|
0
|
} |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
sub _root_command { |
830
|
|
|
|
|
|
|
# root -display : display current path |
831
|
|
|
|
|
|
|
# root -set path : set path |
832
|
|
|
|
|
|
|
# root -clear : alias to root -set (without a path) |
833
|
|
|
|
|
|
|
# root path params: execute path from real command root |
834
|
|
|
|
|
|
|
|
835
|
0
|
|
|
0
|
|
0
|
my ($o, %p) = @_ ; |
836
|
0
|
|
|
|
|
0
|
my @argv = @{$p{ARGV}} ; |
|
0
|
|
|
|
|
0
|
|
837
|
0
|
0
|
|
|
|
0
|
@argv or return $o->cmd("help $p{ARG0}") ; |
838
|
|
|
|
|
|
|
# algo: can't parse those options automaticaly, as it would prevent user's options to optional root commnad |
839
|
0
|
0
|
|
|
|
0
|
$argv[0] =~ /^\-\-?d/ and return _say $o->{root} ? "root is set to '$o->{root_path}'" : "root is clear." ; |
|
|
0
|
|
|
|
|
|
840
|
0
|
0
|
|
|
|
0
|
$argv[0] =~ /^\-\-?c/ and @argv = ('-set') ; |
841
|
0
|
0
|
|
|
|
0
|
$argv[0] =~ /^\-\-?s/ or do { |
842
|
|
|
|
|
|
|
# just do it, do it! |
843
|
0
|
|
|
|
|
0
|
my $root = delete $o->{root} ; |
844
|
0
|
|
|
|
|
0
|
my @res = $o->cmd(_join $o, @argv) ; |
845
|
0
|
0
|
|
|
|
0
|
$o->{root} = $root if $root ; |
846
|
0
|
|
|
|
|
0
|
return @res ; |
847
|
|
|
|
|
|
|
} ; |
848
|
0
|
|
|
|
|
0
|
shift @argv ; # -set, it is |
849
|
0
|
|
|
|
|
0
|
my ($cmd, $path, @args) ; |
850
|
0
|
0
|
|
|
|
0
|
if (@argv) { |
851
|
0
|
|
|
|
|
0
|
my $root = delete $o->{root} ; |
852
|
0
|
|
|
|
|
0
|
($cmd, $path, @args) = _travela $o, @argv ; |
853
|
0
|
0
|
|
|
|
0
|
$o->{root} = $root if $root ; |
854
|
0
|
0
|
|
|
|
0
|
return _say $cmd unless ref $cmd ; |
855
|
0
|
0
|
|
|
|
0
|
return _say "No such prefix: " . _join $o, $path, @args if @args ; |
856
|
0
|
0
|
|
|
|
0
|
return _say "$path: is a command. Only a node can be set as root." if 'ARRAY' eq ref $cmd ; |
857
|
|
|
|
|
|
|
} |
858
|
0
|
0
|
|
|
|
0
|
if ( $o->{root}) { |
859
|
0
|
|
|
|
|
0
|
_say "clear root '$o->{root_path}'" ; |
860
|
0
|
|
|
|
|
0
|
_root_cmds_clr $o ; |
861
|
|
|
|
|
|
|
} |
862
|
0
|
0
|
|
|
|
0
|
if ( $cmd ) { |
863
|
0
|
|
|
|
|
0
|
_root_cmds_set $o, $cmd, $path ; |
864
|
0
|
|
|
|
|
0
|
_say "set new root: '$path'" ; |
865
|
|
|
|
|
|
|
} |
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
sub _check_pager { |
869
|
8
|
|
|
8
|
|
13
|
my ($o, $c) = @_ ; |
870
|
8
|
50
|
|
|
|
47
|
my $p = $o->{pager} or return (@_, $o->{piper}=undef); # just in case programmer delete {pager} during run |
871
|
4
|
0
|
|
4
|
|
3258
|
ref $p or $o->{pager} = sub { use FileHandle ; new FileHandle "| $p" }; |
|
4
|
|
|
0
|
|
47505
|
|
|
4
|
|
|
|
|
24
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
872
|
0
|
|
|
|
|
0
|
my $r = $o->{pager_re} ; |
873
|
0
|
0
|
0
|
|
|
0
|
!$r or ref $r or $r = do{ my $d = "$r($o->{delimiterRE})*" ; $o->{pager_re} = qr/$d/}; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
874
|
0
|
|
0
|
|
|
0
|
$o->{piper} = !$r || $c =~ s/$r// ; |
875
|
0
|
|
|
|
|
0
|
($o, $c) |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
sub _check_silent_aliases { |
879
|
8
|
|
|
8
|
|
13
|
my ($o, $cmd) = @_ ; |
880
|
8
|
50
|
|
|
|
24
|
return $cmd unless $cmd; |
881
|
8
|
|
33
|
|
|
47
|
my $r = $o->{root} || $o->{cmds}; |
882
|
8
|
|
|
|
|
18
|
my ($c, @a) = _split $o, $cmd; |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
return _join $o, $o->{root_cmd}, (@a ? (-set => @a ) : ('-clear')) |
885
|
|
|
|
|
|
|
if ( $c eq 'cd' and |
886
|
|
|
|
|
|
|
$o->{root_cmd} and |
887
|
8
|
0
|
33
|
|
|
28
|
not exists $r->{cd}); |
|
|
0
|
0
|
|
|
|
|
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
return _join $o, $o->{help_cmd}, @a |
890
|
|
|
|
|
|
|
if $o->{help_cmd} and |
891
|
|
|
|
|
|
|
( ($c eq 'ls' and not exists $r->{ls} ) or |
892
|
8
|
50
|
33
|
|
|
80
|
($c eq 'help' and not exists $r->{help}) ); |
|
|
|
33
|
|
|
|
|
893
|
|
|
|
|
|
|
|
894
|
8
|
|
|
|
|
29
|
$cmd |
895
|
|
|
|
|
|
|
} |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
=head2 cmd |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
$cli -> cmd ( "help -tree" ) ; |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
Execute the given string parameter, similarly to user input. This one might be useful to execute |
902
|
|
|
|
|
|
|
commands in a script, or testing. |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
=cut |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
sub cmd { |
907
|
7
|
|
|
7
|
1
|
13
|
my ($o, $clt) = @_; |
908
|
7
|
100
|
|
|
|
25
|
$o->{record_cmd}->($clt) if 'CODE' eq ref $o->{record_cmd}; |
909
|
|
|
|
|
|
|
|
910
|
7
|
50
|
|
|
|
217
|
my ($cmd, $path, @args, $fh) = _travel $o, $clt or return ; |
911
|
7
|
|
|
|
|
21
|
local %SIG ; |
912
|
|
|
|
|
|
|
|
913
|
7
|
50
|
33
|
|
|
22
|
if ($o->{piper} and $fh = $o->{pager}->()) { |
914
|
0
|
|
|
|
|
0
|
$o->{stdout} = select ; |
915
|
0
|
|
|
|
|
0
|
select $fh ; |
916
|
0
|
|
|
0
|
|
0
|
$SIG{PIPE} = sub {} ; |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
|
919
|
7
|
|
|
|
|
21
|
my $res = $o->_cmd ($cmd, $path, @args) ; |
920
|
|
|
|
|
|
|
|
921
|
7
|
50
|
|
|
|
41
|
if ($fh) { |
922
|
0
|
|
|
|
|
0
|
select $o->{stdout} ; |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
$res |
925
|
7
|
|
|
|
|
51
|
} |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
sub _cmd { |
928
|
7
|
|
|
7
|
|
12
|
my ($o, $cmd, $path, @args) = @_ ; |
929
|
7
|
50
|
|
|
|
21
|
return print $cmd unless ref $cmd ; |
930
|
7
|
50
|
33
|
|
|
48
|
return $o->{empty_cmd}->() if $o->{empty_cmd} and $cmd eq ($o -> {root} || $o->{cmds}) and 0 == length join '', @args; |
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
931
|
7
|
50
|
|
|
|
23
|
return _say "No such command or prefix: " . _join $o, @args if $cmd eq $o->{cmds} ; |
932
|
7
|
50
|
|
|
|
20
|
return _help_message($o, -node => $cmd, -path => $path) unless 'ARRAY' eq ref $cmd ; # help message |
933
|
7
|
|
50
|
|
|
34
|
my %p = _options $cmd->[3] || '', @args ; |
934
|
7
|
50
|
|
|
|
19
|
return print $p{_ERR_} if $p{_ERR_} ; |
935
|
7
|
|
|
|
|
27
|
return $cmd->[1]->($o, ARG0 => $path, %p) ; |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=head2 command |
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
$cli -> command ( "help -tree") ; |
941
|
|
|
|
|
|
|
Is the same as cmd, but echos the command before execution |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
=cut |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
sub command { |
946
|
0
|
|
|
0
|
1
|
|
my ($o, $cmd) = @_ ; |
947
|
0
|
|
|
|
|
|
print "$cmd ..\n" ; |
948
|
0
|
|
|
|
|
|
&cmd |
949
|
|
|
|
|
|
|
} |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
=head2 complete |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
my ($base_line, @word_list) = $cli -> complete ($a_line) ; |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
given a line, this function would return a base line (i.e. truncated to the beginning of the last word), and a list of potential |
956
|
|
|
|
|
|
|
completions. Added to the 'cmd' command, this might be useful when module user implements his own 'loop' command in a non-terminal |
957
|
|
|
|
|
|
|
application |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
=cut |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
sub complete { |
962
|
|
|
|
|
|
|
# line, pos ==> line, list of words |
963
|
0
|
|
|
0
|
1
|
|
my ($o, $line, $pos) = @_ ; |
964
|
0
|
0
|
|
|
|
|
my $lo = substr $line, $pos, -1, '' if defined $pos ; |
965
|
0
|
|
|
|
|
|
my $lu = $line ; |
966
|
0
|
|
|
|
|
|
my $qd = $o -> {delimiterRE} ; |
967
|
0
|
|
|
|
|
|
$lu =~ s/([^$qd]*)$// ; |
968
|
0
|
|
0
|
|
|
|
my $w = $1 || '' ; |
969
|
0
|
|
0
|
|
|
|
my (@list) = _complete_cli($o, $w, $line, $pos || length $lu) ; |
970
|
|
|
|
|
|
|
# if ($lu =~ /^(.*)($qd+)$/) { |
971
|
|
|
|
|
|
|
# # this is duplicating what is done in _complete_cli, TODO: optimize |
972
|
|
|
|
|
|
|
# my ($l, $s) = ($1, $2 ) ; |
973
|
|
|
|
|
|
|
# my ($cmd, $path, @args) = _travel $o, $l ; |
974
|
|
|
|
|
|
|
# $lu = "$path$s" if $path and not @args ; |
975
|
|
|
|
|
|
|
# } |
976
|
0
|
|
|
|
|
|
($lu, @list) |
977
|
|
|
|
|
|
|
} |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
=head2 prompt |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
my $prompt = $cli -> prompt() ; |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
accepts no parameters, return current prompt. |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
=cut |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
sub prompt() { |
989
|
0
|
|
|
0
|
1
|
|
my $o = shift ; |
990
|
0
|
|
0
|
|
|
|
my $p = $o->{prompt} || 'shell' ; |
991
|
0
|
0
|
|
|
|
|
return $p->() if 'CODE' eq ref $p ; |
992
|
0
|
0
|
|
|
|
|
return $p if $p =~ /\W$/ ; |
993
|
0
|
0
|
|
|
|
|
$p .= ':' . $o->{root_path} if $o->{root_path} ; |
994
|
0
|
|
|
|
|
|
$p . '> ' |
995
|
|
|
|
|
|
|
} |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
=head2 history |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
set/get history |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
my @hist = $cli -> history() ; # get history |
1002
|
|
|
|
|
|
|
$cli -> history( @alternative_history ) ; # set history |
1003
|
|
|
|
|
|
|
$cli -> history([@alternative_history]) ; # the very same, by ptr |
1004
|
|
|
|
|
|
|
$cli -> history([]) ; # clear history |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
=cut |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
sub history { |
1009
|
0
|
|
|
0
|
1
|
|
my $o = shift ; |
1010
|
0
|
0
|
|
|
|
|
return unless $o->{term} ; |
1011
|
0
|
0
|
|
|
|
|
return $o->{term}->SetHistory(map {('ARRAY' eq ref $_) ? (@$_) : ($_)} @_ ) if @_ ; |
|
0
|
0
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
return $o->{term}->GetHistory |
1013
|
0
|
|
|
|
|
|
} |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
# =head2 pager |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
# my $old_pager = $o->pager($new_pager); # set new pager |
1019
|
|
|
|
|
|
|
# my $old_pager = $o->pager('') ; # clear pager |
1020
|
|
|
|
|
|
|
# my $cur_pager = $o->pager() ; # keep current pager |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
# =cut |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
# sub pager { |
1025
|
|
|
|
|
|
|
# my ($o, $new) = @_ ; |
1026
|
|
|
|
|
|
|
# my $old = $o->{pager} ; |
1027
|
|
|
|
|
|
|
# $o->{pager} = $new if defined $new ; |
1028
|
|
|
|
|
|
|
# $old |
1029
|
|
|
|
|
|
|
# } |
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
=head1 ALSO SEE |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
Term::ReadLine, Term::ReadKey, Getopt::Long |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
=head1 AUTHOR |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
Josef Ezra, C<< >> |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
=head1 BUGS |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
Please report any bugs or feature requests to me, or to C, or through |
1042
|
|
|
|
|
|
|
the web interface at L. |
1043
|
|
|
|
|
|
|
I am grateful for your feedback. |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
=head2 TODO list |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
nImplement pager. |
1048
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
=head1 SUPPORT |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
perldoc Term::Shell::MultiCmd |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
You can also look for information at: |
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
=over 4 |
1058
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
L |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
L |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
=item * CPAN Ratings |
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
L |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
=item * Search CPAN |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
L |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
=back |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
=head1 ACKNOWLEDGMENTS |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
This module was inspired by the excellent modules Term::Shell, CPAN, and CPANPLUS::Shell. |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
Copyright 2010 Josef Ezra. |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
1087
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
1088
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ for more information. |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
=cut |
1094
|
|
|
|
|
|
|
|
1095
|
|
|
|
|
|
|
'end' |
1096
|
|
|
|
|
|
|
|