File Coverage

blib/lib/Data/Validate/Structure.pm
Criterion Covered Total %
statement 69 160 43.1
branch 22 64 34.3
condition 1 3 33.3
subroutine 15 37 40.5
pod 15 15 100.0
total 122 279 43.7


line stmt bran cond sub pod time code
1             package Data::Validate::Structure;
2              
3 2     2   3180 use strict;
  2         4  
  2         159  
4              
5             =head1 NAME
6              
7             Data::Validate::Structure - handle a structure in custom ways
8              
9             =head1 SYNOPSIS
10              
11             use Data::Validate::Structure;
12              
13             my $structure = Structure->new( $data );
14              
15             # Check sub structures matches exactly
16             $structure == $structure2
17              
18             # Check sub structures matches equaly (array order not important)
19             $structure eq $structure
20              
21             # Check structure contains all of structure2 at least
22             $structure >= $structure2
23              
24             # Check structure2 contains all of structure at least
25             $structure <= $structure2
26              
27             # structure much contain structure2 but not equal it
28             $structure > $structure2
29              
30             # structure2 must contain structure but not equal it
31             $structure < $structure2
32              
33             # Make sure structure does not exactly match structure2
34             $structure != $structure2
35              
36             # Remove all parts of structure2 from structure
37             $structure - $structure2
38             $structure -= $structure2
39              
40             # Merge two structures together
41             $structure + $structure2
42             $structure += $structure2
43              
44             =head1 DESCRIPTION
45            
46             Take a structure and attempt to allow some basic structure
47             to structure testing.
48              
49             =head1 METHODS
50              
51             =cut
52            
53             our $VERSION = "0.09";
54 2     2   13 use Carp;
  2         4  
  2         667  
55              
56             use overload
57 2         57 '""' => \&autoname,
58             '%{}' => \&autovalue,
59             '@{}' => \&autovalue,
60             'bool' => \&autobool,
61             '==' => \&identical,
62             'eq' => \&equal,
63             '!=' => \¬identical,
64             'ne' => \¬equal,
65             '<=' => \&disabled,
66             '>=' => \&disabled,
67             '>' => \&disabled,
68             '<' => \&disabled,
69             '-' => \&disabled,
70             '-=' => \&disabled,
71             '+' => \&plus,
72 2     2   12 '+=' => \&pluseq;
  2         3  
73              
74             =head2 $class->new( $structure )
75              
76             Create a new structure.
77              
78             =cut
79             sub new {
80 4     4 1 949 my ($class, $structure) = @_;
81 4         18 my $self = bless { structure => $structure }, $class;
82 4         117 return $self;
83             }
84              
85             =head2 $structure->disabled()
86              
87             Internal method, wht to do when a function is disabled.
88              
89             =cut
90 0     0 1 0 sub disabled { carp "Structure method disabled";}
91              
92             =head2 $structure->equal( $otherstructure )
93              
94             Test that structure is the same as other structure.
95              
96             =cut
97 2 50   2 1 9 sub equal { return $_[0] if _autoself(); return _equal(@_); }
  2         12  
98              
99             =head2 $structure->notequal( $otherstructure )
100              
101             Test that structure is not the same as other structure.
102              
103             =cut
104 0 0   0 1 0 sub notequal { return $_[0] if _autoself(); return not _equal(@_); }
  0         0  
105              
106             =head2 $structure->_equal( $otherstructure )
107              
108             Internal method for testing structural equiverlance.
109              
110             =cut
111             sub _equal {
112 2     2   5 my ($self, $sct) = @_;
113 2         11 return _eq($self, $sct, StrictArray => 0 );
114             }
115              
116             =head2 $structure->identical( $otherstructure )
117              
118             Return true if structure is identical.
119              
120             =cut
121 0 0   0 1 0 sub identical { return $_[0] if _autoself(); return _identical(@_); }
  0         0  
122              
123             =head2 $structure->notidentical( $otherstructure )
124              
125             Return true if structure is not identical.
126              
127             =cut
128 0 0   0 1 0 sub notidentical { return $_[0] if _autoself(); return not _identical(@_); }
  0         0  
