diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2020-01-30 17:03:55 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2020-01-30 17:07:21 +0200 |
commit | 821e391af97d414cf0e468e2b8ee8243d11e8c77 (patch) | |
tree | ece9c338bde41027e1c81baa477ab0c927e9a7d9 | |
parent | 98ecb66f95cf705fad1d45fde3d1bbe387b9b69e (diff) | |
download | posixruncapture-821e391af97d414cf0e468e2b8ee8243d11e8c77.tar.gz posixruncapture-821e391af97d414cf0e468e2b8ee8243d11e8c77.tar.bz2 |
Accept glob (file handle) or string (file name) with stderr and stdout keyword arguments
* runcap: Upgrade.
* Capture.xs: Pass stdout/stderr value without dereferencing it.
* capture.h (line_closure): New member "fd".
* capture.c (capture_set_output)
(capture_close_output): New functions.
(capture_new): Use capture_set_output.
* lib/POSIX/Run/Capture.pm: Document changes.
* Makefile.PL: Declare TEST_REQUIRES.
* t/10file.t: New file.
-rw-r--r-- | Capture.xs | 6 | ||||
-rw-r--r-- | Makefile.PL | 4 | ||||
-rw-r--r-- | capture.c | 85 | ||||
-rw-r--r-- | capture.h | 9 | ||||
-rw-r--r-- | lib/POSIX/Run/Capture.pm | 24 | ||||
m--------- | runcap | 0 | ||||
-rw-r--r-- | t/10file.t | 24 |
7 files changed, 112 insertions, 40 deletions
@@ -39,11 +39,7 @@ capture_new(package, ...) croak("argv must be an array ref"); } else if (strcmp(kw, "stdout") == 0 || strcmp(kw, "stderr") == 0) { - if (SvROK(val) - && SvTYPE(SvRV(val)) == SVt_PVCV) { - cb[kw[3] == 'o' ? 0 : 1] = SvRV(val); - } else - croak("%s must be a code ref", kw); + cb[kw[3] == 'o' ? 0 : 1] = val; } else if (strcmp(kw, "timeout") == 0) { if (SvIOK(val)) { timeout = SvUV(val); diff --git a/Makefile.PL b/Makefile.PL index 4aee252..7338001 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>', @@ -119,6 +119,61 @@ free_argv(struct capture *cp) } } +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->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, 0777); + 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) { @@ -129,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; @@ -144,22 +200,8 @@ capture_new(SV *program, ARGV argv, unsigned timeout, SV *cb[2], SV *input) 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); @@ -179,14 +221,9 @@ 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); runcap_free(&cp->rc); @@ -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 */ + int fd; /* Close this descriptor at destroy, unless -1 */ }; struct capture { diff --git a/lib/POSIX/Run/Capture.pm b/lib/POSIX/Run/Capture.pm index b6d78b5..57c7d57 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 = '1.00'; +our $VERSION = '1.00_01'; require XSLoader; XSLoader::load('POSIX::Run::Capture', $VERSION); @@ -109,8 +109,8 @@ Creates a new capture object. There are three possible invocation modes. new POSIX::Run::Capture(argv => [ $command, @args ], program => $prog, 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: @@ -131,7 +131,7 @@ Sets the pathname of binary file to run. 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 +145,20 @@ 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> -Sets the I<line monitor> function for standard error stream. See the -description above. +Capture standard output and write it to the file handle I<FH>. + +=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. + +=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>. See the +description of B<stdout> above. =item B<timeout> diff --git a/runcap b/runcap -Subproject 12c5eac517cb62728b5525cab25aff245086525 +Subproject 3bc027ef68aa1f876bfc3b7d1e60ea5d478ddad 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)); + |