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   9758 use strict;
  3         6  
  3         116  
6 3     3   18 use warnings;
  3         6  
  3         94  
7 3     3   4194 use Log::Log4perl qw(:easy);
  3         201097  
  3         22  
8 3     3   6019 use Template;
  3         89652  
  3         98  
9 3     3   3523 use Data::Dumper;
  3         24333  
  3         250  
10 3     3   4048 use Safe;
  3         140697  
  3         4416  
11              
12             our $VERSION = "0.05";
13             our %OPS = map { $_ => 1 }
14             qw(eq ne lt gt < > <= >= == =~ like);
15              
16             ###########################################
17             sub new {
18             ###########################################
19 67     67 0 24607 my($class, %options) = @_;
20              
21 67         366 my $self = {
22             safe => Safe->new(),
23             template => Template->new(),
24             error => "",
25             %options,
26             };
27              
28 67         175094 $self->{safe}->permit();
29              
30 67         662 bless $self, $class;
31             }
32              
33             ###########################################
34             sub interpolate {
35             ###########################################
36 251     251 0 5186 my($self, $input, $vars) = @_;
37              
38 251 100       575 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         135 my %dupe = %$input;
42 35         63 my @keyvals = ();
43 35         84 for my $entry (each %dupe) {
44 70         162 push @keyvals, $self->interpolate( $entry, $vars );
45             }
46 35         186 return { @keyvals };
47             }
48              
49 216         231 my $out;
50 216         324 $input =~ s/(?:\${([\w.]+)})/[%- $1 %]/gx;
51 216         584 $input =~ s/(?:\$([\w.]+)) /[%- $1 %]/gx;
52              
53 216 50       800 $self->{template}->process( \$input, $vars, \$out ) or
54             LOGDIE $self->{template}->error();
55              
56 216         289305 return $out;
57             }
58              
59             ###########################################
60             sub evaluate {
61             ###########################################
62 91     91 0 4730 my($self, $data, $vars, $not_glob, $boolean_or) = @_;
63              
64 91 50       252 $not_glob = 0 unless defined $not_glob;
65 91 100       205 $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         615 };
74              
75 91 50       1108 if( ref($data) eq "ARRAY" ) {
76 91         244 my @data = @$data; # make a copy, so splice() doesn't destroy
77             # the original.
78 91         318 while( my($field, $value) = splice @data, 0, 2 ) {
79 110         151 my $res;
80              
81             my $not;
82              
83 110 50       249 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       322 if($field =~ s/^!//) {
89 10         19 $not = !$not_glob;
90             }
91              
92 110 100       312 if($field eq "or") {
    100          
93 18         58 $res = $self->evaluate($value, $vars, $not, 1);
94             } elsif( $field eq "and") {
95 8         29 $res = $self->evaluate($value, $vars, $not);
96             } else {
97 84         230 $field = $self->interpolate($field, $vars);
98 84         228 $value = $self->interpolate($value, $vars);
99              
100 84 100       350 if(ref($value) eq "") {
    50          
101 49         161 $res = $self->evaluate_single( $field, $value, "eq", $not );
102             } elsif(ref($value) eq "HASH") {
103 35         100 my($op) = keys %$value;
104 35         81 ($value) = values %$value;
105 35         162 $res = $self->evaluate_single( $field, $value, $op, $not );
106             }
107             }
108              
109 109 100 100     960 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         4 DEBUG "evaluate: rc=$rc";
113 1         8 return $rc;
114             } elsif(!$boolean_or and !$res) {
115             # It's a boolean AND, so all it takes is one false result
116 64         94 my $rc = 0;
117 64         281 DEBUG "evaluate: rc=$rc";
118 64         594 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         44 my $rc = 1;
128 25 100       64 $rc = 0 if $boolean_or;
129 25         104 DEBUG "evaluate: rc=$rc";
130 25         251 return $rc;
131             }
132              
133             ###########################################
134             sub evaluate_single {
135             ###########################################
136 84     84 0 205 my($self, $field, $value, $op, $not) = @_;
137              
138 84         196 $op = lc $op ;
139 84 100       238 $op = '=~' if $op eq "like";
140              
141 84         275 $self->error("");
142              
143 84 50       272 if(! exists $OPS{ $op }) {
144 0         0 LOGDIE "Unknown op: $op";
145             }
146              
147 84         206 $field = '"' . esc($field) . '"';
148 84         135 my $cmd;
149              
150 84 100       189 if($op eq "=~") {
151 21 100       64 if($value =~ /\?\{/) {
152 1         5 LOGDIE "Trapped ?{ in regex.";
153             }
154 20         119 $value =~ s#(\\\\|/)#\\$1#g;
155             # If we ever get something like \\/, slap another backslash
156             # onto the "/" to mask it.
157 20 100       54 $value =~ s#(\\+)/# (length($1) % 2) ? "$1/" : "$1\\/"#ge;
  12         52  
158 20         197 $value = qr($value);
159 20         64 $cmd = "$field =~ /$value/";
160             } else {
161 63         123 $value = '"' . esc($value) . '"';
162 63         150 $cmd = "$field $op $value";
163             }
164              
165 83         359 INFO "Test: $cmd";
166 83         997 my $res = $self->{safe}->reval($cmd);
167              
168 83 50       43865 if($@) {
169 0         0 LOGDIE "Evaling [$cmd] failed: $@";
170             }
171              
172 83 100 66     627 if(!$res and !$not or
      33        
      66        
173             $res and $not) {
174 73 50       182 $res = "" if !defined $res;
175 73         292 $self->error("Test [$cmd] returned [$res]");
176             }
177              
178 83 100       330 return ($not ? (!$res) : $res);
179             }
180              
181             ###########################################
182             sub error {
183             ###########################################
184 204     204 0 69333 my($self, $error) = @_;
185              
186 204 100       595 if(defined $error) {
187 157         307 $self->{error} = $error;
188             }
189              
190 204         3066 return $self->{error};
191             }
192              
193             ###############################################
194             sub esc {
195             ###############################################
196 147     147 0 231 my($str, $metas) = @_;
197              
198 147         306 $str =~ s/([\\"])/\\$1/g;
199              
200 147 50       369 if(defined $metas) {
201 0         0 $metas =~ s/\]/\\]/g;
202 0         0 $str =~ s/([$metas])/\\$1/g;
203             }
204              
205 147         395 return $str;
206             }
207              
208             1;
209              
210             __END__