File Coverage

blib/lib/Brick/Composers.pm
Criterion Covered Total %
statement 38 104 36.5
branch 1 32 3.1
condition 0 15 0.0
subroutine 14 29 48.2
pod n/a
total 53 180 29.4


line stmt bran cond sub pod time code
1             package Brick::Composers;
2 5     5   36 use base qw(Exporter);
  5         10  
  5         486  
3 5     5   30 use vars qw($VERSION);
  5         11  
  5         224  
4              
5             $VERSION = '0.901';
6              
7 5     5   27 use Brick::Bucket;
  5         18  
  5         222  
8              
9             package Brick::Bucket;
10 5     5   40 use strict;
  5         8  
  5         181  
11              
12 5     5   29 use Carp qw(carp);
  5         8  
  5         511  
13              
14             =encoding utf8
15              
16             =head1 NAME
17              
18             Brick::Composers - This is the description
19              
20             =head1 SYNOPSIS
21              
22             use Brick::Constraints::Bucket;
23              
24             =head1 DESCRIPTION
25              
26             This module defines composing functions in the
27             Brick::Constraints package. Each function takes a list of code
28             refs and returns a single code ref that wraps all of them. The single
29             code ref returns true or false (but defined), as with other
30             constraints.
31              
32             If a composer cannot create the single code ref (for instance, due to
33             bad input) it returns C of the empty list, indicating a failure
34             in programming rather than a failure of the data to validate.
35              
36             =cut
37              
38             =over 4
39              
40             =item __and( LIST OF CODEREFS )
41              
42             =item __compose_satisfy_all( LIST OF CODEREFS )
43              
44             This is AND with NO short-circuiting.
45              
46             ( A && B && C )
47              
48             This function creates a new constraint that returns true if all of its
49             constraints return true. All constraints are checked so there is no
50             short-circuiting. This allows you to get back all of the errors at
51             once.
52              
53             =cut
54              
55             sub __compose_satisfy_all
56             {
57 6     6   12 my $bucket = shift;
58 6         14 $bucket->__compose_satisfy_N( scalar @_, @_ );
59             }
60              
61             BEGIN {
62 5     5   400 *__and = *__compose_satisfy_all;
63             }
64              
65             =item __or( LIST OF CODEREFS )
66              
67             =item __compose_satisfy_any( LIST OF CODEREFS )
68              
69             This is OR but with NO short-circuiting.
70              
71             ( A || B || C )
72              
73             This function creates a new constraint that returns true if all of its
74             constraints return true. All constraints are checked so there is no
75             short-circuiting.
76              
77             =cut
78              
79             sub __compose_satisfy_any
80             {
81 0     0   0 my $bucket = shift;
82 0         0 $bucket->__compose_satisfy_N_to_M( 1, scalar @_, @_ );
83             }
84              
85             BEGIN {
86 5     5   314 *__or = *__compose_satisfy_any;
87             }
88              
89             =item __none( LIST OF CODEREFS )
90              
91             =item __compose_satisfy_none( LIST OF CODEREFS )
92              
93              
94             ( NOT A && NOT B && NOT C )
95              
96             NOT ( A || B || C )
97              
98             This function creates a new constraint that returns true if all of its
99             constraints return false. All constraints are checked so there is no
100             short-circuiting.
101              
102             =cut
103              
104             sub __compose_satisfy_none
105             {
106 0     0   0 my $bucket = shift;
107 0         0 $bucket->__compose_satisfy_N_to_M( 0, 0, @_ );
108             }
109              
110             BEGIN {
111 5     5   3831 *__none = *__compose_satisfy_none;
112             }
113              
114             =item __compose_satisfy_N( SCALAR, LIST OF CODEREFS )
115              
116             This function creates a new constraint that returns true if exactly N
117             of its constraints return true. All constraints are checked so there
118             is no short-circuiting.
119              
120             =cut
121              
122             sub __compose_satisfy_N
123             {
124 6     6   15 my( $bucket, $n, @subs ) = @_;
125              
126 6         13 $bucket->__compose_satisfy_N_to_M( $n, $n, @subs );
127             }
128              
129             =item __compose_satisfy_N_to_M( LIST OF CODEREFS )
130              
131             This function creates a new constraint that returns true if between N
132             and M (inclusive) of its constraints return true. All constraints are
133             checked so there is no short-circuiting.
134              
135             =cut
136              
137             sub __compose_satisfy_N_to_M
138             {
139 6     6   12 my( $bucket, $n, $m, @subs ) = @_;
140              
141 6 50   0   9 if( grep { ref $_ ne ref sub {} } @subs )
  10         49  
142             {
143 0         0 croak "Got something else when expecting code ref!";
144 0     0   0 return sub {};
145             }
146              
147 6         15 my @caller = $bucket->__caller_chain_as_list();
148              
149 6         11 my @composers = grep { /^__compose/ } map { $_->{sub} } @caller;
  46         90  
  46         70  
150              
151 6         13 my $max = @subs;
152              
153             my $sub = $bucket->add_to_bucket( {
154             name => $composers[-1], # forget the chain of composers
155             code => sub {
156 0     0   0 my $count = 0;
157 0         0 my @dies = ();
158 0         0 foreach my $sub ( @subs )
159             {
160 0         0 my $result = eval { $sub->( @_ ) };
  0         0  
161 0         0 my $at = $@;
162 0 0       0 $count++ unless $at;
163             #print STDERR "\n!!!!Sub died!!!!\n" if ref $at;
164             #print STDERR "\n", Data::Dumper->Dump( [$at], [qw(at)]) if ref $at;
165 0 0 0     0 die if( ! ref $at and $at );
166 0 0       0 push @dies, $at if ref $at;
167             };
168              
169 0 0       0 my $range = $n == $m ? "exactly $n" : "between $n and $m";
170              
171             die {
172             message => "Satisfied $count of $max sub-conditions, needed to satisfy $range",
173 0 0 0     0 handler => $caller[0]{'sub'},
174             errors => \@dies,
175             } unless $n <= $count and $count <= $m;
176              
177 0         0 return 1;
178             },
179 6         49 });
180              
181 6         23 $bucket->comprise( $sub, @subs );
182              
183 6         36 return $sub;
184             }
185              
186             =item __not( CODEREF )
187              
188             =item __compose_not( CODEREF )
189              
190             This composers negates the sense of the code ref. If the code ref returns
191             true, this composer makes it false, and vice versa.
192              
193             =cut
194              
195              
196             sub __compose_not
197             {
198 0     0     my( $bucket, $not_sub ) = @_;
199              
200             my $sub = $bucket->add_to_bucket( {
201 0 0   0     code => sub { if( $not_sub->( @_ ) ) { die {} } else { return 1 } },
  0            
  0            
202 0           } );
203              
204 0           return $sub;
205             }
206              
207              
208             =item __compose_until_pass
209              
210             =item __compose_pass_or_skip
211              
212             Go through the list of closures, trying each one until one suceeds. Once
213             something succeeds, it returns the name of the subroutine that passed.
214              
215             If
216             a closure doesn't die, but doesn't return true, this doesn't fail but
217             just moves on. Return true for the first one that passes,
218             short-circuited the rest.
219              
220             If none of the closures pass (and none of them die), return 0. This might
221             be the odd case of a several selectors (see L), none of
222             which pass.
223              
224             If one of the subs dies, this composer still dies. This can also die
225             for programming (not logic) errors.
226              
227             =cut
228              
229             sub __compose_pass_or_skip
230             {
231 0     0     my( $bucket, @subs ) = @_;
232              
233 0 0   0     if( grep { ref $_ ne ref sub {} } @subs )
  0            
234             {
235 0           croak "Got something else when expecting code ref!";
236 0     0     return sub {};
237             }
238              
239 0           my @caller = $bucket->__caller_chain_as_list();
240              
241             my $sub = $bucket->add_to_bucket( {
242             code => sub {
243 0     0     my $count = 0;
244 0           my @dies = ();
245              
246 0           foreach my $sub ( @subs )
247             {
248 0           my $result = eval { $sub->( @_ ) };
  0            
249 0           my $eval_error = $@;
250              
251             # all true values are success
252 0 0         return "$sub" if $result; # we know we passed
253              
254              
255             # we're a selector: failed with no error
256 0 0 0       return if ( ! defined $result and ! defined $eval_error );
257              
258             # die for everything else - validation error
259 0 0         die if( ref $eval_error );
260             };
261              
262 0           return 0;
263             },
264 0           });
265              
266 0           $bucket->comprise( $sub, @subs );
267              
268 0           return $sub;
269             }
270              
271             BEGIN {
272 5     5   749 *__compose_until_pass = *__compose_pass_or_skip;
273             }
274              
275             =item __compose_until_fail
276              
277             =item __compose_pass_or_stop
278              
279             Keep going as long as the closures return true.
280              
281             The closure that returns undef is a selector.
282              
283             If a closure doesn't die and doesn't don't fail, just move on. Return true for
284             the first one that passes, short-circuited the rest. If none of the
285             closures pass, die with an error noting that nothing passed.
286              
287             This can still die for programming (not logic) errors.
288              
289              
290             $result $@ what action
291             ------------------------------------------------------------
292             1 undef passed go on to next brick
293              
294             undef undef selector stop, return undef, no die
295             failed
296              
297             undef string program stop, die with string
298             error
299              
300             undef ref validator stop, die with ref
301             failed
302              
303             =cut
304              
305             sub __compose_pass_or_stop
306             {
307 0     0     my( $bucket, @subs ) = @_;
308              
309 0 0   0     if( grep { ref $_ ne ref sub {} } @subs )
  0            
310             {
311 0           croak "Got something else when expecting code ref!";
312 0     0     return sub {};
313             }
314              
315 0           my @caller = $bucket->__caller_chain_as_list();
316              
317 0           my $max = @subs;
318              
319             my $sub = $bucket->add_to_bucket( {
320             code => sub {
321 0     0     my $count = 0;
322 0           my @dies = ();
323              
324 0           my $last_result;
325 0           foreach my $sub ( @subs )
326             {
327 5     5   38 no warnings 'uninitialized';
  5         17  
  5         821  
328 0           my $result = eval { $sub->( @_ ) };
  0            
329 0           my $at = $@;
330             #print STDERR "\tstop: Returned result: $result\n";
331             #print STDERR "\tstop: Returned undef!\n" unless defined $result;
332             #print STDERR "\tstop: Returned ref!\n" if ref $at;
333 0           $last_result = $result;
334              
335 0 0         next if $result;
336              
337 0 0         die $at if ref $at;
338              
339 0 0 0       return unless( defined $result and ref $at );
340              
341 0 0 0       die if( ref $at and $at ); # die for program errors
342             #print STDERR "\tStill going\n";
343             };
344              
345 0           return $last_result;
346             },
347 0           });
348              
349 0           $bucket->comprise( $sub, @subs );
350              
351 0           return $sub;
352             }
353              
354             BEGIN {
355 5     5   165 *__compose_until_fail = *__compose_pass_or_stop;
356             }
357              
358             =back
359              
360             =head1 TO DO
361              
362             TBA
363              
364             =head1 SEE ALSO
365              
366             TBA
367              
368             =head1 SOURCE AVAILABILITY
369              
370             This source is in Github:
371              
372             https://github.com/briandfoy/brick
373              
374             =head1 AUTHOR
375              
376             brian d foy, C<< >>
377              
378             =head1 COPYRIGHT
379              
380             Copyright © 2007-2021, brian d foy . All rights reserved.
381              
382             You may redistribute this under the terms of the Artistic License 2.0.
383              
384             =cut
385              
386             1;