File Coverage

blib/lib/App/Dest.pm
Criterion Covered Total %
statement 388 401 96.7
branch 128 172 74.4
condition 47 64 73.4
subroutine 48 48 100.0
pod 21 21 100.0
total 632 706 89.5


line stmt bran cond sub pod time code
1             package App::Dest;
2             # ABSTRACT: Deployment State Manager
3              
4 4     4   863200 use 5.016;
  4         34  
5 4     4   657 use exact;
  4         41072  
  4         18  
6              
7 4     4   4770 use File::Basename qw( dirname basename );
  4         7  
  4         181  
8 4     4   393 use File::Copy 'copy';
  4         1712  
  4         149  
9 4     4   415 use File::Copy::Recursive qw( dircopy rcopy );
  4         3004  
  4         146  
10 4     4   1372 use File::DirCompare ();
  4         11097  
  4         87  
11 4     4   19 use File::Find 'find';
  4         8  
  4         213  
12 4     4   21 use File::Path qw( mkpath rmtree );
  4         6  
  4         171  
13 4     4   2711 use IPC::Run 'run';
  4         75094  
  4         173  
14 4     4   2379 use Path::Tiny 'path';
  4         34960  
  4         206  
15 4     4   1683 use Text::Diff ();
  4         25981  
  4         24574  
