File Coverage

blib/lib/Circle/Rule/Store.pm
Criterion Covered Total %
statement 70 207 33.8
branch 5 50 10.0
condition 1 5 20.0
subroutine 19 42 45.2
pod 0 30 0.0
total 95 334 28.4


line stmt bran cond sub pod time code
1             # You may distribute under the terms of the GNU General Public License
2             #
3             # (C) Paul Evans, 2008-2010 -- leonerd@leonerd.org.uk
4              
5             package Circle::Rule::Store;
6              
7 4     4   22 use strict;
  4         7  
  4         92  
8 4     4   18 use warnings;
  4         6  
  4         137  
9              
10             our $VERSION = '0.173320';
11              
12 4     4   17 use Carp;
  4         7  
  4         389  
13              
14 4     4   1410 use Circle::Rule::Chain;
  4         9  
  4         92  
15 4     4   21 use Circle::Rule::Resultset;
  4         5  
  4         91  
16              
17 4     4   2334 use Text::Balanced qw( extract_bracketed );
  4         33949  
  4         286  
18              
19 4     4   30 use Attribute::Storage qw( get_subattrs );
  4         8  
  4         28  
20              
21             #############################################
22             ### Attribute handlers for command_* subs ###
23             #############################################
24              
25             sub Rule_description :ATTR(CODE)
26             {
27 64     64 0 9020 my $class = shift;
28 64         124 my ( $text ) = @_;
29              
30 64         149 return $text;
31 4     4   301 }
  4         8  
  4         154  
32              
33             sub Rule_format :ATTR(CODE)
34             {
35 64     64 0 6456 my $class = shift;
36 64         115 my ( $format ) = @_;
37              
38 64         151 return $format;
39 4     4   899 }
  4         8  
  4         46  
