File Coverage

blib/lib/List/Filter.pm
Criterion Covered Total %
statement 82 112 73.2
branch 0 2 0.0
condition 0 3 0.0
subroutine 21 28 75.0
pod 19 19 100.0
total 122 164 74.3


line stmt bran cond sub pod time code
1             package List::Filter;
2 4     4   137574 use base qw( Class::Base );
  4         10  
  4         4172  
3             # doom@kzsu.stanford.edu
4             # 07 Mar 2007
5              
6             =head1 NAME
7              
8             List::Filter - named, persistent, shared lists of patterns
9              
10             =head1 SYNOPSIS
11              
12             use List::Filter;
13              
14             my $filter = List::Filter->new(
15             { name => 'skip_boring_stuff',
16             terms => ['-\.vb$', '\-.js$'],
17             method => 'skip_boring_stuff',
18             description => "Skip the really boring stuff",
19             modifiers => "xi",
20             } );
21              
22             # If non-standard behavior is desired in locating the methods via plugins
23             my $filter = List::Filter->new(
24             { name => 'skip_boring_stuff',
25             terms => ['-\.vb$', '\-.js$'],
26             method => 'skip_boring_stuff',
27             description => "Skip the really boring stuff",
28             modifiers => "xi",
29             plugin_root => 'List::Filter::Filters',
30             plugin_exceptions => ["List::Filter::Transforms::NotThisOne"],
31              
32             } );
33              
34              
35             # Alternately:
36             my $filter = List::Filter->new(); # creates an *empty* filter
37              
38             my @terms = ['-\.vb$', '-\.js$'];
39             $filter->set_name('skip_dull');
40             $filter->set_terms( \@terms );
41             $filter->set_method('skip_boring_stuff');
42             $filter->set_description(
43             "Skip the really boring stuff");
44             $filter->set_modifiers( "xi" );
45              
46              
47             # using a filter (using it's internally defined "method")
48             my $output_items = $filter->apply( \@input_items );
49              
50             # using a filter, specifying an alternate "method"
51             my $output_items = $filter->apply( \@input_items, "do_it_like_this" );
52              
53              
54              
55             =head1 DESCRIPTION
56              
57             The List::Filter system is a generalized, extensible way of
58             filtering a list of items by apply a stack of perl regular
59             expressions, with a persistant storage mechanism to allow
60             the sharing of filters between different applications.
61              
62             A List::Filter filter would just be a container object (a hashref
63             with some accessor code), except that it also has an internally
64             generated "dispatcher" object, so that it knows how to "apply"
65             itself.
66              
67             The "method" attribute of a filter object is indeed the name of
68             a method, but not one defined inside this module. Instead
69             there's a "plug-in" system that allows the definition of new
70             methods without modification of the existing code.
71              
72             See L for documentation of the system.
73              
74             =head1 OBJECT ATTRIBUTES
75              
76             =head2 filter attributes (stored associated with the given name)
77              
78             =over
79              
80             =item name
81              
82             The name of the search filter.
83              
84             =item terms
85              
86             A list of filter items, e.g. search terms (essentially regexps).
87              
88             =item method
89              
90             The default method used to apply the search terms.
91              
92             =item modifiers
93              
94             Default modifiers to be applied to the search terms (essentially,
95             regexp modifiers, e.g. "i").
96              
97             =item description
98              
99             A short description of the search filter.
100              
101             =back
102              
103             =head2
104              
105             =over
106              
107             =item dispatcher
108              
109             Internally used field that stores the dispatcher object, a handle used to apply
110             the filter according to it's "method".
111              
112             =item storage_handler
113              
114             ### TODO weirdly enough, I can't figure out where this gets set.
115             ### if it isn't set, then the save method can't work.
116             ### but if the following flag is set, the apply method calls
117             ### the save method... do I ever set this flag at this level?
118              
119             =item save_filters_when_used
120              
121             ### TODO
122              
123             =back
124              
125             =head1 METHODS
126              
127             =over
128              
129             =cut
130              
131 4     4   18558 use 5.8.0;
  4         16  
  4         187  
132 4     4   24 use strict;
  4         15  
  4         139  
133 4     4   20 use warnings;
  4         8  
  4         163  
134             my $DEBUG = 1; # zero before ship
135 4     4   23 use Carp;
  4         23  
  4         305  
136 4     4   21 use Data::Dumper;
  4         7  
  4         376  
