File Coverage

blib/lib/IPC/PrettyPipe.pm
Criterion Covered Total %
statement 172 182 94.5
branch 47 74 63.5
condition 15 20 75.0
subroutine 37 38 97.3
pod 9 11 81.8
total 280 325 86.1


line stmt bran cond sub pod time code
1             package IPC::PrettyPipe;
2              
3             # ABSTRACT: manage human readable external command execution pipelines
4              
5 15     15   1171061 use strict;
  15         65  
  15         483  
6 15     15   79 use warnings;
  15         27  
  15         585  
7              
8             our $VERSION = '0.13';
9 15     15   111 use Carp;
  15         39  
  15         953  
10              
11 15     15   108 use List::Util qw[ sum ];
  15         42  
  15         1223  
12 15     15   7770 use Module::Load qw[ load ];
  15         17584  
  15         91  
13             use Module::Runtime
14 15     15   8959 qw[ check_module_name compose_module_name use_package_optimistically ];
  15         27581  
  15         97  
15 15     15   3222 use Safe::Isa;
  15         2368  
  15         1746  
16 15     15   7676 use Try::Tiny;
  15         31665  
  15         1013  
17              
18 15     15   8900 use Types::Standard -all;
  15         1092255  
  15         174  
19 15     15   746505 use Type::Params qw[ validate ];
  15         177990  
  15         185  
20              
21 15     15   10781 use IPC::PrettyPipe::Types -all;
  15         53  
  15         174  
22              
23 15     15   47974 use IPC::PrettyPipe::Cmd;
  15         70  
  15         579  
24 15     15   119 use IPC::PrettyPipe::Queue;
  15         34  
  15         287  
25 15     15   83 use IPC::PrettyPipe::Arg::Format;
  15         33  
  15         270  
26              
27 15     15   76 use Moo;
  15         31  
  15         267  
28 15     15   11636 use Moo::Role ();
  15         40  
  15         384  
29              
30             with 'IPC::PrettyPipe::Queue::Element';
31              
32 15     15   86 use namespace::clean;
  15         40  
  15         80  
33              
34 15     15   29670 use overload 'fallback' => 1;
  15         35  
  15         83  
35              
36              
37              
38              
39              
40              
41              
42              
43              
44              
45              
46              
47              
48              
49              
50              
51             use overload '|=' => sub {
52 2     2   9 $_[0]->add( $_[1] );
53 2         8 return $_[0];
54 15     15   1592 };
  15         58  
  15         86  
55              
56              
57              
58              
59              
60              
61              
62              
63              
64              
65              
66              
67              
68              
69              
70              
71              
72              
73              
74              
75              
76             use overload '|' => sub {
77 3     3   9 my $swap = pop;
78 3         66 my $pipe = IPC::PrettyPipe->new;
79 3 50       21 $pipe->add( $_ ) for ( $swap ? reverse( @_ ) : @_ );
80 3         27 $pipe;
81 15     15   2033 };
  15         55  
  15         100  