16              
17             our $VERSION = '1.31'; # VERSION
18              
19             sub init {
20 5     5 1 25299 my $self = _new( shift, 'expect_no_root_dir' );
21              
22 4 50       233 mkdir('.dest') or die "Unable to create .dest directory\n";
23 4 50       232 open( my $watch, '>', '.dest/watch' ) or die "Unable to create ~/.dest/watch file\n";
24              
25 4         16 $self = _new();
26              
27 4 50       64 if ( -f 'dest.watch' ) {
28 4 50       113 open( my $watches, '<', 'dest.watch' ) or die "Unable to read dest.watch file\n";
29              
30 4         120 my @watches = map { chomp; $_ } <$watches>;
  4         17  
  4         15  
31              
32 4         7 my @errors;
33 4         11 for my $watch (@watches) {
34             try {
35             $self->add($watch);
36             }
37 4         10 catch {
38             my $e = $_ || $@;
39             push( @errors, $watch . ': ' . $e );
40             };
41             }
42              
43             warn
44             "Created new watch list based on dest.watch file:\n" .
45 4         191 join( "\n", map { ' ' . $_ } @watches ) . "\n" .
46             (
47             (@errors)
48 4 50       18 ? "With the following errors:\n" . join( '', map { ' ' . $_ } @errors )
  0         0  
49             : ''
50             );
51             }
52              
53 4         69 return 0;
54             }
55              
56             sub add {
57 8     8 1 2484 my $self = _new(shift);
58 8 50       17 die "No directory specified; usage: dest add [directory]\n" unless @_;
59              
60 8         20 my @watches = $self->_watch_list;
61             my @adds = map {
62 8         27 my $dir = $_;
  8         12  
63 8 100       85 die "Directory specified does not exist\n" unless ( -d $dir );
64              
65 7         26 my $rel_dir = $self->_rel2root($dir);
66 7 100       1185 die "Directory $dir already added\n" if ( grep { $rel_dir eq $_ } @watches );
  2         10  
67 6         15 $rel_dir;
68             } @_;
69              
70 6 50       27 open( my $watch, '>', $self->_rel2dir('.dest/watch') ) or die "Unable to write ~/.dest/watch file\n";
71 6         81 print $watch $_, "\n" for ( sort @adds, map { $self->_rel2root($_) } @watches );
  1         4  
72 6         173 mkpath("$self->{root_dir}/.dest/$_") for (@adds);
73              
74 6         1251 return 0;
75             }
76              
77             sub rm {
78 1     1 1 621 my $self = _new(shift);
79 1 50       4 die "No directory specified; usage: dest rm [directory]\n" unless @_;
80              
81 1         3 my @watches = $self->_watch_list;
82             my @rms = map {
83 1         4 my $dir = $_;
  1         2  
84 1   50     2 $dir //= '';
85 1         3 $dir =~ s|/$||;
86              
87 1 50       2 die "Directory $dir not currently tracked\n" unless ( grep { $dir eq $_ } @watches );
  1         3  
88              
89 1         3 $self->_rel2root($dir);
90             } @_;
91              
92 1 50       139 open( my $watch_file, '>', $self->_rel2dir('.dest/watch') ) or die "Unable to write ~/.dest/watch file\n";
93 1         4 for my $watch_dir ( map { $self->_rel2root($_) } @watches ) {
  1         3  
94 1 50       131 if ( grep { $watch_dir eq $_ } @rms ) {
  1         4  
95 1         3 rmtree( $self->_rel2dir(".dest/$watch_dir") );
96             }
97             else {
98 0         0 print $watch_file $watch_dir, "\n";
99             }
100             }
101              
102 1         14 return 0;
103             }
104              
105             sub watches {
106 3     3 1 2551 my $self = _new(shift);
107              
108 3         7 my @watches = $self->_watch_list;
109 3 100       52 print join( "\n", @watches ), "\n" if @watches;
110              
111 3         17 return 0;
112             }
113              
114             sub putwatch {
115 1     1 1 579 my $self = _new(shift);
116 1         2 my ($file) = @_;
117 1 50       14 die "File specified does not exist\n" unless ( -f $file );
118              
119 1 50       31 open( my $new_watches, '<', $file ) or die "Unable to read specified file\n";
120              
121 1         24 my @new = map { chomp; $_ } <$new_watches>;
  2         4  
  2         5  
122 1         4 my @old = $self->_watch_list;
123              
124 1         4 for my $old (@old) {
125 1 50       3 next if ( grep { $_ eq $old } @new );
  2         5  
126 0         0 $self->rm($old);
127             }
128 1         3 for my $new (@new) {
129 2 100       2 next if ( grep { $_ eq $new } @old );
  2         4  
130 1         3 $self->add($new);
131             }
132              
133 1         14 return 0;
134             }
135              
136             sub writewatch {
137 1     1 1 591 my $self = _new(shift);
138              
139 1 50       3 copy( $self->_rel2dir('.dest/watch'), $self->_rel2dir('dest.watch') ) or die "$!\n";
140              
141 1         281 return 0;
142             }
143              
144             sub make {
145 2     2 1 2289 my ( $self, $path, $ext ) = @_;
146 2 50       4 die "No name specified; usage: dest make [path]\n" unless ($path);
147              
148 2 100       6 $ext = '.' . $ext if ( defined $ext );
149 2   100     8 $ext //= '';
150              
151             try {
152             mkpath($path);
153             for ( qw( deploy verify revert ) ) {
154             open( my $file, '>', "$path/$_$ext" ) or die;
155             print $file "\n";
156             }
157             }
158 2         3 catch {
159             die "Failed to fully make $path; check permissions or existing files\n";
160             };
161              
162 2         14 $self->expand($path);
163              
164 2         6 return 0;
165             }
166              
167             sub expand {
168 2     2 1 4 my ( $self, $path ) = @_;
169              
170 2         5 print join( ' ', map { <"$path/$_*"> } qw( deploy verify revert ) ), "\n";
  6         275  
171              
172 2         9 return 0;
173             }
174              
175             sub list {
176 2     2 1 6399 my $self = _new(shift);
177 2         5 my ($filter) = @_;
178              
179 2         7 my $tree = $self->_actions_tree($filter);
180 2         7 for my $path ( sort keys %$tree ) {
181 2 50       4 print $path, ( ( @{ $tree->{$path} } ) ? ' actions:' : ' has no actions' ), "\n";
  2         64  
182 2         7 print ' ', $_, "\n" for ( @{ $tree->{$path} } );
  2         51  
183             }
184              
185 2         14 return 0;
186             }
187              
188             sub prereqs {
189 1     1 1 1543 my $self = _new(shift);
190 1         3 my ($filter) = @_;
191              
192 1         2 for my $action ( @{ $self->_prereq_tree($filter)->{actions} } ) {
  1         3  
193 3 100       4 print $action->{action}, ( ( @{ $action->{prereqs} } ) ? ' prereqs:' : ' has no prereqs' ), "\n";
  3         50  
194 3         7 print ' ', $_, "\n" for ( @{ $action->{prereqs} } );
  3         15  
195             }
196              
197 1         7 return 0;
198             }
199              
200             sub status {
201 5     5 1 10070 my $self = _new(shift);
202              
203 5 50       13 if ( -f $self->_rel2dir('dest.watch') ) {
204 5         14 my $diff = Text::Diff::diff( $self->_rel2dir('.dest/watch'), $self->_rel2dir('dest.watch') );
205 5 50       1699 warn "Diff between current watch list and dest.watch file:\n" . $diff . "\n" if ($diff);
206             }
207              
208 5         8 for my $path ( @{ $self->_status_data->{paths} } ) {
  5         17  
209 5         161 print "$path->{state} - $path->{path}\n";
210              
211 5         17 for my $action ( @{ $path->{actions} } ) {
  5         13  
212 12 100       31 unless ( $action->{modified} ) {
213 10 100       115 print ' ' . ( ( $action->{deployed} ) ? '-' : '+' ) . ' ' . $action->{action} . "\n";
214             }
215             else {
216 2         22 print " $action->{action}\n";
217 2         23 print " M $action->{file}\n";
218             }
219             }
220             }
221              
222 5         36 return 0;
223             }
224              
225             sub diff {
226 2     2 1 1471 my $self = _new(shift);
227 2         3 my ($path) = @_;
228              
229 2 100       5 if ( not defined $path ) {
230 1         3 $self->diff($_) for ( $self->_watch_list );
231 1         4 return 0;
232             }
233              
234             File::DirCompare->compare( $self->_rel2dir( '.dest/' . $self->_rel2root($path) ), $path, sub {
235 3     3   2058 my ( $a, $b ) = @_;
236 3   100     9 $a ||= '';
237 3   100     7 $b ||= '';
238              
239 3 100 33     53 return if ( $a =~ /\/dest.wrap$/ or $b =~ /\/dest.wrap$/ or not -f $a or not -f $b );
      66        
      66        
240 1         5 print Text::Diff::diff( $a, $b );
241 1         454 return;
242 1         4 } );
243              
244 1         9 return 0;
245             }
246              
247             sub clean {
248 2     2 1 1422 my $self = _new(shift);
249              
250 2 100       5 if (@_) {
251 1         3 for (@_) {
252 1         4 my $dest = $self->_rel2dir(".dest/$_");
253 1         326 rmtree($dest);
254 1         5 rcopy( $self->_rel2dir($_), $dest );
255             }
256             }
257             else {
258 1         4 for ( map { $self->_rel2root($_) } $self->_watch_list ) {
  1         3  
259 1         147 my $dest = $self->_rel2dir(".dest/$_");
260 1         864 rmtree($dest);
261 1         6 dircopy( $self->_rel2dir($_), $dest );
262             }
263             }
264              
265 2         5882 return 0;
266             }
267              
268             sub preinstall {
269 1     1 1 727 my $self = _new(shift);
270              
271 1 50       4 if (@_) {
272 0         0 for (@_) {
273 0         0 my $dest = $self->_rel2dir(".dest/$_");
274 0         0 rmtree($dest);
275 0         0 mkdir($dest);
276             }
277             }
278             else {
279 1         3 for ( map { $self->_rel2root($_) } $self->_watch_list ) {
  1         4  
280 1         146 my $dest = $self->_rel2dir(".dest/$_");
281 1         848 rmtree($dest);
282 1         49 mkdir($dest);
283             }
284             }
285              
286 1         8 return 0;
287             }
288              
289             sub deploy {
290 5     5 1 20508 my $self = _new(shift);
291 5         19 my ( $dry_run, $action ) = _dry_check(@_);
292 5 50       13 die "File to deploy required; usage: dest deploy file\n" unless ($action);
293              
294 5         11 my $redeploy = delete $self->{redeploy};
295 5         24 my $execute_stack = $self->_build_execute_stack( $action, 'deploy', undef, $redeploy );
296             die "Action already deployed\n" if (
297             not @$execute_stack or
298 5 50 66     38 not scalar( grep { $_->{action} eq $action and $_->{type} eq 'deploy' } @$execute_stack )
  8 100       41  
299             );
300              
301 4 100       9 unless ($dry_run) {
302 3         13 $self->_execute_action($_) for (@$execute_stack);
303             }
304             else {
305 1         6 _dry_run_report($execute_stack);
306             }
307              
308 4         122 return 0;
309             }
310              
311             sub redeploy {
312 1     1 1 1357 my $self = _new(shift);
313 1         9 $self->{redeploy} = 1;
314 1         7 return $self->deploy(@_);
315             }
316              
317             sub revdeploy {
318 1     1 1 9192 my $self = _new(shift);
319 1         10 my ($action) = @_;
320              
321 1         11 $self->revert($action);
322 1         15 return $self->deploy($action);
323             }
324              
325             sub verify {
326 1     1 1 1342 my $self = _new(shift);
327 1         2 my ($action) = @_;
328              
329 1         4 return $self->_execute_action( $self->_build_execute_stack( $action, 'verify' )->[0] );
330             }
331              
332             sub revert {
333 3     3 1 5803 my $self = _new(shift);
334 3         14 my ( $dry_run, $action ) = _dry_check(@_);
335 3 50       15 die "File to revert required; usage: dest revert file\n" unless ($action);
336              
337 3         24 my $execute_stack = $self->_build_execute_stack( $action, 'revert' );
338             die "Action not deployed\n" if (
339             not @$execute_stack or
340 3 100 33     26 not scalar( grep { $_->{action} eq $action and $_->{type} eq 'revert' } @$execute_stack )
  7 50       42  
341             );
342              
343 3 100       9 unless ($dry_run) {
344 2         14 $self->_execute_action($_) for (@$execute_stack);
345             }
346             else {
347 1         4 _dry_run_report($execute_stack);
348             }
349              
350 3         61 return 0;
351             }
352              
353             sub update {
354 3     3 1 5830 my $self = _new(shift);
355 3         17 my ( $dry_run, @incs ) = _dry_check(@_);
356              
357 3         6 my $seen_action = {};
358 3         6 my $execute_stack;
359              
360 3         9 for (
361             sort {
362             ( $b->{modified} || 0 ) <=> ( $a->{modified} || 0 ) ||
363             $a->{action} cmp $b->{action}
364 19 50 100     74 }
      50        
365 15         40 grep { not $_->{matches} }
366 3         16 values %{ $self->_status_data->{actions} }
367             ) {
368 2         7 push( @$execute_stack, @{ $self->_build_execute_stack( $_->{action}, 'revert' ) } ) if (
369             $_->{modified} or $_->{deployed}
370 14 100 100     65 );
371 14         23 push( @$execute_stack, @{ $self->_build_execute_stack( $_->{action}, 'deploy', $seen_action ) } );
  14         43  
372             }
373              
374             $execute_stack = [
375             grep {
376 3 100       17 my $execution = $_;
  8         7  
377 8         6 my $match = 0;
378              
379 8         9 for (@incs) {
380 8 50       21 if ( index( $execution->{file}, $_ ) > -1 ) {
381 8         11 $match = 1;
382 8         9 last;
383             }
384             }
385              
386 8         11 $match;
387             }
388             @$execute_stack
389             ] if (@incs);
390              
391 3 100       8 unless ($dry_run) {
392 2         14 $self->_execute_action($_) for (@$execute_stack);
393             }
394             else {
395 1         3 _dry_run_report($execute_stack);
396             }
397              
398 3         161 return 0;
399             }
400              
401             sub version {
402 1   50 1 1 1299 print 'dest version ', $__PACKAGE__::VERSION || 0, "\n";
403 1         5 return 0;
404             }
405              
406             sub _new {
407 50     50   122 my ( $self, $expect_no_root_dir ) = @_;
408              
409 50 100       184 if ( not ref $self ) {
410 41         106 $self = bless( {}, __PACKAGE__ );
411              
412 41         200 $self->{root_dir} = Path::Tiny->cwd;
413              
414 41         1536 while ( not $self->{root_dir}->child('.dest')->is_dir ) {
415 28 100       1168 if ( $self->{root_dir}->is_rootdir ) {
416 4         67 $self->{root_dir} = '';
417 4         10 last;
418             }
419 24         452 $self->{root_dir} = $self->{root_dir}->parent;
420 24         772 $self->{dir_depth}++;
421             }
422              
423 41 100       1553 if ( $expect_no_root_dir ) {
424 5 100       21 die "Project already initialized\n" if $self->{root_dir};
425             }
426             else {
427 36 50       155 die "Project not initialized\n" unless $self->{root_dir};
428             }
429             }
430              
431 49         97 return $self;
432             }
433              
434             sub _dry_check {
435 11     11   26 my @clean = grep { $_ ne '-d' } @_;
  12         53  
436 11 100       59 return ( @clean != @_ ) ? 1 : 0, @clean;
437             }
438              
439             sub _rel2root {
440 131     131   402 my ( $self, $dir ) = @_;
441 131   100     748 my $path = path( $dir || '.' );
442              
443             try {
444             $path = $path->realpath;
445             }
446 131         4337 catch {
447             $path = $path->absolute;
448             };
449              
450 131         20716 return $path->relative( $self->{root_dir} )->stringify;
451             }
452              
453             sub _rel2dir {
454 449     449   16244 my ( $self, $dir ) = @_;
455 449   50     7119 return ( '../' x ( $self->{dir_depth} || 0 ) ) . ( $dir || '.' );
      50        
456             }
457              
458             sub _watch_list {
459 110     110   145 my ($self) = @_;
460 110 50       413 open( my $watch, '<', $self->_rel2dir('.dest/watch') ) or die "Unable to read ~/.dest/watch file\n";
461 110         1847 return sort map { chomp; $self->_rel2dir($_) } <$watch>;
  105         261  
  105         231  
462             }
463              
464             sub _actions_tree {
465 61     61   97 my ( $self, $filter ) = @_;
466              
467 61         67 my $tree;
468 61         114 for my $path ( $self->_watch_list ) {
469 61         130 my @actions;
470              
471             find( {
472             follow => 1,
473             no_chdir => 1,
474             wanted => sub {
475 1160 100   1160   31743 return unless ( m|/deploy(?:\.[^\/]+)?| );
476 261         898 ( my $action = $_ ) =~ s|/deploy(?:\.[^\/]+)?||;
477              
478 261 50 66     5652 push( @actions, $action ) if (
479             not defined $filter or
480             index( $action, $filter ) > -1
481             );
482             },
483 61         5749 }, $path );
484              
485 61         516 $tree->{$path} = [ sort @actions ];
486             }
487              
488 61         123 return $tree;
489             }
490              
491             sub _prereq_tree {
492 26     26   55 my ( $self, $filter ) = @_;
493 26         56 my $tree = $self->_actions_tree($filter);
494              
495 26         37 my @actions;
496 26         100 for my $path ( sort keys %$tree ) {
497 26         39 for my $action ( @{ $tree->{$path} } ) {
  26         61  
498 114         4438 my ($file) = <"$action/deploy*">;
499 114 50       2978 open( my $content, '<', $file ) or die "Unable to read $file\n";
500              
501             my $prereqs = [
502 59         134 map { $self->_rel2dir($_) }
503 59         103 grep { defined }
504 59 50       297 map { /dest\.prereq\b[\s:=-]+(.+?)\s*$/; $1 || undef }
  59         219  
505 114         1610 grep { /dest\.prereq/ } <$content>
  187         612  
506             ];
507              
508 114         303 $action = {
509             action => $action,
510             prereqs => $prereqs,
511             };
512              
513 114         1129 push( @actions, $action );
514             }
515             }
516              
517 26         217 return { tree => $tree, actions => \@actions };
518             }
519              
520             sub _status_data {
521 33     33   63 my ($self) = @_;
522              
523 33         38 my $actions;
524 33         108 my $tree = $self->_actions_tree;
525 33         105 for my $path ( keys %$tree ) {
526 33         45 for my $action ( @{ $tree->{$path} } ) {
  33         87  
527 141         353 $actions->{$action} = {
528             action => $action,
529             deployed => 1,
530             matches => 1,
531             };
532             }
533             }
534              
535 33         70 my @paths;
536 33         76 for my $path ( $self->_watch_list ) {
537 33         85 my $printed_path = 0;
538 33         37 my $data;
539              
540             try {
541             File::DirCompare->compare(
542             $self->_rel2dir( '.dest/' . $self->_rel2root($path) ),
543             $path,
544             sub {
545 157     157   54371 my ( $a, $b ) = @_;
546 157 100 66     747 return if ( $a and $a =~ /\/dest.wrap$/ or $b and $b =~ /\/dest.wrap$/ );
      100        
      66        
547 128 100       273 $data->{state} = 'diff' unless ( $printed_path++ );
548              
549 128 100 33     243 if ( not $b ) {
    100          
    50          
550             push(
551 18         21 @{ $data->{actions} },
  18         69  
552             {
553             action => $self->_rel2dir( substr( $self->_rel2root($a), 6 ) ),
554             deployed => 1,
555             },
556             );
557             }
558             elsif ( not $a ) {
559             push(
560 93         86 @{ $data->{actions} },
  93         217  
561             {
562             action => $b,
563             deployed => 0,
564             },
565             );
566             }
567             elsif ( $a and $b ) {
568 17         146 ( my $action = $b ) =~ s,/(?:deploy|verify|revert)(?:\.[^\/]+)?$,,;
569             push(
570 17         26 @{ $data->{actions} },
  17         83  
571             {
572             action => $action,
573             modified => 1,
574             file => $b,
575             },
576             );
577             }
578              
579 128         345 return;
580             },
581             )
582             }
583             catch {
584             my $e = $_ || $@;
585             $data->{state} = '?' if ( $e =~ /Not a directory/ );
586             }
587             finally {
588             $data->{state} = 'ok' unless $printed_path;
589 33         183 };
590              
591 33         246 $data->{path} = $path;
592 33         59 push( @paths, $data );
593              
594 33 100       102 if ( ref $data->{actions} eq 'ARRAY' ) {
595 31         42 $actions->{ $_->{action} } = $_ for ( @{ $data->{actions} } );
  31         198  
596             }
597             }
598              
599 33         190 return { paths => \@paths, actions => $actions };
600             }
601              
602             sub _build_execute_stack {
603 25     25   83 my ( $self, $name, $type, $seen_action, $redeploy ) = @_;
604              
605 25         76 my @actions_stack = ($name);
606 25         39 my $prereqs = { map { $_->{action} => $_->{prereqs} } @{ $self->_prereq_tree->{actions} } };
  111         235  
  25         79  
607 25         111 my $state = $self->_status_data->{actions};
608              
609 25 100       88 if ( $type eq 'revert' ) {
610 5         9 my $postreqs;
611 5         30 for my $action ( keys %$prereqs ) {
612 21         25 push( @{ $postreqs->{$_} }, $action ) for ( @{ $prereqs->{$action} } );
  21         48  
  8         60  
613             }
614 5         17 $prereqs = $postreqs;
615             }
616              
617 25         59 my ( @execute_stack, $wraps );
618 25   100     96 $seen_action //= {};
619              
620 25         57 while (@actions_stack) {
621 39         58 my $action = shift @actions_stack;
622 39 100       111 next if ( $seen_action->{$action}++ );
623 30 100       45 push( @actions_stack, @{ $prereqs->{$action} || [] } );
  30         95  
624              
625 30 100       80 my $location = ( $type ne 'revert' )
626             ? $action
627             : $self->_rel2dir( '.dest/' . $self->_rel2root($action) );
628              
629 30         1348 my $file = ( <"$location/$type*"> )[0];
630              
631 30 50       157 unless ( exists $wraps->{$action} ) {
632 30         55 $wraps->{$action} = undef;
633 30         80 my @nodes = split( '/', $self->_rel2root($file) );
634 30         3870 pop @nodes;
635 30 100 100     133 shift @nodes if ( defined $nodes[0] and $nodes[0] eq '.dest' );
636 30         59 while (@nodes) {
637 56         164 my $path = $self->_rel2dir( join( '/', @nodes ) . '/dest.wrap' );
638 56 100       556 if ( -f $path ) {
639 28         80 $wraps->{$action} = $path;
640 28         56 last;
641             }
642 28         87 pop @nodes;
643             }
644             }
645              
646             my $add = sub {
647 27 50   27   50 die "Action file does not exist for: $type $action\n" unless ($file);
648              
649 27 50       63 my $executable = ( $wraps->{$action} ) ? $wraps->{$action} : $file;
650 27 50       255 die 'Execute permission denied for: ' . $executable . "\n" unless ( -x $executable );
651              
652             unshift(
653             @execute_stack,
654             {
655             type => $type,
656             action => $action,
657             file => $file,
658 27         158 wrap => $wraps->{$action},
659             },
660             );
661              
662 27 100       118 if ( $type eq 'deploy' ) {
663 17         646 my $verify_file = ( <"$action/verify*"> )[0];
664 17 50       54 die "Action file does not exist for: verify $action\n" unless ($verify_file);
665              
666             splice(
667             @execute_stack,
668             1,
669             0,
670             {
671             type => 'verify',
672             action => $action,
673             file => $verify_file,
674 17         172 wrap => $wraps->{$action},
675             },
676             );
677             }
678 30         165 };
679              
680 30 100       80 if ( $type eq 'deploy' ) {
    100          
    50          
681 19 100 100     103 $add->() unless ( $state->{$action}{deployed} and not $redeploy );
682             }
683             elsif ( $type eq 'revert' ) {
684 10 100 100     61 $add->() if ( $state->{$action}{deployed} or $state->{$action}{modified} );
685             }
686             elsif ( $type eq 'verify' ) {
687 1         3 $add->();
688             }
689             }
690              
691 25         174 return \@execute_stack;
692             }
693              
694             sub _dry_run_report {
695 3     3   6 my ($execute_stack) = @_;
696 3 50       266 print '' . ( ( $_->{wrap} ) ? $_->{wrap} . ' ' : '' ) . $_->{file}, "\n" for (@$execute_stack);
697             }
698              
699             sub _execute_action {
700 27     27   101 my ( $self, $input ) = @_;
701 27         192 my ( $type, $wrap, $action, $file, $location ) = @$input{ qw( type wrap action file location ) };
702              
703 27         54 my ( $out, $err, $died );
704             my $run = sub {
705             try {
706             run(
707             [ ( grep { defined } $wrap ), $file, $type ],
708             \undef, \$out, \$err,
709             ) or $died = 1;
710             }
711 27     27   63 catch {
712             $err = $_ || $@;
713             };
714              
715 27 50       293665 if ($err) {
716 0         0 ( my $err_str = $err ) =~ s/\s*at\s+.*$//;
717 0         0 chomp($err_str);
718 0 0       0 if ($died) {
719 0         0 die "Failed to execute $file: $err_str\n";
720             }
721             else {
722 0         0 warn "Warnings from executed $file: $err_str\n";
723             }
724             }
725 27         215 };
726              
727 27 100       100 if ( $type eq 'verify' ) {
728 12         30 $run->();
729 12         78 chomp($out);
730 12 50       51 die "$err\n" if ($err);
731              
732 12 50       42 if ($out) {
733 12         435 print "ok - verify: $action\n";
734             }
735             else {
736 0         0 die "not ok - verify: $action\n";
737             }
738             }
739             else {
740 15         414 print "begin - $type: $action\n";
741 15         67 $run->();
742 15         590 print "ok - $type: $action\n";
743             }
744              
745 27         466 my $dest_copy = $self->_rel2dir( '.dest/' . $self->_rel2root($action) );
746 27 100       4576 rmtree($dest_copy) unless ( $type eq 'verify' );
747 27 100       316 dircopy( $action, $dest_copy ) if ( $type eq 'deploy' );
748              
749 27         24936 return 0;
750             }
751              
752             1;
753              
754             __END__