File Coverage

blib/lib/SimpleFlow.pm
Criterion Covered Total %
statement 166 213 77.9
branch 36 64 56.2
condition 15 20 75.0
subroutine 19 19 100.0
pod 0 2 0.0
total 236 318 74.2


line stmt bran cond sub pod time code
1 1     1   121287 use strict;
  1         3  
  1         47  
2 1     1   6 use warnings FATAL => 'all';
  1         2  
  1         86  
3             require 5.010;
4 1     1   9 use feature 'say';
  1         2  
  1         197  
5 1     1   783 use DDP {output => 'STDOUT', array_max => 10, show_memsize => 1};
  1         50888  
  1         16  
6 1     1   1075 use Devel::Confess 'color';
  1         10276  
  1         6  
7 1     1   140 use Cwd 'getcwd';
  1         2  
  1         89  
8             package SimpleFlow;
9             our $VERSION = 0.12;
10 1     1   9 use Time::HiRes;
  1         2  
  1         10  
11 1     1   977 use Term::ANSIColor;
  1         12203  
  1         93  
12 1     1   9 use Scalar::Util 'openhandle';
  1         2  
  1         60  
13 1     1   8 use DDP {output => 'STDOUT', array_max => 10, show_memsize => 1};
  1         2  
  1         12  
14 1     1   155 use Devel::Confess 'color';
  1         3  
  1         8  
15 1     1   184 use Cwd 'getcwd';
  1         2  
  1         57  
16 1     1   6 use warnings FATAL => 'all';
  1         2  
  1         69  
17 1     1   801 use Capture::Tiny 'capture';
  1         7441  
  1         81  
18 1     1   8 use List::Util 'max';
  1         9  
  1         74  
19 1     1   7 use Exporter 'import';
  1         2  
  1         3475  
