File Coverage

blib/lib/Term/Shell.pm
Criterion Covered Total %
statement 216 530 40.7
branch 56 208 26.9
condition 24 135 17.7
subroutine 37 83 44.5
pod 2 60 3.3
total 335 1016 32.9


line stmt bran cond sub pod time code
1             package Term::Shell;
2              
3 3     3   214802 use strict;
  3         32  
  3         91  
4 3     3   16 use warnings;
  3         9  
  3         80  
5              
6 3     3   83 use 5.014;
  3         9  
7              
8 3     3   1988 use Data::Dumper;
  3         21884  
  3         194  
9 3     3   1578 use Term::ReadLine ();
  3         8502  
  3         2660  
10              
11             our $VERSION = '0.10';
12              
13             #=============================================================================
14             # Term::Shell API methods
15             #=============================================================================
16             sub new
17             {
18 3     3 0 1077 my $cls = shift;
19             my $o = bless {
20 3   50     11 term => eval {
      33        
21              
22             # Term::ReadKey throws ugliness all over the place if we're not
23             # running in a terminal, which we aren't during "make test", at
24             # least on FreeBSD. Suppress warnings here.
25       0     local $SIG{__WARN__} = sub { };
26             Term::ReadLine->new('shell');
27             }
28             || undef,
29             },
30             ref($cls)
31             || $cls;
32              
33             # Set up the API hash:
34 3         36068 $o->{command} = {};
35             $o->{API} = {
36             args => \@_,
37             case_ignore => ( $^O eq 'MSWin32' ? 1 : 0 ),
38             check_idle => 0, # changing this isn't supported
39             class => $cls,
40             command => $o->{command},
41             cmd => $o->{command}, # shorthand
42             match_uniq => 1,
43             pager => $ENV{PAGER} || 'internal',
44 3 50 50     338 readline => eval { $o->{term}->ReadLine } || 'none',
      50        
45             script => ( caller(0) )[1],
46             version => $VERSION,
47             };
48              
49             # Note: the rl_completion_function doesn't pass an object as the first
50             # argument, so we have to use a closure. This has the unfortunate effect
51             # of preventing two instances of Term::ReadLine from coexisting.
52             my $completion_handler = sub {
53 0     0   0 $o->rl_complete(@_);
54 3         250 };
55 3 50       29 if ( $o->{API}{readline} eq 'Term::ReadLine::Gnu' )
    50          
56             {
57 0         0 my $attribs = $o->{term}->Attribs;
58 0         0 $attribs->{completion_function} = $completion_handler;
59             }
60             elsif ( $o->{API}{readline} eq 'Term::ReadLine::Perl' )
61             {
62 0         0 $readline::rl_completion_function =
63             $readline::rl_completion_function = $completion_handler;
64             }
65 3         78 $o->find_handlers;
66 3         49 $o->init;
67 3         42 $o;
68             }
69              
70             sub DESTROY
71             {
72 3     3   3264 my $o = shift;
73 3         57 $o->fini;
74             }
75              
76             sub cmd
77             {
78 0     0 0 0 my $o = shift;
79 0         0 $o->{line} = shift;
80 0 0       0 if ( $o->line =~ /\S/ )
81             {
82 0         0 my ( $cmd, @args ) = $o->line_parsed;
83 0         0 $o->run( $cmd, @args );
84 0 0       0 unless ( $o->{command}{run}{found} )
85             {
86 0         0 my @c = sort $o->possible_actions( $cmd, 'run' );
87 0 0 0     0 if ( @c and $o->{API}{match_uniq} )
88             {
89 0         0 print $o->msg_ambiguous_cmd( $cmd, @c );
90             }
91             else
92             {
93 0         0 print $o->msg_unknown_cmd($cmd);
94             }
95             }
96             }
97             else
98             {
99 0         0 $o->run('');
100             }
101             }
102              
103 0     0 0 0 sub stoploop { $_[0]->{stop}++ }
104              
105             sub cmdloop
106             {
107 0     0 0 0 my $o = shift;
108 0         0 $o->{stop} = 0;
109 0         0 $o->preloop;
110 0         0 while ( defined( my $line = $o->readline( $o->prompt_str ) ) )
111             {
112 0         0 $o->cmd($line);
113 0 0       0 last if $o->{stop};
114             }
115 0         0 $o->postloop;
116             }
117             *mainloop = \&cmdloop;
118              
119             sub readline
120             {
121 0     0 1 0 my $o = shift;
122 0         0 my $prompt = shift;
123             return $o->{term}->readline($prompt)
124             if $o->{API}{check_idle} == 0
125 0 0 0     0 or not defined $o->{term}->IN;
126              
127             # They've asked for idle-time running of some user command.
128 0         0 local $Term::ReadLine::toloop = 1;
129             local *Tk::fileevent = sub {
130 0     0   0 my $cls = shift;
131 0         0 my ( $file, $boring, $callback ) = @_;
132 0         0 $o->{fh} = $file; # save the filehandle!
133 0         0 $o->{cb} = $callback; # save the callback!
134 0         0 };
135             local *Tk::DoOneEvent = sub {
136              
137             # We'll totally cheat and do a select() here -- the timeout will be
138             # $o->{API}{check_idle}; if the handle is ready, we'll call &$cb;
139             # otherwise we'll call $o->idle(), which can do some processing.
140 0     0   0 my $timeout = $o->{API}{check_idle};
141 3     3   1559 use IO::Select;
  3         5321  
  3         15499  
142 0 0       0 if ( IO::Select->new( $o->{fh} )->can_read($timeout) )
143             {
144             # Input is ready: stop the event loop.
145 0         0 $o->{cb}->();
146             }
147             else
148             {
149 0         0 $o->idle;
150             }
151 0         0 };
152 0         0 $o->{term}->readline($prompt);
153             }
154              
155 0     0 0 0 sub term { $_[0]->{term} }
156              
157             # These are likely candidates for overriding in subclasses
158       3 0   sub init { } # called last in the ctor
159       3 0   sub fini { } # called first in the dtor
160       0 0   sub preloop { }
161       0 0   sub postloop { }
162       2 0   sub precmd { }
163       2 0   sub postcmd { }
164 0     0 0 0 sub prompt_str { 'shell> ' }
165       0 0   sub idle { }
166 55     55 0 108 sub cmd_prefix { '' }
167 55     55 0 88 sub cmd_suffix { '' }
168              
169             #=============================================================================
170             # The pager
171             #=============================================================================
172             sub page
173             {
174 1     1 0 8 my $o = shift;
175 1         7 my $text = shift;
176 1   33     28 my $maxlines = shift || $o->termsize->{rows};
177 1         26 my $pager = $o->{API}{pager};
178              
179             # First, count the number of lines in the text:
180 1         21 my $lines = ( $text =~ tr/\n// );
181              
182             # If there are fewer lines than the page-lines, just print it.
183 1 50 33     153 if ( $lines < $maxlines or $maxlines == 0 or $pager eq 'none' )
    0 33        
      0        
184             {
185 1         17 print $text;
186             }
187              
188             # If there are more, page it, either using the external pager...
189             elsif ( $pager and $pager ne 'internal' )
190             {
191 0         0 require File::Temp;
192 0         0 my ( $handle, $name ) = File::Temp::tempfile();
193 0         0 select( ( select($handle), $| = 1 )[0] );
194 0         0 print $handle $text;
195 0         0 close $handle;
196 0 0       0 system( $pager, $name ) == 0
197             or print <
198             Warning: can't run external pager '$pager': $!.
199             END
200 0         0 unlink $name;
201             }
202              
203             # ... or the internal one
204             else
205             {
206 0         0 my $togo = $lines;
207 0         0 my $line = 0;
208 0         0 my @lines = split '^', $text;
209 0         0 while ( $togo > 0 )
210             {
211 0         0 my @text = @lines[ $line .. $#lines ];
212 0         0 my $ret = $o->page_internal( \@text, $maxlines, $togo, $line );
213 0 0       0 last if $ret == -1;
214 0         0 $line += $ret;
215 0         0 $togo -= $ret;
216             }
217 0         0 return $line;
218             }
219 1         50 return $lines;
220             }
221              
222             sub page_internal
223             {
224 0     0 0 0 my $o = shift;
225 0         0 my $lines = shift;
226 0         0 my $maxlines = shift;
227 0         0 my $togo = shift;
228 0         0 my $start = shift;
229              
230 0         0 my $line = 1;
231 0         0 while ( $_ = shift @$lines )
232             {
233 0         0 print;
234 0 0       0 last if $line >= ( $maxlines - 1 ); # leave room for the prompt
235 0         0 $line++;
236             }
237 0         0 my $lines_left = $togo - $line;
238 0         0 my $current_line = $start + $line;
239 0         0 my $total_lines = $togo + $start;
240              
241 0         0 my $instructions;
242 0 0       0 if ( $o->have_readkey )
243             {
244 0         0 $instructions = "any key for more, or q to quit";
245             }
246             else
247             {
248 0         0 $instructions = "enter for more, or q to quit";
249             }
250              
251 0 0       0 if ( $lines_left > 0 )
252             {
253 0         0 local $| = 1;
254 0         0 my $l = "---line $current_line/$total_lines ($instructions)---";
255 0         0 my $b = ' ' x length($l);
256 0         0 print $l;
257 0         0 my $ans = $o->readkey;
258 0 0       0 print "\r$b\r" if $o->have_readkey;
259 0 0 0     0 print "\n" if $ans =~ /q/i or not $o->have_readkey;
260 0 0       0 $line = -1 if $ans =~ /q/i;
261             }
262 0         0 $line;
263             }
264              
265             #=============================================================================
266             # Run actions
267             #=============================================================================
268             sub run
269             {
270 0     0 0 0 my $o = shift;
271 0         0 my $action = shift;
272 0         0 my @args = @_;
273 0         0 $o->do_action( $action, \@args, 'run' );
274             }
275              
276             sub complete
277             {
278 0     0 0 0 my $o = shift;
279 0         0 my $action = shift;
280 0         0 my @args = @_;
281 0         0 my @compls = $o->do_action( $action, \@args, 'comp' );
282 0 0       0 return () unless $o->{command}{comp}{found};
283 0         0 return @compls;
284             }
285              
286             sub help
287             {
288 0     0 0 0 my $o = shift;
289 0         0 my $topic = shift;
290 0         0 my @subtopics = @_;
291 0         0 $o->do_action( $topic, \@subtopics, 'help' );
292             }
293              
294             sub summary
295             {
296 3     3 0 29 my $o = shift;
297 3         5 my $topic = shift;
298 3         16 $o->do_action( $topic, [], 'smry' );
299             }
300              
301             #=============================================================================
302             # Manually add & remove handlers
303             #=============================================================================
304             sub add_handlers
305             {
306 5     5 0 16 my $o = shift;
307             my $match = sub {
308 498 100   498   1183 if ( my ($ret) = shift =~ /\A(run|help|smry|comp|catch|alias)_/ )
309             {
310 64         148 return $ret;
311             }
312 434         571 return;
313 5         40 };
314             LOOP1:
315 5         33 for my $hnd (@_)
316             {
317 249         329 my $t = $match->($hnd);
318 249 100       473 next LOOP1 if !defined $t;
319 32         59 my $s = substr( $hnd, length($t) + 1 );
320              
321             # Add on the prefix and suffix if the command is defined
322 32 100       57 if ( length $s )
323             {
324 26         94 substr( $s, 0, 0 ) = $o->cmd_prefix;
325 26         63 $s .= $o->cmd_suffix;
326             }
327 32         150 $o->{handlers}{$s}{$t} = $hnd;
328             }
329             LOOP2:
330 5         18 for my $hnd (@_)
331             {
332 249         343 my $t = $match->($hnd);
333 249 100       460 next LOOP2 if !defined $t;
334 32         54 my $s = substr( $hnd, length($t) + 1 );
335              
336             # Add on the prefix and suffix if the command is defined
337 32 100       70 if ( length $s )
338             {
339 26         47 substr( $s, 0, 0 ) = $o->cmd_prefix;
340 26         44 $s .= $o->cmd_suffix;
341             }
342 32 50       71 if ( $o->has_aliases($s) )
343             {
344 0         0 my @s = $o->get_aliases($s);
345 0         0 for my $alias (@s)
346             {
347 0         0 substr( $alias, 0, 0 ) = $o->cmd_prefix;
348 0         0 $alias .= $o->cmd_suffix;
349 0         0 $o->{handlers}{$alias}{$t} = $hnd;
350             }
351             }
352             }
353             }
354              
355             sub add_commands
356             {
357 0     0 0 0 my $o = shift;
358 0         0 while (@_)
359             {
360 0         0 my ( $cmd, $hnd ) = ( shift, shift );
361 0         0 $o->{handlers}{$cmd} = $hnd;
362             }
363             }
364              
365             sub remove_handlers
366             {
367 0     0 0 0 my $o = shift;
368 0         0 for my $hnd (@_)
369             {
370 0 0       0 next unless $hnd =~ /^(run|help|smry|comp|catch|alias)_/o;
371 0         0 my $t = $1;
372 0         0 my $a = substr( $hnd, length($t) + 1 );
373              
374             # Add on the prefix and suffix if the command is defined
375 0 0       0 if ( length $a )
376             {
377 0         0 substr( $a, 0, 0 ) = $o->cmd_prefix;
378 0         0 $a .= $o->cmd_suffix;
379             }
380 0         0 delete $o->{handlers}{$a}{$t};
381             }
382             }
383              
384             sub remove_commands
385             {
386 0     0 0 0 my $o = shift;
387 0         0 for my $name (@_)
388             {
389 0         0 delete $o->{handlers}{$name};
390             }
391             }
392              
393             *add_handler = \&add_handlers;
394             *add_command = \&add_commands;
395             *remove_handler = \&remove_handlers;
396             *remove_command = \&remove_commands;
397              
398             #=============================================================================
399             # Utility methods
400             #=============================================================================
401             sub termsize
402             {
403 3     3 0 9 my $o = shift;
404 3         8 my ( $rows, $cols ) = ( 24, 78 );
405              
406             # Try several ways to get the terminal size
407             TERMSIZE:
408             {
409 3         4 my $TERM = $o->{term};
  3         7  
410 3 50       8 last TERMSIZE unless $TERM;
411              
412 3         31 my $OUT = $TERM->OUT;
413              
414 3 50 33     38 if ( $TERM and $o->{API}{readline} eq 'Term::ReadLine::Gnu' )
415             {
416 0         0 ( $rows, $cols ) = $TERM->get_screen_size;
417 0         0 last TERMSIZE;
418             }
419              
420 3 50 33     29 if ( $^O eq 'MSWin32' and eval { require Win32::Console } )
  0         0  
421             {
422 0         0 Win32::Console->import;
423              
424             # Win32::Console's DESTROY does a CloseHandle(), so save the object:
425 0   0     0 $o->{win32_stdout} ||= Win32::Console->new( STD_OUTPUT_HANDLE() );
426 0         0 my @info = $o->{win32_stdout}->Info;
427 0         0 $cols = $info[7] - $info[5] + 1; # right - left + 1
428 0         0 $rows = $info[8] - $info[6] + 1; # bottom - top + 1
429 0         0 last TERMSIZE;
430             }
431              
432 3 50       7 if ( eval { require Term::Size } )
  3         586  
433             {
434 0         0 my @x = Term::Size::chars($OUT);
435 0 0 0     0 if ( @x == 2 and $x[0] )
436             {
437 0         0 ( $cols, $rows ) = @x;
438 0         0 last TERMSIZE;
439             }
440             }
441              
442 3 50       14 if ( eval { require Term::Screen } )
  3         447  
443             {
444 0         0 my $screen = Term::Screen->new;
445 0         0 ( $rows, $cols ) = @$screen{qw(ROWS COLS)};
446 0         0 last TERMSIZE;
447             }
448              
449 3 50       11 if ( eval { require Term::ReadKey } )
  3         734  
450             {
451 3         2823 ( $cols, $rows ) = eval {
452 3     3   39 local $SIG{__WARN__} = sub { };
453 3         13 Term::ReadKey::GetTerminalSize($OUT);
454             };
455 3 50       148 last TERMSIZE unless $@;
456             }
457              
458 0 0 0     0 if ( $ENV{LINES} or $ENV{ROWS} or $ENV{COLUMNS} )
      0        
459             {
460 0   0     0 $rows = $ENV{LINES} || $ENV{ROWS} || $rows;
461 0   0     0 $cols = $ENV{COLUMNS} || $cols;
462 0         0 last TERMSIZE;
463             }
464              
465             {
466 0         0 local $^W;
  0         0  
467 0 0       0 if ( open( my $STTY, "-|", "stty", "size" ) )
468             {
469 0         0 my $l = <$STTY>;
470 0         0 ( $rows, $cols ) = split /\s+/, $l;
471 0         0 close $STTY;
472             }
473             }
474             }
475              
476 3         107 return { rows => $rows, cols => $cols };
477             }
478              
479             sub readkey
480             {
481 0     0 0 0 my $o = shift;
482 0 0       0 $o->have_readkey unless $o->{readkey};
483 0         0 $o->{readkey}->();
484             }
485              
486             sub have_readkey
487             {
488 0     0 0 0 my $o = shift;
489 0 0       0 return 1 if $o->{have_readkey};
490 0         0 my $IN = $o->{term}->IN;
491 0 0 0     0 if ( eval { require Term::InKey } )
  0 0       0  
    0          
492             {
493 0         0 $o->{readkey} = \&Term::InKey::ReadKey;
494             }
495 0         0 elsif ( $^O eq 'MSWin32' and eval { require Win32::Console } )
496             {
497             $o->{readkey} = sub {
498 0     0   0 my $c;
499              
500             # from Term::InKey:
501 0         0 eval {
502             # Win32::Console's DESTROY does a CloseHandle(), so save it:
503 0         0 Win32::Console->import;
504 0   0     0 $o->{win32_stdin} ||= Win32::Console->new( STD_INPUT_HANDLE() );
505 0 0       0 my $mode = my $orig = $o->{win32_stdin}->Mode or die $^E;
506 0         0 $mode &= ~( ENABLE_LINE_INPUT() | ENABLE_ECHO_INPUT() );
507 0 0       0 $o->{win32_stdin}->Mode($mode) or die $^E;
508              
509 0 0       0 $o->{win32_stdin}->Flush or die $^E;
510 0         0 $c = $o->{win32_stdin}->InputChar(1);
511 0 0       0 die $^E unless defined $c;
512 0 0       0 $o->{win32_stdin}->Mode($orig) or die $^E;
513             };
514 0 0       0 die "Not implemented on $^O: $@" if $@;
515 0         0 $c;
516 0         0 };
517             }
518 0         0 elsif ( eval { require Term::ReadKey } )
519             {
520             $o->{readkey} = sub {
521 0     0   0 Term::ReadKey::ReadMode( 4, $IN );
522 0         0 my $c = getc($IN);
523 0         0 Term::ReadKey::ReadMode( 0, $IN );
524 0         0 $c;
525 0         0 };
526             }
527             else
528             {
529 0     0   0 $o->{readkey} = sub { scalar <$IN> };
  0         0  
530 0         0 return $o->{have_readkey} = 0;
531             }
532 0         0 return $o->{have_readkey} = 1;
533             }
534             *has_readkey = \&have_readkey;
535              
536             sub prompt
537             {
538 0     0 0 0 my $o = shift;
539 0         0 my ( $prompt, $default, $completions, $casei ) = @_;
540 0         0 my $term = $o->{term};
541              
542             # A closure to read the line.
543 0         0 my $line;
544             my $readline = sub {
545 0     0   0 my ( $sh, $gh ) = @{ $term->Features }{qw(setHistory getHistory)};
  0         0  
546 0 0       0 my @history = $gh ? $term->GetHistory : ();
547 0 0       0 $term->SetHistory() if $sh;
548 0         0 $line = $o->readline($prompt);
549 0 0 0     0 $line = $default
      0        
550             if ( ( not defined $line or $line =~ /^\s*$/ )
551             and defined $default );
552              
553             # Restore the history
554 0 0       0 $term->SetHistory(@history) if $sh;
555 0         0 $line;
556 0         0 };
557              
558             # A closure to complete the line.
559             my $complete = sub {
560 0     0   0 my ( $word, $line, $start ) = @_;
561 0         0 return $o->completions( $word, $completions, $casei );
562 0         0 };
563              
564 0 0 0     0 if ( $term and $term->ReadLine eq 'Term::ReadLine::Gnu' )
    0 0        
565             {
566 0         0 my $attribs = $term->Attribs;
567 0         0 local $attribs->{completion_function} = $complete;
568 0         0 &$readline;
569             }
570             elsif ( $term and $term->ReadLine eq 'Term::ReadLine::Perl' )
571             {
572 0         0 local $readline::rl_completion_function = $complete;
573 0         0 &$readline;
574             }
575             else
576             {
577 0         0 &$readline;
578             }
579 0         0 $line;
580             }
581              
582             sub format_pairs
583             {
584 2     2 0 7 my $o = shift;
585 2         3 my @keys = @{ shift(@_) };
  2         6  
586 2         4 my @vals = @{ shift(@_) };
  2         4  
587 2   50     6 my $sep = shift || ": ";
588 2   50     6 my $left = shift || 0;
589 2   50     15 my $ind = shift || "";
590 2   50     21 my $len = shift || 0;
591 2   50     7 my $wrap = shift || 0;
592              
593 2 50       5 if ($wrap)
594             {
595 0         0 eval {
596 0         0 require Text::Autoformat;
597 0         0 Text::Autoformat->import(qw(autoformat));
598             };
599 0 0       0 if ($@)
600             {
601 0 0       0 warn(
602             "Term::Shell::format_pairs(): Text::Autoformat is required "
603             . "for wrapping. Wrapping disabled" )
604             if $^W;
605 0         0 $wrap = 0;
606             }
607             }
608 2   33     26 my $cols = shift || $o->termsize->{cols};
609 2   66     26 $len < length($_) and $len = length($_) for @keys;
610 2         9 my @text;
611 2         21 for my $i ( 0 .. $#keys )
612             {
613 3 50       17 next unless defined $vals[$i];
614 3         7 my $sz = ( $len - length( $keys[$i] ) );
615 3 50       15 my $lpad = $left ? "" : " " x $sz;
616 3 50       25 my $rpad = $left ? " " x $sz : "";
617 3         9 my $l = "$ind$lpad$keys[$i]$rpad$sep";
618 3   33     42 my $wrap = $wrap & ( $vals[$i] =~ /\s/ and $vals[$i] !~ /^\d/ );
619 3 50       12 my $form = (
620             $wrap
621             ? autoformat(
622             "$vals[$i]", # force stringification
623             { left => length($l) + 1, right => $cols, all => 1 },
624             )
625             : "$l$vals[$i]\n"
626             );
627 3         8 substr( $form, 0, length($l), $l );
628 3         18 push @text, $form;
629             }
630 2         16 my $text = join '', @text;
631 2 50       38 return wantarray ? ( $text, $len ) : $text;
632             }
633              
634             sub print_pairs
635             {
636 0     0 0 0 my $o = shift;
637 0         0 my ( $text, $len ) = $o->format_pairs(@_);
638 0         0 $o->page($text);
639 0         0 return $len;
640             }
641              
642             # Handle backslash translation; doesn't do anything complicated yet.
643             sub process_esc
644             {
645 0     0 0 0 my $o = shift;
646 0         0 my $c = shift;
647 0         0 my $q = shift;
648 0         0 my $n;
649 0 0       0 return '\\' if $c eq '\\';
650 0 0       0 return $q if $c eq $q;
651 0         0 return "\\$c";
652             }
653              
654             # Parse a quoted string
655             sub parse_quoted
656             {
657 0     0 0 0 my $o = shift;
658 0         0 my $raw = shift;
659 0         0 my $quote = shift;
660 0         0 my $i = 1;
661 0         0 my $string = '';
662 0         0 my $c;
663 0   0     0 while ( $i <= length($raw) and ( $c = substr( $raw, $i, 1 ) ) ne $quote )
664             {
665              
666 0 0       0 if ( $c eq '\\' )
667             {
668 0         0 $string .= $o->process_esc( substr( $raw, $i + 1, 1 ), $quote );
669 0         0 $i++;
670             }
671             else
672             {
673 0         0 $string .= substr( $raw, $i, 1 );
674             }
675 0         0 $i++;
676             }
677 0         0 return ( $string, $i );
678             }
679              
680             sub line
681             {
682 0     0 0 0 my $o = shift;
683 0         0 $o->{line};
684             }
685              
686             sub line_args
687             {
688 0     0 0 0 my $o = shift;
689 0   0     0 my $line = shift || $o->line;
690 0         0 $o->line_parsed($line);
691 0 0       0 $o->{line_args} || '';
692             }
693              
694             sub line_parsed
695             {
696 0     0 0 0 my $o = shift;
697 0   0     0 my $args = shift || $o->line || return ();
698 0         0 my @args;
699              
700             # Parse an array of arguments. Whitespace separates, unless quoted.
701 0         0 my $arg = undef;
702 0         0 $o->{line_args} = undef;
703 0         0 for ( my $i = 0 ; $i < length($args) ; $i++ )
704             {
705 0         0 my $c = substr( $args, $i, 1 );
706 0 0 0     0 if ( $c =~ /\S/ and @args == 1 )
707             {
708 0   0     0 $o->{line_args} ||= substr( $args, $i );
709             }
710 0 0       0 if ( $c =~ /['"]/ )
    0          
711             {
712 0         0 my ( $str, $n ) = $o->parse_quoted( substr( $args, $i ), $c );
713 0         0 $i += $n;
714 0 0       0 $arg = ( defined($arg) ? $arg : '' ) . $str;
715             }
716              
717             # We do not parse outside of strings
718             # elsif ($c eq '\\') {
719             # $arg = (defined($arg) ? $arg : '')
720             # . $o->process_esc(substr($args,$i+1,1));
721             # $i++;
722             # }
723             elsif ( $c =~ /\s/ )
724             {
725 0 0       0 push @args, $arg if defined $arg;
726 0         0 $arg = undef;
727             }
728             else
729             {
730 0         0 $arg .= substr( $args, $i, 1 );
731             }
732             }
733 0 0       0 push @args, $arg if defined($arg);
734 0         0 return @args;
735             }
736              
737             sub handler
738             {
739 3     3 1 5 my $o = shift;
740 3         9 my ( $command, $type, $args, $preserve_args ) = @_;
741              
742             # First try finding the standard handler, then fallback to the
743             # catch_$type method. The columns represent "action", "type", and "push",
744             # which control whether the name of the command should be pushed onto the
745             # args.
746 3         15 my @tries = (
747             [ $command, $type, 0 ],
748             [ $o->cmd_prefix . $type . $o->cmd_suffix, 'catch', 1 ],
749             );
750              
751             # The user can control whether or not to search for "unique" matches,
752             # which means calling $o->possible_actions(). We always look for exact
753             # matches.
754 3         16 my @matches = qw(exact_action);
755 3 50       11 push @matches, qw(possible_actions) if $o->{API}{match_uniq};
756              
757 3         12 for my $try (@tries)
758             {
759 4         11 my ( $cmd, $type, $add_cmd_name ) = @$try;
760 4         8 for my $match (@matches)
761             {
762 6         23 my @handlers = $o->$match( $cmd, $type );
763 6 100       17 next unless @handlers == 1;
764 2 50 33     14 unshift @$args, $command
765             if $add_cmd_name and not $preserve_args;
766 2         11 return $o->unalias( $handlers[0], $type );
767             }
768             }
769 1         3 return;
770             }
771              
772             sub completions
773             {
774 0     0 0 0 my $o = shift;
775 0         0 my $action = shift;
776 0   0     0 my $compls = shift || [];
777 0         0 my $casei = shift;
778 0 0       0 $casei = $o->{API}{case_ignore} unless defined $casei;
779 0 0       0 $casei = $casei ? '(?i)' : '';
780 0         0 return grep { $_ =~ /$casei^\Q$action\E/ } @$compls;
  0         0  
781             }
782              
783             #=============================================================================
784             # Term::Shell error messages
785             #=============================================================================
786             sub msg_ambiguous_cmd
787             {
788 0     0 0 0 my ( $o, $cmd, @c ) = @_;
789 0         0 local $" = "\n\t";
790 0         0 <
791             Ambiguous command '$cmd': possible commands:
792             @c
793             END
794             }
795              
796             sub msg_unknown_cmd
797             {
798 0     0 0 0 my ( $o, $cmd ) = @_;
799 0         0 <
800             Unknown command '$cmd'; type 'help' for a list of commands.
801             END
802             }
803              
804             #=============================================================================
805             # Term::Shell private methods
806             #=============================================================================
807             sub do_action
808             {
809 3     3 0 5 my $o = shift;
810 3         6 my $cmd = shift;
811 3   50     16 my $args = shift || [];
812 3   50     8 my $type = shift || 'run';
813 3         22 my ( $fullname, $cmdname, $handler ) = $o->handler( $cmd, $type, $args );
814 3 100       37 $o->{command}{$type} = {
815             cmd => $cmd,
816             name => $cmd,
817             found => defined $handler ? 1 : 0,
818             cmdfull => $fullname,
819             cmdreal => $cmdname,
820             handler => $handler,
821             };
822 3 100       19 if ( defined $handler )
823             {
824             # We've found a handler. Set up a value which will call the postcmd()
825             # action as the subroutine leaves. Then call the precmd(), then return
826             # the result of running the handler.
827 2         13 $o->precmd( \$handler, \$cmd, $args );
828             my $postcmd = Term::Shell::OnScopeLeave->new(
829             sub {
830 2     2   11 $o->postcmd( \$handler, \$cmd, $args );
831             }
832 2         29 );
833 2         14 return $o->$handler(@$args);
834             }
835             }
836              
837             sub uniq
838             {
839 0     0 0 0 my $o = shift;
840 0         0 my %seen;
841 0         0 $seen{$_}++ for @_;
842 0         0 my @ret;
843 0 0       0 for (@_) { push @ret, $_ if $seen{$_}-- == 1 }
  0         0  
844 0         0 @ret;
845             }
846              
847             sub possible_actions
848             {
849 8     8 0 4027 my $o = shift;
850 8         28 my $action = shift;
851 8         15 my $type = shift;
852 8 50       29 my $casei = $o->{API}{case_ignore} ? '(?i)' : '';
853 26         164 my @keys = grep { $_ =~ /$casei^\Q$action\E/ }
854 38         109 grep { exists $o->{handlers}{$_}{$type} }
855 8         13 keys %{ $o->{handlers} };
  8         26  
856 8         45 return @keys;
857             }
858              
859             sub exact_action
860             {
861 4     4 0 7 my $o = shift;
862 4         7 my $action = shift;
863 4         5 my $type = shift;
864 4 50       11 my $casei = $o->{API}{case_ignore} ? '(?i)' : '';
865 6         73 my @key = grep { $action =~ /$casei^\Q$_\E$/ }
866 16         32 grep { exists $o->{handlers}{$_}{$type} }
867 4         5 keys %{ $o->{handlers} };
  4         14  
868 4 100       15 return () unless @key == 1;
869 2         6 return $key[0];
870             }
871              
872             sub is_alias
873             {
874 0     0 0 0 my $o = shift;
875 0         0 my $action = shift;
876 0 0       0 exists $o->{handlers}{$action}{alias} ? 1 : 0;
877             }
878              
879             sub has_aliases
880             {
881 32     32 0 42 my $o = shift;
882 32         42 my $action = shift;
883 32         63 my @a = $o->get_aliases($action);
884 32 50       97 @a ? 1 : 0;
885             }
886              
887             sub get_aliases
888             {
889 32     32 0 38 my $o = shift;
890 32         38 my $action = shift;
891 32         43 my @a = eval {
892 32         62 my $hndlr = $o->{handlers}{$action}{alias};
893 32 50       75 return () unless $hndlr;
894 0         0 $o->$hndlr();
895             };
896 32         54 $o->{aliases}{$_} = $action for @a;
897 32         51 @a;
898             }
899              
900             sub unalias
901             {
902 2     2 0 3 my $o = shift;
903 2         4 my $cmd = shift; # i.e 'foozle'
904 2         4 my $type = shift; # i.e 'run'
905 2 50       12 return () unless $type;
906             return ( $cmd, $cmd, $o->{handlers}{$cmd}{$type} )
907 2 50       23 unless exists $o->{aliases}{$cmd};
908 0         0 my $alias = $o->{aliases}{$cmd};
909              
910             # I'm allowing aliases to call handlers which have been removed. This
911             # means I can set up an alias of '!' for 'shell', then delete the 'shell'
912             # command, so that you can only access it through '!'. That's why I'm
913             # checking the {handlers} entry _and_ building a string.
914 0   0     0 my $handler = $o->{handlers}{$alias}{$type} || "${type}_${alias}";
915 0         0 return ( $cmd, $alias, $handler );
916             }
917              
918             sub find_handlers
919             {
920 5     5 0 19 my $o = shift;
921 5   66     37 my $pkg = shift || $o->{API}{class};
922              
923             # Find the handlers in the given namespace:
924 5         11 my %handlers;
925             {
926             ## no critic
927 3     3   28 no strict 'refs';
  3         6  
  3         202  
928 5         9 my @r = keys %{ $pkg . "::" };
  5         541  
929 5         97 $o->add_handlers(@r);
930             }
931              
932             # Find handlers in its base classes.
933             {
934             ## no critic
935 3     3   19 no strict 'refs';
  3         16  
  3         3403  
  5         8  
  5         9  
936 5         11 my @isa = @{ $pkg . "::ISA" };
  5         83  
937 5         33 for my $pkg (@isa)
938             {
939 2         28 $o->find_handlers($pkg);
940             }
941             }
942             }
943              
944             sub rl_complete
945             {
946 0     0 0 0 my $o = shift;
947 0         0 my ( $word, $line, $start ) = @_;
948              
949             # If it's a command, complete 'run_':
950 0 0 0     0 if ( $start == 0 or substr( $line, 0, $start ) =~ /^\s*$/ )
951             {
952 0         0 my @compls = $o->complete( '', $word, $line, $start );
953 0 0       0 return @compls if $o->{command}{comp}{found};
954             }
955              
956             # If it's a subcommand, send it to any custom completion function for the
957             # function:
958             else
959             {
960 0         0 my $command = ( $o->line_parsed($line) )[0];
961 0         0 my @compls = $o->complete( $command, $word, $line, $start );
962 0 0       0 return @compls if $o->{command}{comp}{found};
963             }
964              
965 0         0 ();
966             }
967              
968             #=============================================================================
969             # Two action handlers provided by default: help and exit.
970             #=============================================================================
971 1     1 0 12 sub smry_exit { "exits the program" }
972              
973             sub help_exit
974             {
975 0     0 0 0 <<'END';
976             Exits the program.
977             END
978             }
979              
980             sub run_exit
981             {
982 0     0 0 0 my $o = shift;
983 0         0 $o->stoploop;
984             }
985              
986 1     1 0 37 sub smry_help { "prints this screen, or help on 'command'" }
987              
988             sub help_help
989             {
990 0     0 0 0 <<'END';
991             Provides help on commands...
992             END
993             }
994              
995             sub comp_help
996             {
997 0     0 0 0 my ( $o, $word, $line, $start ) = @_;
998 0         0 my @words = $o->line_parsed($line);
999 0 0 0     0 return []
      0        
1000             if ( @words > 2 or @words == 2 and $start == length($line) );
1001 0         0 sort $o->possible_actions( $word, 'help' );
1002             }
1003              
1004             sub run_help
1005             {
1006 1     1 0 25 my $o = shift;
1007 1         2 my $cmd = shift;
1008 1 50       4 if ($cmd)
1009             {
1010 0         0 my $txt = $o->help( $cmd, @_ );
1011 0 0       0 if ( $o->{command}{help}{found} )
1012             {
1013 0         0 $o->page($txt);
1014             }
1015             else
1016             {
1017 0         0 my @c = sort $o->possible_actions( $cmd, 'help' );
1018 0 0 0     0 if ( @c and $o->{API}{match_uniq} )
1019             {
1020 0         0 local $" = "\n\t";
1021 0         0 print <
1022             Ambiguous help topic '$cmd': possible help topics:
1023             @c
1024             END
1025             }
1026             else
1027             {
1028 0         0 print <
1029             Unknown help topic '$cmd'; type 'help' for a list of help topics.
1030             END
1031             }
1032             }
1033             }
1034             else
1035             {
1036 1         52 print "Type 'help command' for more detailed help on a command.\n";
1037 1         8 my ( %cmds, %docs );
1038 1         0 my %done;
1039 1         0 my %handlers;
1040 1         2 for my $h ( keys %{ $o->{handlers} } )
  1         7  
1041             {
1042 4 100       11 next unless length($h);
1043             next
1044 3 50       7 unless grep { defined $o->{handlers}{$h}{$_} }
  9         32  
1045             qw(run smry help);
1046 3 50       10 my $dest = exists $o->{handlers}{$h}{run} ? \%cmds : \%docs;
1047 3 100       4 my $smry = do { my $x = $o->summary($h); $x ? $x : "undocumented" };
  3         20  
  3         16  
1048             my $help =
1049             exists $o->{handlers}{$h}{help}
1050             ? (
1051             exists $o->{handlers}{$h}{smry}
1052 3 50       23 ? ""
    100          
1053             : " - but help available"
1054             )
1055             : " - no help available";
1056 3         16 $dest->{" $h"} = "$smry$help";
1057             }
1058 1         3 my @t;
1059 1 50       4 push @t, " Commands:\n" if %cmds;
1060             push @t,
1061             scalar $o->format_pairs(
1062             [ sort keys %cmds ],
1063 1         8 [ map { $cmds{$_} } sort keys %cmds ],
  3         21  
1064             ' - ', 1
1065             );
1066 1 50       23 push @t, " Extra Help Topics: (not commands)\n" if %docs;
1067             push @t,
1068             scalar $o->format_pairs(
1069             [ sort keys %docs ],
1070 1         27 [ map { $docs{$_} } sort keys %docs ],
  0         0  
1071             ' - ', 1
1072             );
1073 1         70 $o->page( join '', @t );
1074             }
1075             }
1076              
1077       0 0   sub run_ { }
1078              
1079             sub comp_
1080             {
1081 0     0 0 0 my ( $o, $word, $line, $start ) = @_;
1082 0         0 my @comp = grep { length($_) } sort $o->possible_actions( $word, 'run' );
  0         0  
1083 0         0 return @comp;
1084             }
1085              
1086             package Term::Shell::OnScopeLeave;
1087              
1088             sub new
1089             {
1090 2   33 2   22 return bless [ @_[ 1 .. $#_ ] ], ref( $_[0] ) || $_[0];
1091             }
1092              
1093             sub DESTROY
1094             {
1095 2     2   7 my $o = shift;
1096 2         17 for my $c (@$o)
1097             {
1098 2         5 $c->();
1099             }
1100              
1101 2         12 return;
1102             }
1103              
1104             1;
1105              
1106             __END__