File Coverage

blib/lib/Switch/Perlish/Smatch.pm
Criterion Covered Total %
statement 66 67 98.5
branch 23 30 76.6
condition 23 29 79.3
subroutine 18 18 100.0
pod 6 6 100.0
total 136 150 90.6


line stmt bran cond sub pod time code
1             package Switch::Perlish::Smatch;
2            
3             $VERSION = '1.0.1';
4            
5             require Exporter;
6             @EXPORT_OK = qw/ smatch value_cmp /;
7             @ISA = 'Exporter';
8            
9 11     11   23080 use strict;
  11         18  
  11         411  
10 11     11   66 use warnings;
  11         21  
  11         325  
11            
12 11     11   63 use vars '%REGISTER';
  11         20  
  11         805  
13 11     11   59 use warnings::register;
  11         17  
  11         2308  
14            
15 11     11   63 use Carp 'croak';
  11         19  
  11         994  
16 11     11   72 use Scalar::Util 'blessed';
  11         21  
  11         2549  
17            
18             ## XXX: Convert %REGISTRY to a class heirarchy?
19             ## XXX: Make tests more consistent?
20             ## XXX: Provide an easy way to default to existing comparators?
21            
22             ## XXX: Should this be done 'smartly?'
23             require Switch::Perlish::Smatch::Value;
24             require Switch::Perlish::Smatch::Undef;
25             require Switch::Perlish::Smatch::Scalar;
26             require Switch::Perlish::Smatch::Array;
27             require Switch::Perlish::Smatch::Hash;
28             require Switch::Perlish::Smatch::Code;
29             require Switch::Perlish::Smatch::Object;
30             require Switch::Perlish::Smatch::Regexp;
31            
32             ## Thanks to merlyn for this snippet.
33             sub _is_num {
34 11     11   67 no warnings;
  11         27  
  11         1635  
35 195     195   1404 return ($_[0] & ~ $_[0]) eq "0";
36             }
37            
38             sub value_cmp {
39 83     83 1 4009 my($a,$b) = @_;
40             ## Try to compare 2 strings then 2 numbers then do a regexp guesstimate.
41 83 100 66     213 !_is_num($a) and !_is_num($b) and return $a eq $b;
42 29 100 100     58 _is_num($a) and _is_num($b) and return $a == $b;
43 11     11   62 no warnings;
  11         20  
  11         8293  
44 4         78 return $a =~ /\A$b\z/;
45             }
46            
47             sub match {
48 183 100   183 1 646 my $self = @_ == 3 ? shift : __PACKAGE__;
49 183         330 my($t, $m) = @_;
50 183         7831 my($t_type, $m_type) = map _get_type($_), $t, $m;
51            
52             ## Default to OBJECT if we don't have a registered class comparator.
53 183 100 100     986 $t_type = 'OBJECT'
54             if blessed($t) and !$self->is_registered($t_type);
55 183 100 100     614 $m_type = 'OBJECT'
56             if blessed($m) and !$self->is_registered($t_type, $m_type);
57             ## Treat REF the same as SCALAR, i.e KISS.
58             $_ eq 'REF' and $_ = 'SCALAR'
59 183   100     1126 for $t_type, $m_type;
60            
61 183         654 return $self->dispatch( $t_type, $m_type, $t, $m );
62             }
63            
64             ## for exporting
65             *smatch = \&match;
66            
67             ## Make this public?
68             sub _get_type {
69 366     366   481 my $foo = shift;
70             ## XXX: Is this the best way to check?
71             ## Get the class name, or the reference type, or we're a value/undef.
72 366   66     3006 return blessed($foo) || ref($foo) || ( defined($foo) ? 'VALUE' : 'UNDEF' );
73             }
74            
75             sub dispatch {
76 192     192 1 484 my($self, $t_type, $m_type) = @_;
77 192 50       456 croak "No comparator found for topic '$t_type' => match '$m_type'"
78             unless $self->is_registered( $t_type, $m_type );
79 192 50       1071 my($t,$m) = @_ == 5 ?
80             @_[3,4] : ( $Switch::Perlish::TOPIC, $Switch::Perlish::MATCH );
81            
82             ## XXX: Subvert the stack with a goto?
83 192         804 $REGISTER{ $t_type }{ $m_type }->( $t, $m );
84             }
85            
86             sub register {
87 709     709 1 4879 my($self, %comp) = @_;
88 709         2222 my($t_type, $m_type, $compare) = @comp{qw/ topic match compare /};
89            
90 709 100 66     1690 warnings::warn("Overriding existing comparator for $t_type<=>$m_type")
91             if $self->is_registered($t_type, $m_type) and warnings::enabled;
92            
93 709         3460 $REGISTER{ $t_type }{ $m_type } = $compare;
94 1     1   20 $REGISTER{ $m_type }{ $t_type } = sub { $compare->(reverse @_) }
95 709 100 66     5706 if exists $comp{reversible} and $comp{reversible};
96             }
97            
98             sub register_package {
99 89     89 1 285 my($self, $pkg, $topic) = @_;
100 89 50       263 my $prefix = defined($_[3]) ? $_[3] : '_';
101 89 50       190 my $reverse = defined($_[4]) ? $_[4] : 0;
102            
103 89 50       288 croak "An empty prefix was provided (registering all subs is not desirable)"
104             if length($prefix) == 0;
105            
106             ## Let perl do the look-up.
107 11     11   72 my $tbl = do { no strict; \%{"$pkg\::"} };
  11         18  
  11         4047  
  89         108  
  89         104  
  89         323  
108            
109 89         5721 for( grep /^$prefix/, keys %$tbl ) {
110 705         898 my $sub;
111             next
112 705 50       1565 unless $sub = *{$tbl->{$_}}{CODE};
  705         2964  
113            
114 705         3354 Switch::Perlish::Smatch->register(
115             topic => $topic,
116             match => substr($_, 1),
117             compare => $sub,
118             reversible => $reverse,
119             );
120             }
121             }
122            
123             sub is_registered {
124 969     969 1 3126 my($self, $t_type, $m_type) = @_;
125            
126 969 100 66     2997 return ( exists $REGISTER{ $t_type } and defined $REGISTER{ $t_type } )
127             if @_ == 2;
128 934 50 66     12139 return ( exists $REGISTER{ $t_type } and defined $REGISTER{ $t_type }
129             and exists $REGISTER{ $t_type }{ $m_type }
130             and defined $REGISTER{ $t_type }{ $m_type } )
131             if @_ == 3;
132            
133 0           croak sprintf "Incorrect number of arguments for is_registered(%s)",
134             join(', ', @_);
135             }
136            
137             1;
138            
139             =pod
140            
141             =head1 NAME
142            
143             Switch::Perlish::Smatch - the 'smart' behind the matching in S::P
144            
145             =head1 VERSION
146            
147             1.0.1 - Updated and cleaned up documentation.
148            
149             =head1 SYNOPSIS
150            
151             use Switch::Perlish::Smatch 'smatch';
152            
153             print 'yep'
154             if smatch $foo => \@bar;
155            
156             =head1 DESCRIPTION
157            
158             Given two values compare them in an intelligent fashion (i.e I)
159             regardless of type. This is done by discerning the types of the values and
160             delegating to the associated subroutine, or Cing if one isn't available.
161            
162             =head2 Glossary
163            
164             =over
165            
166             =item comparators
167            
168             When talking about the subroutine that compares the two values in the document
169             below it will referred to as a I
170            
171             =item comparator category
172            
173             A comparator category holds all the comparators for a given type.
174            
175             =item comparator notation
176            
177             Some handy notation for referring to specific I is
178             C<< FOOE=>BAR >>, where C is the topic and C is the match (i.e the
179             first and second arguments, respectively).
180            
181             =back
182            
183             =head1 METHODS
184            
185             =over
186            
187             =item match( $topic, $match )
188            
189             Try to smart match the C<$topic> against C<$match> by delegating to the
190             appopriate comparator. It returns the result of the match per the comparator,
191             but it can always be assumed that a successful match will evaluate to I
192             and an unsuccessful one I. This can also be exported as C.
193            
194             =item register( %hash )
195            
196             The expected C<%hash> looks like this:
197            
198             topic => $t_type,
199             match => $m_type,
200             compare => $sub,
201            
202             So C<$sub> will be the registered comparator when the topic type is C<$t_type>
203             and the matching value is of type C<$m_type> e.g
204            
205             my $foo = 'a string';
206             my $bar = [qw/ an array /];
207             smatch $foo, $bar;
208            
209             In this case the C<$t_type> is C and the C<$m_type> is C. If
210             one were to override the default comparator for C<< VALUEE=>ARRAY >>
211             using C then it would be done like this:
212            
213             Switch::Perlish::Smatch->register(
214             topic => 'VALUE',
215             match => 'ARRAY',
216             compare => sub {
217             my($t, $m) = @_;
218             return grep /$t/, @$m;
219             },
220             );
221            
222             If you run the code above you should get a warning noting that there is an
223             existing comparator for that type combination. To suppress this and any other
224             warnings from this module just add C.
225            
226             This method is aimed at adding comparators for objects so they can be used
227             seamlessly in C calls. So instead of defaulting to the existing
228             C comparators a user-defined comparator would be used, with more
229             desirable results. For more information see L
230             below.
231            
232             If your comparator is reversible, i.e the arguments can be reversed and the
233             result will be the same, then you can pass in the C argument e.g
234            
235            
236             Switch::Perlish::Smatch->register(
237             topic => 'My::Obj',
238             match => 'ARRAY',
239             compare => sub {
240             my($t, $m) = @_;
241             return $t->cmp( $m );
242             },
243             reversible => 1,
244             );
245            
246             So both the C<< My::Obj<=>VALUE >> and C<< VALUEE=>My::Obj >> comparators
247             will be setup, where C<< VALUEE=>My::Obj >> will behave exactly the same as
248             C<< My::Obj<=>VALUE >>.
249            
250             =item register_package( $package, $category[, $prefix, $reversible] );
251            
252             Given the package name in C<$package>, register all subroutines beginning with
253             C<$prefix> (by default an underscore: C<_>) to the comparator category in
254             C<$category>. This is how the standard comparator functions are registered. An
255             empty C<$prefix> is disallowed as C must be able to know
256             which subroutines to register. If C<$reversible> is passed in and it evaluates
257             to true then all comparators for this package will be reversible.
258            
259             =item is_registered( $t_type[, $m_type] )
260            
261             If one argument is provided, check if there is a comparator category for
262             C<$t_type>. If two arguments are provided then check if the comparator for
263             C<< $t_type<=>$m_type >> has been registered.
264            
265             =item dispatch( $t_type, $m_type[, $topic, $match] )
266            
267             Dispatch to the comparator for C<$t_type> and C<$m_type>, passing along
268             C<$topic> and C<$match> (defaulting to C<$Switch::Perlish::TOPIC> and
269             C<$Switch::Perlish::MATCH>, respectively).
270            
271             =back
272            
273             =head2 Helper subroutines
274            
275             =over
276            
277             =item value_cmp($t, $m)
278            
279             Given two simple values try to compare them in the most natural way i.e try to
280             compare 2 numbers as numbers, 2 strings as strings and any other combination do
281             a regexp match.
282            
283             =back
284            
285             =head1 FURTHER INFO
286            
287             =head2 Creating a new comparator
288            
289             If we have a L object and want I it to something then we need
290             to create a new comparator. This can be implemented in whatever
291             way seems most appropriate, so for the sake of this module we will be testing
292             for the existence of a simple value in C e.g
293            
294             sub cgi_comparator {
295             my($cgi, $val) = @_;
296             return defined( $cgi->param($val) );
297             }
298            
299             Now that we have our comparator for C<< CGIE=>VALUE >> (the above subroutine)
300             and we know what we're comparing (a L object and a simple value) we can
301             register it like this:
302            
303             use Switch::Perlish::Smatch 'smatch';
304            
305             Switch::Perlish::Smatch->register(
306             topic => 'CGI',
307             match => 'VALUE',
308             compare => \&cgi_comparator,
309             );
310            
311             So we can now compare simple values with L objects e.g
312            
313             my $q = CGI->new;
314             my $check = $ARGV[0];
315             printf "%s $check in params!\n",
316             smatch($q, $check) ? 'found' : 'not found';
317            
318             =head2 The default types
319            
320             There are currently 8 default types, all of which have a complete set of
321             comparators implemented. These 8 types are:
322            
323             =over
324            
325             =item VALUE
326            
327             This type covers simple values which are just strings or numbers.
328            
329             =item UNDEF
330            
331             This covers any Cs.
332            
333             =item SCALAR
334            
335             This covers all C references.
336            
337             =item ARRAY
338            
339             Covers arrays.
340            
341             =item HASH
342            
343             Covers hashes.
344            
345             =item CODE
346            
347             Covers coderefs i.e subroutines.
348            
349             =item OBJECT
350            
351             Covers any objects that don't have specific comparators.
352            
353             =item Regexp
354            
355             Covers C objects.
356            
357             =back
358            
359             =head2 How comparators compare
360            
361             For info on how each comparator works see.
362             L.
363            
364             =head1 TODO
365            
366             =over
367            
368             =item *
369            
370             Add more helper subroutines for common operations default, and make them easier
371             to access.
372            
373             =item *
374            
375             Move into own module if people find it sufficiently useful.
376            
377             =item *
378            
379             Add object functionality perhaps (but who wants that?).
380            
381             =item *
382            
383             Maybe add inheritable comparators.
384            
385             =item *
386            
387             Set __ANON__ to comparator name for debugging purposes.
388            
389             =item *
390            
391             Add support for C (and possibly C) types.
392            
393             =item *
394            
395             Store the smatch result somewhere.
396            
397             =item *
398            
399             Allow for choice of which comparators are reversible in C.
400            
401             =back
402            
403             =head1 SEE. ALSO
404            
405             L
406            
407             L
408            
409             L
410            
411             =head1 EXPORT_OK
412            
413             C (an alias to C)
414            
415             C
416            
417             =head1 AUTHOR
418            
419             Dan Brook C<< >>
420            
421             =head1 COPYRIGHT
422            
423             Copyright (c) 2006, Dan Brook. All Rights Reserved. This module is free
424             software. It may be used, redistributed and/or modified under the same
425             terms as Perl itself.
426            
427             =cut