diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Capture.xs | 28 | ||||
-rw-r--r-- | Changes | 26 | ||||
-rw-r--r-- | MANIFEST | 67 | ||||
-rw-r--r-- | Makefile.PL | 8 | ||||
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | capture.c | 152 | ||||
-rw-r--r-- | capture.h | 12 | ||||
-rw-r--r-- | lib/POSIX/Run/Capture.pm | 65 | ||||
m--------- | runcap | 0 | ||||
-rw-r--r-- | t/09linemon.t | 35 | ||||
-rw-r--r-- | t/10file.t | 24 | ||||
-rw-r--r-- | t/11env.t | 35 |
13 files changed, 308 insertions, 149 deletions
@@ -15,3 +15,4 @@ *.o Makefile.old /capture.o +/MANIFEST @@ -8,6 +8,7 @@ capture_new(package, ...) char *package; PREINIT: ARGV argv = NULL; + ARGV env = NULL; unsigned timeout = 0; SV *cb[2] = { &PL_sv_undef, &PL_sv_undef }; SV *prog = &PL_sv_undef; @@ -37,13 +38,15 @@ capture_new(package, ...) argv = XS_unpack_ARGV(val); } else croak("argv must be an array ref"); - } else if (strcmp(kw, "stdout") == 0 - || strcmp(kw, "stderr") == 0) { + } else if (strcmp(kw, "env") == 0) { if (SvROK(val) - && SvTYPE(SvRV(val)) == SVt_PVCV) { - cb[kw[3] == 'o' ? 0 : 1] = SvRV(val); + && SvTYPE(SvRV(val)) == SVt_PVAV) { + env = XS_unpack_ARGV(val); } else - croak("%s must be a code ref", kw); + croak("env must be an array ref"); + } else if (strcmp(kw, "stdout") == 0 + || strcmp(kw, "stderr") == 0) { + cb[kw[3] == 'o' ? 0 : 1] = val; } else if (strcmp(kw, "timeout") == 0) { if (SvIOK(val)) { timeout = SvUV(val); @@ -61,7 +64,7 @@ capture_new(package, ...) croak("unknown keyword argument %s", kw); } } - RETVAL = capture_new(prog, argv, timeout, cb, input); + RETVAL = capture_new(prog, argv, env, timeout, cb, input); OUTPUT: RETVAL @@ -83,6 +86,11 @@ capture_set_argv_ref(cp, argv) ARGV argv; void +capture_set_env_ref(cp, env) + POSIX::Run::Capture cp; + ARGV env; + +void capture_set_program(cp, prog) POSIX::Run::Capture cp; char *prog = NO_INIT; @@ -122,6 +130,14 @@ capture_argv(cp) OUTPUT: RETVAL +ARGV +capture_env(cp) + POSIX::Run::Capture cp; + CODE: + RETVAL = cp->rc.rc_env; + OUTPUT: + RETVAL + void capture_program(cp) POSIX::Run::Capture cp; @@ -1,6 +1,30 @@ Revision history for Perl extension POSIX::Run::Capture. -0.02 Fri Dec 1 08:54:18 +1.05 Fri Mar 15 13:47:40 2024 + - New constructor argument 'env' allows you to supply environment + to the command to be run. + - Fix packaging + +1.04 Fri Mar 15 13:31:50 2024 + - New constructor argument 'env' allows you to supply environment + to the command to be run. + +1.03 Tue Feb 23 12:21:17 2021 + - Fix packaging + +1.02 Sat Feb 13 09:30:49 2021 + - change bugtracker address + +1.01 Fri Jan 31 09:16:30 2020 + - Argument to the 'stderr' or 'stdout' can be a code reference, + glob (file handle) or scalar string. In the latter two cases, + the stream is redirected to the file handle or the named file. + +1.00 Wed Aug 14 08:11:53 2019 + - line monitor is called for every line, whether or not terminated + with a newline + +0.02 Fri Dec 1 08:54:18 2017 - first release 0.01 Thu Jul 20 18:17:18 2017 diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index 9cb5b09..0000000 --- a/MANIFEST +++ /dev/null @@ -1,67 +0,0 @@ -capture.c -capture.h -Capture.xs -Changes -lib/POSIX/Run/Capture.pm -LICENSE -Makefile.PL -MANIFEST This list of files -MANIFEST.SKIP -ppport.h -README -runcap/aclocal.m4 -runcap/compile -runcap/config.guess -runcap/config.sub -runcap/configure -runcap/configure.ac -runcap/depcomp -runcap/getc.c -runcap/getl.c -runcap/install-sh -runcap/install.am -runcap/install.in -runcap/libtool -runcap/ltmain.sh -runcap/Make.am -runcap/Makefile.am -runcap/Makefile.in -runcap/missing -runcap/runcap.3 -runcap/runcap.c -runcap/runcap.h -runcap/runcap.m4 -runcap/seek.c -runcap/shared.am -runcap/static.am -runcap/static.in -runcap/t/atlocal.in -runcap/t/genout.c -runcap/t/INPUT -runcap/t/linemon00.at -runcap/t/linemon01.at -runcap/t/lines.at -runcap/t/longout.at -runcap/t/Makefile.am -runcap/t/Makefile.in -runcap/t/package.m4 -runcap/t/pipe.at -runcap/t/rt.c -runcap/t/seek00.at -runcap/t/seek01.at -runcap/t/simple.at -runcap/t/stdin.at -runcap/t/testsuite -runcap/t/two.at -runcap/tell.c -t/00use.t -t/01argv.t -t/02prog.t -t/03timeout.t -t/04init.t -t/05simple.t -t/06lines.t -t/07two.t -t/08input.t -t/TestCapture.pm -typemap diff --git a/Makefile.PL b/Makefile.PL index 4aee252..b41b5c8 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -9,6 +9,10 @@ WriteMakefile( VERSION_FROM => 'lib/POSIX/Run/Capture.pm', LICENSE => 'gpl_3', PREREQ_PM => {}, # e.g., Module::Name => 1.1 + TEST_REQUIRES => { + 'File::Temp' => 0.23, + 'File::Cmp' => 1.06 + }, MIN_PERL_VERSION => 5.006, ABSTRACT_FROM => 'lib/POSIX/Run/Capture.pm', AUTHOR => 'Sergey Poznyakoff <gray@gnu.org>', @@ -25,6 +29,10 @@ WriteMakefile( url => 'git://git.gnu.org.ua/posixruncapture.git', web => 'http://git.gnu.org.ua/cgit/posixruncapture.git/', }, + bugtracker => { + web => 'https://puszcza.gnu.org.ua/bugs/?group=posixruncapture', + mailto => 'gray+posixruncapture@gnu.org.ua' + } }, provides => Module::Metadata->provides(version => '1.4', dir => 'lib') @@ -1,4 +1,4 @@ -POSIX-Run-Capture version 0.01 +POSIX-Run-Capture version 1.03 ============================== POSIX::Run::Capture runs an external command and captures its output. Both @@ -22,7 +22,7 @@ To install this module type the following: COPYRIGHT AND LICENCE -Copyright (C) 2017 by Sergey Poznyakoff +Copyright (C) 2017-2024 by Sergey Poznyakoff This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the @@ -30,7 +30,12 @@ line_monitor(const char *ptr, size_t sz, void *closure) { struct line_closure *lc = closure; - if (lc->len || ptr[sz-1] != '\n') { + if (sz == 0) { + if (lc->len) { + call_monitor(lc->cv, lc->str, lc->len); + lc->len = 0; + } + } else if (lc->len || ptr[sz-1] != '\n') { size_t newsz = lc->len + sz + 1; if (newsz > lc->size) { @@ -102,20 +107,75 @@ XS_unpack_ARGV(SV *sv) } static void -free_argv(struct capture *cp) +free_argv(char **argv) { - if (cp->rc.rc_argv) { + if (argv) { size_t i; - for (i = 0; cp->rc.rc_argv[i]; i++) { - free(cp->rc.rc_argv[i]); + for (i = 0; argv[i]; i++) { + free(argv[i]); } - free(cp->rc.rc_argv); - cp->rc.rc_argv = NULL; + free(argv); } } +static void +capture_set_output(struct capture *cp, SV *cb[2], int strno) +{ + SV *sv = cb[strno-1]; + + if (sv == &PL_sv_undef) + return; + + if (SvROK(sv)) { + sv = SvRV(sv); + + // FIXME: Do we need SvGETMAGIC (sv);? + if (SvTYPE(sv) == SVt_PVCV) { + SvREFCNT_inc(sv); + cp->closure[strno-1].cv = sv; + cp->rc.rc_cap[strno].sc_linemon = line_monitor; + cp->rc.rc_cap[strno].sc_monarg = &cp->closure[strno-1]; + cp->flags |= RCF_SC_TO_FLAG(RCF_SC_LINEMON, strno); + } else if (SvTYPE(sv) == SVt_PVGV) { + int fd; + + // FIXME: Check if sv is writable + SvREFCNT_inc(sv); + cp->closure[strno-1].cv = sv; + cp->rc.rc_cap[strno].sc_storfd = + PerlIO_fileno(IoOFP(sv_2io(sv))); + cp->flags |= RCF_SC_TO_FLAG(RCF_SC_STORFD, strno); + } else { + static char *what[] = { "stdout", "stderr" }; + croak("%s must be scalar, glob or code ref", + what[strno-1]); + } + } else { + char *filename = SvPV_nolen(sv); + int fd = open(filename, O_CREAT|O_TRUNC|O_RDWR, 0666); + if (fd == -1) { + croak("can't open file %s for writing: %s", + filename, strerror(errno)); + } + cp->rc.rc_cap[strno].sc_storfd = fd; + cp->flags |= RCF_SC_TO_FLAG(RCF_SC_STORFD, strno); + cp->closure[strno-1].fd = fd; + } +} + +static void +capture_close_output(struct capture *cp, int strno) +{ + strno--; + free(cp->closure[strno].str); + if (cp->closure[strno].cv != &PL_sv_undef) + SvREFCNT_dec(cp->closure[strno].cv); + if (cp->closure[strno].fd != -1) + close(cp->closure[strno].fd); +} + struct capture * -capture_new(SV *program, ARGV argv, unsigned timeout, SV *cb[2], SV *input) +capture_new(SV *program, ARGV argv, ARGV env, unsigned timeout, SV *cb[2], SV *input) { struct capture *cp; I32 i, n; @@ -124,7 +184,8 @@ capture_new(SV *program, ARGV argv, unsigned timeout, SV *cb[2], SV *input) if (!cp) croak_nomem(); memset(cp, 0, sizeof *cp); - + cp->closure[0].fd = cp->closure[1].fd = -1; + cp->rc.rc_argv = argv; cp->program = program; @@ -133,28 +194,19 @@ capture_new(SV *program, ARGV argv, unsigned timeout, SV *cb[2], SV *input) cp->rc.rc_program = SvPV_nolen(program); cp->flags |= RCF_PROGRAM; } + + if (env) { + cp->rc.rc_env = env; + cp->flags |= RCF_ENV; + } if (timeout) { cp->rc.rc_timeout = timeout; cp->flags |= RCF_TIMEOUT; } - cp->closure[0].cv = cb[0]; - if (cb[0] != &PL_sv_undef) { - SvREFCNT_inc(cb[0]); - cp->rc.rc_cap[RUNCAP_STDOUT].sc_linemon = line_monitor; - cp->rc.rc_cap[RUNCAP_STDOUT].sc_monarg = &cp->closure[0]; - cp->flags |= RCF_STDOUT_LINEMON; - } - - cp->closure[1].cv = cb[1]; - if (cb[1] != &PL_sv_undef) { - SvREFCNT_inc(cb[1]); - cp->closure[1].cv = cb[1]; - cp->rc.rc_cap[RUNCAP_STDERR].sc_linemon = line_monitor; - cp->rc.rc_cap[RUNCAP_STDERR].sc_monarg = &cp->closure[1]; - cp->flags |= RCF_STDERR_LINEMON; - } + capture_set_output(cp, cb, RUNCAP_STDOUT); + capture_set_output(cp, cb, RUNCAP_STDERR); cp->input = &PL_sv_undef; capture_set_input(cp, input); @@ -174,16 +226,14 @@ capture_DESTROY(struct capture *cp) */ cp->rc.rc_cap[RUNCAP_STDIN].sc_base = NULL; cp->rc.rc_cap[RUNCAP_STDIN].sc_fd = -1; - - free(cp->closure[0].str); - if (cp->closure[0].cv != &PL_sv_undef) - SvREFCNT_dec(cp->closure[0].cv); - free(cp->closure[1].str); - if (cp->closure[1].cv != &PL_sv_undef) - SvREFCNT_dec(cp->closure[1].cv); + capture_close_output(cp, RUNCAP_STDOUT); + capture_close_output(cp, RUNCAP_STDERR); - free_argv(cp); + free_argv(cp->rc.rc_env); + cp->rc.rc_env = NULL; + free_argv(cp->rc.rc_argv); + cp->rc.rc_argv = NULL; runcap_free(&cp->rc); free(cp); @@ -231,10 +281,21 @@ capture_set_input(struct capture *cp, SV *inp) void capture_set_argv_ref(struct capture *cp, ARGV argv) { - free_argv(cp); + free_argv(cp->rc.rc_argv); cp->rc.rc_argv = argv; } +void +capture_set_env_ref(struct capture *cp, ARGV env) +{ + free_argv(cp->rc.rc_env); + cp->rc.rc_env = env; + if (cp->rc.rc_env == NULL) + cp->flags &= ~RCF_ENV; + else + cp->flags |= RCF_ENV; +} + char * capture_next_line(struct capture *cp, int fd) { @@ -256,27 +317,8 @@ capture_next_line(struct capture *cp, int fd) int capture_run(struct capture *cp) { - int res; - if (!cp->rc.rc_argv) - croak("no command line given"); - - res = runcap(&cp->rc, cp->flags); - - if (cp->flags & RCF_STDOUT_LINEMON && cp->closure[0].len) { - call_monitor(cp->closure[0].cv, - cp->closure[0].str, - cp->closure[0].len); - cp->closure[0].len = 0; - } - - if (cp->flags & RCF_STDERR_LINEMON && cp->closure[1].len) { - call_monitor(cp->closure[1].cv, - cp->closure[1].str, - cp->closure[1].len); - cp->closure[1].len = 0; - } - - return res == 0; + croak("no command line given"); + return runcap(&cp->rc, cp->flags) == 0; } @@ -9,10 +9,11 @@ struct line_closure { - char *str; - size_t len; - size_t size; - SV *cv; + char *str; /* Line buffer */ + size_t len; /* Length of the collected line in buffer */ + size_t size; /* Line buffer size */ + SV *cv; /* Ref to Perl callback sub or file handle */ + int fd; /* Close this descriptor at destroy, unless -1 */ }; struct capture { @@ -29,9 +30,10 @@ typedef char** ARGV; ARGV XS_unpack_ARGV(SV *sv); void XS_pack_ARGV(SV *const sv, ARGV argv); -struct capture *capture_new(SV *pn, ARGV argv, unsigned timeout, SV *cb[2], SV *input); +struct capture *capture_new(SV *pn, ARGV argv, ARGV env, unsigned timeout, SV *cb[2], SV *input); void capture_DESTROY(struct capture *rc); char *capture_next_line(struct capture *rc, int fd); int capture_run(struct capture *cp); void capture_set_argv_ref(struct capture *cp, ARGV av); +void capture_set_env_ref(struct capture *cp, ARGV av); void capture_set_input(struct capture *cp, SV *inp); diff --git a/lib/POSIX/Run/Capture.pm b/lib/POSIX/Run/Capture.pm index ca1446a..25fc8be 100644 --- a/lib/POSIX/Run/Capture.pm +++ b/lib/POSIX/Run/Capture.pm @@ -28,7 +28,7 @@ our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); #our @EXPORT = qw(); -our $VERSION = '0.02'; +our $VERSION = '1.05'; require XSLoader; XSLoader::load('POSIX::Run::Capture', $VERSION); @@ -55,6 +55,11 @@ sub set_argv { $self->set_argv_ref([@_]); } +sub set_env { + my $self = shift; + $self->set_env_ref([@_]); +} + 1; __END__ =head1 NAME @@ -67,9 +72,10 @@ POSIX::Run::Capture - run command and capture its output $obj = new POSIX::Run::Capture(argv => [ $command, @args ], program => $prog, + env => [ @environment ], stdin => $fh_or_string, - stdout => sub { ... }, - stderr => sub { ... }, + stdout => $ref_or_string, + stderr => $ref_or_string, timeout => $n); $obj->run; @@ -82,19 +88,22 @@ POSIX::Run::Capture - run command and capture its output $aref = $obj->get_lines($chan); $obj->rewind($chan) + $obj->set_argv(@argv); $obj->set_program($prog); + $obj->set_env(@argv); $obj->set_timeout($n); $obj->set_input($fh_or_string); $aref = $obj->argv; $str = $obj->program + $aref = $obj->env; $num = $obj->timeout; =head1 DESCRIPTION Runs an external command and captures its output. Both standard error and output can be captured. Standard input can be supplied as either a -filehandle or a text. Upon exit, the captured streams can be accessed line +filehandle or a string. Upon exit, the captured streams can be accessed line by line or in one chunk. Callback routines can be supplied that will be called for each complete line of output read, providing a way for synchronous processing. @@ -108,9 +117,10 @@ Creates a new capture object. There are three possible invocation modes. new POSIX::Run::Capture(argv => [ $command, @args ], program => $prog, + env => [ @environment ], stdin => $fh_or_string, - stdout => sub { ... }, - stderr => sub { ... }, + stdout => $ref_or_string, + stderr => $ref_or_string, timeout => $n) When named arguments are used, the following keywords are allowed: @@ -126,12 +136,17 @@ B<program> argument, B<$argv[0]> will be run. Sets the pathname of binary file to run. +=item B<env> + +Defines execution environment. By default, environment variables are +inherited from the calling process. + =item B<stdin> or B<input> Supplies standard input for the command. The argument can be a string or a file handle. -=item B<stdout> +=item B<stdout> =E<gt> I<$coderef> Sets the I<line monitor> function for standard output. Line monitor is invoked each time a complete line is read, or the EOF is hit on the standard @@ -145,10 +160,22 @@ following example monitor function prints its argument to STDOUT: Notice that the last line read can lack the teminating newline character. -=item B<stderr> +=item B<stdout> =E<gt> I<FH> + +Redirect standard output to file handle I<FH>. Obviously, the handle should +be writable. + +=item B<stdout> =E<gt> I<NAME> + +Capture standard output and write it to the file I<NAME>. If the file +exists, it will be truncated. Otherwise, it will be created with permissions +of 0666 modified by the process' "umask" value. -Sets the I<line monitor> function for standard error stream. See the -description above. +=item B<stderr> =E<gt> I<$arg> + +Sets the I<line monitor> function for standard error or redirects it to +the file handle or file, depending on the type of I<$arg> (CODE reference, +GLOB or scalar string). For details, see the description of B<stdout> above. =item B<timeout> @@ -169,19 +196,27 @@ A simplified way of creating the object, equivalent to Crates an empty capture object. Whatever constructor is used, the necessary parameters can be set -or changed later, using B<set_argv>, B<set_program>, B<set_input>, +or changed later, using B<set_argv>, B<set_program>, B<set_env>, B<set_input>, and B<set_timeout>. -Monitors can be defined only when creating the object. +Monitors and redirections can be defined only when creating the object. =head2 Modifying the object. The following methods modify the object: + +=head3 $obj->set_argv(@argv) + +Set arguments array. =head3 $obj->set_program($prog) Sets the pathname of the command to run. +=head3 $obj->set_env(@env) + +Set environment variables. + =head3 $obj->set_timeout($n) Sets runtime timeout, in seconds. @@ -204,6 +239,10 @@ Returns a reference to the B<argv> array associated with the object. Returns the pathname of the executable program. +=head3 $obj->env + +Returns a reference to the array of environment variables. + =head3 $obj->timeout Returns the runtime timeout or B<0> if no timeout is set. @@ -283,7 +322,7 @@ Sergey Poznyakoff, E<lt>gray@gnu.orgE<gt> =head1 COPYRIGHT AND LICENSE -Copyright (C) 2017 by Sergey Poznyakoff +Copyright (C) 2017-2024 by Sergey Poznyakoff This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the diff --git a/runcap b/runcap -Subproject 048800a78f64808116bb9b943837062a67f586a +Subproject e879cd3085ff09f2ff7aff88b9a725d69f209a2 diff --git a/t/09linemon.t b/t/09linemon.t new file mode 100644 index 0000000..1f1666a --- /dev/null +++ b/t/09linemon.t @@ -0,0 +1,35 @@ +# -*- perl -*- + +use lib 't', 'lib'; + +use strict; +use warnings; +use TestCapture; +use Test::More tests => 3; + +our($catbin, $input, $content); + +my @lines; + +TestCapture({ argv => [$catbin, $input], + stdout => sub { push @lines, shift } }); + +is($content, join('', @lines)); + +@lines = (); + +TestCapture({ argv => [$catbin, '-l', 31, $input], + stdout => sub { push @lines, shift } }); + +is_deeply(\@lines, ['CHAPTER I. Down the Rabbit-Hole']); + +@lines = (); + +TestCapture({ argv => [$catbin, '-l', 102, $input], + stdout => sub { push @lines, shift } }); + +is_deeply(\@lines, [ "CHAPTER I. Down the Rabbit-Hole\n", + "\n", + 'Alice was beginning to get very tired of sitting by her sister on the' ]); + + diff --git a/t/10file.t b/t/10file.t new file mode 100644 index 0000000..a5f9045 --- /dev/null +++ b/t/10file.t @@ -0,0 +1,24 @@ +# -*- perl -*- + +use lib 't', 'lib'; + +use strict; +use warnings; +use TestCapture; +use Test::More tests => 2; +use File::Temp; +use File::Cmp qw/fcmp/; +use File::Spec; + +our($catbin, $input, $content); + +my $fh = new File::Temp; +TestCapture({ argv => [$catbin, $input], + stdout => $fh }); +ok(fcmp($fh->filename, $input)); + +$fh = new File::Temp; +TestCapture({ argv => [$catbin, $input], + stdout => $fh->filename }); +ok(fcmp($fh->filename, $input)); + diff --git a/t/11env.t b/t/11env.t new file mode 100644 index 0000000..866369c --- /dev/null +++ b/t/11env.t @@ -0,0 +1,35 @@ +# -*- perl -*- + +use lib 't'; + +use strict; +use warnings; +use TestCapture; +use Test::More tests => 6; +use POSIX::Run::Capture qw(:std); + +my $obj = new POSIX::Run::Capture(['/bin/sh', '-c', 'echo $FOO-$BAZ']); + +$ENV{FOO}='foo'; +$ENV{BAZ}='baz'; +$obj->run; +is($obj->next_line(SD_STDOUT), "foo-baz\n"); + +# Set environment +$obj->set_env('FOO=bar', 'BAZ=quux'); +is(0+@{$obj->env}, 2); +is_deeply($obj->env, [ 'FOO=bar', 'BAZ=quux' ]); + +$obj->run; +is($obj->next_line(SD_STDOUT), "bar-quux\n"); + +# Unset environment +$obj->set_env(); +$obj->run; +is($obj->next_line(SD_STDOUT), "foo-baz\n"); + +ok(TestCapture({ argv => ['/bin/sh', '-c', 'echo $FOO-$BAZ'], + env => ['FOO=bar', 'BAZ=quux'] }, + stdout => { + content => "bar-quux\n" + })); |