129              
130             =head2 $structure->_identical( $otherstructure )
131              
132             Return true if structure is identical (internal).
133              
134             =cut
135             sub _identical {
136 0     0   0 my ($self, $sct) = @_;
137 0         0 return _eq($self, $sct, StrictArray => 1 );
138             }
139              
140             =head2 $structure->_autoself()
141              
142             Return true if the caller was internal.
143              
144             =cut
145             sub _autoself {
146 6     6   10 my ($self) = @_;
147 6         30 my ($package) = caller(1);
148 6 100       24 if($package eq "Data::Validate::Structure") {
149 4         30 return 1;
150             }
151 2         14 return 0;
152             }
153              
154             =head2 $structure->autovalue()
155              
156             Return the structure
157              
158             =cut
159             sub autovalue {
160 4     4 1 7 my ($self) = @_;
161 4 50       11 return $self if _autoself;
162             #my (@a) = caller;
163             #warn join(', ', @a)."\n";
164 0         0 return $self->structure;
165             }
166              
167             =head2 $structure->autoname()
168              
169             Return the structure name
170              
171             =cut
172             sub autoname {
173 0     0 1 0 my ($self) = @_;
174 0 0       0 return $self if _autoself;
175 0         0 return $self->name;
176             }
177              
178             =head2 $structure->autobool()
179              
180             Returns the truth of the structure
181              
182             =cut
183             sub autobool {
184 0     0 1 0 my ($self) = @_;
185 0 0       0 if(ref($self->structure) eq "ARRAY") {
    0          
186 0         0 return scalar(@{$self->structure});
  0         0  
187             } elsif(ref($self->structure) eq "HASH") {
188 0         0 return keys(%{$self->structure});
  0         0  
189             }
190             }
191              
192             =head2 $structure->structure()
193              
194             Return the structure directly
195              
196             =cut
197 4     4 1 15 sub structure { return $_[0]->{'structure'}; }
198              
199              
200             =head2 $structure->name()
201              
202             Return the name directly
203              
204             =cut
205 0     0 1 0 sub name { return $_[0]->{'name'}; }
206              
207             =head2 $structure->_eq( $otherstructure, %p )
208              
209             Return true if other structure is equle.
210              
211             =cut
212             sub _eq {
213 29     29   71 my ($sct1, $sct2, %op) = @_;
214             return _sctdeal(
215             $sct1,
216             $sct2,
217             \&_eq_array,
218             \&_eq_hash,
219 24 100   24   219 sub { return 1 if $_[0] eq $_[1] },
220 29         173 %op, SkipSame => 1,
221             );
222 0         0 return 0;
223             }
224              
225             =head2 $structure->_eq_hash( $otherhash, %p )
226              
227             Return true if other hash is equle.
228              
229             =cut
230             sub _eq_hash {
231 5     5   16 my ($sct1, $sct2, %op) = @_;
232             # check keys of hash to be the same via eqarray
233 5 50       10 return 0 if not _eq_array([keys(%{$sct1})], [keys(%{$sct2})], StrictArray => 0 );
  5         20  
  5         31  
234 5         16 foreach my $key (keys(%{$sct1})) {
  5         16  
235 10 50       38 if(not _eq($sct1->{$key}, $sct2->{$key}, %op)) {
236 0         0 return 0;
237             }
238             }
239 5         46 return 1;
240             }
241              
242             =head2 $structure->_eq_array( $otherarray )
243              
244             Return true if other array is equle.
245              
246             =cut
247             sub _eq_array
248             {
249 5     5   12 my ($sct1, $sct2, %op) = @_;
250             # Check size of array (because this will save time)
251 5 50 33     9 return 1 if @{$sct1} == 0 and @{$sct2} == 0;
  5         35  
  0         0  
252 5 50       8 return 0 if not @{$sct1} == @{$sct2};
  5         9  
  5         15  
253 5 50       16 if($op{'StrictArray'}) {
254             # This will look for strict arrays where the order
255             # is the same and so is the content.
256 0         0 for(my $i = 0; $i <= $#{$sct1}; $i++) {
  0         0  
257 0 0       0 return 0 if not _eq($sct1->[$i], $sct2->[$i], %op);
258             }
259             } else {
260             # This is less strict, it just wants the same content
261             # but not the same order (takes longer to run)
262 5         47 my %used;
263 5         11 for(my $i = 0; $i <= $#{$sct1}; $i++) {
  15         49  
264 10         66 my $ofsct1 = $sct1->[$i];
265 10         14 my $found = 0;
266 10         16 for(my $j = 0; $j <= $#{$sct2}; $j++) {
  21         54  
267 21 100       56 next if $used{$j};
268 17         26 my $ofsct2 = $sct2->[$j];
269 17 100       59 if(_eq($ofsct1, $ofsct2, %op)) {
270 10         22 $used{$j} = 1;
271 10         14 $found = 1;
272 10         24 last;
273             }
274             }
275 10 50       36 return 0 if not $found;
276             }
277             }
278 5         20 return 1;
279             }
280              
281             =head2 $structure->plus( $otherstructure )
282              
283             Return the current structure plus another structure
284              
285             =cut
286             sub plus {
287 0     0 1 0 my ($self, $sct) = @_;
288 0         0 return _plus($self, $sct);
289             }
290              
291             =head2 $structure->pluseq( $otherstructure )
292              
293             Append another structure.
294              
295             =cut
296             sub pluseq {
297 0     0 1 0 my ($self, $sct) = @_;
298 0         0 return _pluseq($self, $sct);
299             }
300              
301             =head2 $structure->_plus( $otherstructure )
302              
303             Internal method for merging two structures.
304              
305             =cut
306             sub _plus {
307 0     0   0 my ($sct1, $sct2, %op) = @_;
308 0         0 my $result = _sctclone($sct1);
309 0         0 _pluseq($result, $sct2);
310 0         0 return $result;
311             }
312              
313             =head2 $structure->_pluseq( $otherstructure )
314              
315             Internal method for returning two structures.
316              
317             =cut
318             sub _pluseq {
319 0     0   0 my ($sct1, $sct2, %op) = @_;
320 0         0 return _sctdeal(
321             $sct1,
322             $sct2,
323             \&_plus_array,
324             \&_plus_hash,
325             \&_plus_scalar,
326             %op,
327             );
328             }
329              
330             =head2 $structure->_plus_hash( $otherstructure )
331              
332             Return the current hash plus another hash
333              
334             =cut
335             sub _plus_hash {
336 0     0   0 my ($sct1, $sct2, %op) = @_;
337 0         0 foreach (keys(%{$sct2})) {
  0         0  
338 0 0       0 if(defined($sct1->{$_})) {
339 0         0 _pluseq($sct1->{$_}, $sct2->{$_}, %op);
340             } else {
341 0         0 $sct1->{$_} = _clone($sct2->{$_});
342             }
343             }
344 0         0 return $sct1;
345             }
346              
347             =head2 $structure->_plus_array( $otherstructure )
348              
349             Return the current array plus another array
350              
351             =cut
352             sub _plus_array {
353 0     0   0 my ($sct1, $sct2, %op) = @_;
354             # Array would simply clone all the elements
355 0         0 foreach (@{$sct2}) {
  0         0  
356 0         0 push @{$sct1}, _clone($_);
  0         0  
357             }
358 0         0 return;
359             }
360              
361             =head2 $structure->_plus_scalar( $otherstructure )
362              
363             Deal with conflicting scalar data (atm we ignore)
364              
365             =cut
366             sub _plus_scalar
367             {
368 0     0   0 my ($sct1, $sct2, %op) = @_;
369             # We do not replace
370 0         0 return $sct1;
371             }
372              
373             =head2 $structure->subtract( $otherstructure )
374              
375             Return the current structure minus a sub structure
376              
377             =cut
378             sub subtract {
379 0     0 1 0 my ($sct1, $sct2, %op) = @_;
380 0         0 my $result = _sctclone($sct1);
381 0         0 _subeq($result, $sct2);
382 0         0 return $result;
383             }
384              
385             =head2 $structure->subeq( $otherstructure )
386              
387             Remove a sub structure from the current structure.
388              
389             =cut
390             sub subeq {
391 0     0 1 0 my ($sct1, $sct2, %op) = @_;
392 0         0 return _sctdeal(
393             $sct1,
394             $sct2,
395             \&_sub_array,
396             \&_sub_hash,
397             \&_sub_scalar,
398             %op,
399             );
400             }
401              
402             =head2 $structure->_sub_array( $otherstructure )
403              
404             Remove array elements using structure (NOT FINISHED).
405              
406             =cut
407             sub _sub_array {
408 0     0   0 my ($sct1, $sct2) = @_;
409             # Not finished, this will require
410             # The ability to remove array elements
411             # that are the same as those specified
412             # And this is more useful for hashes
413             # than arrays
414 0         0 return $sct1;
415             }
416              
417             =head2 $structure->_sub_hash( $otherstructure )
418              
419             Return the current hash minus a sub hash
420              
421             =cut
422             sub _sub_hash {
423 0     0   0 my ($sct1, $sct2) = @_;
424 0         0 foreach (%{$sct2}) {
  0         0  
425 0 0       0 if($sct1->{$_}) {
426 0 0       0 if(not defined(subeq($sct1->{$_}, $sct2->{$_}))) {
427 0         0 delete($sct1->{$_});
428             }
429             }
430             }
431 0 0       0 return undef if not keys(%{$sct1});
  0         0  
432 0         0 return $sct1;
433             }
434              
435             =head2 $structure->_sub_scalar( $otherstructure )
436              
437             Remove a scalar so long as it's eq
438              
439             =cut
440             sub _sub_scalar {
441 0     0   0 my ($sct1, $sct2) = @_;
442 0 0       0 return undef if(not defined($sct2));
443 0 0       0 return undef if($sct1 eq $sct2);
444 0         0 return $sct1;
445             }
446              
447             =head2 $structure->_sctref( $otherstructure )
448              
449             Get the structure reference and the object.
450              
451             =cut
452             sub _sctref {
453 58     58   70 my ($sct) = @_;
454 58         75 my $st = $sct;
455 58 100       143 $st = $sct->structure() if(ref($sct) eq "Data::Validate::Structure");
456 58         82 my $ref = ref($st);
457 58         160 return ($st, $ref);
458             }
459              
460             =head2 $structure->_clone( $otherstructure )
461              
462             Make a clone of a structure.
463              
464             =cut
465             sub _clone {
466 0     0   0 my ($sct) = @_;
467 0 0       0 return $sct if not ref($sct);
468 0         0 my ($st, $ref) = _sctref($sct);
469 0         0 my $result;
470 0 0       0 if($ref eq 'ARRAY') {
    0          
471 0         0 $result = [];
472 0         0 foreach (@{$st}) {
  0         0  
473 0         0 push @{$result}, _clone($_);
  0         0  
474             }
475             } elsif($ref eq 'HASH') {
476 0         0 $result = {};
477 0         0 foreach (keys(%{$st})) {
  0         0  
478 0         0 $result->{$_} = _clone($st->{$_});
479             }
480             } else {
481             # This is for all other kinds of objects
482 0         0 $result = $st;
483             }
484 0         0 return $result;
485             }
486              
487             =head2 $structure->_sctclone( $otherstructure )
488              
489             Make a structure object clone.
490              
491             =cut
492             sub _sctclone {
493 0     0   0 my ($sct) = @_;
494 0         0 return Structure->new( Structure => _clone($sct) );
495             }
496              
497             =head2 $structure->_sctdeal( $otherstructure )
498              
499             Sort out each request so that it goes to the right place
500             and so that the comparisons are fair.
501              
502             =cut
503             sub _sctdeal
504             {
505 29     29   83 my ($sct1, $sct2, $arraysub, $hashsub, $othersub, %op) = @_;
506 29         58 my ($st1,$ref1) = _sctref($sct1);
507 29         61 my ($st2,$ref2) = _sctref($sct2);
508              
509 29 50       96 if($ref1 eq $ref2) { # and defined($st1) and defined($st2)) {
510             #return $sct1 if $op{'SkipSame'} and $sct1 eq $sct2;
511 29 50       87 if($ref1 eq "ARRAY") {
    100          
512 0         0 return $arraysub->($st1, $st2, %op);
513             } elsif($ref1 eq "HASH") {
514 5         24 return $hashsub->($st1, $st2, %op);
515             } else {
516 24         74 return $othersub->($st1, $st2, %op);
517             }
518             }
519              
520             }
521              
522             =head1 AUTHOR
523              
524             Copyright, Martin Owens 2005-2008, Affero General Public License (AGPL)
525              
526             http://www.fsf.org/licensing/licenses/agpl-3.0.html
527              
528             =cut
529             1;