File Coverage

blib/lib/IPC/System/Options.pm
Criterion Covered Total %
statement 165 284 58.1
branch 93 194 47.9
condition 21 43 48.8
subroutine 19 21 90.4
pod 4 5 80.0
total 302 547 55.2


line stmt bran cond sub pod time code
1             package IPC::System::Options;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-01-31'; # DATE
5             our $DIST = 'IPC-System-Options'; # DIST
6             our $VERSION = '0.340'; # VERSION
7              
8 1     1   78983 use strict 'subs', 'vars';
  1         10  
  1         29  
9 1     1   4 use warnings;
  1         2  
  1         24  
10              
11 1     1   395 use Proc::ChildError qw(explain_child_error);
  1         278  
  1         116  
12              
13             my $log;
14             our %Global_Opts;
15              
16             sub import {
17 1     1   9 my $self = shift;
18              
19 1         4 my $caller = caller();
20 1         16 my $i = 0;
21 1         5 while ($i < @_) {
22             # backtick is the older, deprecated name for readpipe
23 4 50       14 if ($_[$i] =~ /\A(system|readpipe|backtick|run|start|import)\z/) {
    0          
24 1     1   6 no strict 'refs';
  1         2  
  1         1333  
25 4         5 *{"$caller\::$_[$i]"} = \&{"$self\::" . $_[$i]};
  4         13  
  4         10  
26             } elsif ($_[$i] =~ /\A-(.+)/) {
27 0 0       0 die "$_[$i] requires an argument" unless $i < @_-1;
28 0         0 $Global_Opts{$1} = $_[$i+1];
29 0         0 $i++;
30             } else {
31 0         0 die "$_[$i] is not exported by ".__PACKAGE__;
32             }
33 4         2076 $i++;
34             }
35             }
36              
37             sub _args2cmd {
38 2 50   2   13 if (@_ == 1) {
39 0         0 return $_[0];
40             }
41 2 50       16 if ($^O eq 'MSWin32') {
42 0         0 require Win32::ShellQuote;
43             return Win32::ShellQuote::quote_system_string(
44 0 0       0 map { ref($_) eq 'SCALAR' ? $$_ : $_ } @_);
  0         0  
45             } else {
46 2         840 require String::ShellQuote;
47             return join(
48             " ",
49 2 100       893 map { ref($_) eq 'SCALAR' ? $$_ : String::ShellQuote::shell_quote($_) } @_
  8         187  
50             );
51             }
52             }
53              
54             sub _system_or_readpipe_or_run_or_start {
55 17     17   46 my $which = shift;
56 17 100       55 my $opts = ref($_[0]) eq 'HASH' ? shift : {};
57 17         56 for (keys %Global_Opts) {
58 0 0       0 $opts->{$_} = $Global_Opts{$_} if !defined($opts->{$_});
59             }
60 17         58 my @args = @_;
61              
62             # check known options
63 17         51 for (keys %$opts) {
64 20 100       313 die "Unknown option '$_'"
65             unless /\A(
66             capture_stdout|capture_stderr|capture_merged|
67             tee_stdout|tee_stderr|tee_merged|
68             chdir|dies?|dry_run|env|lang|log|max_log_output|shell|
69             exit_code_success_criteria|
70             fail_log_level|
71             stdin # XXX: only for run()
72             )\z/x;
73             }
74              
75             # defaults
76 16   50     167 $opts->{fail_log_level} ||= 'error';
77              
78 16   66     109 my $opt_die = $opts->{die} || $opts->{dies};
79              
80 16         27 my $child_error;
81 16         28 my $os_error = "";
82 16         25 my $exit_code_is_success;
83             my $extra_error;
84              
85             my $code_exit_code_is_success = sub {
86 14     14   92 my $exit_code = shift;
87 14 50       58 if (defined $opts->{exit_code_success_criteria}) {
88 0 0       0 if (ref $opts->{exit_code_success_criteria} eq '' ) { return $exit_code == $opts->{exit_code_success_criteria} }
  0 0       0  
    0          
    0          
89 0 0       0 elsif (ref $opts->{exit_code_success_criteria} eq 'ARRAY' ) { return (grep { $exit_code==$_ } @{ $opts->{exit_code_success_criteria} }) ? 1:0 }
  0         0  
  0         0  
90 0         0 elsif (ref $opts->{exit_code_success_criteria} eq 'Regexp') { return $exit_code =~ $opts->{exit_code_success_criteria} }
91 0         0 elsif (ref $opts->{exit_code_success_criteria} eq 'CODE' ) { return $opts->{exit_code_success_criteria}->($exit_code) }
92 0         0 else { die "exit_code_success_criteria must be a number, array of numbers, Regexp, or coderef" }
93             } else {
94 14         202 return $exit_code == 0;
95             }
96 16         122 };
97              
98 16 50       58 if ($opts->{log}) {
99 0         0 require Log::ger::Format::MultilevelLog; # just so scan_prereqs can detect it
100 0         0 require Log::ger::Format;
101 0         0 Log::ger::Format->set_for_current_package(MultilevelLog => (sub_name => 'logger'));
102 0         0 require Log::ger;
103 0         0 Log::ger->import;
104             }
105              
106 16         39 my $cwd;
107 16 100       43 if ($opts->{chdir}) {
108 2         30 require Cwd;
109 2         25 $cwd = Cwd::getcwd();
110 2 50       16 if (!defined $cwd) { # checking $! is always true here, why?
111 0 0       0 $log->error("Can't getcwd: $!") if $log;
112 0         0 $child_error = -1;
113 0         0 $exit_code_is_success = 0;
114 0         0 $os_error = $!;
115 0         0 $extra_error = "Can't getcwd";
116 0         0 goto CHECK_RESULT;
117             }
118 2 100       60 unless (chdir $opts->{chdir}) {
119 1 50       9 $log->error("Can't chdir to '$opts->{chdir}': $!") if $log;
120 1         2 $child_error = -1;
121 1         5 $exit_code_is_success = 0;
122 1         36 $os_error = $!;
123 1         10 $extra_error = "Can't chdir";
124 1         34 goto CHECK_RESULT;
125             }
126             }
127              
128             # set ENV
129 15         39 my %save_env;
130             my %set_env;
131 15 50       52 if ($opts->{lang}) {
132 0         0 $set_env{LC_ALL} = $opts->{lang};
133 0         0 $set_env{LANGUAGE} = $opts->{lang};
134 0         0 $set_env{LANG} = $opts->{lang};
135             }
136 15 100       33 if ($opts->{env}) {
137 1         6 $set_env{$_} = $opts->{env}{$_} for keys %{ $opts->{env} };
  1         7  
138             }
139 15 100       31 if (%set_env) {
140 1         6 for (keys %set_env) {
141 1         3 $save_env{$_} = $ENV{$_};
142 1         16 $ENV{$_} = $set_env{$_};
143             }
144             }
145              
146 15         80 my $wa;
147             my $res;
148              
149 15         0 my $capture_stdout_was_false;
150 15         0 my $emulate_backtick;
151 15         0 my $tmp_capture_stdout;
152              
153             my $code_capture = sub {
154 13     13   19 my $doit = shift;
155              
156 13 50 66     119 if ($opts->{capture_stdout} && $opts->{capture_stderr}) {
    100 66        
    100          
    100          
    50          
    100          
    100          
    100          
157 0         0 require Capture::Tiny;
158 0         0 (${ $opts->{capture_stdout} }, ${ $opts->{capture_stderr} }) =
  0         0  
  0         0  
159             &Capture::Tiny::capture($doit);
160             } elsif ($opts->{capture_merged}) {
161 1         20 require Capture::Tiny;
162 1         73 ${ $opts->{capture_merged} } =
  1         1054  
163             &Capture::Tiny::capture_merged($doit);
164             } elsif ($opts->{capture_stdout}) {
165 3         856 require Capture::Tiny;
166 3         6251 ${ $opts->{capture_stdout} } =
  3         2361  
167             &Capture::Tiny::capture_stdout($doit);
168             } elsif ($opts->{capture_stderr}) {
169 1         19 require Capture::Tiny;
170 1         38 ${ $opts->{capture_stderr} } =
  1         732  
171             &Capture::Tiny::capture_stderr($doit);
172              
173             } elsif ($opts->{tee_stdout} && $opts->{tee_stderr}) {
174 0         0 require Capture::Tiny;
175 0         0 (${ $opts->{tee_stdout} }, ${ $opts->{tee_stderr} }) =
  0         0  
  0         0  
176             &Capture::Tiny::tee($doit);
177             } elsif ($opts->{tee_merged}) {
178 1         15 require Capture::Tiny;
179 1         72 ${ $opts->{tee_merged} } =
  1         1749  
180             &Capture::Tiny::tee_merged($doit);
181             } elsif ($opts->{tee_stdout}) {
182 1         15 require Capture::Tiny;
183 1         47 ${ $opts->{tee_stdout} } =
  1         1395  
184             &Capture::Tiny::tee_stdout($doit);
185             } elsif ($opts->{tee_stderr}) {
186 1         15 require Capture::Tiny;
187 1         48 ${ $opts->{tee_stderr} } =
  1         1295  
188             &Capture::Tiny::tee_stderr($doit);
189             } else {
190 5         17 $doit->();
191             }
192 15         51 };
193              
194 15 100 66     114 if ($which eq 'system') {
    100          
    50          
195              
196 10 50 33     104 if ($opts->{log} || $opts->{dry_run}) {
197 0 0       0 if ($opts->{log}) {
198 1     1   8 no strict 'refs';
  1         2  
  1         228  
199 0         0 my $routine;
200 0         0 my $label = "";
201 0 0       0 if ($opts->{dry_run}) {
202 0         0 $label = "[DRY RUN] ";
203 0         0 $routine = "log_info";
204             } else {
205 0         0 $routine = "log_trace";
206             }
207 0         0 $routine->("%ssystem(%s), env=%s", $label, \@args, \%set_env);
208             } else {
209 0         0 warn "[DRY RUN] system("._args2cmd(@args).")\n";
210             }
211 0 0       0 if ($opts->{dry_run}) {
212 0         0 $child_error = 0;
213 0         0 $exit_code_is_success = 1;
214 0         0 $res = "";
215 0         0 goto CHECK_RESULT;
216             }
217             }
218              
219             my $doit = sub {
220 10 50   10   36464 if ($opts->{shell}) {
    50          
221             # force the use of shell
222 0         0 $res = system _args2cmd(@args);
223             } elsif (defined $opts->{shell}) {
224             # forbid shell
225 0         0 $res = system {$args[0]} @args;
  0         0  
226             } else {
227             # might or might not use shell (if @args == 1)
228 10         31962 $res = system @args;
229             }
230 10         288 $child_error = $?;
231 10 100       369 $exit_code_is_success = $code_exit_code_is_success->($? < 0 ? $? : $? >> 8);
232 10         436 $os_error = $!;
233 10         50 };
234 10         23 $code_capture->($doit);
235              
236             } elsif ($which eq 'readpipe') {
237              
238 3         7 $wa = wantarray;
239              
240 3 50 33     44 if ($opts->{log} || $opts->{dry_run}) {
241 0 0       0 if ($opts->{log}) {
242 1     1   7 no strict 'refs';
  1         1  
  1         467  
243 0         0 my $routine;
244 0         0 my $label = "";
245 0 0       0 if ($opts->{dry_run}) {
246 0         0 $label = "[DRY RUN] ";
247 0         0 $routine = "log_info";
248             } else {
249 0         0 $routine = "log_trace";
250             }
251 0         0 $routine->("%sreadpipe(%s), env=%s", $label, _args2cmd(@args), \%set_env);
252             } else {
253 0         0 warn "[DRY RUN] readpipe("._args2cmd(@args).")\n";
254             }
255 0 0       0 if ($opts->{dry_run}) {
256 0         0 $child_error = 0;
257 0         0 $exit_code_is_success = 1;
258 0         0 $res = "";
259 0         0 goto CHECK_RESULT;
260             }
261             }
262              
263             # we want to avoid the shell, so we don't use the builtin backtick.
264             # instead, we emulate backtick by system + capturing the output
265 3 100 100     27 if (defined $opts->{shell} && !$opts->{shell}) {
266 1         3 $emulate_backtick++;
267             die "Currently cannot backtick() with options shell=0 and capture_merged|tee_*"
268 1 50 33     34 if $opts->{capture_merged} || $opts->{tee_stdout} || $opts->{tee_stderr} || $opts->{tee_merged};
      33        
      33        
269 1 50       17 if (!$opts->{capture_stdout}) {
270 1         9 $capture_stdout_was_false++;
271 1         8 $opts->{capture_stdout} = \$tmp_capture_stdout;
272             }
273             }
274              
275             my $doit = sub {
276 3 100   3   1136 if ($emulate_backtick) {
277             # we don't want shell so we have to emulate backtick with system
278             # + capture the output ourselves
279 1         6 system {$args[0]} @args;
  1         3096  
280             } else {
281 2         7 my $cmd = _args2cmd(@args);
282             #warn "cmd for backtick: " . $cmd;
283             # use backtick, which uses the shell
284 2 50       61 if ($wa) {
285 0         0 $res = [`$cmd`];
286             } else {
287 2         10971 $res = `$cmd`;
288             }
289             }
290 3 50       129 $child_error = $? < 0 ? $? : $? >> 8;
291 3 50       114 $exit_code_is_success = $code_exit_code_is_success->($? < 0 ? $? : $? >> 8);
292 3         90 $os_error = $!;
293 3         26 };
294 3         10 $code_capture->($doit);
295              
296 3 100       29 if ($emulate_backtick) {
297             $res = $capture_stdout_was_false ? $tmp_capture_stdout :
298 1 50       18 ${ $opts->{capture_stdout} };
  0         0  
299 1 50       7 $res = [split /^/m, $res] if $wa;
300 1 50       15 $opts->{capture_stdout} = undef if $capture_stdout_was_false;
301             }
302              
303             # log output
304 3 50       83 if ($opts->{log}) {
305 0         0 my $res_show;
306 0 0       0 if (defined $opts->{max_log_output}) {
307 0         0 $res_show = '';
308 0 0       0 if ($wa) {
309 0         0 for (@$res) {
310 0 0       0 if (length($res_show) + length($_) >=
311             $opts->{max_log_output}) {
312             $res_show .= substr(
313 0         0 $_,0,$opts->{max_log_output}-length($res_show));
314 0         0 last;
315             } else {
316 0         0 $res_show .= $_;
317             }
318             }
319             } else {
320 0 0       0 if (length($res) > $opts->{max_log_output}) {
321 0         0 $res_show = substr($res, 0, $opts->{max_log_output});
322             }
323             }
324             }
325             log_trace("result of readpipe(): %s (%d bytes)",
326             defined($res_show) ? $res_show : $res,
327             defined($res_show) ?
328 0 0       0 $opts->{max_log_output} : length($res))
    0          
    0          
329             if $exit_code_is_success;
330             }
331              
332             } elsif ($which eq 'run' || $which eq 'start') {
333              
334 2 50 33     26 if ($opts->{log} || $opts->{dry_run}) {
335 0 0       0 if ($opts->{log}) {
336 1     1   7 no strict 'refs';
  1         2  
  1         819  
337 0         0 my $routine;
338 0         0 my $label = "";
339 0 0       0 if ($opts->{dry_run}) {
340 0         0 $label = "[DRY RUN] ";
341 0         0 $routine = "log_info";
342             } else {
343 0         0 $routine = "log_trace";
344             }
345 0         0 $routine->("%srun(%s), env=%s", $label,
346             join(", ", @args), \%set_env);
347             } else {
348 0         0 warn "[DRY RUN] $which(".join(", ", @args).")\n";
349             }
350 0 0       0 if ($opts->{dry_run}) {
351 0         0 $child_error = 0;
352 0         0 $exit_code_is_success = 1;
353 0         0 $res = "";
354 0         0 goto CHECK_RESULT;
355             }
356             }
357              
358 2         1185 require IPC::Run;
359 2 100       31451 my $func = $which eq 'run' ? "IPC::Run::run" : "IPC::Run::start";
360 2         25 $res = &{$func}(
361             \@args,
362             defined($opts->{stdin}) ? \$opts->{stdin} : \*STDIN,
363             sub {
364 2 50   2   8746 if ($opts->{capture_stdout}) {
365 2 50       18 if (ref $opts->{capture_stdout} eq 'CODE') {
366 0         0 $opts->{capture_stdout}->($_[0]);
367             } else {
368 2         4 ${$opts->{capture_stdout}} .= $_[0];
  2         14  
369             }
370             } else {
371 0         0 print $_[0];
372             }
373             }, # out
374             sub {
375 0 0   0   0 if ($opts->{capture_stderr}) {
376 0 0       0 if (ref $opts->{capture_sderr} eq 'CODE') {
377 0         0 $opts->{capture_sderr}->($_[0]);
378             } else {
379 0         0 ${$opts->{capture_stderr}} .= $_[0];
  0         0  
380             }
381             } else {
382 0         0 print STDERR $_[0];
383             }
384             }, # err
385 2 50       40 );
386 2 100       7427 if ($which eq 'run') {
387 1         87 $child_error = $?;
388 1 50       27 $exit_code_is_success = $code_exit_code_is_success->($? < 0 ? $? : $? >> 8);
389 1         24 $os_error = $!;
390             } else {
391 1         17 $child_error = 0;
392 1         6 $exit_code_is_success = 1;
393 1         28 $os_error = "";
394             }
395              
396             } # which
397              
398             # restore ENV
399 15 100       107 if (%save_env) {
400 1         10 for (keys %save_env) {
401 1 50       8 if (defined $save_env{$_}) {
402 0         0 $ENV{$_} = $save_env{$_};
403             } else {
404 1         5 undef $ENV{$_};
405             }
406             }
407             }
408              
409             # restore previous working directory
410 15 100       45 if ($cwd) {
411 1 50       32 unless (chdir $cwd) {
412 0 0       0 $log->error("Can't chdir back to '$cwd': $!") if $log;
413 0   0     0 $child_error ||= -1;
414 0         0 $os_error = $!;
415 0         0 $extra_error = "Can't chdir back";
416 0         0 goto CHECK_RESULT;
417             }
418             }
419              
420             CHECK_RESULT:
421 16 100       65 unless ($exit_code_is_success) {
422 3 100 66     42 if ($opts->{log} || $opt_die) {
423             my $msg = sprintf(
424             "%s(%s) failed: %s (%s)%s%s%s",
425             $which,
426             join(" ", @args),
427             defined $extra_error ? "" : $child_error,
428             defined $extra_error ? "$extra_error: $os_error" : explain_child_error($child_error, $os_error),
429             (ref($opts->{capture_stdout}) ?
430             ", captured stdout: <<" .
431 0         0 (defined ${$opts->{capture_stdout}} ? ${$opts->{capture_stdout}} : ''). ">>" : ""),
  0         0  
432             (ref($opts->{capture_stderr}) ?
433             ", captured stderr: <<" .
434 0         0 (defined ${$opts->{capture_stderr}} ? ${$opts->{capture_stderr}} : ''). ">>" : ""),
  0         0  
435             (ref($opts->{capture_merged}) ?
436             ", captured merged: <<" .
437 2 100       80 (defined ${$opts->{capture_merged}} ? ${$opts->{capture_merged}} : ''). ">>" : ""),
  0 100       0  
  0 0       0  
    50          
    0          
    50          
    0          
    50          
438             );
439 2 50       88 logger($opts->{fail_log_level}, $msg) if $opts->{log};
440 2 50       101 die $msg if $opt_die;
441             }
442             }
443              
444 14 100       51 if ($which ne 'start') {
445 13         30 $? = $child_error;
446             }
447              
448 14 50 33     974 return $wa && $which ne 'run' && $which ne 'start' ? @$res : $res;
449             }
450              
451             sub system {
452 12     12 1 36228 _system_or_readpipe_or_run_or_start('system', @_);
453             }
454              
455             # backtick is the older, deprecated name for readpipe
456             sub backtick {
457 0     0 0 0 _system_or_readpipe_or_run_or_start('readpipe', @_);
458             }
459              
460             sub readpipe {
461 3     3 1 6096 _system_or_readpipe_or_run_or_start('readpipe', @_);
462             }
463              
464             sub run {
465 1     1 1 2868 _system_or_readpipe_or_run_or_start('run', @_);
466             }
467              
468             sub start {
469 1     1 1 5895 _system_or_readpipe_or_run_or_start('start', @_);
470             }
471              
472             1;
473             # ABSTRACT: Perl's system(), readpipe()/qx, IPC::Run's run(), start() (with more options)
474              
475             __END__