20             our @EXPORT = qw(say2 task);
21             our @EXPORT_OK = @EXPORT;
22              
23             sub say2 { # say to both command line and log file
24 1     1 0 19 my ($msg, $fh) = @_;
25 1         12 my $current_sub = (split(/::/,(caller(0))[3]))[-1]; # https://stackoverflow.com/questions/2559792/how-can-i-get-the-name-of-the-current-subroutine-in-perl
26 1         39 my @c = caller;
27 1 50       27 if (not openhandle($fh)) {
28 0         0 die "the filehandle given to $current_sub with \"$msg\" from $c[1] line $c[2] isn't actually a filehandle";
29             }
30 1         5 $msg = "\@ $c[1] line $c[2] $msg";
31 1         11 say $msg;
32 1         56 say $fh $msg;
33 1         5 return $msg;
34             }
35              
36             sub task {
37 8     8 0 1225781 my ($args) = @_;
38 8         73 my $current_sub = (split(/::/,(caller(0))[3]))[-1];
39 8 50       410 unless (ref $args eq 'HASH') {
40 0         0 die "args must be given as a hash ref, e.g. \"$current_sub({ data => \@blah })\"";
41             }
42 8         26 my @c = caller;
43 8         211 my @reqd_args = (
44             'cmd', # the shell command
45             );
46 8         24 my @undef_args = grep { !defined $args->{$_}} @reqd_args;
  8         36  
47 8 50       33 if (scalar @undef_args > 0) {
48 0         0 p @undef_args;
49 0         0 die 'the above args are necessary, but were not defined.';
50             }
51 8         38 my @defined_args = ( @reqd_args,
52             'die', # die if not successful; 0 or 1
53             'dry.run', # dry run or not
54             'input.files', # check for input files; SCALAR or ARRAY
55             'log.fh',
56             'note', # a note for the log
57             'overwrite', #
58             'output.files' # product files that need to be checked; can be scalar or array
59             );
60 8         16 my @bad_args = grep { my $key = $_; not grep {$_ eq $key} @defined_args} keys %{ $args };
  18         32  
  18         30  
  144         265  
  8         31  
61 8 50       31 if (scalar @bad_args > 0) {
62 0         0 p @bad_args, array_max => scalar @bad_args;
63 0         0 say "the above arguments are not recognized by $current_sub";
64 0         0 p @defined_args, array_max => scalar @defined_args;
65 0         0 die "The above args are accepted by $current_sub";
66             }
67 8 50 66     56 if (
68             (defined $args->{'log.fh'}) &&
69             (not openhandle($args->{'log.fh'}))
70             ) {
71 0         0 p $args;
72 0         0 die "the filehandle given to $current_sub isn't actually a filehandle";
73             }
74 8         31 my (%input_file_size, @existing_files, @output_files, @empty_filenames);
75 8 100       31 if (defined $args->{'input.files'}) {
76 1         5 my $ref = ref $args->{'input.files'};
77 1         2 my @missing_files;
78 1 50       8 if ($ref eq 'ARRAY') {
    50          
79 0         0 @missing_files = grep {not -f -r $_ } @{ $args->{'input.files'} };
  0         0  
  0         0  
80 0         0 %input_file_size = map { $_ => -s $_ } @{ $args->{'input.files'} };
  0         0  
  0         0  
81 0         0 @empty_filenames = grep {length $_ == 0} @{ $args->{'input.files'} };
  0         0  
  0         0  
82             } elsif ($ref eq '') { # scalar
83 1         3 @missing_files = grep {not -f -r $_ } ($args->{'input.files'});
  1         13  
84 1         4 %input_file_size = map { $_ => -s $_ } ($args->{'input.files'} );
  1         10  
85 1 50       8 @empty_filenames = grep {(defined $_) && (length $_ == 0)} ($args->{'input.files'});
  1         14  
86             } else {
87 0         0 p $args;
88 0         0 die 'ref type "' . $ref . '" is not allowed for "input.files"';
89             }
90 1 50       5 if (scalar @missing_files > 0) {
91 1         18 say STDERR 'this list of arguments:';
92 1         7 p $args;
93 1         7110 say STDERR 'Cannot run because these files are either missing or unreadable in: ' . getcwd();
94 1         6 p @missing_files;
95 1         5914 die 'the above files are missing or are not readable';
96             }
97             }
98 7 50       35 if (scalar @empty_filenames > 0) {
99 0         0 p $args;
100 0         0 die '0-length filenames are not allowed (found in "input.files")';
101             }
102 7         98 my $msg = "\@ $c[1] line $c[2] The command is:\n" . colored(['blue on_bright_red'], $args->{cmd});
103 7         826 say $msg;
104 7 100       39 say {$args->{'log.fh'}} "\@ $c[1] line $c[2] The command is:\n $args->{cmd})" if defined $args->{'log.fh'};
  1         11  
105 7 100       35 if (defined $args->{'output.files'}) { # avoid "uninitialized value" warning
106 4         14 my $ref = ref $args->{'output.files'};
107 4 50       33 if ($ref eq 'ARRAY') {
    50          
108 0         0 @output_files = @{ $args->{'output.files'} };
  0         0  
109             } elsif ($ref eq '') { # a scalar
110 4         19 @output_files = $args->{'output.files'};
111             } else {
112 0         0 p $args;
113 0         0 die "$ref isn't allowed for \"output.files\"";
114             }
115             }
116 7         20 @empty_filenames = grep {length $_ == 0} @output_files; # 0-length filenames aren't allowed
  4         16  
117 7 100       23 if (scalar @empty_filenames > 0) {
118 1         11 p $args;
119 1         6945 die '0-length filenames are not allowed (found in "output.files"';
120             }
121 6 100       53 if (scalar @output_files > 0) {
122 3         8 @existing_files = grep {-f $_} @output_files;
  3         82  
123             }
124             my %r = (
125             cmd => $args->{cmd},
126 6         124 dir => getcwd(),
127             'source.file' => $c[1],
128             'source.line' => $c[2],
129             'output.files' => [@output_files],
130             );
131 6   50     67 $r{'die'} = $args->{'die'} // 1; # by default, true
132 6   100     31 $r{'dry.run'} = $args->{'dry.run'} // 0; # by default, false
133 6   50     56 $r{note} = $args->{note} // '';# by default, false
134 6   100     73 $r{overwrite} = $args->{overwrite} // 0; # by default, false
135 6         16 $r{'will.do'} = 'yes';
136 6 100       22 $r{'will.do'} = 'no: dry run' if $args->{'dry.run'};
137 6         12 my $string_max = 0;
138 6 50       35 if (defined $args->{'input.files'}) {
139 0         0 $r{'input.files'} = $args->{'input.files'};
140 0         0 $r{'input.file.size'} = \%input_file_size;
141             }
142 6         19 my %output_file_size = map {$_ => -s $_} @output_files;
  3         53  
143 6         31 foreach my $val (grep {ref $r{$_} eq ''} keys %r) {
  60         127  
144 54         138 $string_max = max($string_max, length $r{$val});
145             }
146 6 100 100     55 if ((!$args->{overwrite}) && (scalar @output_files > 0) && (scalar @existing_files == scalar @output_files)) { # this has been done before
      66        
147 1         4 $r{done} = 'before';
148 1         2 $r{'will.do'} = 'no';
149 1         8 say colored(['black on_green'], "\"$args->{cmd}\"\n") . ' has been done before';
150 1         115 $r{done} = 'before';
151 1         11 $r{'output.file.size'} = \%output_file_size;
152 1 50       5 p(%r, output => $args->{'log.fh'}, string_max => $string_max) if defined $args->{'log.fh'};
153 1         2 $r{duration} = 0;
154 1         7 p %r, string_max => $string_max;
155 1         13532 return \%r;
156             } else {
157 5         14 $r{done} = 'not yet';
158             }
159 5 100       19 if ($r{'dry.run'}) {
160 1         10 say "\@ $c[1] line $c[2] in $r{dir} the command was going to be:";
161 1         9 say colored(['red on_black'], "\"$args->{cmd}\"");
162 1         60 say 'But this is a dry run';
163 1         5 say '-------------';
164 1         2 $r{duration} = 0;
165 1         13 return \%r;
166             }
167 4         23 my $t0 = Time::HiRes::time();
168             ($r{stdout}, $r{stderr}, $r{'exit'}) = capture {
169 4     4   59429 system( $args->{cmd} );
170 4         196 };
171 4         6586 my $t1 = Time::HiRes::time();
172 4         28 $r{duration} = $t1-$t0;
173 4         29 $r{'exit'} = $r{'exit'} >> 8;
174 4         25 foreach my $std ('stderr', 'stdout') {
175 8         83 $r{$std} =~ s/\s+$//; # remove trailing whitespace/newline
176 8         43 $string_max = max($string_max, length $r{$std});
177             }
178 4         29 $r{signal} = $r{'exit'} & 127;# Useful to see if it was killed by signal 9 or 15
179 4         24 $r{done} = 'now';
180 4         18 $r{'will.do'} = 'done';
181 4         27 my @missing_output_files = grep {not -f -r $_} @output_files;
  2         61  
182 4 50       28 if (scalar @missing_output_files > 0) {
183 0         0 $r{'will.do'} = 'FAILED';
184 0         0 say STDERR "this input to $current_sub:";
185 0         0 p $args;
186 0 0       0 say {$args->{'log.fh'}} "this input to $current_sub:" if defined $args->{'log.fh'};
  0         0  
187 0 0       0 p($args, output => $args->{'log.fh'}, string_max => $string_max) if defined $args->{'log.fh'};
188 0         0 say STDERR 'has these output files missing:';
189 0 0       0 say {$args->{'log.fh'}} 'has these output files missing:' if defined $args->{'log.fh'};
  0         0  
190 0         0 p @missing_output_files;
191 0 0       0 p(@missing_output_files, output => $args->{'log.fh'}, string_max => $string_max) if defined $args->{'log.fh'};
192 0         0 p %r, string_max => $string_max;
193 0 0       0 p(%r, output => $args->{'log.fh'}, string_max => $string_max) if defined $args->{'log.fh'};
194 0 0       0 if ($args->{'die'}) {
195 0         0 die 'those above files should have been made but are missing';
196             } else {
197 0         0 say STDERR 'those above files should have been made but are missing';
198             }
199             }
200 4         33 %output_file_size = map {$_ => -s $_} @output_files;
  2         83  
201 4         28 $r{'output.file.size'} = \%output_file_size;
202             # p %output_file_size;
203 4         20 my @files_with_zero_size = grep { $output_file_size{$_} == 0} @output_files;
  2         19  
204 4 50       21 if (scalar @files_with_zero_size > 0) {
205 0         0 p @files_with_zero_size;
206 0         0 warn 'the above output files have 0 size.';
207             }
208 4 100       34 p(%r, output => $args->{'log.fh'}) if defined $args->{'log.fh'};
209 4 100 66     16027 if (($r{'die'}) && ($r{'exit'} != 0)) {
210 1         16 $r{'will.do'} = 'FAILED';
211 1         23 p %r, string_max => $string_max;
212 1         17119 die "\"$args->{cmd}\" failed from $c[1] line $c[2]"
213             }
214 3         36 p %r, string_max => $string_max;
215 3         63999 return \%r;
216             }
217             1;
218             __END__