File Coverage

blib/lib/YAML/Logic.pm
Criterion Covered Total %
statement 98 105 93.3
branch 41 50 82.0
condition 11 15 73.3
subroutine 12 13 92.3
pod 0 6 0.0
total 162 189 85.7


line stmt bran cond sub pod time code
1             ###########################################
2             package YAML::Logic;
3             ###########################################
4              
5 3     3   6203 use strict;
  3         4  
  3         81  
6 3     3   9 use warnings;
  3         3  
  3         86  
7 3     3   2688 use Log::Log4perl qw(:easy);
  3         128224  
  3         15  
8 3     3   3063 use Template;
  3         58007  
  3         96  
9 3     3   1883 use Data::Dumper;
  3         16159  
  3         195  
10 3     3   1636 use Safe;
  3         89952  
  3         3756  
11              
12             our $VERSION = "0.07";
13             our %OPS = map { $_ => 1 }
14             qw(eq ne lt gt < > <= >= == =~ like);
15              
16             ###########################################
17             sub new {
18             ###########################################
19 67     67 0 18879 my($class, %options) = @_;
20              
21 67         299 my $self = {
22             safe => Safe->new(),
23             template => Template->new(),
24             error => "",
25             %options,
26             };
27              
28 67         134742 $self->{safe}->permit();
29              
30 67         623 bless $self, $class;
31             }
32              
33             ###########################################
34             sub interpolate {
35             ###########################################
36 251     251 0 6205 my($self, $input, $vars) = @_;
37              
38 251 100       533 if(ref($input) eq "HASH") {
39             # When working on the original, we got weird memory errors
40             # in perl5.8, so just copy the hash.
41 35         120 my %dupe = %$input;
42 35         63 my @keyvals = ();
43 35         77 for my $entry (each %dupe) {
44 70         151 push @keyvals, $self->interpolate( $entry, $vars );
45             }
46 35         138 return { @keyvals };
47             }
48              
49 216         219 my $out;
50 216         365 $input =~ s/(?:\$\{([\w.]+)})/[%- $1 %]/gx;
51 216         566 $input =~ s/(?:\$([\w.]+)) /[%- $1 %]/gx;
52              
53             $self->{template}->process( \$input, $vars, \$out ) or
54 216 50       728 LOGDIE $self->{template}->error();
55              
56 216         214307 return $out;
57             }
58              
59             ###########################################
60             sub evaluate {
61             ###########################################
62 91     91 0 3789 my($self, $data, $vars, $not_glob, $boolean_or) = @_;
63              
64 91 50       241 $not_glob = 0 unless defined $not_glob;
65 91 100       167 $boolean_or = 0 unless defined $boolean_or;
66              
67 0     0   0 DEBUG sub { "evaluate: " .
68             Dumper( $data ) . "\n" .
69             Dumper( $vars ) . "\n" .
70             "not_glob=$not_glob " .
71             "boolean_or=$boolean_or " .
72             ""
73 91         520 };
74              
75 91 50       901 if( ref($data) eq "ARRAY" ) {
76 91         204 my @data = @$data; # make a copy, so splice() doesn't destroy
77             # the original.
78 91         300 while( my($field, $value) = splice @data, 0, 2 ) {
79 110         103 my $res;
80              
81             my $not;
82              
83 110 50       224 if(! defined $field) {
84 0         0 LOGDIE "Rule variable not defined (value=$value)",
85             "(maybe YAML rule: !\$var without quotes?";
86             }
87              
88 110 100       295 if($field =~ s/^!//) {
89 10         18 $not = !$not_glob;
90             }
91              
92 110 100       281 if($field eq "or") {
    100          
93 18         33 $res = $self->evaluate($value, $vars, $not, 1);
94             } elsif( $field eq "and") {
95 8         32 $res = $self->evaluate($value, $vars, $not);
96             } else {
97 84         161 $field = $self->interpolate($field, $vars);
98 84         191 $value = $self->interpolate($value, $vars);
99              
100 84 100       239 if(ref($value) eq "") {
    50          
101 49         114 $res = $self->evaluate_single( $field, $value, "eq", $not );
102             } elsif(ref($value) eq "HASH") {
103 35         83 my($op) = keys %$value;
104 35         66 ($value) = values %$value;
105 35         89 $res = $self->evaluate_single( $field, $value, $op, $not );
106             }
107             }
108              
109 109 100 100     792 if($boolean_or and $res) {
    100 100        
110             # It's a boolean OR, so all it takes is one true result
111 1         2 my $rc = 1;
112 1         6 DEBUG "evaluate: rc=$rc";
113 1         9 return $rc;
114             } elsif(!$boolean_or and !$res) {
115             # It's a boolean AND, so all it takes is one false result
116 64         73 my $rc = 0;
117 64         233 DEBUG "evaluate: rc=$rc";
118 64         563 return $rc;
119             }
120             }
121             } else {
122 0         0 LOGDIE "Unknown type: $data";
123             }
124              
125             # Return 1 if all ANDed conditions succeeded, and 0 if all
126             # ORed conditions failed.
127 25         30 my $rc = 1;
128 25 100       47 $rc = 0 if $boolean_or;
129 25         87 DEBUG "evaluate: rc=$rc";
130 25         189 return $rc;
131             }
132              
133             ###########################################
134             sub evaluate_single {
135             ###########################################
136 84     84 0 151 my($self, $field, $value, $op, $not) = @_;
137              
138 84         120 $op = lc $op ;
139 84 100       185 $op = '=~' if $op eq "like";
140              
141 84         143 $self->error("");
142              
143 84 50       192 if(! exists $OPS{ $op }) {
144 0         0 LOGDIE "Unknown op: $op";
145             }
146              
147 84         167 $field = '"' . esc($field) . '"';
148 84         85 my $cmd;
149              
150 84 100       142 if($op eq "=~") {
151 21 100       54 if($value =~ /\?\{/) {
152 1         3 LOGDIE "Trapped ?{ in regex.";
153             }
154 20         90 $value =~ s#(\\\\|/)#\\$1#g;
155             # If we ever get something like \\/, slap another backslash
156             # onto the "/" to mask it.
157 20 100       44 $value =~ s#(\\+)/# (length($1) % 2) ? "$1/" : "$1\\/"#ge;
  12         47  
158 20         200 $value = qr($value);
159 20         54 $cmd = "$field =~ /$value/";
160             } else {
161 63         82 $value = '"' . esc($value) . '"';
162 63         112 $cmd = "$field $op $value";
163             }
164              
165 83         274 INFO "Test: $cmd";
166 83         724 my $res = $self->{safe}->reval($cmd);
167              
168 83 50       35246 if($@) {
169 0         0 LOGDIE "Evaling [$cmd] failed: $@";
170             }
171              
172 83 100 66     537 if(!$res and !$not or
      33        
      66        
173             $res and $not) {
174 73 50       154 $res = "" if !defined $res;
175 73         226 $self->error("Test [$cmd] returned [$res]");
176             }
177              
178 83 100       255 return ($not ? (!$res) : $res);
179             }
180              
181             ###########################################
182             sub error {
183             ###########################################
184 204     204 0 54704 my($self, $error) = @_;
185              
186 204 100       392 if(defined $error) {
187 157         237 $self->{error} = $error;
188             }
189              
190 204         2284 return $self->{error};
191             }
192              
193             ###############################################
194             sub esc {
195             ###############################################
196 147     147 0 176 my($str, $metas) = @_;
197              
198 147         256 $str =~ s/([\\"])/\\$1/g;
199              
200 147 50       275 if(defined $metas) {
201 0         0 $metas =~ s/\]/\\]/g;
202 0         0 $str =~ s/([$metas])/\\$1/g;
203             }
204              
205 147         278 return $str;
206             }
207              
208             1;
209              
210             __END__