82              
83              
84             BEGIN {
85 15     15   1485 IPC::PrettyPipe::Arg::Format->shadow_attrs( fmt => sub { 'arg' . shift } );
  30         15310  
86             }
87              
88              
89              
90              
91              
92              
93              
94              
95              
96              
97              
98              
99              
100              
101              
102              
103              
104              
105              
106              
107              
108              
109              
110              
111              
112              
113              
114              
115             has argfmt => (
116             is => 'ro',
117             lazy => 1,
118             handles => IPC::PrettyPipe::Arg::Format->shadowed_attrs,
119             default => sub { IPC::PrettyPipe::Arg::Format->new_from_attrs( shift ) },
120             );
121              
122              
123              
124              
125              
126              
127              
128              
129              
130              
131              
132              
133              
134              
135              
136              
137              
138              
139              
140              
141              
142              
143              
144              
145              
146              
147              
148             has streams => (
149             is => 'lazy',
150             default => sub {
151             IPC::PrettyPipe::Queue->new( elements => $_[0]->_init_streams );
152             },
153             init_arg => undef,
154             );
155              
156              
157             has _init_streams => (
158             is => 'ro',
159             isa => ArrayRef [ InstanceOf ['IPC::PrettyPipe::Stream'] ],
160             init_arg => 'streams',
161             default => sub { [] },
162             );
163              
164             has _init_cmds => (
165             is => 'ro',
166             init_arg => 'cmds',
167             coerce => AutoArrayRef->coercion,
168             isa => ArrayRef,
169             default => sub { [] },
170             predicate => 1,
171             clearer => 1,
172             );
173              
174              
175              
176              
177              
178              
179              
180              
181              
182              
183              
184              
185              
186              
187              
188              
189              
190              
191             # must delay building cmds until all attributes have been specified
192             has cmds => (
193             is => 'ro',
194             default => sub { IPC::PrettyPipe::Queue->new },
195             init_arg => undef,
196             );
197              
198              
199              
200              
201              
202              
203              
204              
205              
206              
207              
208             has merge_pipes => (
209             is => 'rw',
210             default => 1,
211             isa => Bool,
212             );
213              
214             has _executor_arg => (
215             is => 'rw',
216             init_arg => 'executor',
217             default => sub { 'IPC::Run' },
218             trigger => sub { $_[0]->_clear_executor },
219             );
220              
221              
222             has _executor => (
223             is => 'rw',
224             isa => ConsumerOf ['IPC::PrettyPipe::Executor'],
225             lazy => 1,
226             clearer => 1,
227             default => sub {
228              
229             my $backend = $_[0]->_executor_arg;
230              
231             ## no critic (ProhibitAccessOfPrivateData)
232             return $backend->$_does( 'IPC::PrettyPipe::Executor' )
233             ? $backend
234             : $_[0]->_backend_factory( Execute => $backend );
235             },
236             );
237              
238              
239              
240              
241              
242              
243              
244              
245              
246              
247              
248             sub executor {
249 10     10 1 80 my $self = shift;
250              
251 10 100       201 $self->_executor_arg( @_ )
252             if @_;
253              
254 10         266 return $self->_executor;
255             }
256              
257              
258              
259              
260              
261              
262              
263              
264              
265             sub run {
266 6     6 1 715 my ( $self ) = @_;
267              
268 6         111 $self->_executor->run( $self );
269             }
270              
271              
272             has _renderer_arg => (
273             is => 'rw',
274             init_arg => 'renderer',
275             default => sub { 'Template::Tiny' },
276             trigger => sub { $_[0]->_clear_renderer },
277             );
278              
279             has _renderer => (
280             is => 'rw',
281             isa => ConsumerOf ['IPC::PrettyPipe::Renderer'],
282             lazy => 1,
283             clearer => 1,
284             default => sub {
285              
286             my $backend = $_[0]->_renderer_arg;
287              
288             ## no critic (ProhibitAccessOfPrivateData)
289             return $backend->$_does( 'IPC::PrettyPipe::Renderer' )
290             ? $backend
291             : $_[0]->_backend_factory( Render => $backend );
292             },
293             );
294              
295              
296              
297              
298              
299              
300              
301              
302              
303              
304              
305              
306              
307              
308              
309              
310              
311              
312              
313             sub renderer {
314 53     53 1 34664 my $self = shift;
315              
316 53 100       1286 $self->_renderer_arg( @_ )
317             if @_;
318              
319 53         1176 return $self->_renderer;
320             }
321              
322              
323              
324              
325              
326              
327              
328              
329              
330             sub render {
331              
332 42     42 1 19024 my $self = shift;
333              
334 42         1323 $self->_renderer()->render( $self );
335             }
336              
337              
338              
339              
340              
341              
342             # accept:
343             # new( \%hash )
344             # new( $cmd )
345             # new( @stuff )
346             sub BUILDARGS {
347              
348 110     110 0 131599 shift;
349              
350             return
351 110 0       2240 @_ == 1
    50          
352             ? 'HASH' eq ref( $_[0] )
353             ? $_[0]
354             : { cmds => $_[0] }
355             : {@_};
356              
357             }
358              
359              
360             sub BUILD {
361              
362 110     110 0 3605 my $self = shift;
363              
364 110 50       546 if ( $self->_has_init_cmds ) {
365              
366 110         237 $self->ffadd( @{ $self->_init_cmds } );
  110         546  
367 110         2422 $self->_clear_init_cmds;
368             }
369              
370 110         1098 return;
371              
372             }
373              
374              
375              
376              
377              
378              
379              
380              
381              
382              
383              
384              
385              
386              
387              
388              
389              
390              
391              
392              
393              
394              
395              
396              
397              
398              
399              
400              
401              
402              
403              
404              
405              
406              
407              
408             sub add {
409              
410 139     139 1 282 my $self = shift;
411              
412 139 50       555 unshift @_, 'cmd' if @_ == 1;
413              
414             ## no critic (ProhibitAccessOfPrivateData)
415              
416 139         3032 my $argfmt = $self->argfmt->clone;
417              
418 139         508 my $argfmt_attrs = IPC::PrettyPipe::Arg::Format->shadowed_attrs;
419              
420             my ( $attr ) = validate(
421             \@_,
422             slurpy Dict [
423             cmd => Str | Cmd | Pipe,
424             args => Optional,
425             argfmt => Optional [ InstanceOf ['IPC::PrettyPipe::Arg::Format'] ],
426 139         5217 ( map { $_ => Optional [Str] } keys %{$argfmt_attrs} ),
  278         86179  
  139         210436  
427             ] );
428              
429              
430 139 50       1311139 $argfmt->copy_from( $attr->{argfmt} ) if defined $attr->{argfmt};
431 139         34802 $argfmt->copy_from( IPC::PrettyPipe::Arg::Format->new_from_hash( $attr ) );
432              
433              
434 139 100       789 if ( $attr->{cmd}->$_isa( 'IPC::PrettyPipe::Cmd' ) ) {
    50          
435              
436 123         2324 my $cmd = delete $attr->{cmd};
437              
438 123 50       479 croak(
439             "cannot specify additional arguments when passing a Cmd object\n" )
440             if keys %$attr;
441              
442 123         807 $self->cmds->push( $cmd );
443              
444 123         1087 return $cmd;
445             }
446              
447             elsif ( $attr->{cmd}->$_isa( 'IPC::PrettyPipe' ) ) {
448              
449 16         538 my $cmd = delete $attr->{cmd};
450              
451 16 50       78 croak(
452             "cannot specify additional arguments when passing a Pipe object\n" )
453             if keys %$attr;
454              
455 16 100 100     371 if ( $self->merge_pipes && $cmd->streams->empty ) {
456 11         25 $self->cmds->push( $_ ) foreach @{ $cmd->cmds->elements };
  11         85  
457 11         83 return $self;
458             }
459             else {
460              
461 5         53 $self->cmds->push( $cmd );
462 5         55 return $cmd;
463             }
464             }
465              
466             else {
467              
468             my $cmd = IPC::PrettyPipe::Cmd->new(
469             cmd => $attr->{cmd},
470             argfmt => $argfmt->clone,
471 0 0       0 exists $attr->{args} ? ( args => $attr->{args} ) : (),
472             );
473              
474 0         0 $self->cmds->push( $cmd );
475              
476 0         0 return $cmd;
477              
478             }
479              
480 0         0 die( "NOTREACHED" );
481             }
482              
483              
484              
485              
486              
487              
488              
489              
490              
491              
492              
493              
494              
495              
496              
497              
498              
499              
500              
501              
502              
503              
504              
505              
506              
507              
508              
509              
510              
511              
512              
513              
514              
515              
516              
517              
518              
519              
520              
521              
522              
523              
524             sub ffadd {
525              
526 200     200 1 371 my $self = shift;
527 200         489 my @args = @_;
528              
529 200         3548 my $argfmt = $self->argfmt->clone;
530              
531 200         861 for ( my $idx = 0 ; $idx < @args ; $idx++ ) {
532              
533 143         505 my $t = $args[$idx];
534              
535 143 100       491 if ( $t->$_isa( 'IPC::PrettyPipe::Arg::Format' ) ) {
    100          
    100          
    100          
    50          
    50          
536              
537 4         63 $t->copy_into( $argfmt );
538              
539             }
540              
541             elsif ( $t->$_isa( 'IPC::PrettyPipe::Cmd' ) ) {
542              
543 10         322 $self->add( $t );
544              
545             }
546              
547             elsif ( $t->$_isa( 'IPC::PrettyPipe' ) ) {
548              
549 6         237 $self->add( $t );
550              
551             }
552              
553             elsif ( 'ARRAY' eq ref $t ) {
554              
555 109         2688 my $cmd;
556              
557 109 100       439 if ( ( $cmd = $t->[0] )->$_isa( 'IPC::PrettyPipe' ) ) {
558 3 50       69 croak(
559             "In an array containing an IPC::PrettyPipe object, it must be the only element\n"
560             ) if @$t > 1;
561             }
562              
563             else {
564              
565 106         1263 $cmd = IPC::PrettyPipe::Cmd->new(
566             cmd => shift( @$t ),
567             argfmt => $argfmt->clone
568             );
569 106         564 $cmd->ffadd( @$t );
570             }
571              
572 109         477 $self->add( $cmd );
573             }
574              
575             elsif ( $t->$_isa( 'IPC::PrettyPipe::Stream' ) ) {
576              
577 0         0 $self->stream( $t );
578              
579             }
580              
581             elsif ( !ref $t ) {
582              
583             try {
584              
585 14     14   1002 my $stream = IPC::PrettyPipe::Stream->new(
586             spec => $t,
587             strict => 0,
588             );
589              
590 14 50       85 if ( $stream->requires_file ) {
591              
592 14 50       57 croak( "arg[$idx]: stream operator $t requires a file\n" )
593             if ++$idx == @args;
594              
595 14         294 $stream->file( $args[$idx] );
596             }
597              
598 14         479 $self->stream( $stream );
599             }
600             catch {
601              
602 0 0   0   0 die $_ unless /requires a file|cannot parse/;
603              
604             # This is the "fall through" which takes care
605             # of calling with a simple string argument, which is
606             # taken to be a command to run. It probably shouldn't
607             # be buried at this level of the code.
608              
609 0         0 $self->add( cmd => $t, argfmt => $argfmt );
610 14         644 };
611              
612             }
613              
614             else {
615              
616 0         0 croak( "arg[$idx]: unrecognized parameter to ffadd\n" );
617              
618             }
619              
620             }
621              
622             }
623              
624              
625              
626              
627              
628              
629              
630              
631              
632              
633              
634              
635             sub stream {
636              
637 14     14 1 34 my $self = shift;
638              
639 14         45 my $spec = shift;
640              
641 14 50       50 if ( $spec->$_isa( 'IPC::PrettyPipe::Stream' ) ) {
    0          
642              
643 14 50       262 croak( "too many arguments\n" )
644             if @_;
645              
646 14         313 $self->streams->push( $spec );
647              
648             }
649              
650             elsif ( !ref $spec ) {
651              
652 0 0       0 $self->streams->push(
653             IPC::PrettyPipe::Stream->new(
654             spec => $spec,
655             +@_ ? ( file => @_ ) : () ) );
656             }
657              
658             else {
659              
660 0         0 croak( "illegal stream specification\n" );
661              
662             }
663              
664              
665 14         75 return;
666             }
667              
668              
669              
670              
671              
672              
673              
674              
675              
676              
677              
678              
679             sub valmatch {
680              
681 16     16 1 2177 my $self = shift;
682 16         37 my $pattern = shift;
683              
684             return sum 0,
685 16 100 100     47 map { $_->valmatch( $pattern ) && 1 || 0 } @{ $self->cmds->elements };
  26         110  
  16         89  
686             }
687              
688              
689              
690              
691              
692              
693              
694              
695              
696              
697              
698              
699              
700              
701              
702              
703              
704              
705              
706              
707              
708              
709              
710              
711              
712              
713              
714              
715              
716              
717              
718              
719              
720              
721              
722              
723              
724              
725              
726              
727              
728              
729              
730              
731              
732              
733              
734              
735              
736              
737              
738              
739              
740              
741              
742              
743              
744              
745              
746              
747              
748              
749              
750              
751              
752              
753              
754              
755              
756              
757             sub valsubst {
758              
759 12     12 1 148 my $self = shift;
760              
761 12 100       70 my @args = ( shift, shift, @_ > 1 ? {@_} : @_ );
762              
763 12         98 my ( $pattern, $value, $args ) = validate(
764             \@args,
765             RegexpRef,
766             Str,
767             Optional [
768             Dict [
769             lastvalue => Optional [Str],
770             firstvalue => Optional [Str] ]
771             ],
772             );
773              
774 12         68897 my $nmatch = $self->valmatch( $pattern );
775              
776 12 100       57 if ( $nmatch == 1 ) {
777              
778             ## no critic (ProhibitAccessOfPrivateData)
779 5   66     40 $args->{lastvalue} //= $args->{firstvalue} // $value;
      66        
780 5   66     28 $args->{firstvalue} //= $args->{lastvalue};
781              
782             }
783             else {
784              
785             ## no critic (ProhibitAccessOfPrivateData)
786 7   66     44 $args->{lastvalue} ||= $value;
787 7   66     32 $args->{firstvalue} ||= $value;
788             }
789              
790 12         26 my $match = 0;
791 12         33 foreach ( @{ $self->cmds->elements } ) {
  12         54  
792              
793             ## no critic (ProhibitAccessOfPrivateData)
794              
795             $match++
796             if $_->valsubst( $pattern,
797             $match == 0 ? $args->{firstvalue}
798             : $match == ( $nmatch - 1 ) ? $args->{lastvalue}
799 22 100       133 : $value );
    100          
    50          
800             }
801              
802 12         71 return $match;
803             }
804              
805             sub _backend_factory {
806              
807 40     40   627 my ( $self, $type, $req ) = ( shift, shift, shift );
808              
809 40         242 check_module_name( $req );
810              
811             my $role = compose_module_name( __PACKAGE__,
812             {
813             Render => 'Renderer',
814             Execute => 'Executor'
815 40         1212 }->{$type} );
816              
817 40         2402 my $module;
818              
819 40         167 for my $try ( $req, compose_module_name( "IPC::PrettyPipe::$type", $req ) )
820             {
821 76 100       73292 next unless use_package_optimistically( $try )->DOES( $role );
822              
823 40         57410 $module = $try;
824 40         104 last;
825             }
826              
827             croak(
828 40 50       163 "requested $type module ($req) either doesn't exist or doesn't consume $role\n"
829             ) if !defined $module;
830              
831 40         430 return $module->new( @_ );
832             }
833              
834              
835             sub _storefh {
836              
837 6     6   15 my $self = shift;
838              
839 6         115 my $sfh = IO::ReStoreFH->new;
840              
841 6         136 for my $stream ( @{ $self->streams->elements } ) {
  6         153  
842              
843 4         76 my ( $sub, @fh ) = $stream->apply;
844              
845 4         22 $sfh->store( $_ ) foreach @fh;
846              
847 4         664 $sub->();
848             }
849              
850 6         53 return $sfh;
851             }
852              
853             1;
854              
855             #
856             # This file is part of IPC-PrettyPipe
857             #
858             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
859             #
860             # This is free software, licensed under:
861             #
862             # The GNU General Public License, Version 3, June 2007
863             #
864              
865             __END__