#!perl
our
$VERSION
=
'0.58'
;
our
$DEBUG
;
BEGIN {
$DEBUG
= ! !
$ENV
{IPC_RUN3_SHELL_DEBUG}
unless
$DEBUG
}
sub
debug {
return
unless
$DEBUG
;
return
print
{
ref
$DEBUG
eq
'GLOB'
?
$DEBUG
: \
*STDERR
}
"# "
, __PACKAGE__,
" Debug: "
,
@_
,
"\n"
;
}
my
$dumper
= Data::Dumper->new([])->Terse(1)->Purity(1)
->Useqq(1)->Quotekeys(0)->Sortkeys(1)->Indent(0)->Pair(
'=>'
);
sub
_dcopy {
my
$v
=
shift
;
return
[
map
{ _dcopy(
$_
) }
@$v
]
if
ref
$v
eq
'ARRAY'
;
return
{
map
{
$_
=> _dcopy(
$$v
{
$_
}) }
keys
%$v
}
if
ref
$v
eq
'HASH'
;
return
'CODE'
if
ref
$v
eq
'CODE'
;
return
$v
;
}
sub
pp {
return
$dumper
->Values(_dcopy(\
@_
))->Reset->Dump }
my
@RUN3_OPTS
=
qw/ binmode_stdin binmode_stdout binmode_stderr append_stdout append_stderr return_if_system_error /
;
my
%KNOWN_OPTS
=
map
{
$_
=>1 }
@RUN3_OPTS
,
qw/ show_cmd allow_exit irs chomp stdin stdout stderr fail_on_stderr both stdout_filter /
;
our
$OBJECT_PACKAGE
;
{
package
IPC::Run3::Shell::Autoload;
BEGIN {
$IPC::Run3::Shell::OBJECT_PACKAGE
= __PACKAGE__ }
our
$AUTOLOAD
;
sub
AUTOLOAD {
my
$cmd
=
$AUTOLOAD
;
IPC::Run3::Shell::debug
"Autoloading '$cmd'"
if
$IPC::Run3::Shell::DEBUG
;
$cmd
=~ s/^.*:://;
no
strict
'refs'
;
*$AUTOLOAD
= IPC::Run3::Shell::make_cmd(
$cmd
);
goto
&$AUTOLOAD
;
}
sub
DESTROY {}
}
sub
new {
my
(
$class
,
%opt
) =
@_
;
return
bless
\
%opt
,
$OBJECT_PACKAGE
;
}
my
%EXPORTABLE
=
map
{
$_
=>1}
qw/ make_cmd /
;
*run
= make_cmd();
sub
import
{
my
(
$class
,
@export
) =
@_
;
my
(
$callpack
) =
caller
;
return
import_into(
$class
,
$callpack
,
@export
);
}
sub
import_into {
my
(
$class
,
$callpack
,
@export
) =
@_
;
my
%opt
;
%opt
= (
%opt
, %{
shift
@export
} )
while
ref
$export
[0] eq
'HASH'
;
for
my
$exp
(
@export
) {
if
(!
defined
$exp
) {
warnings::warnif(
'uninitialized'
,
'Use of uninitialized value in import'
);
next
;
}
elsif
( !
ref
(
$exp
) &&
$exp
&& (
my
(
$sym
) =
$exp
=~/^:(\w+)$/ ) ) {
if
(
$sym
eq
'run'
) {
debug
"Exporting '${callpack}::$sym' => make_cmd("
._cmd2str(\
%opt
).
")"
if
$DEBUG
;
no
strict
'refs'
;
*{
"${callpack}::$sym"
} = make_cmd(\
%opt
);
}
elsif
(
$sym
eq
'AUTOLOAD'
) {
debug
"Exporting '${callpack}::$sym'"
if
$DEBUG
;
no
strict
'refs'
;
*{
"${callpack}::AUTOLOAD"
} = \&{
"${OBJECT_PACKAGE}::AUTOLOAD"
};
}
elsif
(
$sym
eq
'FATAL'
) {
debug
"Enabling fatal warnings"
;
warnings->
import
(
FATAL
=>
'IPC::Run3::Shell'
);
}
else
{
croak
"$class can't export \"$sym\""
unless
$EXPORTABLE
{
$sym
};
my
$target
= __PACKAGE__.
"::$sym"
;
debug
"Exporting '${callpack}::$sym' => '$target'"
if
$DEBUG
;
no
strict
'refs'
;
*{
"${callpack}::$sym"
} = \&{
$target
};
}
}
else
{
my
(
$sym
,
@cmd
) =
ref
$exp
eq
'ARRAY'
?
@$exp
: (
$exp
,
$exp
);
croak
"$class: no function name specified"
unless
$sym
;
$sym
= _strify(
$sym
);
croak
"$class: empty command for function \"$sym\""
unless
@cmd
;
debug
"Exporting '${callpack}::$sym' => make_cmd("
._cmd2str(\
%opt
,
@cmd
).
")"
if
$DEBUG
;
no
strict
'refs'
;
*{
"${callpack}::$sym"
} = make_cmd(\
%opt
,
@cmd
);
}
}
return
;
}
sub
make_cmd {
my
@omcmd
=
@_
;
warnings::warnif(__PACKAGE__.
"::make_cmd() may have been called as a method"
)
if
$omcmd
[0] &&
$omcmd
[0] eq __PACKAGE__ ;
return
sub
{
my
@acmd
=
@_
;
my
@mcmd
=
@omcmd
;
my
%opt
= blessed(
$acmd
[0]) &&
$acmd
[0]->isa(
$OBJECT_PACKAGE
) ? %{
shift
@acmd
} : ();
%opt
= (
%opt
, %{
shift
@mcmd
} )
while
ref
$mcmd
[0] eq
'HASH'
;
%opt
= (
%opt
, %{
shift
@acmd
} )
while
ref
$acmd
[0] eq
'HASH'
;
my
@tmp_opts
;
push
@tmp_opts
,
pop
@acmd
while
ref
$acmd
[-1] eq
'HASH'
;
%opt
= (
%opt
, %{
pop
@tmp_opts
} )
while
@tmp_opts
;
if
(
exists
$opt
{__TEST_OPT_A} ||
exists
$opt
{__TEST_OPT_B}) {
return
join
','
, (
exists
$opt
{__TEST_OPT_A} ?
'A='
.(
defined
$opt
{__TEST_OPT_A} ?
$opt
{__TEST_OPT_A} :
'undef'
) : (),
exists
$opt
{__TEST_OPT_B} ?
'B='
.(
defined
$opt
{__TEST_OPT_B} ?
$opt
{__TEST_OPT_B} :
'undef'
) : () );
}
for
(
keys
%opt
) {
warnings::warnif(__PACKAGE__.
": unknown option \"$_\""
)
unless
$KNOWN_OPTS
{
$_
};
}
if
(
defined
$opt
{stdout_filter}) {
croak __PACKAGE__.
": option stdout_filter must be a coderef"
unless
ref
$opt
{stdout_filter} eq
'CODE'
}
my
$allow_exit
=
defined
$opt
{allow_exit} ?
$opt
{allow_exit} : [0];
if
(
$allow_exit
ne
'ANY'
) {
$allow_exit
= [
$allow_exit
]
unless
ref
$allow_exit
eq
'ARRAY'
;
warnings::warnif(__PACKAGE__.
": allow_exit is empty"
)
unless
@$allow_exit
;
for
(
@$allow_exit
) {
warnings::warnif(
'numeric'
,
'Argument "'
.(
defined
(
$_
)?
$_
:"(
undef
)
").'"
isn\'t numeric in allow_exit')
unless
defined
&& looks_like_number(
$_
);
no
warnings
'numeric'
,
'uninitialized'
;
$_
= 0+
$_
;
}
}
croak __PACKAGE__.
": can't use options stderr and fail_on_stderr at the same time"
if
exists
$opt
{stderr} &&
$opt
{fail_on_stderr};
croak __PACKAGE__.
": can't use options both and stdout at the same time"
if
$opt
{both} &&
exists
$opt
{stdout};
croak __PACKAGE__.
": can't use options both and stderr at the same time"
if
$opt
{both} &&
exists
$opt
{stderr};
croak __PACKAGE__.
": can't use options both and fail_on_stderr at the same time"
if
$opt
{both} &&
$opt
{fail_on_stderr};
my
@fcmd
= (
@mcmd
,
@acmd
);
croak __PACKAGE__.
": empty command"
unless
@fcmd
;
@fcmd
=
map
{_strify(
$_
)}
@fcmd
;
my
(
$out
,
$stdout
) = (
''
);
if
(
exists
$opt
{stdout})
{
$stdout
=
$opt
{stdout} }
elsif
(
$opt
{both})
{
$stdout
=
defined
(
wantarray
) ? \
$out
:
undef
}
elsif
(
wantarray
)
{
$stdout
=
$out
= [] }
elsif
(
defined
(
wantarray
))
{
$stdout
= \
$out
}
else
{
$stdout
=
undef
}
my
(
$err
,
$stderr
) = (
''
);
if
(
exists
$opt
{stderr})
{
$stderr
=
$opt
{stderr} }
elsif
(
$opt
{fail_on_stderr})
{
$stderr
= \
$err
}
elsif
(
$opt
{both})
{
$stderr
=
wantarray
? \
$err
: (
defined
(
wantarray
) ? \
$out
:
undef
) }
else
{
$stderr
=
undef
}
my
%r3o
= (
return_if_system_error
=>1 );
for
(
@RUN3_OPTS
) {
$r3o
{
$_
} =
$opt
{
$_
}
if
exists
$opt
{
$_
} }
debug
"run3("
._cmd2str(
@fcmd
).
") "
.pp(\
%opt
)
if
$DEBUG
;
print
{
ref
$opt
{show_cmd} eq
'GLOB'
?
$opt
{show_cmd} : \
*STDERR
}
'$ '
._cmd2str(
@fcmd
).
"\n"
if
$opt
{show_cmd};
local
$/ =
exists
$opt
{irs} ?
$opt
{irs} : $/;
IPC::Run3::run3( \
@fcmd
,
$opt
{stdin},
$stdout
,
$stderr
, \
%r3o
)
or croak __PACKAGE__.
" (internal): run3 \"$fcmd[0]\" failed"
;
my
$exitcode
= $?>>8;
croak
"Command \"$fcmd[0]\" failed: process wrote to STDERR: \"$err\""
if
$opt
{fail_on_stderr} &&
$err
ne
''
&&
$err
ne $/;
if
($? == -1) {
warnings::warnif(
"Command \"$fcmd[0]\" failed: $!"
);
return
}
elsif
($?&127) {
warnings::warnif(
sprintf
(
"Command \"%s\" failed: signal %d, %s coredump"
,
$fcmd
[0], ($?&127), ($?&128)?
'with'
:
'without'
))
}
else
{
warnings::warnif(
"Command \"$fcmd[0]\" failed: exit status $exitcode"
)
unless
$allow_exit
eq
'ANY'
||
grep
{
$_
==
$exitcode
}
@$allow_exit
;
}
return
unless
defined
wantarray
;
if
(
exists
$opt
{stdout})
{
return
$exitcode
}
elsif
(
$opt
{both}) {
chomp
(
$out
,
$err
)
if
$opt
{
chomp
};
if
(
$opt
{stdout_filter}) {
for
(
$out
) {
$opt
{stdout_filter}->() } }
return
wantarray
? (
$out
,
$err
,
$exitcode
) :
$out
}
elsif
(
wantarray
) {
chomp
(
@$out
)
if
$opt
{
chomp
};
if
(
$opt
{stdout_filter}) {
for
(
@$out
) {
$opt
{stdout_filter}->() } }
return
@$out
}
else
{
chomp
(
$out
)
if
$opt
{
chomp
};
if
(
$opt
{stdout_filter}) {
for
(
$out
) {
$opt
{stdout_filter}->() } }
return
$out
}
}
}
sub
_strify {
my
(
$x
) =
@_
;
if
(!
defined
$x
) {
warnings::warnif(
'uninitialized'
,
'Use of uninitialized value in argument list'
);
return
""
}
elsif
(blessed(
$x
) && overload::Overloaded(
$x
)) {
if
(overload::Method(
$x
,
'""'
))
{
return
"$x"
}
elsif
(
defined
(
my
$rv
=
eval
{
"$x"
}))
{
return
$rv
}
elsif
($@=~/\bno method found\b/) {
if
(!
$overload::VERSION
||
$overload::VERSION
<1.04)
{
die
"Package "
.
ref
(
$x
).
" doesn't overload stringification: $@"
}
else
{ croak
"Package "
.
ref
(
$x
).
" doesn't overload stringification: $@"
}
}
else
{
die
$@ }
}
else
{
ref
(
$x
) and warnings::warnif(__PACKAGE__.
": argument list contains references/objects"
);
return
"$x"
}
}
sub
_cmd2str {
my
@c
=
@_
;
my
$o
=
''
;
for
my
$c
(
@c
) {
$o
.=
' '
if
$o
;
if
(
ref
$c
eq
'HASH'
) {
$o
.= pp(
$c
);
}
else
{
my
$s
=
defined
$c
?
"$c"
:
''
;
$s
= pp(
$s
)
if
$s
=~/[^\w\-\=\/\.]/;
$o
.=
$s
;
}
}
return
$o
;
}
1;