File Coverage

blib/lib/System/Sub.pm
Criterion Covered Total %
statement 111 137 81.0
branch 56 86 65.1
condition 7 12 58.3
subroutine 20 23 86.9
pod n/a
total 194 258 75.1


line stmt bran cond sub pod time code
1 6     6   68653 use strict;
  6         7  
  6         155  
2 6     6   21 use warnings;
  6         7  
  6         249  
3             package System::Sub;
4             $System::Sub::VERSION = '0.162800';
5 6     6   2473 use File::Which ();
  6         3960  
  6         116  
6 6     6   2285 use Sub::Name 'subname';
  6         2421  
  6         326  
7 6     6   2313 use Symbol 'gensym';
  6         3793  
  6         320  
8 6     6   4518 use IPC::Run qw(start finish);
  6         184508  
  6         315  
9 6     6   36 use Scalar::Util 1.11 (); # set_prototype(&$) appeared in 1.11
  6         148  
  6         207  
10              
11             our @CARP_NOT;
12              
13 6     6   20 use constant DEBUG => !! $ENV{PERL_SYSTEM_SUB_DEBUG};
  6         7  
  6         3154  
14              
15              
16              
17             my %OPTIONS = (
18             # Value is the expected ref of the option value
19             # undef is no value
20             '>' => '',
21             '<' => '',
22             'ENV' => 'HASH',
23             '?' => 'CODE',
24             );
25              
26             sub _croak
27             {
28 0     0   0 require Carp;
29 0         0 goto &Carp::croak
30             }
31              
32             sub _carp
33             {
34 0     0   0 require Carp;
35 0         0 goto &Carp::carp
36             }
37              
38             sub import
39             {
40 6     6   127 my $pkg = (caller)[0];
41 6         11 shift;
42              
43 6         7 my $common_options;
44 6 100 66     61 $common_options = shift if @_ && ref($_[0]) eq 'ARRAY';
45              
46 6         21 while (@_) {
47 10         12 my $name = shift;
48             # Must be a scalar
49 10 50 33     45 _croak "invalid arg: SCALAR expected" unless defined ref $name && ! ref $name;
50 10         10 my ($fq_name, $proto);
51 10 100       36 if ($name =~ s/\(([^)]*)\)$//s) {
52 3         6 $proto = $1;
53             }
54 10 100       32 if (index($name, ':') > 0) {
55 1         1 $fq_name = $name;
56 1         5 $name = substr($fq_name, 1+rindex($fq_name, ':'));
57             } else {
58 9         14 $fq_name = $pkg.'::'.$name;
59             }
60              
61 10         8 my $options;
62 10 100 100     43 if (@_ && ref $_[0]) {
    100          
63 7         8 $options = shift;
64 7 100       15 splice(@$options, 0, 0, @$common_options) if $common_options;
65             } elsif ($common_options) {
66             # Just duplicate common options
67 2         3 $options = [ @$common_options ];
68             }
69              
70 10         11 my $cmd = $name;
71 10         7 my $args;
72             my %options;
73              
74 10 100       16 if ($options) {
75 9         16 while (@$options) {
76 20         22 my $opt = shift @$options;
77 20         38 (my $opt_short = $opt) =~ s/^[\$\@\%\&]//;
78 20 100       91 if ($opt eq '--') {
    100          
    100          
    100          
    50          
    50          
79 1 50 33     5 _croak 'duplicate @ARGV' if $args && !$common_options;
80 1         1 $args = $options;
81             last
82 1         3 } elsif ($opt eq '()') {
83 2         4 $proto = shift @$options;
84             } elsif ($opt =~ /^\$?0$/s) { # $0
85 9         23 $cmd = shift @$options;
86             } elsif ($opt =~ /^\@?ARGV$/) { # @ARGV
87 5 50       18 _croak "$name: invalid \@ARGV" if ref($options->[0]) ne 'ARRAY';
88 5         11 $args = shift @$options;
89             } elsif (! exists ($OPTIONS{$opt_short})) {
90 0         0 _carp "$name: unknown option $opt";
91             } elsif (defined $OPTIONS{$opt_short}) {
92 3         4 my $value = shift @$options;
93 3 50       13 unless (defined $value) {
    50          
94 0         0 _croak "$name: value expected for option $opt"
95             } elsif (ref($value) ne $OPTIONS{$opt_short}) {
96 0         0 _croak "$name: invalid value for option $opt"
97             }
98 3         7 $options{$opt_short} = $value;
99             } else {
100 0         0 $options{$opt_short} = 1;
101             }
102             }
103             }
104              
105 10 100       105 unless (File::Spec->file_name_is_absolute($cmd)) {
106 1         31 my ($vol, $dir, undef) = File::Spec->splitpath($cmd);
107 1 50       4 if (length($vol)+length($dir) == 0) {
108 1         6 $cmd = File::Which::which($cmd);
109             }
110             }
111              
112             my $sub = defined($cmd)
113             ? _build_sub($name, [ $cmd, ($args ? @$args : ())], \%options)
114 10 100   0   169 : sub { _croak "'$name' not found in PATH" };
  0 50       0  
115              
116             # As set_prototype *has* a prototype, we have to workaround it
117             # with '&'
118 10 100       49 &Scalar::Util::set_prototype($sub, $proto) if defined $proto;
119              
120 6     6   30 no strict 'refs';
  6         11  
  6         3656  
121 10         61 *{$fq_name} = subname $fq_name, $sub;
  10         6727  
122             }
123             }
124              
125             sub _handle_error
126             {
127 2     2   7 my ($name, $code, $cmd, $handler) = @_;
128 2 50       10 if ($handler) {
129 2         9 $handler->($name, $?, $cmd);
130             } else {
131 0         0 _croak "$name error ".($?>>8)
132             }
133             }
134              
135             sub _build_sub
136             {
137 10     10   15 my ($name, $cmd, $options) = @_;
138              
139             return sub {
140 16     16   12248 my ($input, $output_cb);
        7      
        7      
        3      
        3      
        2      
        5      
        5      
141 16 50       70 $output_cb = pop if ref $_[$#_] eq 'CODE';
142 16 50       43 $input = pop if ref $_[$#_];
143 16         45 my @cmd = (@$cmd, @_);
144              
145 16         15 print join(' ', '[', (map { / / ? qq{"$_"} : $_ } @cmd), ']'), "\n"
146             if DEBUG;
147              
148 16         16 my $h;
149 16         61 my $out = gensym; # IPC::Run needs GLOBs
150              
151             # errors from IPC::Run must be reported as comming from our
152             # caller, not from here
153 16         272 local @IPC::Run::CARP_NOT = (@IPC::Run::CARP_NOT, __PACKAGE__);
154              
155 16 100       58 local %ENV = (%ENV, %{$options->{ENV}}) if exists $options->{ENV};
  3         130  
156              
157 16 50       38 if ($input) {
158 0         0 my $in = gensym;
159 0 0       0 $h = start \@cmd,
160             'pipe', $out or _croak $!;
161 0 0       0 binmode($in, $options->{'>'}) if exists $options->{'>'};
162 0 0       0 if (ref $input eq 'ARRAY') {
    0          
163 0         0 print $in map { "$_$/" } @$input;
  0         0  
164             } elsif (ref $input eq 'SCALAR') {
165             # use ${$input}} as raw input
166 0         0 print $in $$input;
167             }
168 0         0 close $in;
169             } else {
170 16 50       70 $h = start \@cmd, \undef, '>pipe', $out or _croak $!;
171             }
172 16 50       56604 binmode($out, $options->{'<'}) if exists $options->{'<'};
173 16 100       65 if (wantarray) {
    100          
174 6         19 my @output;
175 6 50       28 if ($output_cb) {
176 0         0 local $_;
177 0         0 while (<$out>) {
178 0         0 chomp;
179 0         0 push @output, $output_cb->($_)
180             }
181             } else {
182 6         10096 while (my $x = <$out>) {
183 8         20 chomp $x;
184 8         537 push @output, $x
185             }
186             }
187 6         80 close $out;
188 6         50 finish $h;
189 6 50       3451 _handle_error($name, $?, \@cmd, $options->{'?'}) if $? >> 8;
190             return @output
191 6         130 } elsif (defined wantarray) {
192             # Only the first line
193 8         12 my $output;
194 8 50       9576 defined($output = <$out>) and chomp $output;
195 8         70 close $out;
196 8         62 finish $h;
197 8 50       4838 _handle_error($name, $?, \@cmd, $options->{'?'}) if $? >> 8;
198 8 50       28 _croak "no output" unless defined $output;
199 8         130 return $output
200             } else { # void context
201 2 50       8 if ($output_cb) {
202 0         0 local $_;
203 0         0 while (<$out>) {
204 0         0 chomp;
205 0         0 $output_cb->($_)
206             }
207             }
208 2         15 close $out;
209 2         14 finish $h;
210 2 50       42861 _handle_error($name, $?, \@cmd, $options->{'?'}) if $? >> 8;
211             return
212 2         3900 }
213             }
214 10         61 }
215              
216              
217             1;
218             __END__