File Coverage

blib/lib/Zoidberg.pm
Criterion Covered Total %
statement 417 703 59.3
branch 172 432 39.8
condition 73 208 35.1
subroutine 48 65 73.8
pod 7 23 30.4
total 717 1431 50.1


line stmt bran cond sub pod time code
1             package Zoidberg;
2              
3             our $VERSION = '0.981';
4             our $LONG_VERSION = "Zoidberg $VERSION
5              
6             Copyright (c) 2011 Jaap G Karssenberg and Joel Berger. All rights reserved.
7             This program is free software; you can redistribute it and/or
8             modify it under the same terms as Perl.
9              
10             This program is distributed in the hope that it will be useful,
11             but WITHOUT ANY WARRANTY; without even the implied warranty of
12             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13              
14             http://github.com/jberger/Zoidberg";
15              
16 29     29   3145 use strict;
  29         146  
  29         1791  
17 23     23   206 use vars qw/$AUTOLOAD/;
  23         103  
  23         2154  
18             #use warnings;
19             #no warnings 'uninitialized'; # yes, undefined == '' == 0
20 19     19   121 no warnings; # I am leaving this, because I don't totally understand how warnings propagate through -- Joel
  19         46  
  19         1853  
21              
22             require Cwd;
23             require File::Glob;
24 19     19   8834 use File::ShareDir qw/dist_dir/;
  19         31554  
  19         2013  
25 19     19   20991 use File::Copy qw/copy/;
  19         65146  
  19         1387  
26 19     19   18382 use File::Spec::Functions qw/catfile/;
  19         17886  
  19         1755  
27              
28             require Zoidberg::Contractor;
29             require Zoidberg::Shell;
30             require Zoidberg::PluginHash;
31             require Zoidberg::StringParser;
32              
33 19     19   12232 use Zoidberg::DispatchTable;
  19         58  
  19         188  
34             use Zoidberg::Utils
35 19     19   954 qw/:error :output :fs read_data_file merge_hash regex_glob getopt/;
  19         24  
  19         161  
