PageRenderTime 54ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/isrcore/Shell.pm

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