File Coverage

blib/lib/Zoidberg/Shell.pm
Criterion Covered Total %
statement 61 137 44.5
branch 21 74 28.3
condition 5 26 19.2
subroutine 13 35 37.1
pod 11 14 78.5
total 111 286 38.8


line stmt bran cond sub pod time code
1             package Zoidberg::Shell;
2              
3             our $VERSION = '0.981';
4              
5 19     19   8539 use strict;
  19         25  
  19         550  
6 19     19   82 use vars qw/$AUTOLOAD/;
  19         21  
  19         828  
7 19     19   6712 use Zoidberg::Utils qw/:error :output path getopt/;
  19         40  
  19         180  
8             use Exporter::Tidy
9 19         162 default => [qw/AUTOLOAD shell command builtin/],
10             command => [qw/shell command builtin/],
11             jobs => [qw/job @JOBS/],
12             data => [qw/alias unalias set setting/],
13 19     19   4190 other => [qw/source readpipe/];
  19         38  
14              
15             our @JOBS;
16             tie @JOBS, 'Zoidberg::Shell::JobsArray';
17              
18             sub new {
19 16     16 1 95329 shift; #class
20 16     16   3662 eval 'use Zoidberg';
  16         17767  
  16         63  
  16         113  
21 16 50       80 die if $@;
22              
23             # reformat some settings
24 16         177 my %obj = @_;
25 16   50     372 my $set = $obj{settings} || {};
26 16 50 33     111 $$set{rcfiles} = [] if exists $$set{norc} and delete $$set{norc};
27 16 50 33     288 $$set{interactive} = (-t STDIN and -t STDOUT)
28             unless defined $$set{interactive};
29 16         49 $obj{settings} = $set;
30              
31 16         128 Zoidberg->new(%obj);
32             }
33              
34 32     32 1 568 sub current { return $Zoidberg::CURRENT }
35              
36             sub any {
37 0 0   0 1 0 goto &new unless $Zoidberg::CURRENT;
38 0         0 return $Zoidberg::CURRENT;
39             }
40              
41 216 100   216   617 sub _self { ( eval{ $_[0]->isa( __PACKAGE__ ) } ) ? shift : $Zoidberg::CURRENT }
  216         2608  
