File Coverage

blib/lib/Brick/Profile.pm
Criterion Covered Total %
statement 78 80 97.5
branch 13 20 65.0
condition 8 18 44.4
subroutine 15 17 88.2
pod 10 10 100.0
total 124 145 85.5


line stmt bran cond sub pod time code
1             package Brick::Profile;
2 6     6   73209 use strict;
  6         15  
  6         187  
3 6     6   34 use warnings;
  6         10  
  6         176  
4              
5 6     6   31 use vars qw($VERSION);
  6         10  
  6         255  
6              
7 6     6   34 use Carp qw(carp);
  6         12  
  6         270  
8              
9 6     6   479 use Brick;
  6         13  
  6         5993  
10              
11             $VERSION = '0.901';
12              
13             =encoding utf8
14              
15             =head1 NAME
16              
17             Brick::Profile - the validation profile for Brick
18              
19             =head1 SYNOPSIS
20              
21              
22              
23             =head1 DESCRIPTION
24              
25             This class turns a profile description into a ready-to-use profile object
26             that has created all of the code it needs to validate input. In Brick
27             parlance, it creates the bucket and the bricks that need to go into the
28             bucket based on the validation description.
29              
30             =head2 Validation profile
31              
32             The validation profile is an array of arrays. Each item in the array specifies
33             three things: a label for that item, the name of the method to run to
34             validate the item, and optional arguments to pass to the the method.
35              
36             For instance, here's a simple validation description to check if a user
37             is registered with the system. This profile has one item:
38              
39             @profile = (
40             [ username => is_registered => { field => form_name } ],
41             );
42              
43             The label for the item is C. When Brick reports the results
44             of the validation, the label C will be attached to the
45             result for this part of the validation.
46              
47             The method that validates this item is C. When you
48             create the profile, Brick will look for that method in either it's
49             included methods in Brick::Bucket classes or the ones you load with
50             C. This method is called a "brick"
51             because it's one piece of the entire validation.
52              
53             Additionally, Brick will pass the optional arguments, in this case C<{
54             field => form_name }>, to C. A brick merely creates
55             a closure that will run later, so the optional arguments are for
56             the initialization of that closure. The validation doesn't happen
57             until you C it.
58              
59             =head2 Class methods
60              
61             =over 4
62              
63             =item new( BRICK, ARRAY_OF_ARRAYS )
64              
65             Create a new profile object tied to the Brick object.
66              
67             =cut
68              
69             sub new
70             {
71 4     4 1 11 my( $class, $brick, $array_ref ) = @_;
72              
73 4 50       14 unless( $brick->isa( $class->brick_class ) )
74             {
75 0         0 carp "First argument to \$class->new() must be a brick object. " .
76             "Got [$brick]\n";
77 0         0 return;
78             }
79              
80 4         11 my $self = bless {}, $class;
81              
82 4         10 my $lint_errors = $class->lint( $array_ref );
83              
84 4 100 66     23 if( ! defined $lint_errors or $lint_errors )
85             {
86 1         215 carp "Profile did not validate!";
87 1         132 return;
88             }
89              
90 3         14 my( $bucket, $refs ) = $brick->create_bucket( $array_ref );
91              
92 3         12 $self->set_bucket( $bucket );
93 3         13 $self->set_coderefs( $refs );
94 3         9 $self->set_array( $array_ref );
95              
96 3         9 return $self;
97             }
98              
99             =item brick_class()
100              
101             Return the class name to use to access class methods (such as
102             bucket_class) in the Brick namespace. If you want to provide
103             an alternate Brick class for your profile, override this method.
104              
105             =cut
106              
107 14     14 1 49 sub brick_class { require Brick; 'Brick' }
  14         49  