40              
41             sub new
42             {
43 5     5 0 15 my $class = shift;
44 5         15 my %args = @_;
45              
46             my $self = bless {
47             cond => {},
48             action => {},
49              
50             parent => $args{parent},
51              
52 5         38 chains => {},
53             }, $class;
54              
55 5         26 $self->register_cond( not => $self );
56 5         19 $self->register_cond( any => $self );
57 5         15 $self->register_cond( all => $self );
58              
59 5         38 return $self;
60             }
61              
62             sub register_cond
63             {
64 18     18 0 23 my $self = shift;
65 18         46 my ( $name, $obj ) = @_;
66              
67 18 50       52 croak "Already have a condition function called $name" if exists $self->{cond}->{$name};
68              
69 18         57 foreach my $method ( "parse_cond_$name", "deparse_cond_$name", "eval_cond_$name" ) {
70 54 50       62 eval { $obj->can( $method ) } or
  54         197  
71             croak "Expected that $obj can $method";
72             }
73              
74 18         69 $self->{cond}->{$name} = { obj => $obj };
75             }
76              
77             sub list_conds
78             {
79 0     0 0 0 my $self = shift;
80 0         0 return ( keys %{ $self->{cond} } ),
81 0 0       0 ( $self->{parent} ? $self->{parent}->list_conds : () );
82             }
83              
84             sub get_cond
85             {
86 0     0 0 0 my $self = shift;
87 0         0 my ( $name ) = @_;
88              
89 0 0       0 return $self->{cond}->{$name} if $self->{cond}->{$name};
90 0 0       0 return $self->{parent}->get_cond( $name ) if $self->{parent};
91              
92 0         0 die "No such condition '$name'\n";
93             }
94              
95             sub parse_cond
96             {
97 0     0 0 0 my $self = shift;
98             # my ( $spec ) = @_ but we'll use $_[0] for alias
99              
100 0 0       0 $_[0] =~ s/^(\w+)\s*// or die "Expected a condition name\n";
101 0         0 my $condname = $1;
102              
103 0         0 my $cond = $self->get_cond( $condname );
104              
105 0         0 my $condspec;
106 0 0       0 if( $_[0] =~ m/^\(/ ) {
107 0         0 $condspec = extract_bracketed( $_[0], q{("')} );
108 0 0       0 defined $condspec or die "Bad argument spec '$condspec' for condition $condname\n";
109 0         0 s/^\(\s*//, s/\s*\)$// for $condspec;
110             }
111              
112 0         0 my $method = "parse_cond_$condname";
113              
114 0         0 my @condargs = eval { $cond->{obj}->$method( $condspec ) };
  0         0  
115 0 0       0 if( $@ ) {
116 0         0 my $err = $@; chomp $err;
  0         0  
117 0         0 die "$err while parsing condition spec '$condspec' for $condname\n";
118             }
119              
120 0         0 return [ $condname, @condargs ];
121             }
122              
123             sub deparse_cond
124             {
125 0     0 0 0 my $self = shift;
126 0         0 my ( $condref ) = @_;
127              
128 0         0 my ( $name, @args ) = @$condref;
129              
130 0         0 my $cond = $self->get_cond( $name );
131              
132 0         0 my $method = "deparse_cond_$name";
133 0         0 my $argspec = $cond->{obj}->$method( @args );
134              
135 0 0       0 return defined $argspec ? "$name($argspec)" : $name;
136             }
137              
138             sub eval_cond
139             {
140 0     0 0 0 my $self = shift;
141 0         0 my ( $condref, $event, $results ) = @_;
142              
143 0         0 my ( $name, @args ) = @$condref;
144              
145 0         0 my $cond = $self->get_cond( $name );
146              
147 0         0 my $method = "eval_cond_$name";
148 0         0 return $cond->{obj}->$method( $event, $results, @args );
149             }
150              
151             sub describe_cond
152             {
153 0     0 0 0 my $self = shift;
154 0         0 my ( $name ) = @_;
155              
156 0         0 my $cond = $self->get_cond( $name );
157              
158 0         0 my $attrs = get_subattrs( $cond->{obj}->can( "parse_cond_$name" ) );
159            
160             return {
161             desc => $attrs->{Rule_description},
162             format => $attrs->{Rule_format},
163 0         0 };
164             }
165              
166             sub register_action
167             {
168 17     17 0 24 my $self = shift;
169 17         27 my ( $name, $obj ) = @_;
170              
171 17 50       36 croak "Already have a action function called $name" if exists $self->{action}->{$name};
172              
173 17         55 foreach my $method ( "parse_action_$name", "deparse_action_$name", "eval_action_$name" ) {
174 51 50       60 eval { $obj->can( $method ) } or
  51         157  
175             croak "Expected that $obj can $method";
176             }
177              
178 17         60 $self->{action}->{$name} = { obj => $obj };
179             }
180              
181             sub list_actions
182             {
183 0     0 0 0 my $self = shift;
184 0         0 return ( keys %{ $self->{action} } ),
185 0 0       0 ( $self->{parent} ? $self->{parent}->list_actions : () );
186             }
187              
188             sub get_action
189             {
190 0     0 0 0 my $self = shift;
191 0         0 my ( $name ) = @_;
192              
193 0 0       0 return $self->{action}->{$name} if $self->{action}->{$name};
194 0 0       0 return $self->{parent}->get_action( $name ) if $self->{parent};
195              
196 0         0 die "No such action '$name'\n";
197             }
198              
199             sub parse_action
200             {
201 0     0 0 0 my $self = shift;
202             # my ( $spec ) = @_ but we'll use $_[0] for alias
203              
204 0 0       0 $_[0] =~ s/^(\w+)\s*// or die "Expected an action name, found '$_[0]'\n";
205 0         0 my $actionname = $1;
206              
207 0         0 my $action = $self->get_action( $actionname );
208              
209 0         0 my $actionspec;
210 0 0       0 if( $_[0] =~ m/^\(/ ) {
211 0         0 $actionspec = extract_bracketed( $_[0], q{("')} );
212 0 0       0 defined $actionspec or die "Bad argument spec '$actionspec' for action $actionname\n";
213 0         0 s/^\(\s*//, s/\s*\)$// for $actionspec;
214             }
215              
216 0         0 my $method = "parse_action_$actionname";
217              
218 0         0 my @actionargs = eval { $action->{obj}->$method( $actionspec ) };
  0         0  
219 0 0       0 if( $@ ) {
220 0         0 my $err = $@; chomp $err;
  0         0  
221 0         0 die "$err while parsing condition spec '$actionspec' for $actionname\n";
222             }
223              
224 0         0 return [ $actionname, @actionargs ];
225             }
226              
227             sub deparse_action
228             {
229 0     0 0 0 my $self = shift;
230 0         0 my ( $actionref ) = @_;
231              
232 0         0 my ( $name, @args ) = @$actionref;
233              
234 0         0 my $action = $self->get_action( $name );
235              
236 0         0 my $method = "deparse_action_$name";
237 0         0 my $argspec = $action->{obj}->$method( @args );
238              
239 0 0       0 return defined $argspec ? "$name($argspec)" : $name;
240             }
241              
242             sub eval_action
243             {
244 0     0 0 0 my $self = shift;
245 0         0 my ( $actionref, $event, $results ) = @_;
246              
247 0         0 my ( $name, @args ) = @$actionref;
248              
249 0         0 my $action = $self->get_action( $name );
250              
251 0         0 my $method = "eval_action_$name";
252 0         0 return $action->{obj}->$method( $event, $results, @args );
253             }
254              
255             sub describe_action
256             {
257 0     0 0 0 my $self = shift;
258 0         0 my ( $name ) = @_;
259              
260 0         0 my $action = $self->get_action( $name );
261              
262 0         0 my $attrs = get_subattrs( $action->{obj}->can( "parse_action_$name" ) );
263            
264             return {
265             desc => $attrs->{Rule_description},
266             format => $attrs->{Rule_format},
267 0         0 };
268             }
269              
270             sub new_chain
271             {
272 6     6 0 11 my $self = shift;
273 6         9 my ( $name ) = @_;
274              
275 6   33     33 $self->{chains}->{$name} ||= Circle::Rule::Chain->new( $self );
276             }
277              
278             sub chains
279             {
280 0     0 0 0 my $self = shift;
281 0         0 return keys %{ $self->{chains} };
  0         0  
282             }
283              
284             sub get_chain
285             {
286 0     0 0 0 my $self = shift;
287 0         0 my ( $chainname ) = @_;
288              
289 0   0     0 return $self->{chains}->{$chainname} || die "No such rulechain called $chainname\n";
290             }
291              
292             sub run
293             {
294 4     4 0 12 my $self = shift;
295 4         9 my ( $chainname, $event ) = @_;
296              
297 4 50       27 my $chain = $self->{chains}->{$chainname} or die "No such rulechain called $chainname\n";
298              
299 4         37 $chain->run( $event );
300             }
301              
302             # Internal rules for boolean logic
303              
304             sub parse_cond_not
305             : Rule_description("Invert the sense of a sub-condition")
306             : Rule_format('condition')
307             {
308 0     0 0 0 my $self = shift;
309 0         0 my ( $spec ) = @_;
310              
311 0         0 return $self->parse_cond( $spec );
312 4     4   7304 }
  4         8  
  4         16  
313              
314             sub deparse_cond_not
315             {
316 0     0 0   my $self = shift;
317 0           my ( $cond ) = @_;
318              
319 0           return $self->deparse_cond( $cond );
320             }
321              
322             sub eval_cond_not
323             {
324 0     0 0   my $self = shift;
325 0           my ( $event, $results, $cond ) = @_;
326              
327             # Construct a new result set which we throw away
328 0           return not $self->eval_cond( $cond, $event, Circle::Rule::Resultset->new() );
329             }
330              
331             sub parse_cond_any
332             : Rule_description("Check if any sub-condition is true")
333             : Rule_format('condition ...')
334             {
335 0     0 0 0 my $self = shift;
336 0         0 my ( $spec ) = @_;
337              
338 0         0 my @conds;
339 0         0 while( length $spec ) {
340 0         0 push @conds, $self->parse_cond( $spec );
341              
342 0         0 $spec =~ s/\s+//; # trim ws
343             }
344              
345 0 0       0 @conds or die "Expected at least one condition\n";
346              
347 0         0 return @conds;
348 4     4   1081 }
  4         14  
  4         17  
349              
350             sub deparse_cond_any
351             {
352 0     0 0   my $self = shift;
353 0           my ( @conds ) = @_;
354              
355 0           return join( " ", map { $self->deparse_cond( $_ ) } @conds );
  0            
356             }
357              
358             sub eval_cond_any
359             {
360 0     0 0   my $self = shift;
361 0           my ( $event, $results, @conds ) = @_;
362              
363 0           foreach my $cond ( @conds ) {
364 0 0         return 1 if $self->eval_cond( $cond, $event, $results );
365             }
366              
367 0           return 0;
368             }
369              
370             sub parse_cond_all
371             : Rule_description("Check if all sub-conditions are true")
372             : Rule_format('condition ...')
373             {
374 0     0 0 0 my $self = shift;
375 0         0 my ( $spec ) = @_;
376              
377 0         0 my @conds;
378 0         0 while( length $spec ) {
379 0         0 push @conds, $self->parse_cond( $spec );
380              
381 0         0 $spec =~ s/\s+//; # trim ws
382             }
383              
384 0 0       0 @conds or die "Expected at least one condition\n";
385              
386 0         0 return @conds;
387 4     4   989 }
  4         8  
  4         14  
388              
389             sub deparse_cond_all
390             {
391 0     0 0   my $self = shift;
392 0           my ( @conds ) = @_;
393              
394 0           return join( " ", map { $self->deparse_cond( $_ ) } @conds );
  0            
395             }
396              
397             sub eval_cond_all
398             {
399 0     0 0   my $self = shift;
400 0           my ( $event, $results, @conds ) = @_;
401              
402             # Construct sub-results because we don't want any results to apply if a
403             # later failure causes us to fail after an earlier cond was successful and
404             # stored results
405 0           my $subresults = Circle::Rule::Resultset->new();
406              
407 0           foreach my $cond ( @conds ) {
408 0 0         return 0 unless $self->eval_cond( $cond, $event, $subresults );
409             }
410              
411 0           $results->merge_from( $subresults );
412 0           return 1;
413             }
414              
415             0x55AA;