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   204700 use strict;
  3         31  
  3         90  
4 3     3   15 use warnings;
  3         6  
  3         75  
5              
6 3     3   71 use 5.014;
  3         10  
7              
8 3     3   1956 use Data::Dumper;
  3         20621  
  3         185  
9 3     3   1500 use Term::ReadLine ();
  3         8216  
  3         2129  
10              
11             our $VERSION = '0.10';
12              
13             #=============================================================================
14             # Term::Shell API methods
15             #=============================================================================
16             sub new
17             {
18 3     3 0 1105 my $cls = shift;
19             my $o = bless {
20 3   50     8 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         41852 $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     336 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         259 };
55 3 50       25 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         77 $o->find_handlers;
66 3         38 $o->init;
67 3         35 $o;
68             }
69              
70             sub DESTROY
71             {
72 3     3   3184 my $o = shift;
73 3         60 $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   1492 use IO::Select;
  3         4999  
  3         14374  
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 98 sub cmd_prefix { '' }
167 55     55 0 93 sub cmd_suffix { '' }
168              
169             #=============================================================================
170             # The pager
171             #=============================================================================
172             sub page
173             {
174 1     1 0 14 my $o = shift;
175 1         4 my $text = shift;
176 1   33     32 my $maxlines = shift || $o->termsize->{rows};
177 1         22 my $pager = $o->{API}{pager};
178              
179             # First, count the number of lines in the text:
180 1         11 my $lines = ( $text =~ tr/\n// );
181              
182             # If there are fewer lines than the page-lines, just print it.
183 1 50 33     128 if ( $lines < $maxlines or $maxlines == 0 or $pager eq 'none' )
    0 33        
      0        
184             {
185 1         18 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         52 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 27 my $o = shift;
297 3         4 my $topic = shift;
298 3         18 $o->do_action( $topic, [], 'smry' );
299             }
300              
301             #=============================================================================
302             # Manually add & remove handlers
303             #=============================================================================
304             sub add_handlers
305             {
306 5     5 0 13 my $o = shift;
307             my $match = sub {
308 498 100   498   1159 if ( my ($ret) = shift =~ /\A(run|help|smry|comp|catch|alias)_/ )
309             {
310 64         130 return $ret;
311             }
312 434         559 return;
313 5         31 };
314             LOOP1:
315 5         28 for my $hnd (@_)
316             {
317 249         336 my $t = $match->($hnd);
318 249 100       447 next LOOP1 if !defined $t;
319 32         60 my $s = substr( $hnd, length($t) + 1 );
320              
321             # Add on the prefix and suffix if the command is defined
322 32 100       53 if ( length $s )
323             {
324 26         91 substr( $s, 0, 0 ) = $o->cmd_prefix;
325 26         67 $s .= $o->cmd_suffix;
326             }
327 32         143 $o->{handlers}{$s}{$t} = $hnd;
328             }
329             LOOP2:
330 5         16 for my $hnd (@_)
331             {
332 249         343 my $t = $match->($hnd);
333 249 100       460 next LOOP2 if !defined $t;
334 32         52 my $s = substr( $hnd, length($t) + 1 );
335              
336             # Add on the prefix and suffix if the command is defined
337 32 100       72 if ( length $s )
338             {
339 26         46 substr( $s, 0, 0 ) = $o->cmd_prefix;
340 26         46 $s .= $o->cmd_suffix;
341             }
342 32 50       70 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 7 my $o = shift;
404 3         9 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       9 last TERMSIZE unless $TERM;
411              
412 3         37 my $OUT = $TERM->OUT;
413              
414 3 50 33     37 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     31 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         582  
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       16 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       12 if ( eval { require Term::ReadKey } )
  3         749  
450             {
451 3         2390 ( $cols, $rows ) = eval {
452 3     3   34 local $SIG{__WARN__} = sub { };
453 3         12 Term::ReadKey::GetTerminalSize($OUT);
454             };
455 3 50       143 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         91 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         4 my @keys = @{ shift(@_) };
  2         5  
586 2         5 my @vals = @{ shift(@_) };
  2         5  
587 2   50     7 my $sep = shift || ": ";
588 2   50     7 my $left = shift || 0;
589 2   50     22 my $ind = shift || "";
590 2   50     11 my $len = shift || 0;
591 2   50     21 my $wrap = shift || 0;
592 2 50       6 if ($wrap)
593             {
594 0         0 eval {
595 0         0 require Text::Autoformat;
596 0         0 Text::Autoformat->import(qw(autoformat));
597             };
598 0 0       0 if ($@)
599             {
600 0 0       0 warn(
601             "Term::Shell::format_pairs(): Text::Autoformat is required "
602             . "for wrapping. Wrapping disabled" )
603             if $^W;
604 0         0 $wrap = 0;
605             }
606             }
607 2   33     23 my $cols = shift || $o->termsize->{cols};
608 2   66     32 $len < length($_) and $len = length($_) for @keys;
609 2         6 my @text;
610 2         20 for my $i ( 0 .. $#keys )
611             {
612 3 50       17 next unless defined $vals[$i];
613 3         6 my $sz = ( $len - length( $keys[$i] ) );
614 3 50       19 my $lpad = $left ? "" : " " x $sz;
615 3 50       28 my $rpad = $left ? " " x $sz : "";
616 3         9 my $l = "$ind$lpad$keys[$i]$rpad$sep";
617 3   33     36 my $wrap = $wrap & ( $vals[$i] =~ /\s/ and $vals[$i] !~ /^\d/ );
618 3 50       15 my $form = (
619             $wrap
620             ? autoformat(
621             "$vals[$i]", # force stringification
622             { left => length($l) + 1, right => $cols, all => 1 },
623             )
624             : "$l$vals[$i]\n"
625             );
626 3         7 substr( $form, 0, length($l), $l );
627 3         7 push @text, $form;
628             }
629 2         15 my $text = join '', @text;
630 2 50       47 return wantarray ? ( $text, $len ) : $text;
631             }
632              
633             sub print_pairs
634             {
635 0     0 0 0 my $o = shift;
636 0         0 my ( $text, $len ) = $o->format_pairs(@_);
637 0         0 $o->page($text);
638 0         0 return $len;
639             }
640              
641             # Handle backslash translation; doesn't do anything complicated yet.
642             sub process_esc
643             {
644 0     0 0 0 my $o = shift;
645 0         0 my $c = shift;
646 0         0 my $q = shift;
647 0         0 my $n;
648 0 0       0 return '\\' if $c eq '\\';
649 0 0       0 return $q if $c eq $q;
650 0         0 return "\\$c";
651             }
652              
653             # Parse a quoted string
654             sub parse_quoted
655             {
656 0     0 0 0 my $o = shift;
657 0         0 my $raw = shift;
658 0         0 my $quote = shift;
659 0         0 my $i = 1;
660 0         0 my $string = '';
661 0         0 my $c;
662 0   0     0 while ( $i <= length($raw) and ( $c = substr( $raw, $i, 1 ) ) ne $quote )
663             {
664              
665 0 0       0 if ( $c eq '\\' )
666             {
667 0         0 $string .= $o->process_esc( substr( $raw, $i + 1, 1 ), $quote );
668 0         0 $i++;
669             }
670             else
671             {
672 0         0 $string .= substr( $raw, $i, 1 );
673             }
674 0         0 $i++;
675             }
676 0         0 return ( $string, $i );
677             }
678              
679             sub line
680             {
681 0     0 0 0 my $o = shift;
682 0         0 $o->{line};
683             }
684              
685             sub line_args
686             {
687 0     0 0 0 my $o = shift;
688 0   0     0 my $line = shift || $o->line;
689 0         0 $o->line_parsed($line);
690 0 0       0 $o->{line_args} || '';
691             }
692              
693             sub line_parsed
694             {
695 0     0 0 0 my $o = shift;
696 0   0     0 my $args = shift || $o->line || return ();
697 0         0 my @args;
698              
699             # Parse an array of arguments. Whitespace separates, unless quoted.
700 0         0 my $arg = undef;
701 0         0 $o->{line_args} = undef;
702 0         0 for ( my $i = 0 ; $i < length($args) ; $i++ )
703             {
704 0         0 my $c = substr( $args, $i, 1 );
705 0 0 0     0 if ( $c =~ /\S/ and @args == 1 )
706             {
707 0   0     0 $o->{line_args} ||= substr( $args, $i );
708             }
709 0 0       0 if ( $c =~ /['"]/ )
    0          
710             {
711 0         0 my ( $str, $n ) = $o->parse_quoted( substr( $args, $i ), $c );
712 0         0 $i += $n;
713 0 0       0 $arg = ( defined($arg) ? $arg : '' ) . $str;
714             }
715              
716             # We do not parse outside of strings
717             # elsif ($c eq '\\') {
718             # $arg = (defined($arg) ? $arg : '')
719             # . $o->process_esc(substr($args,$i+1,1));
720             # $i++;
721             # }
722             elsif ( $c =~ /\s/ )
723             {
724 0 0       0 push @args, $arg if defined $arg;
725 0         0 $arg = undef;
726             }
727             else
728             {
729 0         0 $arg .= substr( $args, $i, 1 );
730             }
731             }
732 0 0       0 push @args, $arg if defined($arg);
733 0         0 return @args;
734             }
735              
736             sub handler
737             {
738 3     3 1 6 my $o = shift;
739 3         7 my ( $command, $type, $args, $preserve_args ) = @_;
740              
741             # First try finding the standard handler, then fallback to the
742             # catch_$type method. The columns represent "action", "type", and "push",
743             # which control whether the name of the command should be pushed onto the
744             # args.
745 3         15 my @tries = (
746             [ $command, $type, 0 ],
747             [ $o->cmd_prefix . $type . $o->cmd_suffix, 'catch', 1 ],
748             );
749              
750             # The user can control whether or not to search for "unique" matches,
751             # which means calling $o->possible_actions(). We always look for exact
752             # matches.
753 3         14 my @matches = qw(exact_action);
754 3 50       11 push @matches, qw(possible_actions) if $o->{API}{match_uniq};
755              
756 3         9 for my $try (@tries)
757             {
758 4         10 my ( $cmd, $type, $add_cmd_name ) = @$try;
759 4         8 for my $match (@matches)
760             {
761 6         20 my @handlers = $o->$match( $cmd, $type );
762 6 100       18 next unless @handlers == 1;
763 2 50 33     14 unshift @$args, $command
764             if $add_cmd_name and not $preserve_args;
765 2         11 return $o->unalias( $handlers[0], $type );
766             }
767             }
768 1         19 return;
769             }
770              
771             sub completions
772             {
773 0     0 0 0 my $o = shift;
774 0         0 my $action = shift;
775 0   0     0 my $compls = shift || [];
776 0         0 my $casei = shift;
777 0 0       0 $casei = $o->{API}{case_ignore} unless defined $casei;
778 0 0       0 $casei = $casei ? '(?i)' : '';
779 0         0 return grep { $_ =~ /$casei^\Q$action\E/ } @$compls;
  0         0  
780             }
781              
782             #=============================================================================
783             # Term::Shell error messages
784             #=============================================================================
785             sub msg_ambiguous_cmd
786             {
787 0     0 0 0 my ( $o, $cmd, @c ) = @_;
788 0         0 local $" = "\n\t";
789 0         0 <
790             Ambiguous command '$cmd': possible commands:
791             @c
792             END
793             }
794              
795             sub msg_unknown_cmd
796             {
797 0     0 0 0 my ( $o, $cmd ) = @_;
798 0         0 <
799             Unknown command '$cmd'; type 'help' for a list of commands.
800             END
801             }
802              
803             #=============================================================================
804             # Term::Shell private methods
805             #=============================================================================
806             sub do_action
807             {
808 3     3 0 5 my $o = shift;
809 3         5 my $cmd = shift;
810 3   50     8 my $args = shift || [];
811 3   50     9 my $type = shift || 'run';
812 3         22 my ( $fullname, $cmdname, $handler ) = $o->handler( $cmd, $type, $args );
813 3 100       30 $o->{command}{$type} = {
814             cmd => $cmd,
815             name => $cmd,
816             found => defined $handler ? 1 : 0,
817             cmdfull => $fullname,
818             cmdreal => $cmdname,
819             handler => $handler,
820             };
821 3 100       17 if ( defined $handler )
822             {
823             # We've found a handler. Set up a value which will call the postcmd()
824             # action as the subroutine leaves. Then call the precmd(), then return
825             # the result of running the handler.
826 2         12 $o->precmd( \$handler, \$cmd, $args );
827             my $postcmd = Term::Shell::OnScopeLeave->new(
828             sub {
829 2     2   7 $o->postcmd( \$handler, \$cmd, $args );
830             }
831 2         26 );
832 2         19 return $o->$handler(@$args);
833             }
834             }
835              
836             sub uniq
837             {
838 0     0 0 0 my $o = shift;
839 0         0 my %seen;
840 0         0 $seen{$_}++ for @_;
841 0         0 my @ret;
842 0 0       0 for (@_) { push @ret, $_ if $seen{$_}-- == 1 }
  0         0  
843 0         0 @ret;
844             }
845              
846             sub possible_actions
847             {
848 8     8 0 3834 my $o = shift;
849 8         24 my $action = shift;
850 8         16 my $type = shift;
851 8 50       31 my $casei = $o->{API}{case_ignore} ? '(?i)' : '';
852 26         248 my @keys = grep { $_ =~ /$casei^\Q$action\E/ }
853 38         76 grep { exists $o->{handlers}{$_}{$type} }
854 8         14 keys %{ $o->{handlers} };
  8         26  
855 8         33 return @keys;
856             }
857              
858             sub exact_action
859             {
860 4     4 0 6 my $o = shift;
861 4         6 my $action = shift;
862 4         5 my $type = shift;
863 4 50       10 my $casei = $o->{API}{case_ignore} ? '(?i)' : '';
864 6         80 my @key = grep { $action =~ /$casei^\Q$_\E$/ }
865 16         30 grep { exists $o->{handlers}{$_}{$type} }
866 4         7 keys %{ $o->{handlers} };
  4         12  
867 4 100       13 return () unless @key == 1;
868 2         7 return $key[0];
869             }
870              
871             sub is_alias
872             {
873 0     0 0 0 my $o = shift;
874 0         0 my $action = shift;
875 0 0       0 exists $o->{handlers}{$action}{alias} ? 1 : 0;
876             }
877              
878             sub has_aliases
879             {
880 32     32 0 44 my $o = shift;
881 32         46 my $action = shift;
882 32         55 my @a = $o->get_aliases($action);
883 32 50       83 @a ? 1 : 0;
884             }
885              
886             sub get_aliases
887             {
888 32     32 0 41 my $o = shift;
889 32         41 my $action = shift;
890 32         45 my @a = eval {
891 32         58 my $hndlr = $o->{handlers}{$action}{alias};
892 32 50       67 return () unless $hndlr;
893 0         0 $o->$hndlr();
894             };
895 32         104 $o->{aliases}{$_} = $action for @a;
896 32         51 @a;
897             }
898              
899             sub unalias
900             {
901 2     2 0 4 my $o = shift;
902 2         3 my $cmd = shift; # i.e 'foozle'
903 2         4 my $type = shift; # i.e 'run'
904 2 50       7 return () unless $type;
905             return ( $cmd, $cmd, $o->{handlers}{$cmd}{$type} )
906 2 50       22 unless exists $o->{aliases}{$cmd};
907 0         0 my $alias = $o->{aliases}{$cmd};
908              
909             # I'm allowing aliases to call handlers which have been removed. This
910             # means I can set up an alias of '!' for 'shell', then delete the 'shell'
911             # command, so that you can only access it through '!'. That's why I'm
912             # checking the {handlers} entry _and_ building a string.
913 0   0     0 my $handler = $o->{handlers}{$alias}{$type} || "${type}_${alias}";
914 0         0 return ( $cmd, $alias, $handler );
915             }
916              
917             sub find_handlers
918             {
919 5     5 0 14 my $o = shift;
920 5   66     37 my $pkg = shift || $o->{API}{class};
921              
922             # Find the handlers in the given namespace:
923 5         11 my %handlers;
924             {
925             ## no critic
926 3     3   34 no strict 'refs';
  3         7  
  3         201  
927 5         10 my @r = keys %{ $pkg . "::" };
  5         511  
928 5         84 $o->add_handlers(@r);
929             }
930              
931             # Find handlers in its base classes.
932             {
933             ## no critic
934 3     3   20 no strict 'refs';
  3         7  
  3         3115  
  5         6  
  5         10  
935 5         9 my @isa = @{ $pkg . "::ISA" };
  5         75  
936 5         33 for my $pkg (@isa)
937             {
938 2         24 $o->find_handlers($pkg);
939             }
940             }
941             }
942              
943             sub rl_complete
944             {
945 0     0 0 0 my $o = shift;
946 0         0 my ( $word, $line, $start ) = @_;
947              
948             # If it's a command, complete 'run_':
949 0 0 0     0 if ( $start == 0 or substr( $line, 0, $start ) =~ /^\s*$/ )
950             {
951 0         0 my @compls = $o->complete( '', $word, $line, $start );
952 0 0       0 return @compls if $o->{command}{comp}{found};
953             }
954              
955             # If it's a subcommand, send it to any custom completion function for the
956             # function:
957             else
958             {
959 0         0 my $command = ( $o->line_parsed($line) )[0];
960 0         0 my @compls = $o->complete( $command, $word, $line, $start );
961 0 0       0 return @compls if $o->{command}{comp}{found};
962             }
963              
964 0         0 ();
965             }
966              
967             #=============================================================================
968             # Two action handlers provided by default: help and exit.
969             #=============================================================================
970 1     1 0 18 sub smry_exit { "exits the program" }
971              
972             sub help_exit
973             {
974 0     0 0 0 <<'END';
975             Exits the program.
976             END
977             }
978              
979             sub run_exit
980             {
981 0     0 0 0 my $o = shift;
982 0         0 $o->stoploop;
983             }
984              
985 1     1 0 4 sub smry_help { "prints this screen, or help on 'command'" }
986              
987             sub help_help
988             {
989 0     0 0 0 <<'END';
990             Provides help on commands...
991             END
992             }
993              
994             sub comp_help
995             {
996 0     0 0 0 my ( $o, $word, $line, $start ) = @_;
997 0         0 my @words = $o->line_parsed($line);
998 0 0 0     0 return []
      0        
999             if ( @words > 2 or @words == 2 and $start == length($line) );
1000 0         0 sort $o->possible_actions( $word, 'help' );
1001             }
1002              
1003             sub run_help
1004             {
1005 1     1 0 18 my $o = shift;
1006 1         7 my $cmd = shift;
1007 1 50       4 if ($cmd)
1008             {
1009 0         0 my $txt = $o->help( $cmd, @_ );
1010 0 0       0 if ( $o->{command}{help}{found} )
1011             {
1012 0         0 $o->page($txt);
1013             }
1014             else
1015             {
1016 0         0 my @c = sort $o->possible_actions( $cmd, 'help' );
1017 0 0 0     0 if ( @c and $o->{API}{match_uniq} )
1018             {
1019 0         0 local $" = "\n\t";
1020 0         0 print <
1021             Ambiguous help topic '$cmd': possible help topics:
1022             @c
1023             END
1024             }
1025             else
1026             {
1027 0         0 print <
1028             Unknown help topic '$cmd'; type 'help' for a list of help topics.
1029             END
1030             }
1031             }
1032             }
1033             else
1034             {
1035 1         55 print "Type 'help command' for more detailed help on a command.\n";
1036 1         8 my ( %cmds, %docs );
1037 1         0 my %done;
1038 1         0 my %handlers;
1039 1         2 for my $h ( keys %{ $o->{handlers} } )
  1         6  
1040             {
1041 4 100       11 next unless length($h);
1042             next
1043 3 50       8 unless grep { defined $o->{handlers}{$h}{$_} }
  9         32  
1044             qw(run smry help);
1045 3 50       9 my $dest = exists $o->{handlers}{$h}{run} ? \%cmds : \%docs;
1046 3 100       5 my $smry = do { my $x = $o->summary($h); $x ? $x : "undocumented" };
  3         19  
  3         14  
1047             my $help =
1048             exists $o->{handlers}{$h}{help}
1049             ? (
1050             exists $o->{handlers}{$h}{smry}
1051 3 50       23 ? ""
    100          
1052             : " - but help available"
1053             )
1054             : " - no help available";
1055 3         16 $dest->{" $h"} = "$smry$help";
1056             }
1057 1         2 my @t;
1058 1 50       65 push @t, " Commands:\n" if %cmds;
1059             push @t,
1060             scalar $o->format_pairs(
1061             [ sort keys %cmds ],
1062 1         12 [ map { $cmds{$_} } sort keys %cmds ],
  3         22  
1063             ' - ', 1
1064             );
1065 1 50       20 push @t, " Extra Help Topics: (not commands)\n" if %docs;
1066             push @t,
1067             scalar $o->format_pairs(
1068             [ sort keys %docs ],
1069 1         20 [ map { $docs{$_} } sort keys %docs ],
  0         0  
1070             ' - ', 1
1071             );
1072 1         74 $o->page( join '', @t );
1073             }
1074             }
1075              
1076       0 0   sub run_ { }
1077              
1078             sub comp_
1079             {
1080 0     0 0 0 my ( $o, $word, $line, $start ) = @_;
1081 0         0 my @comp = grep { length($_) } sort $o->possible_actions( $word, 'run' );
  0         0  
1082 0         0 return @comp;
1083             }
1084              
1085             package Term::Shell::OnScopeLeave;
1086              
1087             sub new
1088             {
1089 2   33 2   20 return bless [ @_[ 1 .. $#_ ] ], ref( $_[0] ) || $_[0];
1090             }
1091              
1092             sub DESTROY
1093             {
1094 2     2   5 my $o = shift;
1095 2         13 for my $c (@$o)
1096             {
1097 2         6 $c->();
1098             }
1099              
1100 2         11 return;
1101             }
1102              
1103             1;
1104              
1105             __END__