File Coverage

blib/lib/Zoidberg/Fish/Commands.pm
Criterion Covered Total %
statement 76 340 22.3
branch 32 214 14.9
condition 2 27 7.4
subroutine 12 39 30.7
pod 30 32 93.7
total 152 652 23.3


line stmt bran cond sub pod time code
1             package Zoidberg::Fish::Commands;
2              
3             our $VERSION = '0.981';
4              
5 17     17   1211 use strict;
  17         20  
  17         616  
6             #use AutoLoader 'AUTOLOAD';
7 17     17   103 use Cwd;
  17         34  
  17         1286  
8 17     17   118 use Env qw/@CDPATH @DIRSTACK/;
  17         34  
  17         155  
9 17     17   3790 use base 'Zoidberg::Fish';
  17         34  
  17         10358  
10 17     17   121 use Zoidberg::Utils qw/:default path getopt usage path2hashref/;
  17         35  
  17         93  
11              
12             # FIXME what to do with commands that use block input ?
13             # currently hacked with statements like join(' ', @_)
14              
15             =head1 NAME
16              
17             Zoidberg::Fish::Commands - Zoidberg plugin with builtin commands
18              
19             =head1 SYNOPSIS
20              
21             This module is a Zoidberg plugin, see Zoidberg::Fish for details.
22              
23             =head1 DESCRIPTION
24              
25             This object contains internal/built-in commands
26             for the Zoidberg shell.
27              
28             =head2 EXPORT
29              
30             None by default.
31              
32             =cut
33              
34             sub init {
35 16     16 1 165 $_[0]{dir_hist} = [$ENV{PWD}]; # FIXME try to read log first
36 16         100 $_[0]{_dir_hist_i} = 0;
37             }
38              
39             =head1 COMMANDS
40              
41             =over 4
42              
43             =item cd [-v|--verbose] [I|-|(+|-)I]
44              
45             =item cd (-l|--list)
46              
47             Changes the current working directory to I.
48             When used with a single dash changes to OLDPWD.
49              
50             This command uses the environment variable 'CDPATH'. It serves as
51             a search path when the directory you want to change to isn't found
52             in the current directory.
53              
54             This command also uses a directory history.
55             The '-number' and '+number' switches are used to change directory
56             to an positive or negative offset in this history.
57              
58             =cut
59              
60             sub cd { # TODO [-L|-P] see man 1 bash
61 0     0 1 0 my $self = shift;
62 0         0 my ($dir, $done, $verbose);
63 0 0 0     0 if (@_ == 1 and $_[0] eq '-') { # cd -
64 0         0 $dir = $ENV{OLDPWD};
65 0         0 $verbose++;
66             }
67             else {
68 0         0 my ($opts, $args) = getopt 'list,-l verbose,-v +* -* @', @_;
69 0 0       0 if (@$args) { # 'normal' cd
70 0 0       0 error 'to many arguments' if @$args > 1;
71 0         0 $dir = $$args[0];
72             }
73              
74 0 0       0 if (%$opts) {
75 0 0       0 $verbose++ if $$opts{verbose};
76 0 0       0 if (my ($opt) = grep /^[+-][^\d+lv]$/, @{$$opts{_opts}}) {
  0 0       0  
    0          
77 0         0 error "unrecognized option '$opt'";
78             }
79 0         0 elsif ($$opts{list}) { # list dirhist
80 0 0       0 error 'to many args' if @$args;
81 0         0 return $$self{shell}->builtin(qw/history --type pwd +1 -2/); # last pwd is current
82             }
83             elsif (my ($idx) = grep /^[+-]\d+$/, @{$$opts{_opts}}) {
84             # cd back/forward in history
85 0 0       0 error 'to many args' if @$args;
86 0 0       0 $idx -= 1 if $idx < 1; # last pwd is current
87 0         0 ($dir) = $$self{shell}->builtin(qw/history --type pwd/, $idx, $idx);
88 0         0 $verbose++;
89             }
90             }
91             }
92              
93 0 0       0 if ($dir) {
94             # due to things like autofs we must *try* every possibility
95             # instead of checking '-d'
96 0         0 $done = chdir path($dir);
97 0 0       0 if ($done) { message $dir if $verbose }
  0 0       0  
    0          
98             elsif ($dir !~ m#^\.{0,2}/#) {
99 0         0 for (@CDPATH) {
100 0 0       0 next unless $done = chdir path("$_/$dir");
101 0         0 message "$_/$dir"; # verbose
102 0         0 last;
103             }
104             }
105             }
106             else {
107 0 0       0 message $ENV{HOME} if $verbose;
108 0         0 $done = chdir($ENV{HOME});
109             }
110              
111 0 0       0 unless ($done) {
112 0 0       0 error $dir.': Not a directory' unless -d $dir;
113 0         0 error "Could not change to dir: $dir";
114             }
115             }
116              
117             #1;
118              
119             #__END__
120              
121             =item exec I
122              
123             Execute I. This effectively ends the shell session,
124             process flow will B return to the prompt.
125              
126             =cut
127              
128             sub exec { # FIXME not completely stable I'm afraid
129 0     0 1 0 my $self = shift;
130 0         0 $self->{shell}->{round_up} = 0;
131 0         0 $self->{shell}->shell_string({fork_job => 0}, join(" ", @_));
132             # the process should not make it to this line
133 0         0 $self->{shell}->{round_up} = 1;
134 0         0 $self->{shell}->exit;
135             }
136              
137             =item eval I
138              
139             Eval I like a shell command. Main use of this is to
140             run code stored in variables.
141              
142             =cut
143              
144             sub eval {
145 0     0 1 0 my $self = shift;
146 0         0 $$self{shell}->shell(@_);
147             }
148              
149             =item export I=I
150              
151             Set the environment variable I to I.
152              
153             TODO explain how export moved varraibles between the perl namespace and the environment
154              
155             =cut
156              
157             sub export { # TODO if arg == 1 and not hash then export var from zoid::eval to env :D
158 5     5 1 12 my $self = shift;
159 5         42 my ($opt, $args, $vals) = getopt 'unexport,n print,p *', @_;
160 5         37 my $class = $$self{shell}{settings}{perl}{namespace};
161 17     17   20218 no strict 'refs';
  17         36  
  17         407291  
162 5 100       171 if ($$opt{unexport}) {
    50          
163 1         3 for (@$args) {
164 1         8 s/^([\$\@]?)//;
165 1 50       7 next unless exists $ENV{$_};
166 1 50       4 if ($1 eq '@') { @{$class.'::'.$_} = split ':', delete $ENV{$_} }
  0         0  
  0         0  
167 1         6 else { ${$class.'::'.$_} = delete $ENV{$_} }
  1         17  
168             }
169             }
170             elsif ($$opt{print}) {
171 0         0 output [ map {
172 0         0 my $val = $ENV{$_};
173 0         0 $val =~ s/'/\\'/g;
174 0         0 "export $_='$val'";
175             } sort keys %ENV ];
176             }
177             else { # really export
178 4         10 for (@$args) {
179 6         45 s/^([\$\@]?)//;
180 6 50       27 if ($1 eq '@') { # arrays
181 0         0 my @env = defined($$vals{$_}) ? (@{$$vals{$_}}) :
  0         0  
182 0 0       0 defined(*{$class.'::'.$_}{ARRAY}) ? (@{$class.'::'.$_}) : () ;
  0 0       0  
183 0 0       0 $ENV{$_} = join ':', @env if @env;
184             }
185             else { # scalars
186 5         54 my $env = defined($$vals{$_}) ? $$vals{$_} :
187 6 100       30 defined(${$class.'::'.$_}) ? ${$class.'::'.$_} : undef ;
  2 100       12  
188 6 100       131 $ENV{$_} = $env if defined $env;
189             }
190             }
191             }
192             }
193              
194             =item setenv I I
195              
196             Like B, but with a slightly different syntax.
197              
198             =cut
199              
200             sub setenv {
201 0     0 1 0 shift;
202 0         0 my $var = shift;
203 0         0 $ENV{$var} = join ' ', @_;
204             }
205              
206             =item unsetenv I
207              
208             Set I to undefined.
209              
210             =cut
211              
212             sub unsetenv {
213 0     0 1 0 my $self = shift;
214 0         0 delete $ENV{$_} for @_;
215             }
216              
217             =item set [+-][abCefnmnuvx]
218              
219             =item set [+o|-o] I
220              
221             Set or unset a shell option. Although sometimes confusing
222             a '+' switch unsets the option, while the '-' switch sets it.
223              
224             Short options correspond to the following names:
225              
226             a => allexport *
227             b => notify
228             C => noclobber
229             e => errexit *
230             f => noglob
231             m => monitor *
232             n => noexec *
233             u => nounset *
234             v => verbose
235             x => xtrace *
236             *) Not yet supported by the rest of the shell
237              
238             See L for a description what these and other options do.
239              
240             FIXME takes also hash arguments
241              
242             =cut
243              
244             sub set {
245 13     13 1 80 my $self = shift;
246 13 50       68 unless (@_) { error 'should print out all shell vars, but we don\'t have these' }
  0         0  
247 13         187 my ($opts, $keys, $vals) = getopt
248             'allexport,a notify,b noclobber,C errexit,e
249             noglob,f monitor,m noexec,n nounset,u
250             verbose,v xtrace,x -o@ +o@ *', @_;
251             # other posix options: ignoreeof, nolog & vi - bash knows a bit more
252              
253 13         38 my %settings;
254 13 100       70 if (%$opts) {
255 2         18 $settings{$_} = $$opts{$_}
256 2         7 for grep {$_ !~ /^[+-]/} @{$$opts{_opts}};
  2         8  
257 2 100       6 if ($$opts{'-o'}) { $settings{$_} = 1 for @{$$opts{'-o'}} }
  1         11  
  1         12  
258 2 100       8 if ($$opts{'+o'}) { $settings{$_} = 0 for @{$$opts{'+o'}} }
  1         3  
  1         11  
259             }
260              
261 13 100       49 for (@$keys) { $settings{$_} = defined($$vals{$_}) ? delete($$vals{$_}) : 1 }
  11         88  
262              
263 13         104 for my $opt (keys %settings) {
264 13 100       100 if ($opt =~ m#/#) {
265 9         121 my ($hash, $key, $path) = path2hashref($$self{shell}{settings}, $opt);
266 9 50       47 error "$path: no such hash in settings" unless $hash;
267 9         176 $$hash{$key} = $settings{$opt};
268             }
269 4         48 else { $$self{shell}{settings}{$opt} = $settings{$opt} }
270             }
271             }
272              
273             =item source I
274              
275             Run the B script I. This script is B the same
276             as the commandline syntax. Try using L in these
277             scripts.
278              
279             =cut
280              
281             sub source {
282 0     0 1 0 my $self = shift;
283             # FIXME more intelligent behaviour -- see bash man page
284 0         0 $self->{shell}->source(@_);
285             }
286              
287             =item alias
288              
289             =item alias I
290              
291             =item alias I=I
292              
293             =item alias I I
294              
295             Make I an alias to I. Aliases work like macros
296             in the shell, this means they are substituted before the commnd
297             code is interpreted and can contain complex statements.
298              
299             Without I shows the alias defined for I if any;
300             without arguments lists all aliases that are currently defined.
301              
302             Aliases are simple substitutions at the start of a command string.
303             If you want something more intelligent like interpolating arguments
304             into a string define a builtin command; see L.
305              
306             =cut
307              
308             sub alias {
309 2     2 1 5 my $self = shift;
310 2 50 33     21 unless (@_) { # FIXME doesn't handle namespaces / sub hashes
    50 33        
311 0         0 my $ref = $$self{shell}{aliases};
312 0         0 output [
313             map {
314 0         0 my $al = $$ref{$_};
315 0 0       0 $al =~ s/(\\)|'/$1 ? '\\\\' : '\\\''/eg;
  0         0  
316 0         0 "alias $_='$al'",
317 0         0 } grep {! ref $$ref{$_}} keys %$ref
318             ];
319 0         0 return;
320             }
321             elsif (@_ == 1 and ! ref($_[0]) and $_[0] !~ /^-|=/) {
322 0         0 my $cmd = shift;
323 0         0 my $alias;
324 0 0       0 if ($cmd =~ m#/#) {
    0          
325 0         0 my ($hash, $key, $path) = path2hashref($$self{shell}{aliases}, $cmd);
326 0 0       0 error "$path: no such hash in aliases" unless $hash;
327 0         0 $alias = $$hash{$key};
328             }
329             elsif (exists $$self{shell}{aliases}{$cmd}) {
330 0         0 $alias = $$self{shell}{aliases}{$cmd};
331             }
332 0         0 else { error $cmd.': no such alias' }
333 0 0       0 $alias =~ s/(\\)|'/$1 ? '\\\\' : '\\\''/eg;
  0         0  
334 0         0 output "alias $cmd='$alias'";
335 0         0 return;
336             }
337            
338 2         20 my (undef, $keys, $val) = getopt '*', @_;
339 2 50       23 return unless @$keys;
340 2         5 my $aliases;
341 2 50       20 if (@$keys == (keys %$val)) { $aliases = $val } # bash style
  0 50       0  
342 2         19 elsif (! (keys %$val)) { $aliases = {$$keys[0] => join ' ', splice @$keys, 1} }# tcsh style
343 0         0 else { error 'syntax error' } # mixed style !?
344              
345 2         9 for my $cmd (keys %$aliases) {
346 2 100       11 if ($cmd =~ m#/#) {
347 1         18 my ($hash, $key, $path) = path2hashref($$self{shell}{aliases}, $cmd);
348 1 50       11 error "$path: no such hash in aliases" unless $hash;
349 1         19 $$hash{$key} = $$aliases{$cmd};
350             }
351 1         17 else { $$self{shell}{aliases}{$cmd} = $$aliases{$cmd} }
352             }
353             }
354              
355             =item unalias I
356              
357             Remove an alias definition.
358              
359             =cut
360              
361             sub unalias {
362 0     0 1 0 my $self = shift;
363 0         0 my ($opts, $args) = getopt 'all,a @', @_;
364 0 0       0 if ($$opts{all}) { %{$self->{shell}{aliases}} = () }
  0         0  
  0         0  
365             else {
366 0         0 for (@$args) {
367 0 0       0 error "alias: $_: not found" unless exists $self->{shell}{aliases}{$_};
368 0         0 delete $self->{shell}{aliases}{$_};
369             }
370             }
371             }
372              
373             =item hash I
374              
375             =item hash -r
376              
377             TODO
378              
379             Command to manipulate the commands hash and command lookup logic.
380              
381             =item read [-r] I I
382              
383             Read a line from STDIN, split the line in words
384             and assign the words to the named enironment variables.
385             Remaining words are stored in the last variable.
386              
387             Unless '-r' is specified the backslash is treated as
388             an escape char and is it possible to escape the newline char.
389              
390             =cut
391              
392             sub read {
393 0     0 1 0 my $self = shift;
394 0         0 my ($opts, $args) = getopt 'raw,r @';
395              
396 0         0 my $string = '';
397 0         0 while () {
398 0 0       0 unless ($$opts{raw}) {
399 0         0 my $more = 0;
400 0         0 $_ =~ s/(\\\\)|\\(.)|\\$/
401 0 0       0 if ($1) { '\\' }
  0 0       0  
402 0         0 elsif (length $2) { $2 }
403 0         0 else { $more++; '' }
  0         0  
404             /eg;
405 0         0 $string .= $_;
406 0 0       0 last unless $more;
407             }
408             else {
409 0         0 $string = $_;
410 0         0 last;
411             }
412             }
413 0 0       0 return unless @$args;
414              
415             # TODO honour $IFS here instead of word_gram
416 0         0 my @words = $$self{shell}{stringparser}->split('word_gram', $string);
417 0         0 debug "read words: ", \@words;
418 0 0       0 if (@words > @$args) {
419 0         0 @words = @words[0 .. $#$args - 1];
420 0         0 my $pre = join '\s*', @words;
421 0         0 $string =~ s/^\s*$pre\s*//;
422 0         0 push @words, $string;
423             }
424              
425 0   0     0 $ENV{$_} = shift @words || '' for @$args;
426             }
427              
428             =item newgrp
429              
430             TODO
431              
432             =cut
433              
434 0     0 1 0 sub newgrp { todo }
435              
436             =item umask
437              
438             TODO
439              
440             =cut
441              
442 0     0 1 0 sub umask { todo }
443              
444             =item false
445              
446             A command that always returns an error without doing anything.
447              
448             =cut
449              
450 10     10 1 156 sub false { error {silent => 1}, 'the "false" builtin' }
451              
452             =item true
453              
454             A command that never fails and does absolutely nothing.
455              
456             =cut
457              
458 0     0 1   sub true { 1 }
459              
460             # ######### #
461             # Dir stack #
462             # ######### #
463              
464             =item dirs
465              
466             Output the current dir stack.
467              
468             TODO some options
469              
470             Note that the dir stack is ont related to the dir history.
471             It was only implemented because historic implementations have it.
472              
473             =cut
474              
475 0 0   0 1   sub dirs { output @DIRSTACK ? [reverse @DIRSTACK] : $ENV{PWD} }
476             # FIXME some options - see man bash
477              
478             =item popd I
479              
480             Pops a directory from the dir stack and Bs to that directory.
481              
482             TODO some options
483              
484             =cut
485              
486             sub popd { # FIXME some options - see man bash
487 0     0 1   my $self = shift;
488 0 0         error 'popd: No other dir on stack' unless $#DIRSTACK;
489 0           pop @DIRSTACK;
490 0 0         my $dir = $#DIRSTACK ? $DIRSTACK[-1] : pop(@DIRSTACK);
491 0           $self->cd($dir);
492             }
493              
494             =item pushd I
495              
496             Push I on the dir stack.
497              
498             TODO some options
499              
500             =cut
501              
502             sub pushd { # FIXME some options - see man bash
503 0     0 1   my ($self, $dir) = (@_);
504 0           my $pwd = $ENV{PWD};
505 0   0       $dir ||= $ENV{PWD};
506 0           $self->cd($dir);
507 0 0         @DIRSTACK = ($pwd) unless scalar @DIRSTACK;
508 0           push @DIRSTACK, $dir;
509             }
510              
511             ##################
512              
513             =item pwd
514              
515             Prints the current PWD.
516              
517             =cut
518              
519             sub pwd {
520 0     0 1   my $self = shift;
521 0           output $ENV{PWD};
522             }
523              
524             =item symbols [-a|--all] [I]
525              
526             Output a listing of symbols in the specified class.
527             Class defaults to the current perl namespace, by default
528             C.
529              
530             All symbols are prefixed by their sigil ('$', '@', '%', '&'
531             or '*') where '*' is used for filehandles.
532              
533             By default sub classes (hashes containing '::')
534             and special symbols (symbols without letters in their name)
535             are hidden. Use the --all switch to see these.
536              
537             =cut
538              
539             sub symbols {
540 17     17   146 no strict 'refs';
  17         55  
  17         38847  
541 0     0 1   my $self = shift;
542 0           my ($opts, $class) = getopt 'all,a @', @_;
543 0 0         error 'to many arguments' if @$class > 1;
544 0   0       $class = shift(@$class)
545             || $$self{shell}{settings}{perl}{namespace} || 'Zoidberg::Eval';
546 0           my @sym;
547 0           for (keys %{$class.'::'}) {
  0            
548 0 0         unless ($$opts{all}) {
549 0 0         next if /::/;
550 0 0         next unless /[a-z]/i;
551             }
552 0 0         push @sym, '$'.$_ if defined ${$class.'::'.$_};
  0            
553 0 0         push @sym, '@'.$_ if *{$class.'::'.$_}{ARRAY};
  0            
554 0 0         push @sym, '%'.$_ if *{$class.'::'.$_}{HASH};
  0            
555 0 0         push @sym, '&'.$_ if *{$class.'::'.$_}{CODE};
  0            
556 0 0         push @sym, '*'.$_ if *{$class.'::'.$_}{IO};
  0            
557             }
558 0           output [sort @sym];
559             }
560              
561             =item reload I [I, ..]
562              
563             =item reload I [I, ..]
564              
565             Force (re-)loading of a module file. Typically used for debugging modules,
566             where you reload the module after each modification to test it interactively.
567              
568             TODO: recursive switch that scans for 'use' statements
569              
570             =cut
571              
572             sub reload {
573 0     0 1   shift; # self
574 0           for (@_) {
575 0           my $file = shift;
576 0 0         if ($file =~ m!/!) { $file = path($file) }
  0            
577             else {
578 0           $file .= '.pm';
579 0           $file =~ s{::}{/}g;
580             }
581 0   0       $file = $INC{$file} || $file;
582 0           eval "do '$file'";
583 0 0         error if $@;
584             }
585             }
586              
587             =item help [I|command I]
588              
589             Prints out a help text.
590              
591             =cut
592              
593             sub help { # TODO topics from man1 pod files ??
594 0     0 1   my $self = shift;
595 0 0         unless (@_) {
596 0           output << 'EOH';
597             Help topics:
598             about
599             command
600              
601             see also man zoiduser
602             EOH
603 0           return;
604             }
605              
606 0           my $topic = shift;
607 0 0         if ($topic eq 'about') { output "$Zoidberg::LONG_VERSION\n" }
  0 0          
608             elsif ($topic eq 'command') {
609 0 0         error usage unless scalar @_;
610 0           $self->help_command(@_)
611             }
612 0           else { $self->help_command($topic, @_) }
613             }
614              
615             sub help_command {
616 0     0 0   my ($self, @cmd) = @_;
617 0           my @info = $self->type_command(@cmd);
618 0 0         if ($info[0] eq 'alias') { output "'$cmd[0]' is an alias\n > $info[1]" }
  0 0          
    0          
    0          
619             elsif ($info[0] eq 'builtin') {
620 0           output "'$cmd[0]' is a builtin command,";
621 0 0         if (@info == 1) {
622 0           output "but there is no information available about it.";
623             }
624             else {
625 0           output "it belongs to the $info[1] plugin.";
626 0 0         if (@info == 3) { output "\n", Zoidberg::Utils::help($cmd[0], $info[2]) }
  0            
627 0           else { output "\nNo other help available" }
628             }
629             }
630             elsif ($info[0] eq 'system') {
631 0           output "'$cmd[0]' seems to be a system command, try\n > man $cmd[0]";
632             }
633             elsif ($info[0] eq 'PERL') {
634 0           output "'$cmd[0]' seems to be a perl command, try\n > perldoc -f $cmd[0]";
635             }
636 0           else { todo "Help functionality for context: $info[1]" }
637             }
638              
639             =item which [-a|--all|-m|--module] ITEM
640              
641             Finds ITEM in PATH or INC if the -m or --module option was used.
642             If the -a or --all option is used all it doesn't stop after the first match.
643              
644             TODO it should identify aliases
645              
646             TODO what should happen with contexts other then CMD ?
647              
648             =cut
649              
650             sub which {
651 0     0 1   my $self = shift;
652 0           my ($opt, $cmd) = getopt 'module,m all,a @', @_;
653 0           my @info = $self->type_command(@$cmd);
654 0           $cmd = shift @$cmd;
655 0           my @dirs;
656              
657 0 0         if ($$opt{module}) {
658 0           $cmd =~ s#::#/#g;
659 0 0         $cmd .= '.pm' unless $cmd =~ /\.\w+$/;
660 0           @dirs = @INC;
661             }
662             else {
663 0 0         error "$cmd is a, or belongs to a $info[0]"
664             unless $info[0] eq 'system';
665             # TODO aliases
666 0           @dirs = split ':', $ENV{PATH};
667             }
668              
669 0           my @matches;
670 0           for (@dirs) {
671 0 0         next unless -e "$_/$cmd";
672 0           push @matches, "$_/$cmd";
673 0 0         last unless $$opt{all};
674             }
675 0 0         if (@matches) { output [@matches] }
  0            
676 0           else { error "no $cmd in PATH" }
677 0           return;
678             }
679              
680             sub type_command {
681 0     0 0   my ($self, @cmd) = @_;
682            
683 0 0 0       if (
684             exists $$self{shell}{aliases}{$cmd[0]}
685             and $$self{shell}{aliases}{$cmd[0]} !~ /^$cmd[0]\b/
686             ) {
687 0           my $alias = $$self{shell}{aliases}{$cmd[0]};
688 0           $alias =~ s/'/\\'/g;
689 0           return 'alias', "alias $cmd[0]='$alias'";
690             }
691              
692 0           my $block = $$self{shell}->parse_block({pretend => 1}, [@cmd]);
693 0           my $context = uc $$block[0]{context};
694 0 0 0       if (!$context or $context eq 'CMD') {
695 0 0         return 'system' unless exists $$self{shell}{commands}{$cmd[0]};
696 0           my $tag = $$self{shell}{commands}->tag($cmd[0]);
697 0 0         return 'builtin' unless $tag;
698 0           my $file = tied( %{$$self{shell}{objects}} )->[1]{$tag}{module};
  0            
699 0           return 'builtin', $tag, $file;
700             }
701 0           else { return $context }
702             }
703              
704             # ############ #
705             # Job routines #
706             # ############ #
707              
708             =item jobs [-l,--list|-p,--pgids] I
709              
710             Lists current jobs.
711              
712             If job specs are given as arguments only lists those jobs.
713              
714             The --pgids option only lists the process group ids for the jobs
715             without additional information.
716              
717             The --list option gives more verbose output, it adds the process group id
718             of the job and also shows the stack of commands pending for this job.
719              
720             This command is not POSIX compliant. It uses '-l' in a more verbose
721             way then specified by POSIX. If you wat to make sure you have POSIX
722             compliant verbose output try: C.
723              
724             =cut
725              
726             sub jobs {
727 0     0 1   my $self = shift;
728 0           my ($opts, $args) = getopt 'list,l pgids,p @', @_;
729 0           $args = @$args
730 0 0         ? [ map {$$self{shell}->job_by_spec($_)} @$args ]
731             : $$self{shell}->{jobs} ;
732 0 0         if ($$opts{pgids}) {
733 0           output [ map $$_{pgid}, @$args ];
734             }
735             else {
736 0           output $_->status_string(undef, $$opts{list})
737 0           for sort {$$a{id} <=> $$b{id}} @$args;
738             }
739             }
740              
741             =item bg I
742              
743             Run the job corresponding to I as an asynchronous background process.
744              
745             Without argument uses the "current" job.
746              
747             =cut
748              
749             sub bg {
750 0     0 1   my ($self, $id) = @_;
751 0 0         my $j = $$self{shell}->job_by_spec($id)
    0          
752             or error 'No such job'.($id ? ": $id" : '');
753 0           debug "putting bg: $$j{id} == $j";
754 0           $j->bg;
755             }
756              
757             =item fg I
758              
759             Run the job corresponding to I as a foreground process.
760              
761             Without argument uses the "current" job.
762              
763             =cut
764              
765             sub fg {
766 0     0 1   my ($self, $id) = @_;
767 0 0         my $j = $$self{shell}->job_by_spec($id)
    0          
768             or error 'No such job'.($id ? ": $id" : '');
769 0           debug "putting fg: $$j{id} == $j";
770 0           $j->fg;
771             }
772              
773             =item wait
774              
775             TODO
776              
777             =cut
778              
779 0     0 1   sub wait { todo }
780              
781             =item kill -l
782              
783             =item kill [-w | -s I|-n I|-I] (I|I)
784              
785             Sends a signal to a process or a process group.
786             By default the "TERM" signal is used.
787              
788             The '-l' option list all possible signals.
789              
790             The -w or --wipe option is zoidberg specific. It not only kills the job, but also
791             wipes the list that would be executed after the job ends.
792              
793             =cut
794              
795             # from bash-2.05/builtins/kill.def:
796             # kill [-s sigspec | -n signum | -sigspec] [pid | job]... or kill -l [sigspec]
797             # Send the processes named by PID (or JOB) the signal SIGSPEC. If
798             # SIGSPEC is not present, then SIGTERM is assumed. An argument of `-l'
799             # lists the signal names; if arguments follow `-l' they are assumed to
800             # be signal numbers for which names should be listed. Kill is a shell
801             # builtin for two reasons: it allows job IDs to be used instead of
802             # process IDs, and, if you have reached the limit on processes that
803             # you can create, you don't have to start a process to kill another one.
804              
805             # Notice that POSIX specifies another list format then the one bash uses
806              
807             sub kill {
808 0     0 1   my $self = shift;
809 0           my ($opts, $args) = getopt 'wipe,-w list,-l sigspec,-s signum,-n -* @', @_;
810 0 0         if ($$opts{list}) { # list sigs
811 0 0         error 'too many options' if @{$$opts{_opts}} > 1;
  0            
812 0           my %sh = %{ $$self{shell}{_sighash} };
  0            
813 0 0         my @k = @$args ? (grep exists $sh{$_}, @$args) : (keys %sh);
814 0           output [ map {sprintf '%2i) %s', $_, $sh{$_}} sort {$a <=> $b} @k ];
  0            
  0            
815 0           return;
816             }
817 0 0         else { error 'to few arguments' unless @$args }
818              
819 0   0       my $sig = $$opts{signum} || '15'; # sigterm, the default
820 0 0         if ($$opts{_opts}) {
821 0           for ($$opts{signum}, grep s/^-//, @$args) {
822 0 0         next unless $_;
823 0           my $sig = $$self{shell}->sig_by_spec($_);
824 0 0         error $_.': no such signal' unless defined $sig;
825             }
826             }
827              
828 0           for (@$args) {
829 0 0         if (/^\%/) {
830 0 0         my $j = $$self{shell}->job_by_spec($_)
831             or error "$_: no such job";
832 0           $j->kill($sig, $$opts{wipe});
833             }
834 0           else { CORE::kill($sig, $_) }
835             }
836             }
837              
838             =item disown
839              
840             TODO
841              
842             =cut
843              
844             sub disown { # dissociate job ... remove from @jobs, nohup
845 0     0 1   todo 'see bash manpage for implementaion details';
846              
847             # is disowning the same as deamonizing the process ?
848             # if it is, see man perlipc for example code
849              
850             # does this suggest we could also have a 'own' to hijack processes ?
851             # all your pty are belong:0
852             }
853              
854             =back
855              
856             =head2 Job specs
857              
858             TODO tell bout job specs
859              
860             =head1 AUTHOR
861              
862             Jaap Karssenberg || Pardus [Larus] Epardus@cpan.orgE
863             R.L. Zwart, Erlzwart@cpan.orgE
864              
865             Copyright (c) 2011 Jaap G Karssenberg and Joel Berger. All rights reserved.
866             This program is free software; you can redistribute it and/or
867             modify it under the same terms as Perl itself.
868              
869             =head1 SEE ALSO
870              
871             L, L
872              
873             =cut
874              
875             1;
876