36              
37             our @ISA = qw/Zoidberg::Contractor Zoidberg::Shell/;
38              
39             =head1 NAME
40              
41             Zoidberg - A modular perl shell
42              
43             =head1 SYNOPSIS
44              
45             You should use the B system command to start the Zoidberg shell.
46             To embed the Zoidberg shell in another perl program use the L
47             module.
48              
49             =head1 DESCRIPTION
50              
51             I
52              
53             This module contains the core dispatch and event logic of the Zoidberg shell.
54             Also it is used as a 'main object' so other objects can find each other here;
55             all other objects are nested below this object.
56             Also it contains some parser code.
57              
58             This object inherits from both L
59             and L.
60              
61             =head1 METHODS
62              
63             =over 4
64              
65             =cut
66              
67             our %OBJECTS; # used to store refs to ALL Zoidberg objects in a process
68             our $CURRENT; # current Zoidberg object
69              
70             our $_base_dir; # relative path for some settings
71             our @_parser_settings = qw/
72             split_script split_words
73             parse_env parse_fd parse_aliases parse_def_contexts
74             expand_comm expand_param expand_path
75             /;
76              
77             our %_settings = ( # default settings
78             output => { error => 'red', debug => 'yellow' },
79             clothes => {
80             keys => [qw/settings commands aliases events error/],
81             subs => [qw/shell alias unalias setting set source mode plug unplug/],
82             },
83             perl => {
84             keywords => [qw/
85             if unless for foreach while until
86             print
87             push shift unshift pop splice
88             delete
89             do eval
90             tie untie
91             my our use no sub package
92             import bless
93             /],
94             namespace => 'Zoidberg::Eval',
95             opts => 'Z',
96             },
97             hide_private_method => 1,
98             hide_hidden_files => 1,
99             naked_zoid => 0,
100             ( map {($_ => 1)} @_parser_settings ),
101             ##Insert defaults here##
102             rcfiles => [
103             ( $ENV{PAR_TEMP} ? "$ENV{PAR_TEMP}/inc/etc/zoidrc" : '/etc/zoidrc' ),
104             "$ENV{HOME}/.zoidrc",
105             "$ENV{HOME}/.zoid/zoidrc",
106             ],
107             data_dirs => [
108             "$ENV{HOME}/.zoid",
109             ( $ENV{PAR_TEMP} ? "$ENV{PAR_TEMP}/inc/share" : ( qw# /usr/local/share/zoid /usr/share/zoid # ) ),
110             dist_dir('Zoidberg'),
111             ],
112             );
113             our %_grammars = ( # default grammar
114             _base_gram => {
115             esc => '\\',
116             quotes => {
117             '"' => '"',
118             "'" => "'",
119             '`' => '`',
120             },
121             nests => {
122             '{' => '}',
123             '(' => ')',
124             },
125             },
126             script_gram => {
127             tokens => [
128             [ ';', 'EOS' ],
129             [ "\n", 'EOL' ],
130             [ '&&', 'AND' ],
131             [ '||', 'OR' ],
132             [ '|', '_CUT' ],
133             [ qr/(?])&/ , 'EOS_BG' ],
134             [ '==>', 'XFW' ],
135             [ '<==', 'XBW' ],
136             ],
137             },
138             word_gram => qr/\s/,
139             redirect_gram => {
140             s_esc => qr/[\\\-\=]/,
141             tokens => [
142             [ qr/<\S+>/, '_SELF' ],
143             [ '>&', 'DUP_OUT' ],
144             [ '>|', 'CLOB_OUT' ],
145             [ '>!', 'CLOB_OUT' ],
146             [ '>>', 'APP_OUT' ],
147             [ '<&', 'DUP_IN' ],
148             [ '<<', 'ERROR' ],
149             [ '<>', 'RW' ],
150             [ '>', 'OUT' ],
151             [ '<', 'IN' ],
152             ],
153             },
154             dezoid_gram => {
155             tokens => [
156             [ qr/->/, 'ARR' ], # ARRow
157             [ qr/[\$\@][A-Za-z_][\w\-]*(?
158             ],
159             quotes => { "'" => "'" }, # interpolate also between '"'
160             nests => {},
161             },
162             expand_comm_gram => {
163             tokens => {
164             '$(' => {
165             token => 'COMM',
166             tokens => {')' => '_CUT'},
167             },
168             '`' => {
169             token => 'COMM',
170             tokens => {'`' => '_CUT'},
171             }
172             },
173             },
174             # expand_braces_gram => {
175             # tokens => {
176             # '{' => {
177             # token => 'BRACE',
178             # tokens => { '}' => '_CUT' },
179             # },
180             # },
181             # },
182             );
183              
184             =item C
185              
186             Initialize secondary objects and sets config.
187             C<%attr> contains non-default attributes and is used to set runtime settings.
188              
189             You probably don't want to use this to construct a new Zoidberg shell object,
190             better use L.
191              
192             =cut
193              
194             sub new { # FIXME maybe rename this to init ?
195 16     16 1 47 my $class = shift;
196 16 50       113 my $self = @_ ? { @_ } : {};
197 16   100     448 $$self{$_} ||= {} for qw/settings commands aliases events objects/;
198 16   50     143 $$self{no_words} ||= [];
199 16         32 push @{$$self{no_words}}, qw/PERL SUBZ/; # parser stuff
  16         64  
200 16         33 $$self{round_up}++;
201 16   50     143 $$self{topic} ||= '';
202              
203 16         32 bless($self, $class);
204              
205 16         208 $OBJECTS{"$self"} = $self;
206 16 50       65 $CURRENT = $self unless ref( $CURRENT ) eq $class; # could be autovivicated
207 16         48 $self->{shell} = $self; # for Contractor
208              
209             ## settings
210 16         128 $$self{_settings} = merge_hash(\%_settings, $$self{settings});
211 16 50       94 $$self{_settings}{data_dirs}
212             || error 'You should at least set a config value for \'data_dirs\'';
213              
214 16         34 my %set;
215 16         113 tie %set, 'Zoidberg::SettingsHash', $$self{_settings}, $self;
216 16         34 $$self{settings} = \%set;
217              
218             ## commands
219 16         291 $$self{commands} = Zoidberg::DispatchTable->new(
220             $self, {
221             exit => '->exit',
222             plug => '->plug',
223             unplug => '->unplug',
224             mode => '->mode',
225             readline => "->stdin('zoid-$VERSION\$ ')",
226             readmore => "->stdin('> ')",
227             builtin => '->builtin',
228             command => '->command',
229 16         80 ( %{$$self{commands}} )
230             }
231             );
232              
233             ## events
234 16         129 $$self{events} = Zoidberg::DispatchTable->new($self, $$self{events});
235             $$self{events}{envupdate} = sub {
236 295     295   3364499 my $pwd = Cwd::cwd();
237 295 50       13895 return if $pwd eq $ENV{PWD};
238 0         0 @ENV{qw/OLDPWD PWD/} = ($ENV{PWD}, $pwd);
239 0         0 $self->broadcast('newpwd');
240 0 0       0 $self->builtin('log', $pwd, 'pwd') if $$self{settings}{interactive};
241 16         194 };
242              
243             ## parser
244 16         113 $$self{parser} = Zoidberg::DispatchTable->new($self, $$self{parser});
245              
246             ## stringparser
247 16   50     236 $$self{grammars} ||= \%_grammars;
248 16         191 $$self{stringparser} = Zoidberg::StringParser->new(
249             $$self{grammars}{_base_gram}, $$self{grammars},
250             {allow_broken => 1, no_esc_rm => 1} );
251              
252             ## initialize contractor
253 16         160 $self->shell_init;
254              
255             ## plugins
256 16         32 my %objects;
257 16         159 tie %objects, 'Zoidberg::PluginHash', $self;
258 16         67 $self->{objects} = \%objects;
259              
260             # autoloading of contexts after plugin loading
261             # because of bootstrapping issues
262             $$self{parser}{_AUTOLOAD} = sub {
263 32     32   62 my $c = shift;
264 32         339 debug "trying to autoload $c";
265 32 50       422 if ($c =~ /::/) {
266 0         0 $c =~ m#(.*?)(::|->)$#;
267 0         0 my ($class, $type) = ($1, $2);
268 0         0 debug "loading class $class";
269 0         0 $$self{parser}{$c} = {};
270             $$self{parser}{$c}{handler} = sub {
271 0         0 my (undef, $sub, @args) = @{ shift() };
  0         0  
272 0 0       0 unshift @args, $class if $type eq '->';
273 19     19   32856 no strict 'refs';
  19         54  
  19         2193  
274 0         0 $sub = $class.'::'.$sub;
275 0         0 $sub->(@args);
276 0         0 };
277             $$self{parser}{$c}{intel} = sub {
278 0         0 my $block = shift;
279 0 0       0 return undef if @$block > 2;
280 19     19   110 no strict 'refs';
  19         38  
  19         266089  
281 0         0 my @p = grep m/^$$block[1]/,
282 0         0 grep defined *{$class.'::'.$_}{CODE}, keys %{$class.'::'};
  0         0  
283 0 0       0 push @p, grep m/^$$block[1]/, keys %{$$self{aliases}{'mode_'.$c}}
  0         0  
284             if exists $$self{aliases}{'mode_'.$c};
285 0         0 $$block[0]{poss} = \@p;
286 0         0 return $block;
287 0         0 };
288             }
289 32         365 else { eval { $self->plug($c) } }
  32         496  
290 32 50       230 debug 'did you know 5.6.2 sucks ?' if $] < 5.008; # don't ask ... i suspect another vivication bug
291 32 50       175 return exists($$self{parser}{$c}) ? $$self{parser}{$c} : undef ;
292 16         267 };
293              
294             ## let's load the rcfiles
295             $$self{events}{loadrc} = sub {
296             #check for existant rcfiles in the known locations
297 16     16   33 my @rcfiles = grep {-f $_} @{$$self{_settings}{rcfiles}};
  16         435  
  16         97  
298             #if no zoidrc file is found, create one from the template in the dist_dir
299 16 50       81 unless (@rcfiles) {
300 0         0 my $rc_template = catfile(dist_dir('Zoidberg'), "zoidrc.example");
301 0         0 my $new_rc = catfile($ENV{HOME}, ".zoidrc");
302 0         0 warn "### No zoidrc file was found. A new zoidrc file will be created for you at $new_rc. If you really intend to use without a zoidrc file, simply create an empty zoidrc file in that location or at /etc/zoidrc\n\n";
303 0 0       0 if( copy( $rc_template, $new_rc) ) {
304 0         0 push @rcfiles, $new_rc;
305             } else {
306 0         0 warn "### Could not copy $rc_template to $new_rc\n\n";
307             }
308            
309             }
310 16         229 $self->source(@rcfiles);
311 16         162 };
312 16         83 $self->broadcast('loadrc');
313              
314 16         124 $self->broadcast('envupdate'); # set/log pwd and maybe init other env stuff
315              
316 16         1374 return $self;
317             }
318              
319 19 50   19   2628 sub import { bug "You should use Zoidberg::Shell to import from" if @_ > 1 }
320              
321             # hooks overloading Contracter # FIXME these are not used !?
322             *pre_job = \&parse_block;
323             *post_job = \&broadcast;
324              
325             # ############ #
326             # Main routine #
327             # ############ #
328              
329             =item C
330              
331             Spans interactive shell reading from a secondary ReadLine object or from STDIN.
332              
333             To quit this loop the routine C of this package should be called.
334             Most common way to do this is pressing ^D.
335              
336             =cut
337              
338             sub main_loop {
339 0     0 1 0 my $self = shift;
340              
341 0         0 $$self{_continue} = 1;
342 0         0 while ($$self{_continue}) {
343 0         0 $self->reap_jobs();
344 0         0 $self->broadcast('prompt');
345 0         0 my ($cmd) = $self->builtin('readline');
346 0 0       0 if ($@) {
347 0         0 complain "\nInput routine died. (You can interrupt zoid NOW)";
348 0         0 local $SIG{INT} = 'DEFAULT';
349 0         0 sleep 1; # infinite loop protection
350             }
351             else {
352 0         0 $self->reap_jobs();
353              
354 0 0 0     0 unless (defined $cmd || $$self{_settings}{ignoreeof}) {
355 0         0 debug 'readline returned undef .. exiting';
356 0         0 $self->exit();
357             }
358 0         0 else { $$self{_warned_bout_jobs} = 0 }
359              
360 0 0       0 last unless $$self{_continue};
361              
362 0 0       0 $self->shell_string({interactive => 1}, $cmd) if length $cmd;
363             }
364             }
365             }
366              
367             # ############ #
368             # Parser stuff #
369             # ############ #
370              
371             sub shell_string {
372 110     110 0 567 my ($self, $meta, $string) = @_;
373 110 50       737 ($meta, $string) = ({}, $meta) unless ref($meta) eq 'HASH';
374 110         568 local $CURRENT = $self;
375              
376 110 50       33037 PARSE_STRING:
377             my @list = $$self{_settings}{split_script}
378             ? ($$self{stringparser}->split('script_gram', $string)) : ($string) ;
379 110 50 33     1896 my $b = $$self{stringparser}{broken} ? 1
    50          
380             : (@list and ! ref $list[-1] and $list[-1] !~ /^EO/) ? 2 : 0 ;
381 110 50 33     704 if ($b and ! $$self{_settings}{interactive}) { # FIXME should be STDIN on non interactive
    50          
382 0 0       0 error qq#Operator at end of input# if $b == 2;
383 0         0 my $gram = $$self{stringparser}{broken}[1];
384 0         0 error qq#Unmatched $$gram{_open}[1] at end of input: $$gram{_open}[0]#;
385             }
386             elsif ($b) {
387 0         0 ($string) = $self->builtin('readmore');
388 0         0 debug "\n\ngot $string\n\n\n";
389 0 0       0 if ($@) {
390 0         0 complain "\nInput routine died.\n$@";
391 0         0 return;
392             }
393 0         0 goto PARSE_STRING;
394             }
395              
396 110 50       400 if ($$meta{interactive}) {
397 0         0 $self->broadcast('cmd', $string);
398 0         0 $$self{previous_cmd} = $string;
399 0 0       0 print STDERR $string if $$self{_settings}{verbose}; # posix spec
400             }
401              
402 110         866 debug 'block list: ', \@list;
403 110   33     613 $$self{fg_job} ||= $self;
404 110         1050 $$self{fg_job}->shell_list($meta, @list); # calling a contractor
405             }
406              
407             sub prepare_block {
408 225     225 0 682 my ($self, $block) = @_;
409 225         1043 my $t = ref $block;
410 225 100       1313 if ($t eq 'SCALAR') { $block = [{env => {pwd => $ENV{PWD}}}, $$block] }
  141 50       1294  
411             elsif ($t eq 'ARRAY') {
412 84 100 66     549 if (ref($$block[0]) eq 'HASH') { $$block[0]{env}{pwd} ||= $ENV{PWD} }
  26         313  
413 58         988 else { unshift @$block, {env => {pwd => $ENV{PWD}}} }
414             }
415             else {
416 0 0       0 bug $t ? "prepare_block can't handle type $t"
417             : "block ain't a ref !??" ;
418             }
419 225         1151 return $block;
420             }
421              
422             sub parse_block { # call as late as possible before execution
423             # FIXME can this be more optimised for builtin() call ?
424 200     200 0 654 my $self = shift;
425 200 50       873 my $meta = (ref($_[0]) eq 'HASH') ? shift : {};
426 200         443 my $block = shift;
427              
428             # check settings
429 200         989 $$meta{$_} = $$self{_settings}{$_} for grep {! defined $$meta{$_}} @_parser_settings;
  1800         10620  
430             # FIXME mode settings, uc || lc ?
431            
432             # decipher block
433 334         635 PARSE_BLOCK:
434             my @words;
435 334         928 my $t = ref $block;
436 334 100 66     2844 if (!$t or $t eq 'SCALAR') {
    50          
    0          
437 134 50       159 ($meta, @words) = @{ $self->parse_env([$meta, $t ? $$block : $block]) };
  134         948  
438 134 50 0     1727 ++$$meta{no_mode} and (length $words[0] or shift @words) if @words && $words[0] =~ s/^\!\s*//;
      0        
      66        
439             }
440             elsif ($t eq 'ARRAY') {
441 200 50       1378 $meta = { %$meta, %{shift @$block} } if ref($$block[0]) eq 'HASH';
  200         1898  
442 200 100 66     2317 unless (@$block > 1 or $$meta{plain_words}) {
443 134         611 debug "block aint a word block";
444 134         405 $block = shift @$block;
445 134         2305 goto PARSE_BLOCK;
446             }
447 66         847 @words = @$block;
448 66 50 0     1501 ++$$meta{no_mode} and shift @words if @words && $words[0] eq '!';
      33        
449             }
450 0         0 elsif ($t eq 'CODE') { return [{context => 'PERL', %$meta}, $block] }
451 0         0 else { bug "parse tree contains $t reference" }
452              
453             # do aliases
454 200         1712 debug 'meta: ', $meta; # , 'words: ', [[@words]];
455 200 50 66     3807 if (@words and ! $$meta{pretend} and $$meta{parse_aliases}) {
      66        
456 195         1689 my @blocks = $self->parse_aliases($meta, @words);
457 195 50       1711 if (@blocks > 1) { return @blocks } # probably an alias contained pipe or logic operator
  0 50       0  
458 0         0 elsif (! @blocks) { return undef }
459             else {
460 195         355 ($meta, @words) = @{ shift(@blocks) };
  195         2207  
461             }
462             }
463             # post alias stuff
464 200         1586 $$meta{zoidcmd} = join ' ', @words; # unix haters guide pdf page 60
465             #FIXME how does this hadle escaped whitespacec ?
466 200 50       849 $$meta{no_mode}++ if $words[0] eq 'mode'; # explicitly after alias expansion .. ! is before alias expansion
467              
468             # check custom filters
469 200         3363 for my $sub ($$self{parser}->stack('filter')) {
470 0         0 my $r = $sub->([$meta, @words]);
471 0 0       0 ($meta, @words) = @$r if $r; # skip on undef
472             }
473 200 100 66     1841 return undef unless $$meta{context} or @words;
474              
475 195 50       1302 $$meta{context} = 'SUBZ' if $$meta{zoidcmd} =~ /^\s*\(.*\)\s*$/s; # check for subshell
476              
477             # check builtin contexts/filters
478 195 50 33     12400 unless ($$meta{context} or ! $$meta{parse_def_contexts}) {
479 195         932 debug 'trying builtin contexts';
480 195         539 my $perl_regexp = join '|', @{$self->{_settings}{perl}{keywords}};
  195         2476  
481 195 100 33     11920 if (
    50 66        
    50 66        
    100          
482             $$meta{zoidcmd} =~ s/^\s*(\w*){(.*)}(\w*)\s*$/$2/s or $$meta{pretend} and
483             $$meta{zoidcmd} =~ s/^\s*(\w*){(.*)$/$2/s
484             ) { # all kinds of blocks with { ... }
485 36 50 50     237 unless (length $1) { @$meta{qw/context opts/} = ('PERL', $3 || '') }
  36 0       525  
  0         0  
486             elsif (grep {$_ eq $1} qw/s m tr y/) {
487 0         0 $$meta{zoidcmd} = $1.'{'.$$meta{zoidcmd}.'}'.$3; # always the exceptions
488 0 0       0 @$meta{qw/context opts/} = ('PERL', ($1 eq 'm') ? 'g' : 'p')
489             }
490             else {
491 0   0     0 @$meta{qw/context opts/} = (uc($1), $3 || '');
492 0         0 @words = $$self{stringparser}->split('word_gram', $$meta{zoidcmd});
493             }
494             }
495             elsif ($$meta{zoidcmd} =~ s/^\s*(\w+):\s+//) { # little bit o psh2 compat
496 0         0 $$meta{context} = uc $1;
497 0         0 shift @words;
498             }
499 0         0 elsif (@words == 1 and $words[0] =~ /^%/) { unshift @words, 'fg' } # and another exception
500             elsif ($words[0] =~ /^\s*(->|[\$\@\%\&\*\xA3]\S|\w+::|\w+[\(\{]|($perl_regexp)\b)/s) {
501 10         102 $$meta{context} = 'PERL';
502             }
503             }
504              
505 195         982 $$meta{env}{ZOIDCMD} = $$meta{zoidcmd}; # unix haters guide, pdf page 60
506 195 50 33     860 if ($$self{_settings}{mode} and ! $$meta{no_mode}) {
507 0         0 my $m = $$self{_settings}{mode};
508 0 0 0     0 $$meta{context} ||= ($m =~ /::/) ? $m : uc($m);
509             }
510              
511 195 50 33     646 return [$meta, @words] if $$meta{pretend} and @words == 1;
512              
513             # check custom contexts
514 195 100       733 unless ($$meta{context}) {
515 149         962 debug 'trying custom contexts';
516 149         820 for my $pair ($$self{parser}->stack('word_list', 'TAGS')) {
517 0         0 my $r = $$pair[0]->([$meta, @words]);
518 0 0       0 unless ($r) { next }
  0 0       0  
519 0         0 elsif (ref $r) { ($meta , @words) = @$r }
520 0 0       0 else { $$meta{context} = length($r) > 1 ? $r : $$pair[1] }
521 0 0       0 last if $$meta{context};
522             }
523             }
524              
525             # use default builtin context
526 195 100 66     1926 unless ($$meta{context} or ! $$meta{parse_def_contexts}) {
527 149         593 debug 'using default context';
528 149         739 $$meta{context} = 'CMD';
529             }
530              
531 195 50 66     2440 if (
    100 33        
    50          
532             exists $$self{parser}{$$meta{context}} and
533             exists $$self{parser}{$$meta{context}}{parser}
534 390         2780 ) { # custom parser
535 0         0 ($meta, @words) = @{ $$self{parser}{$$meta{context}}{parser}->([$meta, @words]) };
  0         0  
536             }
537 195         771 elsif (grep {$$meta{context} eq $_} @{$$self{no_words}}) { # no words
538 46 50       257 @words = $$meta{pretend}
539             ? $$self{stringparser}->split('word_gram', $$meta{zoidcmd})
540             : ( $$meta{zoidcmd} ) ;
541 46 50       305 $$meta{fork_job} = 1 if $$meta{context} eq 'SUBZ';
542 46 50 33     893 ($meta, @words) = @{ $self->parse_perl([$meta, @words]) }
  46         524  
543             if ! $$meta{pretend} and $$meta{context} eq 'PERL';
544             }
545             elsif (@words and ! $$meta{pretend}) { # expand and set topic
546 149 50       636 ($meta, @words) = @{ $self->parse_words([$meta, @words]) } unless $$meta{plain_words};
  149         1461  
547 149 100 66     2476 $$self{topic} =
548             # FIXME exists($$meta{fd}{0}) ? $$meta{fd}{0}[0] :
549             (@words > 1 and $words[-1] !~ /^-/) ? $words[-1] : $$self{topic};
550 149 100 33     2759 $$meta{fork_job} = 1 if $$meta{context} eq 'CMD' and
      66        
551             $$meta{cmdtype} ne 'builtin' and ! exists $$self{commands}{$words[0]};
552             }
553 195         1970 return [$meta, @words];
554             }
555              
556             our %_redir_ops = (
557             IN => '<', OUT => '>',
558             CLOB_OUT => '>!', APP_OUT => '>>',
559             RW => '+<', DUP_OUT => '>&', DUP_IN => '<&'
560             );
561              
562             sub parse_env {
563 134     134 0 306 my ($self, $block) = @_;
564 134         500 my ($meta, @words) = @$block;
565              
566 134 50 33     1301 if (@words > 1 or ! $$meta{split_words}) {
567 0         0 $$meta{string} = join ' ', @words;
568             }
569             else {
570 134         439 $$meta{string} = $words[0];
571 134         1011 @words = $$self{stringparser}->split('word_gram', $words[0])
572             }
573             # FIXME parse word_gram and redir_gram at same time
574              
575             # parse environment
576 134 50       758 if ($$meta{parse_env}) {
577 134         443 my $_env = delete $$meta{env}; # PWD and SHELL
578 134         1200 while ($words[0] =~ /^(\w[\w\-]*)=(.*)/s) {
579 6         78 $$meta{compl} = shift @words;
580 6         96 $$meta{env}{$1} = $2
581             }
582 134 50 66     1008 if (! @words and $$meta{env}) { # special case
    100          
583 0         0 @words = ('export', map $_.'='.$$meta{env}{$_}, keys %{$$meta{env}});
  0         0  
584 0         0 delete $$meta{env}; # duplicate would make var local
585             }
586             elsif ($$meta{env}) {
587 6         60 delete $$meta{compl}; # @words > 0
588 6         1224 for (keys %{$$meta{env}}) {
  6         42  
589 6         48 my (undef, @w) = @{ $self->parse_words([$meta, $$meta{env}{$_}]) };
  6         162  
590 6         30 $$meta{env}{$_} = join ':', @w;
591             }
592             }
593 134         506 for (keys %$_env) {
594 134 50       1183 $$meta{env}{$_} = $$_env{$_} unless defined $$meta{env}{$_};
595             }
596             }
597              
598             # parse redirections
599 134 50       636 return [$meta, @words] unless $$meta{parse_fd};
600 134         796 my @s_words = map [ $$self{stringparser}->split('redirect_gram', $_) ], @words;
601 134 50       633 return [$meta, @words] if ! grep {! ref $_} map @$_, @s_words;
  428         2122  
602 0   0     0 $$meta{fd} ||= [];
603 0         0 my @re;
604              
605 0         0 PARSE_REDIR_S_WORD:
606 0         0 my @parts = @{shift @s_words};
607 0         0 my $last = $#parts; # length of @parts changes later on
608 0         0 for (0 .. $#parts) {
609 0 0 0     0 next unless defined $parts[$_] and ! ref $parts[$_];
610 0         0 my $op = delete $parts[$_];
611 0 0       0 if ($op =~ /[^A-Z_]/) { # _SELF escape for ""
    0          
612 0         0 $parts[$_] = \$op;
613 0         0 next;
614             }
615             elsif ($op eq 'ERROR') {
616 0 0       0 error 'redirection operation not supported'
617             unless $$meta{pretend};
618             }
619              
620 0         0 my ($n, $word);
621 0 0 0     0 if ($_ > 0 and ref $parts[$_-1]) { # find file descriptor number
622 0 0       0 if (${$parts[$_-1]} =~ /^\d+$/) { $n = ${delete $parts[$_-1]} }
  0         0  
  0         0  
  0         0  
623             else {
624 0 0       0 ${$parts[$_-1]} =~ s/(\\\\)|(\\\d+)$|(\d+)$/$1 || $2/eg;
  0         0  
  0         0  
625 0         0 $n = $3;
626             }
627             }
628              
629 0 0 0     0 if ($_ < $#parts and ref $parts[$_+1]) { # find argument
    0 0        
630 0         0 $word = ${ delete $parts[$_+1] };
  0         0  
631 0 0 0     0 $$meta{compl} = $word if $_+1 == $last and ! @s_words; # complete last word
632             }
633             elsif (@s_words and ref $s_words[0][0]) {
634 0         0 $word = ${ delete $s_words[0][0] };
  0         0  
635 0 0 0     0 $$meta{compl} = $word if @s_words == 1 and ! @{$s_words[0]};
  0         0  
636             }
637             else {
638 0 0 0     0 error 'redirection needs argument'
639             unless $op =~ /^DUP/ or $$meta{pretend};
640 0         0 $$meta{compl} = '';
641             }
642              
643 0 0       0 unless ($$meta{pretend}) {
644 0 0 0     0 $n ||= ($op =~ /OUT$/) ? 1 : 0;
645 0         0 my (undef, @w) = @{ $self->parse_words([$meta, $word]) };
  0         0  
646 0 0       0 if (@w == 1) { push @{$$meta{fd}}, $n.$_redir_ops{$op}.$w[0] }
  0 0       0  
  0         0  
647 0         0 elsif (@w > 1) { error 'redirection argument expands to multiple words' }
648 0         0 else { error 'redirection needs argument' } # @w < 1
649             }
650             }
651 0         0 push @re, map $$_, @parts;
652 0 0       0 goto PARSE_REDIR_S_WORD if @s_words;
653              
654 0         0 return [$meta, @re];
655             }
656              
657             sub parse_aliases { # recursive sub (aliases are 3 way recursive, 2 ways are in this sub)
658 208     208 0 903 my ($self, $meta, @words) = @_;
659 208 50 33     3261 my $aliases = ($$self{_settings}{mode} && ! $$meta{no_mode})
660             ? $$self{aliases}{'mode_'.$$self{_settings}{mode}}
661             : $$self{aliases};
662 208 100 66     3247 return [$meta, @words] unless ref $aliases and exists $$aliases{$words[0]};
663 13   50     351 $$meta{alias_stack} ||= [];
664 13 50       26 return [$meta, @words] if grep {$_ eq $words[0]} @{$$meta{alias_stack}};
  0         0  
  13         143  
665 13         39 push @{$$meta{alias_stack}}, $words[0];
  13         78  
666              
667 13         143 my $string = $$aliases{$words[0]};
668 13         143 debug "$words[0] is aliased to: $string";
669 13         91 shift @words;
670              
671             =cut
672              
673             # saving code for later usage in pipelines
674             # this is not the right place for it
675              
676             { # variable substitution in the macro
677             local @_ = @words;
678             my $n = ( $string =~ s# (?
679             if ($1) { $words[$1] }
680             elsif ($2) { eval "join ' ', \@_[$2]" }
681             else { join ' ', @words }
682             #xge );
683             @words = () if $n;
684             }
685              
686             =cut
687              
688 13         130 my @as = @{$$meta{alias_stack}}; # force copy
  13         52  
689 13 50       260 my @l = map {
690 13 50       351 ref($_) ? [
691             { alias_stack => [@as] },
692             $$self{stringparser}->split('word_gram', $$_)
693             ] : $_
694             } $$meta{split_script} ? ($$self{stringparser}->split('script_gram', $string)) : ($string);
695              
696 13 50       130 if ( my ($firstref) = grep ref($_), @l ) {
697 13         78 $$firstref[0] = $meta; # re-insert %meta
698 13 50 0     247 ++$$meta{no_mode} and (length $$firstref[1] or delete $$firstref[1])
      0        
      33        
699             if @$firstref > 1 and $$firstref[1] =~ s/^\!\s*//; # check mode
700             }
701              
702 13 50       741 if ($string =~ /\s$/) { # recurs for 2nd word - see posix spec
    50          
703 0         0 my @l1 = $self->parse_aliases({}, @words); # recurs
704 0 0 0     0 push @{$l[-1]}, splice(@{ shift(@l1) }, 1) if ref $l[-1] and ref $l1[0];
  0         0  
  0         0  
705 0         0 push @l, @l1;
706             }
707 13         39 elsif (@l == 1) { return $self->parse_aliases(@{$l[0]}, @words) } # recurs
  13         182  
708             else {
709 0 0       0 if (ref $l[-1]) { push @{$l[-1]}, @words }
  0         0  
  0         0  
710 0         0 else { push @l, \@words }
711             }
712              
713 0         0 return @l;
714             }
715              
716             sub parse_words { # expand words etc.
717 164     164 0 515 my ($self, $block) = @_;
718              
719             # custom stack
720 164         850 for ($$self{parser}->stack('word_expansion')) {
721 0         0 my $re = $_->($block);
722 0 0       0 $block = $re if $re;
723             }
724              
725             # default expansions
726             # expand_comm resets zoidcmd, all other stuff is left for appliction level re-parsing
727             @$block = $self->$_(@$block)
728 164         2246 for grep $$block[0]{$_}, qw/expand_param expand_comm expand_path/;
729              
730             # remove quote
731 164         646 my ($meta, @words) = @$block;
732 164         475 for (@words) {
733 758 100       5064 if (/^([\/\w]+=)?(['"])(.*)\2$/s) {
734             # quote removal and escape removal within quotes
735 39         343 $_ = $1.$3;
736 39 100       271 if ($2 eq '\'') { $_ =~ s/\\([\\'])/$1/ge }
  29         116  
  1         5  
737 10         144 else { $_ =~ s/\\(.)/$1/ge }
  0         0  
738             }
739             # FIXME also do escape removal here
740             # is now done by File::Glob
741             }
742              
743 164         1336 return [$meta, @words];
744             }
745              
746             =cut
747              
748             # so far no luck of getting this to work - maybe combine intgrate
749             # this with stringparser some how :S
750              
751             our $_IFS = [undef, qr/\s+/, qr/\s+/];
752             sub _split_on_IFS { # bloody heavy routine for such a simple parsing rule
753             my $self = shift;
754             unless ($ENV{IFS} eq $$_IFS[0]) {
755             debug "generating new IFS regexes";
756             if (! defined $ENV{IFS}) { $_IFS = [undef, qr/\s+/, qr/\s+/] }
757             elsif ($ENV{IFS} eq '') { $_IFS = [''] }
758             else {
759             my $ifs_white = join '', ($ENV{IFS} =~ m/(\s)/g);
760             my $ifs_char = join '', ($ENV{IFS} =~ m/(\S)/g);
761             $_IFS = [ $ENV{IFS}, qr/[$ifs_white]+/,
762             qr/[$ifs_white]*[$ifs_char][$ifs_white]*|[$ifs_white]+/ ];
763             }
764             debug "IFS = ['$ENV{IFS}', $$_IFS[1], $$_IFS[2]]";
765             }
766             debug "IFS = ['$ENV{IFS}', $$_IFS[1], $$_IFS[2]]";
767             return @_ if defined $$_IFS[0] and $$_IFS[0] eq '';
768             return map {
769             $_ =~ s/(\\\\)|^$$_IFS[1]|(?
770             $$self{stringparser}->split($$_IFS[2], $_)
771             } @_;
772             }
773              
774             =cut
775              
776             =cut
777              
778             sub expand_braces {
779             my ($self, $meta, @words) = @_;
780             my @re;
781             for (@words) {
782             my @parts = $$self{stringparser}->split('expand_braces_gram', $_);
783             error $$self{stringparser}{broken} if $$self{stringparser}{broken};
784             # FIXME let stringparser do the error throwing ?
785             unless (@parts > 1) {
786             push @re, $_;
787             next;
788             }
789             for (0 .. $#parts) {
790             if ($parts[$_] eq 'BRACE') {
791             my $braced = delete $parts[$_+1];
792              
793             }
794             elsif (ref $parts[$_]) { $parts[$_] = ${$parts[$_]} }
795             }
796             push @re, join '', map {ref($_) ? (@$_) : $_} @parts;
797             }
798             return ($meta, @re);
799             }
800              
801             =cut
802              
803             sub expand_param {
804             # make sure $() and @() remain untouched ... `` are considered quotes
805 19     19   297 no strict 'refs';
  19         71  
  19         9273725  
806 164     164 0 1401 my ($self, $meta, @words) = @_;
807 164         244 my ($e);
808            
809 164         733 my $class = $$self{_settings}{perl}{namespace};
810             @words = map { # substitute vars
811 743 100       1944 if (/^([\/\w]+=)?'.*'$/s) { $_ }# skip quoted words
  29         316  
812             else {
813 714         958 my $old = $_;
814 714 0       1641 s{(?
  0 0       0  
815 714         3045 s{ (?
816 32   33     614 my ($w, $i) = ($1 || $2, $3);
817 32 50 0     298 $e ||= "no advanced expansion for \$\{$w\}" if $w =~ /[^\w-]/;
818 32 0 33     794 if ($w eq '_') { $w = $$self{topic} }
  0 50       0  
    50          
    0          
819 0         0 elsif (exists $$meta{env}{$w} or exists $ENV{$w}) {
820 32 50       220 $w = exists( $$meta{env}{$w} ) ? $$meta{env}{$w} : $ENV{$w} ;
821 32 100       225 $w = $i ? (split /:/, $w)[$i] : $w;
822             }
823 0         0 elsif ($i ? defined(*{$class.'::'.$w}{ARRAY}) : defined(*{$class.'::'.$w}{SCALAR})) {
824 0 0       0 $w = $i ? ${$class.'::'.$w}[$i] : ${$class.'::'.$w};
  0         0  
  0         0  
825             }
826 0         0 else { $w = '' }
827 32         189 $w =~ s/\\/\\\\/g; # literal backslashes
828 32         167 $w;
829             }exg;
830 714 100 100     2206 if ($_ eq $old or $_ =~ /^".*"$/) { $_ }
  691         1889  
831 23         2093 else { $$self{stringparser}->split('word_gram', $_) }
832             # TODO honour IFS here -- POSIX tells us so
833             }
834             }
835              
836             @words = map { # substitute arrays
837 164 100       390 if (m/^ \@ (?: \{ (.*?) \} | ([\w-]+) ) $/x) {
  741         1414  
838 1   33     64 my $w = $1 || $2;
839 1 50 0     15 $e ||= "no advanced expansion for \@\{$w\}" if $w =~ /[^\w-]/;
840 1 50 0     15 $e ||= '@_ is reserved for future syntax usage' if $2 eq '_';
841 1 50 33     27 if (exists $$meta{env}{$w} or exists $ENV{$w}) {
  0 0       0  
842 1 50       8 $w = (exists $$meta{env}{$w}) ? $$meta{env}{$w} : $ENV{$w};
843 1         10 map {s/\\/\\\\/g; $_} split /:/, $w;
  3         5  
  3         12  
844             }
845             elsif (defined *{$class.'::'.$w}{ARRAY}) {
846 0         0 map {s/\\/\\\\/g; $_} @{$class.'::'.$w};
  0         0  
  0         0  
  0         0  
847             }
848 0         0 else { () }
849             }
850 740         2225 else { $_ }
851             } @words;
852 164 50       670 error $e if $e; # "Attempt to free unreferenced scalar" when dying inside the map !?
853 164         2158 return ($meta, @words);
854             }
855              
856             sub expand_comm {
857 164     164 0 767 my ($self, $meta, @words) = @_;
858 164         234 my @re;
859 164         901 my $m = {capture => 1, env => $$meta{env}};
860 164         431 for (@words) {
861 755 100       3200 if (/^([\/\w]+=)?'.*'$/s) {
    50          
862 29         188 push @re, $_;
863             }
864             elsif (/^\@\((.*?)\)$/s) {
865 0         0 debug "\@() subz: $1";
866 0         0 push @re, $self->shell($m, $1); # list context
867             }
868             else {
869 726 100       2501 my $quote = $1 if s/^(")(.*)\1$/$2/s;
870 726         4690 my @parts = $$self{stringparser}->split('expand_comm_gram', $_);
871 726 50       2602 error $$self{stringparser}{broken} if $$self{stringparser}{broken};
872             # FIXME let stringparser do the error throwing ?
873 726 50       1868 unless (@parts > 1) {
874 726 100       11080 push @re, $quote ? $quote.$_.$quote : $_;
875 726         2090 next;
876             }
877 0         0 for (0 .. $#parts) {
878 0 0       0 if ($parts[$_] eq 'COMM') {
    0          
879 0         0 debug '$() subz: '.$parts[$_+1];
880 0         0 $parts[$_] = $self->shell($m, ${delete $parts[$_+1]}); # scalar context
  0         0  
881 0 0 0     0 if ($_ < $#parts-1 and ${$parts[$_+2]} =~ s/^\[(\d*)\]//) {
  0         0  
882 0         0 $parts[$_] = $parts[$_][$1];
883 0         0 chomp $parts[$_];
884             }
885 0         0 else { $parts[$_] = "$parts[$_]" } # just to be sure bout overload
886             }
887 0         0 elsif (ref $parts[$_]) { $parts[$_] = ${$parts[$_]} }
  0         0  
888             }
889 0         0 my $word = join '', @parts; # map {ref($_) ? (@$_) : $_} @parts;
890 0 0       0 if ($quote) { push @re, $quote.$word.$quote }
  0         0  
891 0         0 else { push @re, $$self{stringparser}->split('word_gram', $word) }
892             # TODO honour IFS here - POSIX says so
893             }
894             }
895 164         1833 $$meta{env}{ZOIDCMD} = $$meta{zoidcmd} = join ' ', @re;
896 164         2131 return $meta, @re;
897             }
898              
899             # See File::Glob for explanation of behaviour
900             our $_GLOB_OPTS = File::Glob::GLOB_TILDE() | File::Glob::GLOB_QUOTE() | File::Glob::GLOB_BRACE();
901             our $_NC_GLOB_OPTS = $_GLOB_OPTS | File::Glob::GLOB_NOCHECK();
902              
903             sub expand_path { # path expansion
904             # FIXME add 'failglob' setting (useful in scripts)
905 164     164 0 569 my ($self, $meta, @files) = @_;
906 164 50       4465 return $meta, @files if $$self{_settings}{noglob};
907 164 50       807 my $opts = $$self{_settings}{nullglob} ? $_GLOB_OPTS : $_NC_GLOB_OPTS;
908 164 50       643 $opts |= File::Glob::GLOB_NOCASE() if $$self{_settings}{nocaseglob};
909             return $meta, map {
910 164 100       395 if (/^([\/\w]+=)?(['"])/) { $_ } # quoted
  755 50       6638  
  39 100       731  
911             elsif (/^m\{(.*)\}([imsx]*)$/) { # regex globs
912 0         0 my @r = regex_glob($1, $2);
913 0 0       0 if (@r) { @r }
  0         0  
914 0 0       0 else { $_ =~ s/\\\\|\\(.)/$1||'\\'/eg; $_ }
  0         0  
  0         0  
915             }
916             elsif (/^~|[*?\[\]{}]/) { # normal globs
917             # TODO: {x..y} brace expansion
918 11 50       168 $_ =~ s#(\\\\)|(?
  4 50       60  
919             unless $$self{_settings}{voidbraces}; # brace pre-parsing
920 11         626 my @r = File::Glob::bsd_glob($_, $opts);
921 11         242 debug "glob: $_ ==> ".join(', ', @r);
922 11 50       81 ($_ !~ /^-/) ? (grep {$_ !~ /^-/} @r) : (@r);
  14         153  
923             # protect against implict switches as file names
924             }
925 705 50       2030 else { $_ =~ s/\\\\|\\(.)/$1||'\\'/eg; $_ } # remove escapes # FIXME should be done in parse_words like quote removal
  1         8  
  705         4707  
926             } @files ;
927             }
928              
929             sub parse_perl { # parse switches
930 46     46 0 126 my ($self, $block) = @_;
931 46         109 my ($meta, $string) = @$block;
932 46         1014 my %opts = map {($_ => 1)} split '', $$self{_settings}{perl}{opts};
  46         391  
933 46 50       389 $opts{z} = 0 if delete $opts{Z};
934 46         222 $opts{$_}++ for split '', $$meta{opts};
935 46 50       177 $opts{z} = 0 if delete $opts{Z};
936 46         244 debug 'perl block options: ', \%opts;
937              
938 46 50       373 ($meta, $string) = $self->_expand_zoid($meta, $string) unless $opts{z};
939              
940 46 50       295 if ($opts{g}) { $string = "\nwhile () {\n\tif (eval {".$string."}) { print \$_; }\n}" }
  0 50       0  
    50          
941 0         0 elsif ($opts{p}) { $string = "\nwhile () {\n\t".$string.";\n\tprint \$_\n}" }
942 0         0 elsif ($opts{n}) { $string = "\nwhile () {\n\t".$string.";\n}" }
943              
944 46 50       245 $string = "no strict;\n".$string unless $opts{z};
945              
946 46         253 return [$meta, $string];
947             }
948              
949             sub _expand_zoid {
950 53     53   16198 my ($self, $meta, $code) = @_;
951              
952 53         289 my @parts = $$self{stringparser}->split('dezoid_gram', $code);
953 53         189 my @idx = grep {! ref $parts[$_]} 0 .. $#parts;
  148         377  
954 53 100       122 @parts = map {ref($_) ? $$_ : $_} @parts;
  148         451  
955              
956 53         119 my $pre = '';
957 53         162 for (@idx) { # probably could be done much cleaner
958 56         126 my $token = delete $parts[$_];
959 56 50       236 my $next = ($_ < $#parts) ? $parts[$_+1] : '';
960 56 100       282 my $prev = $_ ? $parts[$_-1] : '';
961              
962 56         197 my $class = $$self{_settings}{perl}{namespace};
963 56 100       431 if ($token =~ /^([\@\$])(\w+)/) {
    100          
964 49         149 my ($sigil, $name) = ($1, $2);
965 49 100 66     462 if ( # global, reserved or non-env var
    100 100        
      100        
      66        
966 294         1223 $next =~ /^::/
967             or grep {$name eq $_} qw/_ ARGV ENV SIG INC JOBS/
968             or ! exists $ENV{$name} and ! exists $$meta{env}{$name}
969 41         151 ) { $parts[$_] = $token }
970             elsif ($sigil eq '@' or $next =~ /^\[/) { # array
971 19     19   258 no strict 'refs';
  19         39  
  19         44166  
972 1         33 $pre .= "Env->import('$token');\n"
973 1 50 33     11 unless defined *{$class.'::'.$name}{ARRAY} and @{$class.'::'.$name};
  0         0  
974 1         7 $parts[$_] = $token;
975             }
976 7         65 else { $parts[$_] = '$ENV{'.$name.'}' } # scalar
977             }
978             # else token eq 'ARR'
979 1         4 elsif ($prev =~ /[\w\}\)\]]$/) { $parts[$_] = '->' }
980 6         17 else { $parts[$_] = '$shell->' }
981             }
982              
983 53         533 return $meta, $pre . join '', grep defined($_), @parts;
984             }
985              
986             # ########## #
987             # Exec stuff #
988             # ########## #
989              
990             sub eval_block { # real exec code
991 90     90 0 196 my ($self, $ref) = @_;
992 90         394 my $context = $$ref[0]{context};
993              
994 90 50       898 if ($$self{parser}{$context}{handler}) {
    50          
995 0         0 debug "going to call handler for context: $context";
996 0         0 $$self{parser}{$context}{handler}->($ref);
997             }
998             elsif ($self->can('_do_'.lc($context))) {
999 90         248 my $sub = '_do_'.lc($context);
1000 90         469 debug "going to call sub: $sub";
1001 90         637 $self->$sub(@$ref);
1002             }
1003             else {
1004 0 0       0 $context
1005             ? error "No handler defined for context $context"
1006             : bug 'No context defined !'
1007             }
1008             }
1009              
1010             # FIXME FIXME remove _do_* subs below and store them in {parser}
1011              
1012             sub _do_subz { # sub shell, forked if all is well
1013 0     0   0 my ($self, $meta) = @_;
1014 0         0 my $cmd = $$meta{zoidcmd};
1015 0 0       0 $cmd = $1 if $cmd =~ /^\s*\((.*)\)\s*$/s;
1016 0         0 %$meta = map {($_ => $$meta{$_})} qw/env/; # FIXME also add parser opts n stuff
  0         0  
1017             # FIXME reset mode n stuff ?
1018 0         0 $self->shell_string($meta, $cmd);
1019 0 0       0 error $$self{error} if $$self{error}; # forward the error
1020             }
1021              
1022             sub _do_cmd {
1023 44     44   605 my ($self, $meta, $cmd, @args) = @_;
1024             # exec = exexvp which checks $PATH for us
1025             # the block syntax to force use of execvp, not shell for one argument list
1026             # If a command is not found, the exit status shall be 127. If the command name is found,
1027             # but it is not an executable utility, the exit status shall be 126.
1028 44   50     1896 $$meta{cmdtype} ||= '';
1029 44 100 66     1437 if ($cmd =~ m|/|) { # executable file
    100          
1030 1 50       16 error 'builtin should not contain a "/"' if $$meta{cmdtype} eq 'builtin';
1031 1 50       60 error {exit_status => 127}, $cmd.': No such file or directory' unless -e $cmd;
1032 1 50       8 error {exit_status => 126}, $cmd.': is a directory' if -d _;
1033 1 50       7 error {exit_status => 126}, $cmd.': Permission denied' unless -x _;
1034 1         21 debug 'going to exec file: ', join ', ', $cmd, @args;
1035 1 0       10 exec {$cmd} $cmd, @args or error {exit_status => 127}, $cmd.': command not found';
  1         0  
1036             }
1037             elsif ($$meta{cmdtype} eq 'builtin' or exists $$self{commands}{$cmd}) { # built-in, not forked I hope
1038 30 50       198 error {exit_status => 127}, $cmd.': no such builtin' unless exists $$self{commands}{$cmd};
1039 30         254 debug 'going to do built-in: ', join ', ', $cmd, @args;
1040 30         308 local $Zoidberg::Utils::Error::Scope = $cmd;
1041 30         196 $$self{commands}{$cmd}->(@args);
1042             }
1043             else { # command in path ?
1044 13         717 debug 'going to exec: ', join ', ', $cmd, @args;
1045 13 0       156 exec {$cmd} $cmd, @args or error {exit_status => 127}, $cmd.': command not found';
  13         0  
1046             }
1047             }
1048              
1049             sub _do_perl {
1050 46     46   93 my ($shell, $_Meta, $_Code) = @_;
1051 46   50     261 my $_Class = $$shell{_settings}{perl}{namespace} || 'Zoidberg::Eval';
1052 46 50       282 $_Code .= ";\n\$_Class = __PACKAGE__;" if $_Code =~ /package/;
1053 46         166 $_Code = "package $_Class;\n$_Code";
1054 46         100 undef $_Class;
1055 46         245 debug "going to eval perl code: << '...'\n$_Code\n...";
1056              
1057 46         533 local $Zoidberg::Utils::Error::Scope = ['zoid', 0];
1058 46         237 $_ = $$shell{topic};
1059 46 50       198 $? = $$shell{error}{exit_status} if ref $$shell{error};
1060 46 50   16   9630 ref($_Code) ? eval { $_Code->() } : eval $_Code;
  0     16   0  
  16         224  
  16         32  
  16         1101  
  16         215  
  16         23  
  16         166160  
1061 46 50       1260 if ($@) { # post parse errors
1062 0 0       0 die if ref $@; # just propagate the exception
1063 0         0 $@ =~ s/ at \(eval \d+\) line (\d+)(\.|,.*\.)$/ at line $1/;
1064 0         0 error { string => $@, scope => [] };
1065             }
1066             else {
1067 46         278 $$shell{topic} = $_;
1068 46 50       198 $$shell{settings}{perl}{namespace} = $_Class if $_Class;
1069 46 50       533 print "\n" if $$shell{_settings}{interactive}; # ugly hack
1070             }
1071             }
1072              
1073             # ############## #
1074             # some functions #
1075             # ############## #
1076              
1077             =item mode [mode]
1078              
1079             Without arguments prints the current mode.
1080             With arguments sets the mode.
1081              
1082             =cut
1083              
1084             sub mode {
1085 0     0 1 0 my $self = shift;
1086 0 0       0 unless (@_) {
1087 0 0       0 output $$self{_settings}{mode} if $$self{_settings}{mode};
1088 0         0 return;
1089             }
1090 0         0 my $mode = shift;
1091 0 0 0     0 if ($mode eq '-' or $mode eq 'default') {
1092 0         0 $$self{settings}{mode} = undef;
1093             }
1094             else {
1095 0 0       0 my $m = ($mode =~ /::/) ? $mode : uc($mode);
1096 0         0 error $mode.': No such context defined'
1097 0 0 0     0 unless grep {lc($mode) eq $_} qw/perl cmd sh/
1098             or $$self{parser}{$m}{handler} ; # allow for autoloading
1099 0         0 $$self{settings}{mode} = $mode;
1100             }
1101             }
1102              
1103             =item plug
1104              
1105             TODO
1106              
1107             =cut
1108              
1109             sub plug {
1110 32     32 1 70 my $self = shift;
1111 32         509 my ($opts, $args) = getopt 'list,l verbose,v @', @_;
1112 32 50       137 if ($$opts{list}) { # list info
1113 0         0 my @items = keys %{$$self{objects}};
  0         0  
1114 0 0       0 if (@$args) {
1115 0         0 my $re = join '|', @$args;
1116 0         0 @items = grep m/$re/i, @items;
1117             }
1118 0 0       0 if ($$opts{verbose}) { # FIXME nicer PLuginHash interface for this
1119 0         0 my ($raw, $meta) = @{ tied( %{$$self{objects}} ) };
  0         0  
  0         0  
1120 0 0       0 @items = map {
1121 0         0 $_ .' '. $$meta{$_}{module}
1122             . (exists($$raw{$_}) ? ' (loaded)' : '')
1123             } @items;
1124             }
1125 0         0 output \@items;
1126             }
1127             else { # load plugin
1128 32 50       106 error 'usage: plug name [args]' unless @$args;
1129 32 50       632 error $$args[0].': no such plugin'
1130             unless exists $$self{objects}{ $$args[0] };
1131 0         0 tied( %{$$self{objects}} )->load(@$args);
  0         0  
1132             }
1133             }
1134              
1135             =item unplug
1136              
1137             TODO
1138              
1139             =cut
1140              
1141             sub unplug {
1142 0     0 1 0 my $self = shift;
1143 0         0 my ($opt, $args) = getopt 'all,a @', @_;
1144 0 0       0 if ($$opt{all}) { tied( %{$$self{objects}} )->CLEAR() }
  0         0  
  0         0  
1145             else {
1146 0 0       0 error "usage: unplug name" unless @$args == 1;
1147 0         0 delete $$self{objects}{$$args[0]};
1148             }
1149             }
1150              
1151 0     0 0 0 sub dev_null {} # does absolutely nothing
1152              
1153             sub stdin { # stub STDIN input
1154 0     0 0 0 my (undef, $prompt, $preput) = @_;
1155 0         0 local $/ = "\n";
1156 0 0       0 print $prompt if length $prompt;
1157 0 0       0 my $string = length($preput) ? $preput . : ;
1158 0         0 output $string;
1159             };
1160              
1161             sub list_clothes {
1162 0     0 0 0 my $self = shift;
1163 0         0 my @return = map {'{'.$_.'}'} sort @{$self->{_settings}{clothes}{keys}};
  0         0  
  0         0  
1164 0         0 push @return, sort @{$self->{_settings}{clothes}{subs}};
  0         0  
1165 0         0 return [@return];
1166             }
1167              
1168             # ########### #
1169             # Event logic #
1170             # ########### #
1171              
1172             sub broadcast { # eval to be sure we return
1173 1375367     1375367 0 6054496 my ($self, $event) = (shift(), shift());
1174 1375367 100       16034998 return unless exists $self->{events}{$event};
1175 311         5099 debug "Broadcasting event: $event";
1176 311         2832 for my $sub ($$self{events}->stack($event)) {
1177 327         995 eval { $sub->($event, @_) };
  327         2833  
1178 327 50       15886 complain("$sub died on event $event ($@)") if $@;
1179             }
1180             }
1181              
1182 0     0 0 0 sub call { bug 'deprecated routine used' }
1183              
1184             # ########### #
1185             # auto loader #
1186             # ########### #
1187              
1188             our $ERROR_CALLER;
1189              
1190             sub AUTOLOAD {
1191 9     9   2428 my $self = shift;
1192 9         109 my $call = (split/::/,$AUTOLOAD)[-1];
1193              
1194 9         81 local $ERROR_CALLER = 1;
1195 9 50       84 error "Undefined subroutine &Zoidberg::$call called" unless ref $self;
1196 9         212 debug "Zoidberg::AUTOLOAD got $call";
1197              
1198 9 50       207 if (exists $self->{objects}{$call}) {
1199 19     19   148 no strict 'refs';
  19         54  
  19         17365  
1200 0     0   0 *{ref($self).'::'.$call} = sub { return $self->{objects}{$call} };
  0         0  
  0         0  
1201 0         0 goto \&{$call};
  0         0  
1202             }
1203             else { # Shell like behaviour
1204 9         108 debug "No such method or object: '$call', trying to shell() it";
1205 9         69 @_ = ([$call, @_]); # force words parsing
1206 9         103 goto \&Zoidberg::Shell::shell;
1207             }
1208             }
1209              
1210             # ############# #
1211             # Exit routines #
1212             # ############# #
1213              
1214             =item C
1215              
1216             Called by plugins to exit zoidberg -- this ends a interactive C
1217             loop. This does not clean up or destroy any objects, C can be
1218             called again to restart it.
1219              
1220             =cut
1221              
1222             sub exit {
1223 0     0 1 0 my $self = shift;
1224 0 0 0     0 if (@{$$self{jobs}} and ! $$self{_warned_bout_jobs}) {
  0         0  
1225 0         0 complain "There are unfinished jobs";
1226 0         0 $$self{_warned_bout_jobs}++;
1227             }
1228             else {
1229 0         0 message join ' ', @_;
1230 0         0 $self->{_continue} = 0;
1231             }
1232             # FIXME this should force ReadLine to quit
1233             }
1234              
1235             =item C
1236              
1237             This method should be called to clean up the shell objects.
1238             A C method will be called recursively for all secondairy objects.
1239              
1240             =cut
1241              
1242             sub round_up {
1243 2     2 1 533 my $self = shift;
1244 2         33 $self->broadcast('exit');
1245 2 50       35 if ($self->{round_up}) {
1246 2         13 tied( %{$$self{objects}} )->round_up(); # round up loaded plugins
  2         50  
1247 2         20 Zoidberg::Contractor::round_up($self);
1248 2         0 undef $self->{round_up};
1249             }
1250             }
1251              
1252             sub DESTROY {
1253 1     1   472 my $self = shift;
1254 1 50       5 if ($$self{round_up}) {
1255 0         0 warn "Zoidberg was not properly cleaned up.\n";
1256 0         0 $self->round_up;
1257             }
1258 1         123 delete $OBJECTS{"$self"};
1259             }
1260              
1261             package Zoidberg::SettingsHash;
1262              
1263             our $VERSION = '0.981';
1264              
1265             sub TIEHASH {
1266 16     16   47 my ($class, $ref, $shell) = @_;
1267 16         276 bless [$ref, $shell], $class;
1268             }
1269              
1270             sub STORE {
1271 103     103   2786 my ($self, $key, $val) = @_;
1272 103         315 my $old = $$self[0]{$key};
1273 103         504 $$self[0]{$key} = $val;
1274 103         915 $$self[1]->broadcast('set_'.$key, $val, $old); # new, old
1275 103         455 1;
1276             }
1277              
1278             #sub set_default {
1279             # my ($self, $key, @list) = @_;
1280             # $$self[0]{_SettingsHash_def}{$key} = \@list;
1281             #}
1282              
1283             sub DELETE {
1284 0     0   0 my ($self, $key) = @_;
1285 0         0 my $val = delete $$self[0]{$key};
1286 0         0 $$self[1]->broadcast('set_'.$key, undef, $val); # new, old
1287 0         0 return $val;
1288             }
1289              
1290 0     0   0 sub CLEAR { $_[0]->DELETE($_) for keys %{$_[0][0]} }
  0         0  
1291              
1292             sub FETCH {
1293 8149     8149   97984 return $_[0][0]{$_[1]}
1294             # unless !defined $_[0][0]{$_[1]}
1295             # and exists $_[0][0]{_SettingsHash_def}{$_[1]};
1296             # check for default (environment) values
1297             # for my $def (@{$_[0][0]{_SettingsHash_def}{$_[1]}}) {
1298             # $def = $ENV{$1} if $def =~ /^\$(.*)/;
1299             # return $def if defined $def;
1300             # }
1301             }
1302              
1303 0     0   0 sub EXISTS { exists $_[0][0]{$_[1]} }
1304              
1305 0     0   0 sub FIRSTKEY { my $a = scalar keys %{$_[0][0]}; each %{$_[0][0]} }
  0         0  
  0         0  
  0         0  
1306              
1307 0     0   0 sub NEXTKEY { each %{$_[0][0]} }
  0         0  
1308              
1309             package Zoidberg::Eval;
1310              
1311             our $VERSION = '0.981';
1312              
1313             # included to bootstrap a bit of default environment
1314             # for the perl syntax
1315              
1316 19     19   334 use strict;
  19         39  
  19         925  
1317 19     19   131 use vars qw/$AUTOLOAD/;
  19         40  
  19         1729  
1318              
1319 19     19   25991 use Data::Dumper;
  19         144935  
  19         1465  
1320 19     19   2123 use Zoidberg::Shell qw/:all/;
  19         42  
  19         229  
1321 19     19   5956 use Zoidberg::Utils qw/:error :output :fs regex_glob/;
  19         38  
  19         135  
1322             require Env;
1323              
1324             $| = 1;
1325             $Data::Dumper::Sortkeys = 1;
1326              
1327             sub pp { # pretty print
1328 0 0   0   0 local $Data::Dumper::Maxdepth = shift if $_[0] =~ /^\d+$/;
1329 0 0       0 if (wantarray) { return Dumper @_ }
  0         0  
1330 0         0 else { print Dumper @_ }
1331             }
1332              
1333             {
1334 19     19   6664 no warnings;
  19         53  
  19         4536  
1335             sub AUTOLOAD {
1336             ## Code inspired by Shell.pm ##
1337 0     0   0 my $cmd = (split/::/, $AUTOLOAD)[-1];
1338 0 0       0 return undef if $cmd eq 'DESTROY';
1339 0 0       0 shift if ref($_[0]) eq __PACKAGE__;
1340 0         0 debug "Zoidberg::Eval::AUTOLOAD got $cmd";
1341 0         0 @_ = ([$cmd, @_]); # force words
1342 0 0       0 unshift @{$_[0]}, '!'
  0         0  
1343             if lc( $Zoidberg::CURRENT->{settings}{mode} ) eq 'perl';
1344 0         0 goto \&shell;
1345             }
1346             }
1347              
1348             1;
1349              
1350             __END__