File Coverage

blib/lib/Commands/Guarded.pm
Criterion Covered Total %
statement 95 111 85.5
branch 25 40 62.5
condition 1 3 33.3
subroutine 22 27 81.4
pod 9 11 81.8
total 152 192 79.1


line stmt bran cond sub pod time code
1             package Commands::Guarded;
2              
3 8     8   60817 use 5.006;
  8         30  
  8         318  
4 8     8   46 use strict;
  8         16  
  8         352  
5 8     8   194 use warnings;
  8         21  
  8         269  
6 8     8   39 use Carp;
  8         15  
  8         671  
7 8     8   8002 use IO::File;
  8         98880  
  8         3317  
8              
9             require Exporter;
10              
11             our @ISA = qw(Exporter);
12              
13             our %EXPORT_TAGS = (
14             utils => [ qw(
15             fgrep
16             readf
17             appendf
18             writef
19             ) ],
20             step => [qw(
21             step
22             ensure
23             using
24             sanity
25             rollback
26             )],
27             other => [qw(
28             verbose
29             clear_rollbacks
30             )]
31             );
32              
33             $EXPORT_TAGS{default} = $EXPORT_TAGS{step};
34              
35             foreach (keys %EXPORT_TAGS) {
36             push @{$EXPORT_TAGS{'all'}}, @{$EXPORT_TAGS{$_}}
37             }
38              
39             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} });
40              
41             our @EXPORT = ( @{ $EXPORT_TAGS{'default'}} );
42              
43             our $VERSION = '1.01';
44              
45             # A constructor that's exported (horrors!) -- everything starts here
46              
47             sub step ( $@ ) {
48 11     11 1 73 my $step = __PACKAGE__->new(@_);
49 11 100       35 unless (defined wantarray) {
50 10         39 $step->do();
51 5         61 return;
52             }
53 1         3 return $step;
54             }
55              
56             # Define blocks
57              
58             my @defined_blocks = qw(
59             ensure
60             using
61             sanity
62             rollback
63             );
64              
65             # Create an exportable subroutine called BLOCK_block for each name above
66             # that blesses the block passed as the appropriate class. Autocreate
67             # the class and make it a subclass of Commands::Guarded::Block.
68              
69             foreach my $block (@defined_blocks) {
70             my $block_block = "$block" . "_block";
71             my $class = "Commands::Guarded::Block::$block_block";
72 8     8   159 no strict 'refs';
  8         23  
  8         12816  
73             @{"${class}::ISA"} = qw(Commands::Guarded::Block);
74             # install the exportable sub
75             *$block = sub ( &;@ ) {
76 24     24   910 my ($block, @rest) = @_;
77 24         103 $block = bless $block, $class;
78 24         98 return ($block, @rest);
79             };
80             # install the accessor method
81 99     99   385 *$block_block = sub { $_[0]->{$block_block} };
82             }
83              
84             # The only method for this class, so we just install it here rather than creating
85             # a separate package file
86              
87             sub Commands::Guarded::Block::add {
88             # Add block to enclosing step
89 24     24   34 my $self = shift;
90 24         137 my ($type) = (ref($self) =~ /.*::(.*)/);
91 24         44 my $step = shift;
92 24         296 $step->{$type} = $self;
93             }
94              
95             # Verbosity on (or off); defaults to env variable or 0
96             my $verbose = exists $ENV{GUARDED_VERBOSE} ? $ENV{GUARDED_VERBOSE} : 0;
97             sub verbose (;$) {
98 34 100   34 1 138 if (@_) {
99 7         20 $verbose = shift;
100             }
101 34         90 $verbose;
102             }
103              
104             sub new {
105 11     11 0 28 my $class = shift;
106 11   33     80 $class = ref($class) || $class;
107 11         35 my ($name, @blocks) = @_;
108 11         42 my $self = bless {
109             name => $name,
110             }, $class;
111 11         28 foreach my $block (@blocks) {
112 24         147 $block->add($self);
113             }
114 11 100       56 if (not exists $self->{using_block}) {
115 2     1   8 $self->{using_block} = sub { 1 };
  1         2  
116             }
117 11 50       40 croak "Missing 'ensure' block for step"
118             unless exists $self->{ensure_block};
119 11         30 return $self;
120             }
121              
122             sub _diag ( @ ) {
123 27 50   27   1276 print STDERR @_ if verbose;
124             }
125              
126             # Rollback handlers
127              
128             our @rollbacks;
129              
130             sub _register_rollback {
131 19     19   32 my $self = shift;
132 19 100       46 if (defined $self->rollback_block) {
133 1         4 push @rollbacks, [$self->rollback_block => \@_];
134             }
135             }
136              
137             sub clear_rollbacks {
138 0     0 1 0 @rollbacks = ();
139             }
140              
141             sub _do_rollbacks () {
142 5     5   546 while (@rollbacks) {
143 1         2 my $rollback = pop @rollbacks;
144 1         1 my $sub = $rollback->[0];
145 1         2 my @args = @{$rollback->[1]};
  1         3  
146 1         9 $sub->(@args);
147             }
148             }
149              
150             sub _fail ( @ ) {
151 5     5   23 _do_rollbacks;
152 5         1140 croak @_;
153             }
154              
155              
156             # The only accessor not dynamically created
157              
158             sub name {
159 32     32 0 53 my $self = shift;
160 32         632 my $name = $self->{name};
161 32 100       711 if (@_) {
162 15         67 $name .= "(@_)";
163             }
164 32         694 $name;
165             }
166              
167             sub _check_sanity {
168 32     32   38 my $self = shift;
169 32 100       70 if (defined $self->sanity_block) {
170 5 100       9 $self->sanity_block->(@_)
171             or _fail "Sanity check for " . $self->name(@_) . " failed";
172             }
173             }
174              
175             sub _do_pre_using {
176 20     20   25 my $self = shift;
177 20         64 $self->_check_sanity(@_);
178 19         56 $self->_register_rollback(@_);
179 19         53 return $self->ensure_block->(@_);
180             }
181              
182             sub do {
183 10     10 1 15 my $self = shift;
184 10 100       33 unless ($self->_do_pre_using(@_)) {
185 7         52 _diag "Doing step " . $self->name(@_) . "\n";
186 7         13 my @returns;
187             # Preserve calling context in case we're being used for return value
188             # (But why would anyone want to do that?)
189 7 50       31 if (wantarray) {
    50          
190 0         0 @returns = $self->using_block->(@_);
191             } elsif (defined wantarray) {
192 0         0 $returns[0] = $self->using_block->(@_);
193             } else {
194 7         24 $self->using_block->(@_);
195             }
196 7         30 $self->_check_sanity(@_);
197 6 100       20 if ($self->ensure_block->(@_)) {
198 3         24 _diag "Step " . $self->name(@_) . " succeeded\n";
199 3         8 return @returns;
200             }
201 3         19 _fail "Step " . $self->name(@_) . " failed";
202             }
203 2         19 _diag "Skipping step " . $self->name . "\n";
204 2         4 return;
205             }
206              
207             sub do_foreach {
208 1     1 1 10 my $self = shift;
209 1         2 my @usings;
210 1         3 foreach my $arg (@_) {
211 10 100       22 unless ($self->_do_pre_using($arg)) {
212 5         23 push @usings, $arg;
213             } else {
214 5         28 _diag "Skipping step " . $self->name($arg) . "\n";
215             }
216             }
217 1         3 foreach my $arg (@usings) {
218 5         19 _diag "Doing step " . $self->name($arg) . "\n";
219 5         10 $self->using_block->($arg);
220 5         17 $self->_check_sanity($arg);
221 5 50       6 if ($self->ensure_block->($arg)) {
222 5         20 _diag "Step " . $self->name($arg) . " succeeded\n";
223             } else {
224 0         0 _fail "Step " . $self->name . " failed";
225             }
226             }
227 1         3 return;
228             }
229              
230             # Useful utilities
231              
232             sub readf ( $ ) {
233 0 0   0 1   my $fh = new IO::File $_[0]
234             or die "Can't open $_[0] for reading: $!\n";
235 0           $fh;
236             }
237              
238             sub writef ( $ ) {
239 0 0   0 1   my $fh = new IO::File ">$_[0]"
240             or die "Can't open $_[0] for writing: $!\n";
241 0           $fh;
242             }
243              
244             sub appendf ( $ ) {
245 0 0   0 1   my $fh = new IO::File ">>$_[0]"
246             or die "Can't open $_[0] for appending: $!\n";
247 0           $fh;
248             }
249              
250             sub fgrep ( $$ ) {
251 0     0 1   my ($re, $fh) = @_;
252 0 0         unless (ref $fh) {
253 0           $fh = readf $fh;
254             }
255 0           while (<$fh>) {
256 0 0         return 1 if /$re/;
257             }
258 0           return 0;
259             }
260              
261             1;
262             __END__