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