108              
109             =back
110              
111             =head2 Instance methods
112              
113             =over
114              
115             =item lint( PROFILE_ARRAYREF );
116              
117             Examine the profile and complain about irregularities in format. This
118             only checks the format; it does not try to determine if the profile
119             works or makes sense. It returns a hash whose key is the index of the
120             profile element and whose value is an anonymous hash to indicate what
121             had the error:
122              
123             format - the element is an arrayref
124             name - the name is a scalar
125             method - is a code ref or can be found in the package
126             $brick->bucket_class returns
127             args - the last element is a hash reference
128              
129             If the profile is not an array reference, C immediately returns
130             undef or the empty list. In scalar context, C returns 0 for
131             format success and the number of errors (so true) for format failures.
132             If there is a format error (e.g. an element is not an array ref), it
133             immediately returns the number of errors up to that point.
134              
135             my $lint = $brick->profile_class->lint( \@profile );
136              
137             print do {
138             if( not defined $lint ) { "Profile must be an array ref\n" }
139             elsif( $lint ) { "Did not validate, had $lint problems" }
140             else { "Woo hoo! Everything's good!" }
141             };
142              
143             In list context, it returns a hash (a list of one element). The result
144             will look something like this hash, which has keys for the elements
145             that lint thinks are bad, and the values are anonymous hashes with
146             keys for the parts that failed:
147              
148             %lint = (
149             1 => {
150             method => "Could not find method foo in package",
151             },
152             4 => {
153             args => "Arguments should be a hash ref, but it was a scalar",
154             }
155             );
156              
157             If you are using C to generate some of the methods at
158             runtime (i.e. after C has a chance to check for it), use a
159             C method to let C know that it will be available later.
160              
161             TO DO:
162              
163             Errors for duplicate names?
164              
165             =cut
166              
167             sub lint
168             {
169 12     12 1 26 my( $class, $array ) = @_;
170              
171             return unless(
172 12 50 33     20 eval { $array->isa( ref [] ) } or
  12         154  
173             UNIVERSAL::isa( $array, ref [] )
174             );
175              
176 12         35 my $lint = {};
177              
178 12         37 foreach my $index ( 0 .. $#$array )
179             {
180 12         33 my $h = $lint->{$index} = {};
181              
182 12 100 66     16 unless( eval { $array->[$index]->isa( ref [] ) } or
  12         95  
183             UNIVERSAL::isa( $array->[$index], ref [] )
184             )
185             {
186 2         7 $h->{format} = "Not an array reference!";
187 2         4 last;
188             }
189              
190 10         23 my( $name, $method, $args ) = @{ $array->[$index] };
  10         25  
191              
192 10 50       21 $h->{name} = "Profile name is not a simple scalar!" if ref $name;
193              
194             $h->{args} = "Couldn't find method [$method]" unless
195 10     0   133 eval { $method->isa( ref sub {} ) } or
196       0     UNIVERSAL::isa( $method, sub {} ) or
197 10 50 33     15 eval { $class->brick_class->bucket_class->can( $method ) };
  10   33     32  
198              
199             $h->{args} = "Args is not a hash reference" unless
200 10 50 33     35 eval { $args->isa( ref {} ) } or
  10         81  
201             UNIVERSAL::isa( $args, ref {} );
202              
203             # args needs what?
204              
205 10 50       23 delete $lint->{$index} if 0 == keys %{$lint->{$index}};
  10         50  
206             }
207              
208 12 100       59 wantarray ? %$lint : ( scalar keys %$lint );
209             }
210              
211             =item explain()
212              
213             Turn the profile into a textual description without applying it to any
214             data. This does not add the profile to instance and it does not add
215             the constraints to the bucket.
216              
217             If everything goes right, this returns a single string that represents
218             the profile.
219              
220             If the profile does not pass the C test, this returns undef or the
221             empty list.
222              
223             If you want to do something with a datastructure, you probably want to
224             write a different method very similar to this instead of trying to parse
225             the output.
226              
227             Future notes: maybe this is just really a dispatcher to things that do
228             it in different ways (text output, hash output).
229              
230             =cut
231              
232             sub explain
233             {
234 2     2 1 6 my( $profile ) = @_;
235              
236 2         7 my $bucket = $profile->get_bucket;
237 2         5 my $coderefs = $profile->get_coderefs;
238 2         6 my $array = $profile->get_array;
239              
240             my @entries = map {
241 2         5 my $e = $bucket->get_from_bucket( $_ );
  2         6  
242 2         4 [ map { $e->$_ } qw(get_coderef get_name) ]
  4         13  
243             } @$coderefs;
244              
245             #print STDERR Data::Dumper->Dump( [ \@entries ], [qw(entries)] );
246              
247 2         3 my $level = 0;
248 2         4 my $str = '';
249 2         8 foreach my $index ( 0 .. $#entries )
250             {
251 2         4 my $tuple = $entries[$index];
252              
253 2         4 my @uses = ( [ $level, $tuple->[0] ] );
254              
255             #print STDERR Data::Dumper->Dump( [ \@uses ], [qw(uses)] );
256              
257 2         7 while( my $pair = shift @uses )
258             {
259 14         27 my $entry = $bucket->get_from_bucket( $pair->[1] );
260             #print Data::Dumper->Dump( [ $entry ], [qw(entry)] );
261 14 50       43 next unless $entry;
262              
263 14         33 $str .= "\t" x $pair->[0] . $entry->get_name . "\n";
264              
265             unshift @uses, map {
266 12         35 [ $pair->[0] + 1, $_ ]
267 14         18 } @{ $entry->get_comprises( $pair->[1] ) };
  14         28  
268             #print Data::Dumper->Dump( [ \@uses ], [qw(uses)] );
269             }
270              
271 2         5 $str.= "\n";
272             }
273              
274 2         7 $str;
275             }
276              
277             =item get_bucket
278              
279             =cut
280              
281             sub get_bucket
282             {
283             $_[0]->{bucket}
284 3     3 1 6 }
285              
286             =item set_bucket
287              
288             =cut
289              
290             sub set_bucket
291             {
292 3     3 1 13 $_[0]->{bucket} = $_[1];
293             }
294              
295             =item get_coderefs
296              
297             =cut
298              
299             sub get_coderefs
300             {
301 3     3 1 6 $_[0]->{coderefs};
302             }
303              
304             =item set_coderefs
305              
306             =cut
307              
308             sub set_coderefs
309             {
310 3     3 1 7 $_[0]->{coderefs} = $_[1];
311             }
312              
313             =item get_array
314              
315             =cut
316              
317             sub get_array
318             {
319 3     3 1 6 $_[0]->{array};
320             }
321              
322             =item set_array
323              
324             =cut
325              
326             sub set_array
327             {
328 3     3 1 7 $_[0]->{array} = $_[1];
329             }
330              
331             =back
332              
333             =head2 Using a different class
334              
335             If you don't want to use this class, you can specify a different class
336             to use in your Brick subclass. Override the Brick::profile_class()
337             method to specify the name of the class that you want to use instead.
338             That might be a subclass or an unrelated class. Your class will need
339             to use the same interface even though it does things differently.
340              
341             =head1 TO DO
342              
343             TBA
344              
345             =head1 SEE ALSO
346              
347             L, L
348              
349             =head1 SOURCE AVAILABILITY
350              
351             This source is in Github:
352              
353             https://github.com/briandfoy/brick
354              
355             =head1 AUTHOR
356              
357             brian d foy, C<< >>
358              
359             =head1 COPYRIGHT
360              
361             Copyright © 2007-2021, brian d foy . All rights reserved.
362              
363             You may redistribute this under the terms of the Artistic License 2.0.
364              
365             =cut
366              
367             1;