File Coverage

blib/lib/Term/Shell/MultiCmd.pm
Criterion Covered Total %
statement 168 410 40.9
branch 55 284 19.3
condition 30 137 21.9
subroutine 28 55 50.9
pod 10 10 100.0
total 291 896 32.4


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