File Coverage

blib/lib/Term/Shell/MultiCmd.pm
Criterion Covered Total %
statement 143 360 39.7
branch 43 246 17.4
condition 22 114 19.3
subroutine 25 50 50.0
pod 10 10 100.0
total 243 780 31.1


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