File Coverage

blib/lib/Data/Classifier.pm
Criterion Covered Total %
statement 79 100 79.0
branch 24 34 70.5
condition 3 3 100.0
subroutine 11 13 84.6
pod 5 8 62.5
total 122 158 77.2


line stmt bran cond sub pod time code
1             package Data::Classifier;
2              
3             our $VERSION = '0.01';
4              
5 1     1   40507 use strict;
  1         2  
  1         39  
6 1     1   5 use warnings;
  1         3  
  1         32  
7              
8 1     1   6 use Carp qw(croak);
  1         6  
  1         196  
9 1     1   989 use YAML qw(LoadFile Load);
  1         15850  
  1         79  
10              
11 1     1   982 use Data::Classifier::Result;
  1         2  
  1         1374  
12              
13             #public interface
14             sub new {
15 1     1 0 13 my ($class, %opts) = @_;
16 1         2 my $self = {};
17              
18 1         2 bless($self, $class);
19              
20 1 50       6 if (defined($opts{file})) {
    50          
    0          
21 0         0 $self->{TREE} = LoadFile($opts{file});
22             } elsif (defined($opts{yaml})) {
23 1         6 $self->{TREE} = Load($opts{yaml});
24             } elsif (defined($opts{tree})) {
25 0         0 $self->{TREE} = $opts{tree};
26             } else {
27 0         0 croak "You must specify one of file, yaml, or tree";
28             }
29              
30 1         20987 $self->{DEBUG} = $opts{debug};
31              
32 1         8 return $self;
33             }
34              
35             sub process {
36 3     3 1 21 my ($self, $attributes) = @_;
37 3         4 my ($result, $result_class);
38              
39 3         10 $self->debug("starting classification");
40              
41 3         5 $self->{CLASS_STACK} = [];
42              
43 3         9 $self->recursive_search($attributes, $self->{TREE});
44              
45 3         5 $result = $self->{CLASS_STACK};
46              
47 3         5 $self->{CLASS_STACK} = undef;
48              
49 3         11 $self->debug("classification done\n\n");
50              
51 3         8 return $self->return_result($result);
52             }
53              
54             #this method should be overloaded by base classes to change the class
55             #that is returned by process
56             sub return_result {
57 3     3 1 4 my ($self, $result) = @_;
58 3         16 return Data::Classifier::Result->new($result);
59             }
60              
61             sub dump {
62 0     0 1 0 my ($self) = @_;
63              
64 0         0 return Dumper($self->{TREE});
65             }
66              
67             #private interface
68             sub recursive_search {
69 13     13 1 75 my ($self, $attributes, $node) = @_;
70 13         21 my $name = $node->{name};
71 13         16 my $matchmap = $node->{match};
72 13         13 my $children = $node->{children};
73 13         14 my $class_stack = $self->{CLASS_STACK};
74 13         46 my $node_match = 0;
75 13         13 my $recurse_match = 0;
76 13         13 my $generic_match = 0;
77              
78 13         32 $self->debug("testing $name");
79              
80 13         22 push(@$class_stack, $node);
81            
82 13 100       29 if (! defined($matchmap)) {
    50          
83             #no rules for this class, so we will be a member of it only if a lower
84             #rule applies
85 6         5 $node_match = 1;
86 6         7 $generic_match = 1;
87             } elsif (ref($matchmap) ne 'HASH') {
88 0         0 $self->tree_error("match was not a map");
89             } else {
90 7 100       15 if ($self->check_match($matchmap, $attributes)) {
91 2         2 $node_match = 1;
92             }
93             }
94              
95 13 100       27 if ($node_match) {
96             #check the children for a more specific class
97 8         14 $self->debug("looking at child classes");
98              
99 8 100       17 if (defined($children)) {
100 6 50       16 if (ref($children) ne 'ARRAY') {
101 0         0 $self->tree_error("children must be a sequence");
102             }
103              
104 6         10 foreach my $child (@$children) {
105 10 100       22 if ($self->recursive_search($attributes, $child)) {
106 4         7 $recurse_match = 1;
107 4         6 last;
108             }
109             }
110             }
111             }
112              
113 13 100 100     51 if ($generic_match && ! $recurse_match) {
    100          
114             #didn't match a lower level class after a generic match, so this is really no match
115 2         3 pop(@$class_stack);
116 2         6 return 0;
117             } elsif (! $node_match) {
118 5         35 pop(@$class_stack);
119 5         16 return 0;
120             }
121              
122 6         14 return 1;
123             }
124              
125             #only return true if everything in $matchlist matches the stuff in $attributes
126             sub check_match {
127 7     7 1 10 my ($self, $matchlist, $attributes) = @_;
128 7         7 my $match = 0;
129              
130             #no idea why this has to be here, but with out it, matches fail
131             #for very odd reasons - not sure if it's my bug or a perl bug, but it's
132             #very strange
133 7         9 keys(%$matchlist);
134              
135 7         22 while(my ($attribute, $regex) = each(%$matchlist)) {
136 8         13 my $to_test = $attributes->{$attribute};
137              
138 8 50       14 if (! defined($to_test)) {
139 0         0 $self->debug("nothing to test");
140 0         0 return 0;
141             }
142              
143 8 50       16 if (! defined($regex)) {
144 0         0 $self->debug("no regex");
145 0         0 die "regex";
146             }
147              
148 8 100       109 if ($to_test !~ m/$regex/) {
149 5         12 $self->debug("match failure");
150 5         15 return 0;
151             }
152              
153 3         11 $self->debug("testing data $attribute $regex '$to_test'");
154              
155 3         9 $match = 1;
156             }
157              
158 2 50       4 if ($match) {
159 2         6 $self->debug("success");
160              
161 2         6 return 1;
162             }
163              
164 0         0 $self->debug("fell through with no matches");
165              
166 0         0 return 0;
167             }
168              
169             sub tree_error {
170 0     0 0 0 my ($self, $msg) = @_;
171 0         0 my $class_stack = $self->{CLASS_STACK};
172 0         0 my @names;
173              
174 0         0 foreach my $one (@$class_stack) {
175 0         0 push(@names, $one->{name});
176             }
177              
178 0         0 $self->debug("ERROR: Class tree was not consistent: $msg\n");
179 0         0 $self->debug("\tClass path: ", join('::', @names), "\n");
180              
181 0         0 die "can not continue after class tree error";
182             }
183              
184             sub debug {
185 37     37 0 53 my ($self, $msg) = @_;
186 37 50       92 print STDERR "DEBUG: $msg\n" if $self->{DEBUG};
187             }
188              
189             1;
190              
191             __END__