File Coverage

lib/Class/Action.pm
Criterion Covered Total %
statement 192 247 77.7
branch 91 128 71.0
condition 30 64 46.8
subroutine 22 35 62.8
pod 33 33 100.0
total 368 507 72.5


line stmt bran cond sub pod time code
1             package Class::Action;
2              
3 7     7   251794 use warnings;
  7         19  
  7         254  
4 7     7   38 use strict;
  7         13  
  7         23024  
5              
6             $Class::Action::VERSION = '0.4';
7              
8             sub new {
9 6     6 1 18561 my ( $class, $args_hr ) = @_;
10              
11 6 100 66     155 my $self = bless {
    100 100        
    50          
12             'auto_rollback' => exists $args_hr->{'auto_rollback'} && defined $args_hr->{'auto_rollback'} ? $args_hr->{'auto_rollback'} : 1,
13             'last_errstr' => undef,
14             'current_step' => undef, # N == index, undef == not started, '' == finished
15             'step_stack' => ref $args_hr->{'step_stack'} eq 'ARRAY' ? $args_hr->{'step_stack'} : [],
16             'exec_stack' => [],
17             '_needs_reset' => 0,
18             'global_data' => ref $args_hr->{'global_data'} eq 'HASH' ? $args_hr->{'global_data'} : {},
19             'enable_cwd' => $args_hr->{'enable_cwd'} || 0,
20             }, $class;
21              
22             # for my $name qw(set_steps_from_class append_steps_from_class prepend_steps_from_class) {
23             # if ( exists $args_hr->{$name} ) {
24             # $self->$name( ref $args_hr->{$name} eq 'ARRAY' ? @{ $args_hr->{$name} } : $args_hr->{$name} );
25             # }
26             # }
27 6 100       42 if ( exists $args_hr->{'set_steps_from_class'} ) {
28 2 100       22 $self->set_steps_from_class( ref $args_hr->{'set_steps_from_class'} eq 'ARRAY' ? @{ $args_hr->{'set_steps_from_class'} } : $args_hr->{'set_steps_from_class'} );
  1         6  
29             }
30              
31 6         21 return $self;
32             }
33              
34             sub set_steps {
35 11     11 1 401 my ( $self, @steps ) = @_;
36 11 50 33     121 @{ $self->{'step_stack'} } = @steps == 1 && ref $steps[0] eq 'ARRAY' ? @{ $steps[0] } : @steps;
  11         139  
  11         28  
37 11         24 return @{ $self->{'step_stack'} };
  11         35  
38             }
39              
40             sub set_steps_from_class {
41 11     11 1 14844 my ( $self, $class, @args ) = @_;
42              
43 11 50 33     147 if ( !ref($class) && $class =~ m/\A[a-zA-Z0-9_]+(?:\:\:[a-zA-Z0-9_]+)*\z/ ) {
44 11         951 eval qq{ require $class; 1 };
45             }
46              
47 11 50       163 if ( $class->can('get_class_action_steps') ) {
48 11         52 $self->set_steps( $class->get_class_action_steps(@args) );
49             }
50             else {
51 0         0 $self->set_steps();
52 0         0 require Carp;
53 0         0 Carp::carp("$class does not implement get_class_action_steps()");
54 0         0 return;
55             }
56              
57 11         32 return @{ $self->{'step_stack'} };
  11         35  
58             }
59              
60             sub get_steps {
61 0     0 1 0 my ($self) = @_;
62 0         0 return @{ $self->{'step_stack'} };
  0         0  
63             }
64              
65             sub append_steps {
66 0     0 1 0 my ( $self, @steps ) = @_;
67 0 0 0     0 push @{ $self->{'step_stack'} }, @steps == 1 && ref $steps[0] eq 'ARRAY' ? @{ $steps[0] } : @steps;
  0         0  
  0         0  
68 0         0 return @{ $self->{'step_stack'} };
  0         0  
69             }
70              
71             sub append_steps_from_class {
72 0     0 1 0 my ( $self, $class, @args ) = @_;
73              
74 0 0 0     0 if ( !ref($class) && $class =~ m/\A[a-zA-Z0-9_]+(?:\:\:[a-zA-Z0-9_]+)*\z/ ) {
75 0         0 eval qq{ require $class; 1 };
76             }
77              
78 0 0       0 if ( $class->can('get_class_action_steps') ) {
79 0         0 $self->append_steps( $class->get_class_action_steps(@args) );
80             }
81             else {
82 0         0 require Carp;
83 0         0 Carp::carp("$class does not implement get_class_action_steps()");
84 0         0 return;
85             }
86              
87 0         0 return @{ $self->{'step_stack'} };
  0         0  
88             }
89              
90             sub prepend_steps {
91 0     0 1 0 my ( $self, @steps ) = @_;
92 0 0 0     0 unshift @{ $self->{'step_stack'} }, @steps == 1 && ref $steps[0] eq 'ARRAY' ? @{ $steps[0] } : @steps;
  0         0  
  0         0  
93 0         0 return @{ $self->{'step_stack'} };
  0         0  
94             }
95              
96             sub prepend_steps_from_class {
97 0     0 1 0 my ( $self, $class, @args ) = @_;
98              
99 0 0 0     0 if ( !ref($class) && $class =~ m/\A[a-zA-Z0-9_]+(?:\:\:[a-zA-Z0-9_]+)*\z/ ) {
100 0         0 eval qq{ require $class; 1 };
101             }
102              
103 0 0       0 if ( $class->can('get_class_action_steps') ) {
104 0         0 $self->prepend_steps( $class->get_class_action_steps(@args) );
105             }
106             else {
107 0         0 require Carp;
108 0         0 Carp::carp("$class does not implement get_class_action_steps()");
109 0         0 return;
110             }
111              
112 0         0 return @{ $self->{'step_stack'} };
  0         0  
113             }
114              
115             sub clone {
116 1     1 1 2649 my ($self) = @_;
117 1         4 my $class = ref($self);
118 1 50       5 return if !$class;
119              
120 1         2 my %copy = %{$self}; # copy data
  1         11  
121 1         3 my @step_list;
122              
123             # get your own fresh stack
124 1         4 for my $step ( @{ $copy{'step_stack'} } ) {
  1         2  
125 3         28 push @step_list, $step->clone_obj();
126             }
127 1         7 $copy{'step_stack'} = \@step_list;
128 1         3 $copy{'global_data'} = {};
129 1         3 $copy{'exec_stack'} = [];
130              
131 1         4 my $clone = bless \%copy, $class;
132 1         4 return $clone->reset; # reset the internal state so that it is fresh
133             }
134              
135             # sub commit {
136             # my ($self, @step_args) = @_;
137             # local $self->{'auto_rollback'} = 1;
138             # return $self->execute(@step_args);
139             # }
140              
141             sub reset {
142 14     14 1 15139 my ($self) = @_;
143              
144 14         29 for my $step ( @{ $self->{'step_stack'} } ) {
  14         55  
145 46         423 $step->reset_obj_state();
146             }
147              
148 14         127 delete $self->{'starting_cwd'};
149 14         42 delete $self->{'_execute'};
150 14         27 delete $self->{'_rollback'};
151 14         33 delete $self->{'_undo'};
152              
153 14         37 $self->{'current_step'} = undef;
154 14         29 $self->{'last_errstr'} = undef;
155 14         40 $self->{'_needs_reset'} = 0;
156              
157 14         42 %{ $self->{'global_data'} } = ();
  14         43  
158 14         28 @{ $self->{'exec_stack'} } = ();
  14         103  
159              
160 14         37 return $self;
161             }
162              
163             sub execute {
164 15     15 1 539 my ( $self, @step_args ) = @_;
165              
166 15 100 100     110 $self->reset() if exists $self->{'_execute'} && !$self->{'_execute'}; # we've been successfully executed so reset and go again
167 15 100       86 $self->reset() if $self->{'_needs_reset'}++; # called when in "examine results" state
168 15 50 33     98 return if $self->{'_execute'} || $self->{'_rollback'}; # execute() called after failed execute() or after failed rollback()
169              
170 15 100       50 $self->set_starting_cwd() if $self->{'enable_cwd'};
171              
172 15         35 $self->{'_execute'}++;
173 15         40 my $execute_failed = 0;
174              
175 15         24 my $step; # more memory efficient than while my $var
176             STEP:
177 15         88 while ( $step = $self->next_step() ) {
178 55         169 my $ref = ref($step);
179 55 100       162 if (!$ref) {
    100          
180 9         71 $step = $step->new(@step_args);
181             }
182             elsif ($ref eq 'ARRAY') {
183 1         3 $step = $step->[0]->new( @{$step}[ 1 .. scalar(@{$step}) - 1 ], \@step_args );
  1         10  
  1         3  
184             }
185            
186 55         166 delete $step->{'last_errstr'};
187              
188 55 100       214 if ( !$step->execute( $self->{'global_data'}, @step_args ) ) {
189 10 100       106 if ( $step->retry_execute( $self->{'global_data'}, @step_args ) ) {
190 5 50       69 $self->{'last_errstr'} = $step->{'last_errstr'} if exists $step->{'last_errstr'};
191              
192 5   33     8 push @{ $self->{'exec_stack'} }, { 'errstr' => $step->{'last_errstr'}, 'type' => 'execute', 'step' => ( $step->state || ref($step) ), 'ns' => ref($step), 'status' => undef };
  5         23  
193 5         51 $step->exec_stack_runtime_handler( $self->{'exec_stack'}->[-1] );
194              
195 5         15 redo STEP;
196             }
197             else {
198 5 50       49 $self->{'last_errstr'} = $step->{'last_errstr'} if exists $step->{'last_errstr'};
199              
200 5   33     7 push @{ $self->{'exec_stack'} }, { 'errstr' => $step->{'last_errstr'}, 'type' => 'execute', 'step' => ( $step->state || ref($step) ), 'ns' => ref($step), 'status' => 0 };
  5         19  
201 5         57 $step->exec_stack_runtime_handler( $self->{'exec_stack'}->[-1] );
202              
203 5         20 $step->clean_failed_execute( $self->{'global_data'}, @step_args );
204 5         42 $step->reset_obj_state();
205 5         46 $execute_failed++;
206              
207 5         11 last STEP;
208             }
209             }
210             else {
211 45 50       3988 $self->{'last_errstr'} = $step->{'last_errstr'} if exists $step->{'last_errstr'};
212              
213 45   66     57 push @{ $self->{'exec_stack'} }, { 'errstr' => $step->{'last_errstr'}, 'type' => 'execute', 'step' => ( $step->state || ref($step) ), 'ns' => ref($step), 'status' => 1 };
  45         216  
214 45         569 $step->exec_stack_runtime_handler( $self->{'exec_stack'}->[-1] );
215             }
216             }
217              
218 15 100       74 if ($execute_failed) {
219 5 100       58 $self->rollback(@step_args) if $self->{'auto_rollback'};
220 5         27 return;
221             }
222              
223 10         21 $self->{'_needs_reset'}--;
224 10         18 $self->{'_execute'}--;
225 10         170 return 1;
226             }
227              
228             sub rollback {
229 11     11 1 2431 my ( $self, @step_args ) = @_;
230 11 100       37 if ( !$self->{'__rollback_is_undo'} ) {
231 9 100 33     58 return if !exists $self->{'_execute'} || !$self->{'_execute'}; # rollback() called before execute() or after a successful execute()
232 7 100 100     41 return if exists $self->{'_rollback'} && !$self->{'_rollback'}; # rollback() called after successful rollback()
233 6 100       26 return if $self->{'_rollback'}++; # rollback() called after failed rollback()
234             }
235              
236 7         14 my $rollback_failed = 0;
237              
238 7         12 my $step; # more memory efficient than while my $var
239             UNDO:
240 7         33 while ( $step = $self->prev_step() ) {
241 23         58 my $ref = ref($step);
242 23 50       67 if (!$ref) {
    50          
243 0         0 $step = $step->new(@step_args);
244             }
245             elsif ($ref eq 'ARRAY') {
246 0         0 $step = $step->[0]->new( @{$step}[ 1 .. scalar(@{$step}) - 1 ], \@step_args );
  0         0  
  0         0  
247             }
248            
249 23         39 delete $step->{'last_errstr'};
250              
251 23 100       93 if ( !$step->undo( $self->{'global_data'}, @step_args ) ) {
252 6 100       99 if ( $step->retry_undo( $self->{'global_data'}, @step_args ) ) {
253 3 50       41 $self->{'last_errstr'} = $step->{'last_errstr'} if exists $step->{'last_errstr'};
254              
255 3 50 33     6 push @{ $self->{'exec_stack'} },
  3         15  
256             {
257             'errstr' => $step->{'last_errstr'},
258             'type' => ( $self->{'_rollback_is_undo'} ? 'undo' : 'rollback' ),
259             'step' => ( $step->state || ref($step) ),
260             'ns' => ref($step),
261             'status' => undef
262             };
263 3         39 $step->exec_stack_runtime_handler( $self->{'exec_stack'}->[-1] );
264              
265 3         7 redo UNDO;
266             }
267             else {
268 3 50       32 $self->{'last_errstr'} = $step->{'last_errstr'} if exists $step->{'last_errstr'};
269              
270 3 50 33     6 push @{ $self->{'exec_stack'} }, { 'errstr' => $step->{'last_errstr'}, 'type' => ( $self->{'_rollback_is_undo'} ? 'undo' : 'rollback' ), 'step' => ( $step->state || ref($step) ), 'ns' => ref($step), 'status' => 0 };
  3         19  
271 3         33 $step->exec_stack_runtime_handler( $self->{'exec_stack'}->[-1] );
272              
273 3         10 $step->clean_failed_undo( $self->{'global_data'}, @step_args );
274 3         77 $step->reset_obj_state();
275 3         25 $rollback_failed++;
276              
277 3         7 last UNDO;
278             }
279             }
280             else {
281 17 50       144 $self->{'last_errstr'} = $step->{'last_errstr'} if exists $step->{'last_errstr'};
282              
283 17 50 33     20 push @{ $self->{'exec_stack'} }, { 'errstr' => $step->{'last_errstr'}, 'type' => ( $self->{'_rollback_is_undo'} ? 'undo' : 'rollback' ), 'step' => ( $step->state || ref($step) ), 'ns' => ref($step), 'status' => 1 };
  17         81  
284 17         178 $step->exec_stack_runtime_handler( $self->{'exec_stack'}->[-1] );
285             }
286             }
287              
288 7 100       25 return if $rollback_failed;
289 4         8 $self->{'_rollback'}--;
290 4         17 return 1;
291             }
292              
293             sub undo {
294 4     4 1 14 my ( $self, @step_args ) = @_;
295              
296 4 100 100     38 return if !exists $self->{'_execute'} || $self->{'_execute'} || !$self->is_at_end(); # succesful execute() has happened
      66        
297              
298 2         7 $self->{'__rollback_is_undo'} = 1;
299 2         5 $self->{'_undo'}++;
300 2         6 my $rc = $self->rollback(@step_args);
301 2         3 delete $self->{'__rollback_is_undo'};
302 2 100       7 $self->{'_undo'}-- if $rc;
303 2 100       16 return 1 if $rc;
304 1         4 return;
305             }
306              
307             sub execute_failed {
308 0 0   0 1 0 return 1 if $_[0]->{'_execute'};
309 0         0 return;
310             }
311              
312             sub execute_called {
313 0 0   0 1 0 return 1 if exists $_[0]->{'_execute'};
314 0         0 return;
315             }
316              
317             sub rollback_failed {
318 0 0   0 1 0 return 1 if $_[0]->{'_rollback'};
319 0         0 return;
320             }
321              
322             sub rollback_called {
323 0 0   0 1 0 return 1 if exists $_[0]->{'_rollback'};
324 0         0 return;
325             }
326              
327             sub undo_failed {
328 3 100   3 1 15 return 1 if $_[0]->{'_undo'};
329 2         8 return;
330             }
331              
332             sub undo_called {
333 4 100   4 1 29 return 1 if exists $_[0]->{'_undo'};
334 2         9 return;
335             }
336              
337 6     6 1 42 sub get_enable_cwd { $_[0]->{'enable_cwd'} }
338              
339 2     2 1 7 sub set_enable_cwd { $_[0]->{'enable_cwd'} = $_[1] }
340              
341             sub get_starting_cwd {
342 8 100   8 1 69 return if !exists $_[0]->{'starting_cwd'};
343 3         24 return $_[0]->{'starting_cwd'};
344             }
345              
346             sub set_starting_cwd {
347 3     3 1 632 require Cwd;
348 3         8 my $current = $_[0]->{'starting_cwd'};
349 3         9165 $_[0]->{'starting_cwd'} = Cwd::cwd();
350 3 100       76 return $current if $current;
351 2         44 return 1;
352             }
353              
354             sub next_step {
355 61     61 1 4601 my ($self) = @_;
356              
357 61         81 my $stack_length = @{ $self->{'step_stack'} };
  61         116  
358              
359 61 100       139 if ( !$stack_length ) {
360 3         41 require Carp;
361 3         16 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
362 3         19 Carp::carp('This action has no steps.');
363 3         1842 return;
364             }
365              
366             # why would this happen? (i.e. !used via while()) carp ?
367 58 100       125 return if $self->is_at_end();
368              
369 50 100       108 if ( $self->is_at_start() ) {
370 13   50     80 $self->{'current_step'} ||= -1; # first time next_step() is called set current_step to 0 - 1 numeric index
371             }
372              
373 50         70 $self->{'current_step'}++;
374 50 100       159 if ( $self->{'current_step'} == ( $stack_length - 1 ) ) {
375 8         23 my $current_step = $self->{'current_step'};
376 8         18 $self->{'current_step'} = '';
377 8         38 return $self->{'step_stack'}->[$current_step];
378             }
379 42         221 return $self->{'step_stack'}->[ $self->{'current_step'} ];
380             }
381              
382             sub prev_step {
383 25     25 1 44 my ($self) = @_;
384              
385 25         29 my $stack_length = @{ $self->{'step_stack'} };
  25         46  
386              
387             # why would this happen? (i.e. none set) carp ?
388 25 100       56 if ( !$stack_length ) {
389 1         6 require Carp;
390 1         3 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
391 1         3 Carp::carp('This action has no steps.');
392 1         9 return;
393             }
394              
395             # why would this happen? (i.e. !used via while()) carp ?
396 24 100       44 return if $self->is_at_start();
397              
398 20 100       37 if ( $self->is_at_end() ) {
399 2         5 $self->{'current_step'} = ( $stack_length - 1 ); # first time prev-step() is called set current_step to $stack_length numeric index
400             }
401              
402 20         36 my $current_step = $self->{'current_step'};
403 20         30 $self->{'current_step'}--;
404              
405 20 100       45 if ( $self->{'current_step'} < 0 ) {
406              
407 4         6 $self->{'current_step'} = undef;
408 4         15 return $self->{'step_stack'}->[$current_step];
409             }
410              
411 16         52 return $self->{'step_stack'}->[$current_step];
412             }
413              
414             sub is_at_start {
415 74 100   74 1 218 return 1 if !defined $_[0]->{'current_step'};
416 57         126 return;
417             }
418              
419             sub is_at_end {
420 80 100 100 80 1 422 return 1 if defined $_[0]->{'current_step'} && $_[0]->{'current_step'} eq '';
421 68         158 return;
422             }
423              
424 0     0 1 0 sub get_current_step { $_[0]->{'current_step'} }
425              
426 0     0 1 0 sub get_errstr { $_[0]->{'last_errstr'} }
427              
428 0     0 1 0 sub set_errstr { $_[0]->{'last_errstr'} = $_[1] }
429              
430 0     0 1 0 sub get_auto_rollback { $_[0]->{'auto_rollback'} }
431              
432 1     1 1 9 sub set_auto_rollback { $_[0]->{'auto_rollback'} = $_[1] }
433              
434 9     9 1 461 sub get_execution_state { return [ @{ $_[0]->{'exec_stack'} } ] }
  9         52  
435              
436             1;