42              
43             # ################ #
44             # Parser interface #
45             # ################ #
46              
47             sub AUTOLOAD {
48             ## Code inspired by Shell.pm ##
49 0     0   0 my $cmd = (split/::/, $AUTOLOAD)[-1];
50 0 0       0 return undef if $cmd eq 'DESTROY';
51 0 0       0 shift if ref($_[0]) eq __PACKAGE__;
52 0         0 debug "Zoidberg::Shell::AUTOLOAD got $cmd";
53 0         0 @_ = ([{plain_words => 1}, $cmd, @_]); # force words
54 0         0 goto \&shell;
55             }
56              
57             sub shell { # FIXME FIXME should not return after ^Z
58 182     182 1 9437 my $self = &_self;
59 182 50       1268 my $meta = (ref($_[0]) eq 'HASH') ? shift : {};
60 182 50       14616 my $pipe = ($_[0] =~ /^-\||\|-$/) ? shift : undef ;
61 182 50       1165 todo 'pipeline syntax' if $pipe;
62 182         536 my $save_error = $$self{error};
63 182   66     1326 $$self{fg_job} ||= $self;
64 182         890 $$meta{capture} = defined wantarray;
65 182         561 $$meta{wantarray} = wantarray;
66 182 50       947 local $$self{_settings}{output}{error} = 'mute'
67             if delete $$meta{die_silently};
68 182         302 my @re;
69 182 100       979 if (grep {ref $_} @_) { @re = $$self{fg_job}->shell_list($meta, @_) }
  457 100       2396  
  23         341  
70 49         795 elsif (@_ > 1) { @re = $$self{fg_job}->shell_list($meta, \@_) }
71 110         1568 else { @re = $self->shell_string($meta, @_) }
72 168         1202 $@ = $$self{error};
73 168         2559 $$self{error} = $save_error;
74 168         1614 _return($@, @re);
75             }
76              
77             sub _return { # fairly complex output logic :S
78             # the heuristic is that list context is the fastest .. can this be optimised further ?
79 168 100   168   1074 my $error = shift() ? 0 : 1;
80 168 100       44730 unless (@_) { return Zoidberg::Utils::Output::Scalar->new($error, '') }
  75 50       1619  
    50          
81             elsif (wantarray) {
82 0 0 0     0 return map { # try to do the same list heuristics as utils::output
83 0         0 (ref($_) eq 'ARRAY' and ! grep ref($_), @$_) ? (@$_) : $_
84             } @_;
85             }
86             elsif (! grep ref($_), @_) { # only strings
87 93         2948 return Zoidberg::Utils::Output::Scalar->new($error, join('', @_)) ;
88             }
89             else { # one or more refs in @_
90 0 0       0 if (@_ == 1) { # must be ref
91 0 0 0     0 return( (ref($_[0]) eq 'ARRAY' and ! grep ref($_), @{$_[0]})
92             ? Zoidberg::Utils::Output::Scalar->new($error, undef, shift())
93             : Zoidberg::Utils::Output::Scalar->new($error, shift() ) ) ;
94             }
95             else { # mixed data - hope we get it right
96 0 0 0     0 return Zoidberg::Utils::Output::Scalar->new($error, undef, [ map {
97 0         0 (ref($_) eq 'ARRAY' and ! grep ref($_), @$_) ? (@$_) : $_
98             } @_ ] ) ;
99             }
100             }
101             }
102              
103             sub builtin {
104 0     0 0 0 unshift @_, 'builtin';
105 0         0 goto \&_shell_cmd;
106             }
107              
108             sub command {
109 0     0 0 0 unshift @_, 'system';
110 0         0 goto \&_shell_cmd;
111             }
112              
113             sub _shell_cmd {
114 0     0   0 my $type = shift;
115 0         0 my $self = &_self;
116 0 0       0 my $meta = (ref($_[0]) eq 'HASH') ? shift : {};
117 0         0 $$meta{capture} = defined wantarray;
118 0         0 $$meta{wantarray} = wantarray;
119 0 0       0 local $$self{_settings}{output}{error} = 'mute'
120             if delete $$meta{die_silently};
121 0         0 my $save_error = $$self{error};
122 0   0     0 $$self{fg_job} ||= $self;
123 0         0 my @re = $$self{fg_job}->shell_job( $meta,
124             [{context => 'CMD', cmdtype => $type}, @_] );
125 0         0 $@ = $$self{error};
126 0         0 $$self{error} = $save_error;
127 0         0 _return($@, @re);
128             }
129              
130             sub readpipe { # TODO handle STDOUT differently, see perlop
131 0     0 0 0 my $self = &_self;
132 0         0 my @re = $self->shell_string( {capture => 1}, @_ );
133 0 0       0 return wantarray ? @re : join('', @re);
134             }
135              
136             sub system {
137             # quick parser independent way of calling system commands
138             # not exported to avoid conflict with perlfunc system
139 0     0 1 0 my $self = &_self;
140 0 0       0 open CMD, '|-', @_ or error "Could not open pipe to: $_[0]";
141 0         0 my @re = ();
142 0         0 close CMD;
143 0 0       0 return wantarray ? @re : join('', @re);
144             # FIXME where does the error code of cmd go?
145             }
146              
147             # ############# #
148             # Some builtins #
149             # ############# #
150              
151             sub alias {
152 18     18 1 6419 my $self = &_self;
153 18         201 my (undef, $hash) = getopt '%', @_;
154 18         249 $$self{aliases}{$_} = $$hash{$_} for keys %$hash;
155             # TODO merge code from Commands here
156             }
157              
158             sub unalias {
159 0     0 1 0 my $self = &_self;
160 0         0 my ($opts, $args) = getopt 'all,a @', @_;
161 0 0       0 if ($$opts{all}) { %{$$self{aliases}} = () }
  0         0  
  0         0  
162             else {
163 0         0 for (@$args) {
164 0 0       0 error "alias: $_: not found"
165             unless delete $$self{aliases}{$_};
166             }
167             }
168             # TODO merge code from Commands here
169             # use path2hashref
170             }
171              
172             sub set {
173             # TODO merge code from Commands here
174             # use path2hashref
175 0     0 1 0 my $self = &_self;
176 0 0       0 if (ref($_[0]) eq 'HASH') { # perl style, merge hashes
    0          
177 0 0       0 error 'set: only first argument is used' if $#_;
178 0         0 %{$self->{settings}} = ( %{$self->{settings}}, %{$_[0]} );
  0         0  
  0         0  
  0         0  
179             }
180 0         0 elsif (ref $_[0]) { error 'set: no support for data type'.ref($_[0]) }
181 0         0 else { $self->{settings}{$_}++ for @_ }
182             }
183              
184             sub setting {
185             # use path2hashref
186 0     0 1 0 my $self = &_self;
187 0         0 my $key = shift;
188 0 0       0 return exists($$self{settings}{$key}) ? $$self{settings}{$key} : undef;
189             }
190              
191             sub source {
192 16     16 1 80 my $self = &_self;
193 16         48 local $Zoidberg::CURRENT = $self;
194 16         49 for my $file (@_) {
195 16         340 my $file = path($file);
196 16 50       453 error "source: no such file: $file" unless -f $file;
197 16         96 debug "going to source: $file";
198             # FIXME more intelligent behaviour -- see bash man page
199 16         1408 eval q{package Main; our $SCRIPT = $file; do $file; die $@ if $@ };
200             # FIXME wipe Main
201 16 50       160 complain if $@;
202             }
203             }
204              
205 0     0 1 0 sub job { $Zoidberg::CURRENT->job_by_spec(pop @_) }
206              
207             package Zoidberg::Shell::JobsArray;
208              
209             our $VERSION = '0.981';
210              
211 19     19   83 sub TIEARRAY { bless \$Zoidberg::Shell::VERSION, shift } # what else is there to bless ?
212              
213 0 0   0   0 sub FETCH { $_[1] ? $Zoidberg::CURRENT->job_by_id($_[1]) : $$Zoidberg::CURRENT{fg_job} }
214              
215 0     0   0 sub STORE { die "Can't overwrite jobs, try 'push'\n" } # STORE has no meaning for this thing
216              
217             sub DELETE {
218 0     0   0 my $j = $Zoidberg::CURRENT->job_by_id($_[1]);
219 0 0       0 return unless $j;
220 0         0 $j->kill(undef, 'WIPE');
221             }
222              
223             sub POP {
224 0     0   0 my $last_id;
225 0         0 $$_{id} > $last_id and $last_id = $$_{id}
226 0   0     0 for @{$Zoidberg::CURRENT->{jobs}};
227 0         0 $_[0]->DELETE($last_id);
228             }
229              
230 0     0   0 sub SHIFT { $_[0]->DELETE(1) }
231              
232             sub PUSH {
233 0 0   0   0 ref($_[1])
234             ? $Zoidberg::CURRENT->shell_list( {prepare => 1}, $_[1] )
235             : $Zoidberg::CURRENT->shell_string( {prepare => 1}, $_[1] ) ;
236             }
237              
238 0     0   0 sub UNSHIFT { die "Can't overwrite jobs, try 'push'\n" }
239              
240 0     0   0 sub FETCHSIZE { 1 + scalar @{ $Zoidberg::CURRENT->{jobs} } }
  0         0  
241              
242 0     0   0 sub EXISTS { $Zoidberg::CURRENT->job_by_id($_[1]) }
243              
244 0     0   0 sub CLEAR { $_->kill(undef, 'WIPE') for @{$Zoidberg::CURRENT->{jobs}} }
  0         0  
245              
246 0     0   0 sub SPLICE { die "Can't overwrite jobs, try 'delete' or 'push'\n" }
247              
248             1;
249              
250             __END__