137 4     4   3423 use Hash::Util qw( unlock_keys lock_keys );
  4         10462  
  4         41  
138              
139 4     4   3663 use List::Filter::Dispatcher;
  4         14  
  4         207  
140 4     4   5257 use Memoize;
  4         18623  
  4         4751  
141             memoize( 'generate_dispatcher' );
142              
143             our $VERSION = '0.04';
144              
145             =item new
146              
147             Instantiates a new List::Filter object.
148              
149             Takes an optional hashref as an argument, with named fields
150             identical to the names of the object attributes:
151              
152             name
153             description
154             terms
155             method
156             modifiers
157              
158             With no arguments, the newly created filter will be empty.
159              
160             There is also the attribute:
161              
162             storage_handler
163              
164             which is intended to point to the storage handler set-up so that
165             the filter has the capbility of saving itself to storage later.
166             See L below.
167              
168             There's a related flag (typically set by the storage handler):
169              
170             save_filters_when_used
171              
172             There are two additional optional arguments,
173              
174             plugin_root
175             plugin_exceptions
176              
177             That are used in creating the dispatcher object which locates the
178             code used to apply the filter (typically as specified by the
179             "method" attribute):
180              
181             L
182              
183             =cut
184              
185             # Note:
186             # "new" is inherited from Class::Base, it calls the following
187             # "init" routine automatically
188              
189             =item init
190              
191             Initialize object attributes and then lock them down to prevent
192             accidental creation of new ones.
193              
194             Note: there is no leading underscore on name "init", though it's
195             arguably an "internal" routine (i.e. not likely to be of use to
196             client code).
197              
198             =cut
199              
200             sub init {
201 28     28 1 6377 my $self = shift;
202 28         51 my $args = shift;
203 28         49 unlock_keys( %{ $self } );
  28         149  
204              
205             # Generate the dispatcher object, used to apply filter's method
206 28         219 my $plugin_root = $args->{ plugin_root };
207 28         54 my $plugin_exceptions = $args->{ plugin_exceptions };
208              
209 28         1027 my $dispatcher = $self->generate_dispatcher(
210             $plugin_root,
211             $plugin_exceptions,
212             );
213              
214             my $attributes = {
215             name => $args->{ name },
216             method => $args->{ method },
217             description => $args->{ description },
218             terms => $args->{ terms },
219             modifiers => $args->{ modifiers },
220             dispatcher => $dispatcher,
221             storage_handler => $args->{ storage_handler },
222             save_filters_when_used => $args->{ save_filters_when_used },
223 28         576 };
224              
225             # add attributes to object
226 28         51 my @fields = (keys %{ $attributes });
  28         155  
227 28         62 @{ $self }{ @fields } = @{ $attributes }{ @fields }; # hash slice
  28         155  
  28         122  
228              
229 28         64 lock_keys( %{ $self } );
  28         100  
230 28         397 return $self;
231             }
232              
233              
234              
235             =item generate_dispatcher
236              
237             Generate the dispatcher object, used to apply a filter's method
238              
239             =cut
240              
241             sub generate_dispatcher {
242             my $self = shift;
243             my $plugin_root = shift;
244             my $plugin_exceptions = shift;
245              
246             my $class = ref $self; # smells funny in here, eh?
247              
248             my $default_plugin_root =
249             {
250             'List::Filter' => 'List::Filter::Filters', # note: irregular naming
251             'List::Filter::Transform' => 'List::Filter::Transform::Internal',
252             };
253              
254             unless( $plugin_root ) {
255             # Convention: unless there's a specified alternative, just use
256             # plural of the class name (i18n? Fergeddhaboudit.)
257             my $default = $default_plugin_root->{ $class } || $class . 's';
258             $plugin_root = $default;
259             }
260              
261             my $dispatcher = List::Filter::Dispatcher->new(
262             { plugin_root => $plugin_root,
263             plugin_exceptions => $plugin_exceptions,
264             } );
265              
266             return $dispatcher;
267             }
268              
269              
270             =back
271              
272             =head2 the stuff that does the Real Work
273              
274             =over
275              
276             =item apply
277              
278             Apply applies the filter object, typically acting as a filter.
279              
280             Inputs:
281             (1) aref of input items to be operated on
282             (2) method to use to apply filter to input items (optional)
283             defaults to method specified inside the filter
284              
285             Return:
286             aref of output items
287              
288             =cut
289              
290             # This is just a wrapper around the dispatcher's "apply".
291             # Note that here the filter creates a dispatcher, which then contains
292             # the filter that created it (ah, OOP 'metaphors'... ).
293             sub apply {
294 0     0 1 0 my $self = shift;
295 0         0 my $items = shift;
296 0   0     0 my $method = shift || $self->method;
297              
298             # save copy of filter to "write_storage" location before using it
299 0 0       0 if ($self->save_filters_when_used) {
300 0         0 $self->save;
301             }
302              
303 0         0 $self->debug( "List::Filter apply: $method used on $items\n" );
304              
305 0         0 my $dispatcher = $self->dispatcher;
306              
307 0         0 my $output_aref
308             = $dispatcher->apply( $self, # heh
309             $items,
310             { method => $method,
311             },
312             );
313              
314 0         0 return $output_aref;
315             }
316              
317             =item save
318              
319             Saves a copy of the filter to the using the storage_handler
320             stored inside the object.
321              
322             =cut
323              
324             sub save {
325 0     0 1 0 my $self = shift;
326 0         0 my $storage_handler = $self->storage_handler;
327              
328 0         0 $storage_handler->save( $self ); # if only it were always this easy...
329              
330 0         0 return $self;
331             }
332              
333             =back
334              
335             =head2 basic setters and getters
336              
337             =over
338              
339             =item name
340              
341             Getter for object attribute name
342              
343             =cut
344              
345             sub name {
346 23     23 1 3120 my $self = shift;
347 23         49 my $name = $self->{ name };
348 23         78 return $name;
349             }
350              
351             =item set_name
352              
353             Setter for object attribute set_name
354              
355             =cut
356              
357             sub set_name {
358 2     2 1 15 my $self = shift;
359 2         4 my $name = shift;
360 2         5 $self->{ name } = $name;
361 2         5 return $name;
362             }
363              
364             =item method
365              
366             Getter for object attribute method
367              
368             =cut
369              
370             sub method {
371 29     29 1 1018 my $self = shift;
372 29         61 my $method = $self->{ method };
373 29         114 return $method;
374             }
375              
376             =item set_method
377              
378             Setter for object attribute set_method
379              
380             =cut
381              
382             sub set_method {
383 2     2 1 15 my $self = shift;
384 2         5 my $method = shift;
385 2         4 $self->{ method } = $method;
386 2         5 return $method;
387             }
388              
389              
390             =item description
391              
392             Getter for object attribute description
393              
394             =cut
395              
396             sub description {
397 25     25 1 3267 my $self = shift;
398 25         58 my $description = $self->{ description };
399 25         96 return $description;
400             }
401              
402             =item set_description
403              
404             Setter for object attribute set_description
405              
406             =cut
407              
408             sub set_description {
409 2     2 1 15 my $self = shift;
410 2         4 my $description = shift;
411 2         7 $self->{ description } = $description;
412 2         8 return $description;
413             }
414              
415              
416             =item terms
417              
418             Getter for object attribute terms
419              
420             =cut
421              
422             sub terms {
423 28     28 1 7261 my $self = shift;
424 28         58 my $terms = $self->{ terms };
425 28         108 return $terms;
426             }
427              
428             =item set_terms
429              
430             Setter for object attribute set_terms
431              
432             =cut
433              
434             sub set_terms {
435 2     2 1 28 my $self = shift;
436 2         4 my $terms = shift;
437 2         6 $self->{ terms } = $terms;
438 2         6 return $terms;
439             }
440              
441              
442             =item modifiers
443              
444             Getter for object attribute modifiers
445              
446             =cut
447              
448             sub modifiers {
449 23     23 1 3504 my $self = shift;
450 23         48 my $modifiers = $self->{ modifiers };
451 23         83 return $modifiers;
452             }
453              
454             =item set_modifiers
455              
456             Setter for object attribute set_modifiers
457              
458             =cut
459              
460             sub set_modifiers {
461 2     2 1 14 my $self = shift;
462 2         4 my $modifiers = shift;
463 2         5 $self->{ modifiers } = $modifiers;
464 2         5 return $modifiers;
465             }
466              
467              
468             =item dispatcher
469              
470             Getter for object attribute dispatcher
471              
472             =cut
473              
474             sub dispatcher {
475 0     0 1 0 my $self = shift;
476 0         0 my $dispatcher = $self->{ dispatcher };
477 0         0 return $dispatcher;
478             }
479              
480             =item set_dispatcher
481              
482             Setter for object attribute set_dispatcher
483              
484             =cut
485              
486             sub set_dispatcher {
487 0     0 1 0 my $self = shift;
488 0         0 my $dispatcher = shift;
489 0         0 $self->{ dispatcher } = $dispatcher;
490 0         0 return $dispatcher;
491             }
492              
493             =item storage_handler
494              
495             Getter for object attribute storage_handler
496              
497             =cut
498              
499             sub storage_handler {
500 0     0 1 0 my $self = shift;
501 0         0 my $storage_handler = $self->{ storage_handler };
502 0         0 return $storage_handler;
503             }
504              
505             =item set_storage_handler
506              
507             Setter for object attribute set_storage_handler
508              
509             =cut
510              
511             sub set_storage_handler {
512 12     12 1 26 my $self = shift;
513 12         28 my $storage_handler = shift;
514 12         23 $self->{ storage_handler } = $storage_handler;
515 12         25 return $storage_handler;
516             }
517              
518              
519             =item save_filters_when_used
520              
521             Getter for object attribute save_filters_when_used
522              
523             =cut
524              
525             sub save_filters_when_used {
526 0     0 1   my $self = shift;
527 0           my $save_filters_when_used = $self->{ save_filters_when_used };
528 0           return $save_filters_when_used;
529             }
530              
531             =item set_save_filters_when_used
532              
533             Setter for object attribute set_save_filters_when_used
534              
535             =cut
536              
537             sub set_save_filters_when_used {
538 0     0 1   my $self = shift;
539 0           my $save_filters_when_used = shift;
540 0           $self->{ save_filters_when_used } = $save_filters_when_used;
541 0           return $save_filters_when_used;
542             }
543              
544              
545              
546              
547             1;
548              
549              
550              
551             =head1 MOTIVATION
552              
553             =head2 Why not just an href?
554              
555             Why do we have List::Filter objects instead of just
556             filter hash references? There's the usual reasoning of using
557             abstraction to preserve flexibility (later, implementation can be
558             changed from href to aref, qualification code might be added to
559             the accessors, and so on).
560              
561             It also makes a convenient place to ensure that a "lock_keys" has
562             been done before the href is used (to help catch typos during
563             development).
564              
565             =head2 Why not a fixed method?
566              
567             A more interesting question is why is there a "method" attribute
568             for each filter? A more standard OOP approach to this kind of
569             polymorphism (each filter is supposed to know it should be used)
570             would be to simply have a class for each type of filter.
571              
572             This would be inelegant for a few reasons:
573              
574             (1) it would make the use of the filters more rigid. the
575             internally specified "method" name is only the default way the
576             filter should be applied, there are cases where you might like
577             to deviate from it (e.g. you might invert an "omit" filter to do
578             a "select" to check just what it is you've been skipping).
579              
580             (2) it would multiply classes for no good reason, and I think it
581             would make it a little clumsier to add new Filter "methods".
582              
583              
584             =head2 the storage handler framework (lookup/save)
585              
586             Each filter can hold a pointer to it's "storage handler", which
587             is intended to be set by the "lookup" method of that handler as
588             the filter is returned. This gives the filter the capability to
589             save itself later, and that's not as crazy as it sounds (not
590             quite) because there's a path of storage locations, and the place
591             it's read from need not be where it's saved to).
592              
593             The way it works normally (?) is that the storage handler
594             instructs the filter that when it is applied it will save a copy
595             of itself. The storage write location is most likely going to be
596             a yaml file that the user has access to, but the storage read
597             location can be somewhere else (e.g. a "standard" filter, which
598             is defined in the code, and hence not writeable). The idea here
599             is that any filter that you've used, you get an accessible copy
600             of, suitable for editing if you'd like to make changes.
601              
602              
603             =head1 SEE ALSO
604              
605             L
606             L
607              
608             =head1 AUTHOR
609              
610             Joseph Brenner, Edoom@kzsu.stanford.eduE
611              
612             =head1 COPYRIGHT AND LICENSE
613              
614             Copyright (C) 2007 by Joseph Brenner
615              
616             This library is free software; you can redistribute it and/or modify
617             it under the same terms as Perl itself, either Perl version 5.8.2 or,
618             at your option, any later version of Perl 5 you may have available.
619              
620             =head1 BUGS
621              
622             None reported... yet.
623              
624             =cut