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