line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Pb; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
118621
|
use 5.14.0; |
|
1
|
|
|
|
|
13
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
27
|
|
5
|
1
|
|
|
1
|
|
438
|
use autodie ':all'; |
|
1
|
|
|
|
|
15497
|
|
|
1
|
|
|
|
|
4
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.01_04'; # TRIAL VERSION |
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
23300
|
use Exporter; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
77
|
|
10
|
|
|
|
|
|
|
our @EXPORT = |
11
|
|
|
|
|
|
|
( |
12
|
|
|
|
|
|
|
qw< command base_command flow >, # base structure of the command itself |
13
|
|
|
|
|
|
|
qw< arg opt must_be one_of also >, # for declaring command arguments and options |
14
|
|
|
|
|
|
|
qw< log_to control_via >, # attributes of the command |
15
|
|
|
|
|
|
|
qw< verify SH CODE RUN >, # keywords inside a flow |
16
|
|
|
|
|
|
|
qw< $FLOW %OPT >, # variable containers that flows need access to |
17
|
|
|
|
|
|
|
qw< pwd >, # pass-through from PerlX::bash |
18
|
|
|
|
|
|
|
); |
19
|
|
|
|
|
|
|
|
20
|
1
|
|
|
1
|
|
521
|
use Moo; |
|
1
|
|
|
|
|
11220
|
|
|
1
|
|
|
|
|
8
|
|
21
|
1
|
|
|
1
|
|
2080
|
use CLI::Osprey; |
|
1
|
|
|
|
|
24524
|
|
|
1
|
|
|
|
|
6
|
|
22
|
|
|
|
|
|
|
|
23
|
1
|
|
|
1
|
|
75773
|
use Safe::Isa; |
|
1
|
|
|
|
|
575
|
|
|
1
|
|
|
|
|
179
|
|
24
|
1
|
|
|
1
|
|
556
|
use Type::Tiny; |
|
1
|
|
|
|
|
14897
|
|
|
1
|
|
|
|
|
44
|
|
25
|
1
|
|
|
1
|
|
521
|
use PerlX::bash 0.05 qw< bash pwd >; |
|
1
|
|
|
|
|
20612
|
|
|
1
|
|
|
|
|
74
|
|
26
|
1
|
|
|
1
|
|
440
|
use Import::Into; |
|
1
|
|
|
|
|
500
|
|
|
1
|
|
|
|
|
34
|
|
27
|
1
|
|
|
1
|
|
9
|
use Sub::Install qw< install_sub >; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
10
|
|
28
|
1
|
|
|
1
|
|
179
|
use File::Basename; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
85
|
|
29
|
|
|
|
|
|
|
|
30
|
1
|
|
|
1
|
|
518
|
use Pb::Command::Context; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
3302
|
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub import |
34
|
|
|
|
|
|
|
{ |
35
|
1
|
|
|
1
|
|
9
|
my $caller = caller; |
36
|
1
|
|
|
|
|
19
|
_setup_signal_handlers(); |
37
|
1
|
|
|
|
|
533
|
strict->import::into($caller); |
38
|
1
|
|
|
|
|
221
|
warnings->import::into($caller); |
39
|
1
|
|
|
|
|
177
|
feature->import::into($caller, ':5.14'); |
40
|
1
|
|
|
|
|
290
|
autodie->import::into({level=>1}, ':all'); # `autodie` requires a bit of magic ... |
41
|
1
|
|
|
|
|
6092
|
goto \&Exporter::import; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# This is a global, sort of ... it has a global lifetime, certainly, but not global visibility. |
46
|
|
|
|
|
|
|
# Think of it like a singleton. Most of our methods can either be called as object methods, in |
47
|
|
|
|
|
|
|
# which case they operate on the object invocant, or just as straight functions, in which case they |
48
|
|
|
|
|
|
|
# operate on this guy. `$CMD` is set by `Pb->go` (which is down at the very bottom of this file). |
49
|
|
|
|
|
|
|
my $CMD; |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# And this is how we implement that optional invocant. |
52
|
0
|
0
|
|
0
|
|
0
|
sub _pb_args { $_[0]->$_can('_osprey_config') ? @_ : ($CMD, @_) } |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
################### |
56
|
|
|
|
|
|
|
# CONTEXT OBJECTS # |
57
|
|
|
|
|
|
|
################### |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# This will be cloned and have command-specific values added to it when the flow executes. |
60
|
|
|
|
|
|
|
our $FLOW = Pb::Command::Context->new; |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
our %OPT; # key == option name, value == option value |
63
|
|
|
|
|
|
|
our %CONTROL; # key == command name, value == control structure |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
################## |
67
|
|
|
|
|
|
|
# GLOBAL OPTIONS # |
68
|
|
|
|
|
|
|
################## |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
option pretend => |
71
|
|
|
|
|
|
|
( |
72
|
|
|
|
|
|
|
is => 'ro', doc => "don't run commands; just print them", |
73
|
|
|
|
|
|
|
); |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
option interactive => |
76
|
|
|
|
|
|
|
( |
77
|
|
|
|
|
|
|
is => 'ro', doc => "only run commands if user approves each one", |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
############### |
82
|
|
|
|
|
|
|
# SCAFFOLDING # |
83
|
|
|
|
|
|
|
############### |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# this will hold all the different flows |
86
|
|
|
|
|
|
|
my %FLOWS; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# this is for the `base_command` (if there is one) |
89
|
|
|
|
|
|
|
my $BASE_CMD; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# This takes an option def (i.e. a hashref built from the properties of an `opt` clause) and turns |
93
|
|
|
|
|
|
|
# it into the arguments to an `option` call (`option` is defined by CLI::Osprey). |
94
|
|
|
|
|
|
|
sub _option_args |
95
|
|
|
|
|
|
|
{ |
96
|
0
|
|
|
0
|
|
0
|
my $def = shift; |
97
|
0
|
|
|
|
|
0
|
my %props = ( is => 'ro' ); |
98
|
0
|
0
|
|
|
|
0
|
unless ( $def->{type}->is_a_type_of('Bool') ) |
99
|
|
|
|
|
|
|
{ |
100
|
0
|
|
|
|
|
0
|
$props{format} = 's'; |
101
|
|
|
|
|
|
|
} |
102
|
0
|
|
|
|
|
0
|
return $def->{name} => %props; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# This builds subcommands. If it weren't for the fact that we need our subcommands to be able to |
106
|
|
|
|
|
|
|
# have their own options, we could simply do `subcommand $name => $cmd`. However, that creates an |
107
|
|
|
|
|
|
|
# object of class CLI::Osprey::InlineSubcommand, and those can't have options. :-( |
108
|
|
|
|
|
|
|
sub _install_subcommand |
109
|
|
|
|
|
|
|
{ |
110
|
0
|
|
|
0
|
|
0
|
my ($name, $action, $optdefs) = @_; |
111
|
0
|
|
|
|
|
0
|
my $pkg = $name =~ s/-/_/r; |
112
|
0
|
0
|
|
|
|
0
|
fatal("illegal command name [$name]") if $pkg !~ /\A[a-zA-Z_][a-zA-Z0-9_]*\z/; |
113
|
0
|
|
|
|
|
0
|
$pkg = "Pb::Subcommand::$pkg"; |
114
|
0
|
|
|
|
|
0
|
eval "package $pkg { use Moo; use CLI::Osprey; }"; |
115
|
0
|
|
|
|
|
0
|
install_sub({ code => $action, into => $pkg, as => 'run' }); |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# handle options |
118
|
0
|
|
0
|
|
|
0
|
my $option = $pkg->can('option') // die("Can't install options into subcommand package! [$name]"); |
119
|
0
|
|
|
|
|
0
|
$option->( _option_args($_) ) foreach @$optdefs; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# NOTE: can pass a `desc =>` to the `subcommand` (useful for help?) |
122
|
0
|
|
|
|
|
0
|
subcommand $name => $pkg; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# This build the "base command," which is really just the default subcommand. |
126
|
|
|
|
|
|
|
sub _install_base_command |
127
|
|
|
|
|
|
|
{ |
128
|
0
|
|
|
0
|
|
0
|
my ($action, $optdefs) = @_; |
129
|
0
|
|
|
|
|
0
|
option( _option_args($_) ) foreach @$optdefs; |
130
|
0
|
|
|
|
|
0
|
$BASE_CMD = $action; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# This guarantees that `END` blocks are not only called when your program `exit`s or `die`s, but |
135
|
|
|
|
|
|
|
# also when it's terminated due to a signal (where possible to catch). This is super-important for |
136
|
|
|
|
|
|
|
# things like making sure pidfiles get cleaned up. I'm pretty sure that the only times your `END` |
137
|
|
|
|
|
|
|
# blocks won't get called if your program exits after this runs is for uncatchable signals (i.e. |
138
|
|
|
|
|
|
|
# `KILL`) and if you call `exec`. I'd worry more about that latter one, but it seems pretty |
139
|
|
|
|
|
|
|
# unlikely in a Leadpipe context. |
140
|
|
|
|
|
|
|
sub _setup_signal_handlers |
141
|
|
|
|
|
|
|
{ |
142
|
|
|
|
|
|
|
# This list compiled via the following methodology: |
143
|
|
|
|
|
|
|
# * Examine the signal(7) man page on a current (at the time) Linux version (this one just |
144
|
|
|
|
|
|
|
# so happened to be Linux Mint 18.2, kernel 4.10.0-38-generic). |
145
|
|
|
|
|
|
|
# * Find all signals which are labeled either "Term" or "Core" (i.e. all signals which will |
146
|
|
|
|
|
|
|
# actually cause your process to exit). |
147
|
|
|
|
|
|
|
# * Eliminate everything already in sigtrap.pm's "normal-signals" list. |
148
|
|
|
|
|
|
|
# * Eliminate everything already in sigtrap.pm's "error-signals" list. |
149
|
|
|
|
|
|
|
# * Eliminate "KILL," because you can't catch it anyway. |
150
|
|
|
|
|
|
|
# * Eliminate "USR1" and "USR2" on the grounds that we shouldn't assume anything about |
151
|
|
|
|
|
|
|
# "user-defined signals." |
152
|
|
|
|
|
|
|
# * Whatever was leftover is the list below. |
153
|
1
|
|
|
1
|
|
4
|
my @EXTRA_SIGNALS = qw< ALRM POLL PROF VTALRM XCPU XFSZ IOT STKFLT IO PWR LOST UNUSED >; |
154
|
1
|
|
|
|
|
432
|
require sigtrap; |
155
|
|
|
|
|
|
|
# Because of the `untrapped`, this won't bork any signals you've previously set yourself. |
156
|
|
|
|
|
|
|
# Signals you _subsequently_ set yourself will of course override these. |
157
|
|
|
|
|
|
|
sigtrap->import( handler => sub |
158
|
|
|
|
|
|
|
{ |
159
|
0
|
|
|
0
|
|
0
|
my $signal = shift; |
160
|
|
|
|
|
|
|
# Weirdly (or maybe not so much; I dunno), while `END` blocks don't get called if a |
161
|
|
|
|
|
|
|
# `'DEFAULT'` signal handler leads to an exit, they _do_ for custom handlers. So this |
162
|
|
|
|
|
|
|
# `sub` literally doesn't need to do _anything_. But, hey: while we're here, may as |
163
|
|
|
|
|
|
|
# well alert the user as to what's going down. |
164
|
0
|
|
|
|
|
0
|
$FLOW->raise_error("terminated due to signal $signal"); |
165
|
0
|
|
|
|
|
0
|
say STDERR "received signal: $signal"; |
166
|
|
|
|
|
|
|
}, |
167
|
|
|
|
|
|
|
untrapped => 'normal-signals', 'error-signals', |
168
|
1
|
|
|
|
|
1166
|
grep { exists $SIG{$_} } @EXTRA_SIGNALS |
|
12
|
|
|
|
|
31
|
|
169
|
|
|
|
|
|
|
); |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
##################### |
174
|
|
|
|
|
|
|
# COMMAND STRUCTURE # |
175
|
|
|
|
|
|
|
##################### |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub command |
179
|
|
|
|
|
|
|
{ |
180
|
0
|
|
|
0
|
1
|
|
state $PASSTHRU_ARGS = { map { $_ => 1 } qw< log_to flow > }; |
|
0
|
|
|
|
|
|
|
181
|
0
|
|
|
|
|
|
state $CONTEXT_VAR_XLATE = { LOGFILE => 'log_to', }; |
182
|
0
|
|
|
|
|
|
my $name = shift; |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# these are all used in the closure below |
185
|
0
|
|
|
|
|
|
my %args; # arguments to this command definition |
186
|
0
|
|
|
|
|
|
my $argdefs = []; # definition of args to the command invocation |
187
|
0
|
|
|
|
|
|
my $optdefs = []; # definition of opts to the command invocation |
188
|
|
|
|
|
|
|
# process args: most are simple, some are trickier |
189
|
0
|
|
|
|
|
|
while (@_) |
190
|
|
|
|
|
|
|
{ |
191
|
0
|
0
|
|
|
|
|
if ($PASSTHRU_ARGS->{$_[0]}) |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
192
|
|
|
|
|
|
|
{ |
193
|
0
|
|
|
|
|
|
my $arg = shift; |
194
|
0
|
|
|
|
|
|
$args{$arg} = shift; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
elsif ($_[0] eq 'arg') |
197
|
|
|
|
|
|
|
{ |
198
|
0
|
|
|
|
|
|
shift; # just the 'arg' marker |
199
|
0
|
0
|
|
|
|
|
fatal("base commands cannot take arguments (try an option instead)") if $name eq ':DEFAULT'; |
200
|
0
|
|
|
|
|
|
my $arg = {}; |
201
|
0
|
|
|
|
|
|
$arg->{name} = shift; |
202
|
0
|
|
|
|
|
|
$arg->{type} = shift; |
203
|
|
|
|
|
|
|
fatal("not a constraint [" . (ref $arg->{type} || $arg->{type}) . "]") |
204
|
0
|
0
|
0
|
|
|
|
unless $arg->{type}->$_isa('Type::Tiny'); |
205
|
0
|
|
|
|
|
|
push @$argdefs, $arg; |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
elsif ($_[0] eq 'opt') |
208
|
|
|
|
|
|
|
{ |
209
|
0
|
|
|
|
|
|
shift; # just the 'opt' marker |
210
|
0
|
|
|
|
|
|
my $opt = {}; |
211
|
0
|
|
|
|
|
|
$opt->{name} = shift; |
212
|
0
|
0
|
|
|
|
|
$opt->{type} = $_[0]->$_isa('Type::Tiny') ? shift : must_be('Bool'); |
213
|
0
|
0
|
|
|
|
|
if ($_[0] eq 'properties') |
214
|
|
|
|
|
|
|
{ |
215
|
0
|
|
|
|
|
|
shift; |
216
|
0
|
|
|
|
|
|
my $extra_props = shift; |
217
|
0
|
|
|
|
|
|
$opt->{$_} = $extra_props->{$_} foreach keys %$extra_props; |
218
|
|
|
|
|
|
|
} |
219
|
0
|
|
|
|
|
|
push @$optdefs, $opt; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
elsif ($_[0] eq 'control') |
222
|
|
|
|
|
|
|
{ |
223
|
0
|
|
|
|
|
|
shift; # just the 'control' marker |
224
|
0
|
|
|
|
|
|
my $control = shift; |
225
|
0
|
0
|
|
|
|
|
fatal("`control_via' requires hashref") unless ref $control eq 'HASH'; |
226
|
0
|
|
|
|
|
|
$CONTROL{$name} = $control; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
else |
229
|
|
|
|
|
|
|
{ |
230
|
0
|
|
|
|
|
|
fatal("unknown command attribute [$_[0]]"); |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# Save the flow (including processing any args) under our name. Doing args here rather than in |
235
|
|
|
|
|
|
|
# the `$subcmd` below enables the `RUN` directive to pass args as well. |
236
|
|
|
|
|
|
|
$FLOWS{$name} = sub |
237
|
|
|
|
|
|
|
{ |
238
|
0
|
|
|
0
|
|
|
$FLOW->validate_args(@_, $argdefs); |
239
|
0
|
0
|
|
|
|
|
fatal($FLOW->error) if $FLOW->error; |
240
|
0
|
|
|
|
|
|
$args{flow}->(); |
241
|
0
|
|
|
|
|
|
}; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
my $subcmd = sub |
244
|
|
|
|
|
|
|
{ |
245
|
0
|
|
|
0
|
|
|
my ($osprey) = @_; # currently unused |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Figure out what context vars we need to set based on our the `command` properties. |
248
|
0
|
|
|
|
|
|
my $context_vars = {}; |
249
|
0
|
|
|
|
|
|
foreach ( keys %$CONTEXT_VAR_XLATE ) |
250
|
|
|
|
|
|
|
{ |
251
|
0
|
|
|
|
|
|
my $arg = $CONTEXT_VAR_XLATE->{$_}; |
252
|
0
|
0
|
|
|
|
|
$context_vars->{$_} = $args{$arg} if exists $args{$arg}; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
# Build the context for this command based on the (skeletal) global one, doing 3 major |
256
|
|
|
|
|
|
|
# things: adding in new context vars from our `command` definition, validing any |
257
|
|
|
|
|
|
|
# command-specific opts, and processing the control structure (if any). |
258
|
0
|
|
|
|
|
|
my $context = $FLOW->setup_context($context_vars, $optdefs, $CONTROL{$name}); |
259
|
0
|
0
|
|
|
|
|
if ($context->error) # either an opt didn't validate or the control structure had an error |
260
|
|
|
|
|
|
|
{ |
261
|
0
|
|
|
|
|
|
fatal($context->error); |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
else # set global access vars for flows |
264
|
|
|
|
|
|
|
{ |
265
|
0
|
|
|
|
|
|
$FLOW = $context; |
266
|
0
|
|
|
|
|
|
%OPT = $FLOW->opts; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Script args are flow args (switches were already processed by Osprey and validated above). |
270
|
0
|
|
|
|
|
|
$FLOWS{$name}->(@ARGV); |
271
|
0
|
|
|
|
|
|
}; |
272
|
0
|
0
|
|
|
|
|
$name eq ':DEFAULT' ? _install_base_command($subcmd, $optdefs) : _install_subcommand($name => $subcmd, $optdefs); |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
|
276
|
0
|
|
|
0
|
1
|
|
sub base_command { unshift @_, ':DEFAULT'; &command } |
|
0
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
|
280
|
0
|
|
|
0
|
1
|
|
sub arg ($) { arg => shift } |
281
|
|
|
|
|
|
|
|
282
|
0
|
|
|
0
|
1
|
|
sub opt (@) { opt => @_ } |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
sub must_be ($) |
285
|
|
|
|
|
|
|
{ |
286
|
0
|
|
|
0
|
1
|
|
my $type = shift; |
287
|
|
|
|
|
|
|
# slightly cheating, but this private method handles the widest range of things that might be a |
288
|
|
|
|
|
|
|
# type (including if it's already a Type::Tiny to start with) |
289
|
0
|
|
|
|
|
|
my ($t) = eval { Type::Tiny::_loose_to_TypeTiny($type) }; |
|
0
|
|
|
|
|
|
|
290
|
0
|
0
|
|
|
|
|
fatal("not a valid type [$type]") unless defined $t; |
291
|
0
|
|
0
|
0
|
|
|
$t->create_child_type(message => sub { ($_ // '<>') . " is not a " . $t->name }); |
|
0
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
sub one_of ($) |
295
|
|
|
|
|
|
|
{ |
296
|
0
|
|
|
0
|
1
|
|
require Type::Tiny::Enum; |
297
|
0
|
|
|
|
|
|
my $v = shift; |
298
|
0
|
|
0
|
0
|
|
|
Type::Tiny::Enum->new( values => $v, message => sub { ($_ // '<>') . " must be one of: " . join(', ', @$v) }); |
|
0
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
0
|
0
|
|
0
|
1
|
|
sub also { properties => { map { s/^-// ? ($_ => 1) : $_ } @_ } } |
|
0
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
|
305
|
0
|
|
|
0
|
1
|
|
sub log_to ($) { log_to => shift } |
306
|
|
|
|
|
|
|
|
307
|
0
|
|
|
0
|
1
|
|
sub control_via ($) { control => shift } |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
|
311
|
0
|
|
|
0
|
1
|
|
sub flow (&) { flow => shift } |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
############## |
315
|
|
|
|
|
|
|
# DIRECTIVES # |
316
|
|
|
|
|
|
|
############## |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub verify (&$) |
320
|
|
|
|
|
|
|
{ |
321
|
0
|
|
|
0
|
1
|
|
my ($check, $fail_msg) = @_; |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
# we need to ensure verify code gets executed no matter what |
324
|
0
|
|
|
|
|
|
my $save_runmode = $FLOW->runmode; |
325
|
0
|
|
|
|
|
|
$FLOW->_set_runmode('VERIFY'); |
326
|
0
|
0
|
|
|
|
|
unless ( $check->() ) |
327
|
|
|
|
|
|
|
{ |
328
|
|
|
|
|
|
|
# Doing the error this way is a bit roundabout, but it guarantees failure here won't create |
329
|
|
|
|
|
|
|
# a statusfile that might keep our next run from happening due to `unless_clean_exit`. |
330
|
0
|
|
|
|
|
|
$FLOW->start_conditions_not_met("pre-flow check failed [$fail_msg]"); |
331
|
0
|
|
|
|
|
|
fatal($FLOW->error); |
332
|
|
|
|
|
|
|
} |
333
|
0
|
|
|
|
|
|
$FLOW->_set_runmode($save_runmode); |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# figure out whether a directive should be executed, based on runmode |
338
|
|
|
|
|
|
|
sub _should_doit |
339
|
|
|
|
|
|
|
{ |
340
|
0
|
|
|
0
|
|
|
my ($dtype, $action) = @_; |
341
|
|
|
|
|
|
|
|
342
|
0
|
0
|
|
|
|
|
if ( $FLOW->runmode eq 'NOACTION' ) |
|
|
0
|
|
|
|
|
|
343
|
|
|
|
|
|
|
{ |
344
|
0
|
|
|
|
|
|
my $msg = "would run"; |
345
|
0
|
0
|
|
|
|
|
$msg .= $dtype eq 'shell command' ? ':' : " $dtype"; |
346
|
0
|
0
|
|
|
|
|
$msg .= " $action" if $action; |
347
|
0
|
|
|
|
|
|
say $msg; |
348
|
0
|
|
|
|
|
|
return 0; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
elsif ( $FLOW->runmode eq 'ASKACTION' ) |
351
|
|
|
|
|
|
|
{ |
352
|
0
|
|
|
|
|
|
my $prompt = "run $dtype?"; |
353
|
0
|
0
|
|
|
|
|
$prompt .= " $action" if $action; |
354
|
0
|
|
|
|
|
|
$prompt .= " [y/N] "; |
355
|
0
|
|
|
|
|
|
print $prompt; |
356
|
0
|
|
|
|
|
|
return =~ /^y/i; |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
# other run modes mean just do it |
359
|
0
|
|
|
|
|
|
return 1; |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
sub SH (@) |
365
|
|
|
|
|
|
|
{ |
366
|
0
|
|
|
0
|
1
|
|
my @cmd = @_; |
367
|
|
|
|
|
|
|
|
368
|
0
|
0
|
|
|
|
|
return unless _should_doit('shell command', "@cmd"); |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# In the rare case where `--pretend` is set but `runmode` is *not* "NOACTION," don't send our |
371
|
|
|
|
|
|
|
# output to the logfile. |
372
|
0
|
0
|
0
|
|
|
|
push @cmd, ">>$FLOW->{LOGFILE}", "2>&1" if exists $FLOW->{LOGFILE} and not $OPT{pretend}; |
373
|
|
|
|
|
|
|
|
374
|
0
|
|
|
|
|
|
my $exitval = bash @cmd; |
375
|
0
|
0
|
|
|
|
|
if (defined wantarray) # someone cares about our exit value |
376
|
|
|
|
|
|
|
{ |
377
|
0
|
|
|
|
|
|
return $exitval; |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
else # just a straight `SH` directive; die unless clean exit |
380
|
|
|
|
|
|
|
{ |
381
|
0
|
0
|
|
|
|
|
fatal("command [@_] exited non-zero [$exitval]") unless $exitval == 0; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
sub CODE (@) |
388
|
|
|
|
|
|
|
{ |
389
|
0
|
|
|
0
|
1
|
|
my $block = pop; |
390
|
0
|
|
|
|
|
|
my ($name) = @_; |
391
|
|
|
|
|
|
|
|
392
|
0
|
0
|
|
|
|
|
return unless _should_doit('code block', $name ? "[$name]" : ''); |
|
|
0
|
|
|
|
|
|
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
# If we have a logfile, better make sure our code block is printing to it rather than STDOUT, if |
395
|
|
|
|
|
|
|
# it prints anything. |
396
|
0
|
|
|
|
|
|
my $log; |
397
|
0
|
0
|
|
|
|
|
if ( my $logfile = $FLOW->logfile ) |
398
|
|
|
|
|
|
|
{ |
399
|
0
|
|
|
|
|
|
open($log, '>>', $logfile); |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
|
402
|
0
|
|
|
|
|
|
my $retval; |
403
|
|
|
|
|
|
|
eval |
404
|
0
|
|
|
|
|
|
{ |
405
|
|
|
|
|
|
|
# Note that you can't do an `if` block here, because |
406
|
|
|
|
|
|
|
# that would make a separate scope for the `local`. |
407
|
0
|
0
|
|
|
|
|
local *STDOUT = $log if $log; |
408
|
0
|
0
|
|
|
|
|
local *STDERR = $log if $log; |
409
|
0
|
|
|
|
|
|
$retval = $block->(); |
410
|
|
|
|
|
|
|
}; |
411
|
0
|
0
|
0
|
|
|
|
if (not $retval or $@) |
412
|
|
|
|
|
|
|
{ |
413
|
0
|
0
|
|
|
|
|
my $msg = "code block" . ($name ? " [$name]" : ''); |
414
|
0
|
0
|
0
|
|
|
|
$msg .= $@ |
415
|
|
|
|
|
|
|
? " died [" . $@ =~ s/( at \S+ line \d+\.?)\n.*\Z//rs . "]" |
416
|
|
|
|
|
|
|
: " returned false value [" . ($retval // 'undef') . "]"; |
417
|
0
|
|
|
|
|
|
fatal($msg); |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
sub RUN (@) |
424
|
|
|
|
|
|
|
{ |
425
|
0
|
|
|
0
|
1
|
|
my ($flow, @args) = @_; |
426
|
0
|
|
|
|
|
|
$FLOWS{$flow}->(@args); |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
#################### |
431
|
|
|
|
|
|
|
# SUPPORT ROUTINES # |
432
|
|
|
|
|
|
|
#################### |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
sub fatal |
436
|
|
|
|
|
|
|
{ |
437
|
0
|
|
|
0
|
1
|
|
my ($self, $msg) = &_pb_args; |
438
|
0
|
|
0
|
|
|
|
my $me = $FLOW->{ME} // basename($0); |
439
|
0
|
|
|
|
|
|
say STDERR "$me: $msg"; |
440
|
0
|
|
|
|
|
|
$FLOW->raise_error($msg); |
441
|
0
|
|
|
|
|
|
exit 1; |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
#################### |
446
|
|
|
|
|
|
|
# DEFAULT COMMANDS # |
447
|
|
|
|
|
|
|
#################### |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
subcommand help => sub { shift->osprey_help }; |
450
|
|
|
|
|
|
|
subcommand commands => sub |
451
|
|
|
|
|
|
|
{ |
452
|
|
|
|
|
|
|
my $class = shift; |
453
|
|
|
|
|
|
|
my %sc = $class->_osprey_subcommands; |
454
|
|
|
|
|
|
|
say foreach sort keys %sc; |
455
|
|
|
|
|
|
|
}; |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
subcommand info => sub |
458
|
|
|
|
|
|
|
{ |
459
|
|
|
|
|
|
|
my $self = shift; |
460
|
|
|
|
|
|
|
foreach (@_) |
461
|
|
|
|
|
|
|
{ |
462
|
|
|
|
|
|
|
$self->fatal("no such setting [$_]") unless $FLOW->has_var($_); |
463
|
|
|
|
|
|
|
say $FLOW->{$_}; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
}; |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
############## |
469
|
|
|
|
|
|
|
# GO GO GO!! # |
470
|
|
|
|
|
|
|
############## |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
# This is only used when there's a base command (but Osprey needs it regardless). |
474
|
|
|
|
|
|
|
sub run |
475
|
|
|
|
|
|
|
{ |
476
|
0
|
0
|
|
0
|
0
|
|
$BASE_CMD->(@_) if $BASE_CMD; |
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
sub go |
480
|
|
|
|
|
|
|
{ |
481
|
0
|
0
|
0
|
0
|
0
|
|
shift @ARGV and $FLOW->set_debug($1) if @ARGV and $ARGV[0] =~ /^DEBUG=(\d+)$/; |
|
|
|
0
|
|
|
|
|
482
|
|
|
|
|
|
|
|
483
|
0
|
|
|
|
|
|
$CMD = shift->new_with_options; |
484
|
0
|
|
|
|
|
|
$FLOW->connect_to($CMD); # this connects the context to the command |
485
|
|
|
|
|
|
|
|
486
|
0
|
|
|
|
|
|
$CMD->run; |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
1; |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# ABSTRACT: a workflow system made from Perl and bash |
495
|
|
|
|
|
|
|
# COPYRIGHT |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
__END__ |