File Coverage

blib/lib/Sub/Pipeline.pm
Criterion Covered Total %
statement 94 109 86.2
branch 25 36 69.4
condition 4 8 50.0
subroutine 25 28 89.2
pod 11 11 100.0
total 159 192 82.8


line stmt bran cond sub pod time code
1 5     5   125480 use strict;
  5         11  
  5         190  
2 5     5   25 use warnings;
  5         12  
  5         259  
3             package Sub::Pipeline;
4             {
5             $Sub::Pipeline::VERSION = '0.011';
6             }
7             # ABSTRACT: subs composed of sequential pieces
8              
9 5     5   25 use Carp ();
  5         11  
  5         125  
10 5     5   4740 use Params::Util 0.22 qw(_CODELIKE);
  5         27761  
  5         375  
11 5     5   4432 use Sub::Install;
  5         8427  
  5         31  
12              
13              
14             sub new {
15 9     9 1 7492 my ($class, $arg) = @_;
16 9   50     79 $arg->{on_success} ||= 'value';
17              
18 9         29 my $self = bless {} => $class;
19              
20 9 50       42 $self->order(@{ $arg->{order} }) if $arg->{order};
  9         44  
21 9         16 $self->pipe($_ => $arg->{pipe}{$_}) for (keys %{ $arg->{pipe} });
  9         63  
22 9         48 $self->on_success($arg->{on_success});
23              
24 9         30 return $self;
25             }
26              
27              
28             sub order {
29 20     20 1 35 my $self = shift;
30 20 100       72 return @{ $self->{order} } unless @_;
  11         70  
31              
32 9         208 $self->{order} = [ @_ ];
33 9         26 return @_;
34             }
35              
36              
37             sub pipe { ## no critic Homonym
38 83     83 1 157 my ($self, $name, $code) = @_;
39 83 100       291 return $self->{pipe}{$name} if @_ == 2;
40 41 50       111 Carp::croak "pipe piece must be a code reference" unless ref $code eq 'CODE';
41 41         256 $self->{pipe}{$name} = $code;
42             }
43              
44              
45             my %_behavior = map { $_ => 1 } qw(throw return value);
46              
47             sub on_success {
48 21     21 1 1960 my $self = shift;
49 21 100       114 return $self->{behavior} unless @_;
50              
51 12         27 my ($behavior) = @_;
52 12 50       43 Carp::croak "invalid value for on_success" unless $_behavior{ $behavior };
53 12         40 $self->{behavior} = $behavior;
54             }
55              
56              
57             sub check {
58 1     1 1 39 my ($self) = @_;
59 1         4 for my $pipe ($self->order) {
60 3         6 my $code = $self->pipe($pipe);
61 3 100 66     16 unless ((ref $code eq 'CODE') or overload::Method($code, '&{}')) {
62 1         2305 Sub::Pipeline::PipeMissing->throw(pipe => $pipe);
63             }
64             }
65 0         0 return 1;
66             }
67              
68              
69             sub _initial_state {
70 11     11   21 my ($self) = @_;
71 11         33 return {};
72             }
73              
74             sub _call_parts {
75 11     11   26 my ($self, $order, $on_success, $get_part, $arg) = @_;
76              
77 11         86 push @$arg, $self->_initial_state;
78              
79 11         61 for my $pipe (@$order) {
80 45         101 my $code = $get_part->($pipe);
81 45 100       170 unless (_CODELIKE($code)) {
82 1         18 Sub::Pipeline::PipeMissing->throw(pipe => $pipe);
83             }
84 44         80 eval { $code->(@$arg) };
  44         201  
85 44 100       23059 next unless $@;
86 10 50       161 if (my $e = Sub::Pipeline::Success->caught) {
87 10 100       218 return $e if $on_success eq 'return';
88 9 100       230 return $e->value if $on_success eq 'value';
89 2 50       16 $e->rethrow if $on_success eq 'throw';
90 0         0 Carp::confess "unknown on_success behavior: " . $on_success;
91             } else {
92 0         0 Carp::cluck $@;
93             }
94             }
95             }
96              
97             sub call {
98 9     9 1 101 my $self = shift;
99              
100             $self->_call_parts(
101             [ $self->order ],
102             $self->on_success,
103 39     39   110 sub { $self->pipe($_[0]) },
104             \@_
105 9         69 );
106             }
107              
108              
109             sub as_code {
110 2     2 1 10 my ($self) = @_;
111 2     3   36 sub { $self->call(@_) };
  3         1699  
112             }
113              
114              
115             sub load_from_package {
116 1     1 1 8 my ($self, $package) = @_;
117              
118 1         13 for my $pipe ($self->order) {
119 5         38 my $code = $package->can($pipe);
120 5 50       15 Carp::croak "package $package has no sub $pipe" unless $code;
121 5         14 $self->pipe($pipe => $code);
122             }
123             }
124              
125              
126             sub save_to_package {
127 0     0 1 0 my ($self, $package, $arg) = @_;
128              
129 0 0       0 my $installer
130             = Sub::Install->can($arg->{reinstall} ? 'reinstall_sub' : 'install_sub');
131              
132 0         0 for my $pipe ($self->order) {
133 0         0 $installer->({
134             into => $package,
135             as => $pipe,
136             code => $self->pipe($pipe),
137             });
138             }
139              
140 0         0 my $on_success = $self->on_success;
141              
142             my $caller = sub {
143 0     0   0 $self->_call_parts(
144             [ $self->order ],
145             $self->on_success,
146             $package->can($_[0]),
147             \@_,
148             );
149 0         0 };
150              
151 0         0 $installer->({ into => $package, as => 'call', code => $caller });
152             }
153              
154              
155             sub install_pipeline {
156 1     1 1 728 my ($self, $arg) = @_;
157              
158 1   33     4 ($arg->{into}) ||= caller(0);
159              
160 1 50       14 my $installer
161             = Sub::Install->can($arg->{reinstall} ? 'reinstall_sub' : 'install_sub');
162              
163 1 50       4 Carp::croak "install_pipeline requires an 'as' parameter" unless $arg->{as};
164 1         4 $installer->({
165             code => $self->as_code,
166             into => $arg->{into},
167             as => $arg->{as}
168             });
169             }
170              
171              
172             sub install_new {
173 0     0 1 0 my ($self, $arg) = @_;
174              
175 0         0 my $install_arg = {};
176 0         0 $install_arg->{$_} = delete $arg->{$_} for qw(into as reinstall);
177              
178 0         0 $self->new($arg)->install_pipeline($install_arg);
179             }
180              
181             use overload
182 5         38 '&{}' => 'as_code',
183             fallback => 1
184 5     5   11516 ;
  5         9  
185              
186             use Sub::Exporter 0.95 -setup => {
187             groups => { class => \&_class_generator },
188 5         81 collectors => [ order => sub { ref $_[0] eq 'ARRAY' } ],
  1         1716  
189 5     5   5813 };
  5         26089  
190              
191             sub _class_generator {
192 1     1   56 my ($class, $name, $arg, $col) = @_;
193              
194 1         2 my @order = @{ $col->{order} };
  1         4  
195            
196 1 50   2   4 my $order_acc = sub { return @_ ? (@order = @_) : @order; };
  2         34  
197             my $caller = sub {
198 2     2   1807 my ($self) = @_;
199             $class->_call_parts(
200             [ $order_acc->() ],
201             'value', # make configurable
202 6     6   52 sub { $self->can($_[0]) },
203 2         8 \@_,
204             );
205 1         6 };
206              
207             return {
208 1         7 order => $order_acc,
209             call => $caller,
210             };
211             }
212              
213              
214             use Exception::Class 1.22 (
215 5         54 'Sub::Pipeline::Success', { fields => [qw(value)] },
216             'Sub::Pipeline::PipeMissing', { fields => [qw(pipe) ] },
217 5     5   7434 );
  5         46620  
218              
219              
220             1;
221              
222             __END__