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