File Coverage

blib/lib/Symbol/Approx/Sub.pm
Criterion Covered Total %
statement 136 136 100.0
branch 44 48 91.6
condition 2 3 66.6
subroutine 13 13 100.0
pod n/a
total 195 200 97.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Symbol::Approx::Sub - Perl module for calling subroutines by approximate names!
5              
6             =head1 SYNOPSIS
7              
8             use Symbol::Approx::Sub;
9              
10             sub a {
11             # blah...
12             }
13              
14             aa(); # executes a() if aa() doesn't exist.
15              
16             use Symbol::Approx::Sub (xform => 'Text::Metaphone');
17             use Symbol::Approx::Sub (xform => undef,
18             match => 'String::Approx');
19             use Symbol::Approx::Sub (xform => 'Text::Soundex');
20             use Symbol::Approx::Sub (xform => \&my_transform);
21             use Symbol::Approx::Sub (xform => [\&my_transform, 'Text::Soundex']);
22             use Symbol::Approx::Sub (xform => \&my_transform,
23             match => \&my_matcher,
24             choose => \&my_chooser);
25              
26             New B mode.
27              
28             use Symbol::Approx::Sub (suggest => 1);
29              
30             =head1 DESCRIPTION
31              
32             This is _really_ stupid. This module allows you to call subroutines by
33             _approximate_ names. Why you would ever want to do this is a complete
34             mystery to me. It was written as an experiment to see how well I
35             understood typeglobs and AUTOLOADing.
36              
37             To use it, simply include the line:
38              
39             use Symbol::Approx::Sub;
40              
41             somewhere in your program. Then, each time you call a subroutine that doesn't
42             exist in the the current package, Perl will search for a subroutine with
43             approximately the same name. The meaning of 'approximately the same' is
44             configurable. The default is to find subroutines with the same Soundex
45             value (as defined by Text::Soundex) as the missing subroutine. There are
46             two other built-in matching styles using Text::Metaphone and
47             String::Approx. To use either of these use:
48              
49             use Symbol::Approx::Sub (xform => 'Text::Metaphone');
50              
51             or
52              
53             use Symbol::Approx::Sub (xform => undef,
54             match => 'String::Approx');
55              
56             when using Symbol::Approx::Sub.
57              
58             =head2 Configuring The Fuzzy Matching
59              
60             There are three phases to the matching process. They are:
61              
62             =over 4
63              
64             =item *
65              
66             B - a transform subroutine applies some kind of transformation
67             to the subroutine names. For example the default transformer applies the
68             Soundex algorithm to each of the subroutine names. Other obvious
69             tranformations would be to remove all the underscores or to change the
70             names to lower case.
71              
72             A transform subroutine should simply apply its transformation to each
73             item in its parameter list and return the transformed list. For example, a
74             transformer that removed underscores from its parameters would look like
75             this:
76              
77             sub tranformer {
78             map { s/_//g; $_ } @_;
79             }
80              
81             Transform subroutines can be chained together.
82              
83             =item *
84              
85             B - a match subroutine takes a target string and a list of other
86             strings. It matches each of the strings against the target and determines
87             whether or not it 'matches' according to some criteria. For example, the
88             default matcher simply checks to see if the strings are equal.
89              
90             A match subroutine is passed the target string as its first parameter,
91             followed by the list of potential matches. For each string that matches,
92             the matcher should return the index number from the input list. For example,
93             the default matcher is implemented like this:
94              
95             sub matcher {
96             my ($sub, @subs) = @_;
97             my (@ret);
98              
99             foreach (0 .. $#subs) {
100             push @ret, $_ if $sub eq $subs[$_];
101             }
102              
103             @ret;
104             }
105              
106             =item *
107              
108             B - a chooser subroutine takes a list of matches and chooses exactly
109             one item from the list. The default matcher chooses one item at random.
110              
111             A chooser subroutine is passed a list of matches and must simply return one
112             index number from that list. For example, the default chooser is implemented
113             like this:
114              
115             sub chooser {
116             rand @_;
117             }
118              
119             =back
120              
121             You can override any of these behaviours by writing your own transformer,
122             matcher or chooser. You can either define the subroutine in your own
123             script or you can put the subroutine in a separate module which
124             Symbol::Approx::Sub can then use as a I. See below for more details
125             on plug-ins.
126              
127             To use your own function, simply pass a reference to the subroutine to the
128             C line like this:
129              
130             use Symbol::Approx::Sub(xform => \&my_transform,
131             match => \&my_matcher,
132             choose => \&my_chooser);
133              
134             A plug-in is simply a module that lives in the Symbol::Approx::Sub
135             namespace. For example, if you had a line of code like this:
136              
137             use Symbol::Approx::Sub(xform => 'MyTransform');
138              
139             then Symbol::Approx::Sub will try to load a module called
140             Symbol::Approx::Sub::MyTransform and it will use a function from within that
141             module called C as the transform function. Similarly, the
142             matcher function is called C and the chooser function is called
143             C.
144              
145             The default transformer, matcher and chooser are available as plug-ins
146             called Text::Soundex, String::Equal and Random.
147              
148             =head2 Suggest mode
149              
150             Version 3.1.0 introduces a 'suggest' mode. In this mode, instead of just
151             choosing and running an alternative subroutine, your program will still
152             die as it would without Symbol::Approx::Sub, but the error message you
153             see will include the suggested alternative subroutine. As an example,
154             take this code:
155              
156             sub aa {
157             print "Here's aa()";
158             }
159              
160             a();
161              
162             Obviously, if you run this without loading Symbol::Approx::Sub, you'll
163             get an error message. That message will say "Cannot find subroutine
164             main::a". With Symbol::Approx::Sub loaded in its default mode, the
165             module will find C instead of C and will silently run that
166             subroutine instead.
167              
168             And that's what makes Symbol::Approx::Sub nothing more than a clever
169             party trick. It's really not at all useful to run a program when you're
170             not really sure what subroutines will be called.
171              
172             But running in 'suggest' mode changes that behaviour. Instead of just
173             running C silently, the module will still C (as in the
174             non-Symbol::Approx::Sub behaviour) but the message will be a little
175             more helpful, as it will include the name of the subroutine that has
176             been selected as the most likely correction for your typo.
177              
178             So, if you run this code:
179              
180             use Symbol::Approx::Sub (suggest => 1);
181              
182             sub aa {
183             print "Here's aa()";
184             }
185              
186             a();
187              
188             Then your program will die with the error message "Cannot find
189             subroutine main::a. Did you mean main::aa?".
190              
191             I like to think that some eighteen years or so after it was
192             first released, Symbol::Approx::Sub has added a feature that
193             might actually be of some use.
194              
195             Thanks to Alex Balhatchet for suggesting it.
196              
197             =cut
198              
199             package Symbol::Approx::Sub;
200              
201             require 5.010_000;
202 17     17   883249 use strict;
  17         146  
  17         454  
203 17     17   81 use warnings;
  17         31  
  17         759  
204              
205             our ($VERSION, @ISA, $AUTOLOAD);
206              
207 17     17   91 use Devel::Symdump;
  17         27  
  17         327  
208 17     17   7794 use Module::Load;
  17         17158  
  17         91  
209             use Exception::Class (
210 17         175 'SAS::Exception',
211             'SAS::Exception::InvalidOption' => { isa => 'SAS::Exception' },
212             'SAS::Exception::InvalidOption::Transformer' => { isa => 'SAS::Exception::InvalidOption' },
213             'SAS::Exception::InvalidOption::Matcher' => { isa => 'SAS::Exception::InvalidOption' },
214             'SAS::Exception::InvalidOption::Chooser' => { isa => 'SAS::Exception::InvalidOption' },
215             'SAS::Exception::InvalidParameter' => { isa => 'SAS::Exception' },
216 17     17   8411 );
  17         157389  
217              
218             $VERSION = '3.1.2';
219              
220 17     17   22755 use Carp;
  17         37  
  17         1905  
221              
222             # List of functions that we _never_ try to match approximately.
223             my @_BARRED = qw(AUTOLOAD BEGIN CHECK INIT DESTROY END);
224             my %_BARRED = map { $_ => 1 } @_BARRED;
225              
226             # import is called when another script uses this module.
227             # All we do here is overwrite the caller's AUTOLOAD subroutine
228             # with our own.
229              
230             =head1 Subroutines
231              
232             =head2 import
233              
234             Called when the module is Cd. This function installs our AUTOLOAD
235             subroutine into the caller's symbol table.
236              
237             =cut
238              
239             sub import {
240 23     23   10571 my $class = shift;
241              
242 17     17   113 no strict 'refs'; # WARNING: Deep magic here!
  17         34  
  17         22170  
243              
244 23         51 my %param;
245             my %CONF;
246 23 100       112 %param = @_ if @_;
247              
248 23         76 my %defaults = (
249             xform => 'Text::Soundex',
250             match => 'String::Equal',
251             choose => 'Random',
252             suggest => 0,
253             );
254              
255 23         79 foreach (keys %param) {
256             SAS::Exception::InvalidParameter->throw(
257             error => "Invalid parameter $_\n",
258 31 50       113 ) unless exists $defaults{$_};
259             }
260              
261 23         100 _set_transformer(\%param, \%CONF, $defaults{xform});
262 21         89 _set_matcher(\%param, \%CONF, $defaults{match});
263 19         71 _set_chooser(\%param, \%CONF, $defaults{choose});
264              
265 17   66     110 $CONF{suggest} = $param{suggest} // $defaults{suggest};
266              
267             # Now install appropriate AUTOLOAD routine in caller's package
268              
269 17         43 my $pkg = caller(0);
270 17         130 *{"${pkg}::AUTOLOAD"} = _make_AUTOLOAD(%CONF);
  17         18914  
271             }
272              
273             # Work out which transformer(s) to use. The valid options are:
274             # 1/ $param{xform} doesn't exist. Use default transformer.
275             # 2/ $param{xform} is undef. Use no transformers.
276             # 3/ $param{xform} is a reference to a subroutine. Use the
277             # referenced subroutine as the transformer.
278             # 4/ $param{xform} is a scalar. This is the name of a transformer
279             # module which should be loaded.
280             # 5/ $param{xform} is a reference to an array. Each element of the
281             # array is one of the previous two options.
282             sub _set_transformer {
283 23     23   58 my ($param, $CONF, $default) = @_;
284              
285 23 100       58 unless (exists $param->{xform}) {
286 8         32 my $mod = "Symbol::Approx::Sub::$default";
287 8         41 load $mod;
288 8         350 $CONF->{xform} = [\&{"${mod}::transform"}];
  8         44  
289 8         24 return;
290             }
291              
292 15 100       65 unless (defined $param->{xform}) {
293 7         22 $CONF->{xform} = [];
294 7         17 return;
295             }
296              
297 8         22 my $type = ref $param->{xform};
298 8 100       36 if ($type eq 'CODE') {
    100          
    100          
299 1         3 $CONF->{xform} = [$param->{xform}];
300             } elsif ($type eq '') {
301 3         8 my $mod = "Symbol::Approx::Sub::$param->{xform}";
302 3         13 load $mod;
303 3         41 $CONF->{xform} = [\&{"${mod}::transform"}];
  3         23  
304             } elsif ($type eq 'ARRAY') {
305 3         5 foreach (@{$param->{xform}}) {
  3         9  
306 4         7 my $type = ref $_;
307 4 100       9 if ($type eq 'CODE') {
    100          
308 2         2 push @{$CONF->{xform}}, $_;
  2         6  
309             } elsif ($type eq '') {
310 1         2 my $mod = "Symbol::Approx::Sub::$_";
311 1         3 load $mod;
312 1         25 push @{$CONF->{xform}}, \&{"${mod}::transform"};
  1         4  
  1         7  
313             } else {
314 1         4 SAS::Exception::InvalidOption::Transformer->throw(
315             error => 'Invalid transformer passed to Symbol::Approx::Sub'
316             );
317             }
318             }
319             } else {
320 1         8 SAS::Exception::InvalidOption::Transformer->throw(
321             error => 'Invalid transformer passed to Symbol::Approx::Sub'
322             );
323             }
324             }
325              
326             # Work out which matcher to use. The valid options are:
327             # 1/ $param{match} doesn't exist. Use default matcher.
328             # 2/ $param{match} is undef. Use no matcher.
329             # 3/ $param{match} is a reference to a subroutine. Use the
330             # referenced subroutine as the matcher.
331             # 4/ $param{match} is a scalar. This is the name of a matcher
332             # module which should be loaded.
333             sub _set_matcher {
334 21     21   47 my ($param, $CONF, $default) = @_;
335              
336 21 100       67 unless (exists $param->{match}) {
337 11         32 my $mod = "Symbol::Approx::Sub::$default";
338 11         53 load $mod;
339 11         180 $CONF->{match} = \&{"${mod}::match"};
  11         69  
340 11         31 return;
341             }
342              
343 10 100       28 unless (defined $param->{match}) {
344 1         2 $CONF->{match} = undef;
345 1         3 return;
346             }
347              
348 9         21 my $type = ref $param->{match};
349 9 100       32 if ($type eq 'CODE') {
    100          
350 6         23 $CONF->{match} = $param->{match};
351             } elsif ($type eq '') {
352 1         3 my $mod = "Symbol::Approx::Sub::$param->{match}";
353 1         3 load $mod;
354 1         11 $CONF->{match} = \&{"${mod}::match"};
  1         7  
355             } else {
356 2         16 SAS::Exception::InvalidOption::Matcher->throw(
357             error => 'Invalid matcher passed to Symbol::Approx::Sub'
358             );
359             }
360             }
361              
362             # Work out which chooser to use. The valid options are:
363             # 1/ $param{choose} doesn't exist. Use default chooser.
364             # 2/ $param{choose} is undef. Use default chooser.
365             # 3/ $param{choose} is a reference to a subroutine. Use the
366             # referenced subroutine as the chooser.
367             # 4/ $param{choose} is a scalar. This is the name of a chooser
368             # module which should be loaded.
369             sub _set_chooser {
370 19     19   57 my ($param, $CONF, $default) = @_;
371              
372 19 100       86 unless (exists $param->{choose}) {
373 14         54 my $mod = "Symbol::Approx::Sub::$default";
374 14         52 load $mod;
375 14         223 $CONF->{choose} = \&{"${mod}::choose"};
  14         79  
376 14         37 return;
377             }
378              
379 5 100       15 unless (defined $param->{choose}) {
380 1         2 my $mod = "Symbol::Approx::Sub::$default";
381 1         16 load $mod;
382 1         9 $CONF->{choose} = \&{"${mod}::choose"};
  1         4  
383 1         3 return;
384             }
385              
386 4         8 my $type = ref $param->{choose};
387 4 100       16 if ($type eq 'CODE') {
    100          
388 1         3 $CONF->{choose} = $param->{choose};
389             } elsif ($type eq '') {
390 1         3 my $mod = "Symbol::Approx::Sub::$param->{choose}";
391 1         4 load $mod;
392 1         67 $CONF->{choose} = \&{"${mod}::choose"};
  1         8  
393             } else {
394 2         14 SAS::Exception::InvalidOption::Chooser->throw(
395             error => 'Invalid chooser passed to Symbol::Approx::Sub',
396             );
397             }
398             }
399              
400             # Create a subroutine which is called when a given subroutine
401             # name can't be found in the current package. In the import subroutine
402             # above, we have already arranged that our calling package will use
403             # the AUTOLOAD created here instead of its own.
404             sub _make_AUTOLOAD {
405 17     17   57 my %CONF = @_;
406              
407             return sub {
408 25     25   7226 my @c = caller(0);
409 25         377 my ($pkg, $sub) = $AUTOLOAD =~ /^(.*)::(.*)$/;
410              
411             # Get a list of all of the subroutines in the current package
412             # using the get_subs function from GlobWalker.pm
413             # Note that we deliberately omit function names that exist
414             # in the %_BARRED hash
415 25         65 my (@subs, @orig);
416 25         28543 my $sym = Devel::Symdump->new($pkg);
417 542         1006 @orig = @subs = grep { ! $_BARRED{$_} }
418 542         1275 map { s/${pkg}:://; $_ }
  542         930  
419 25         1370 grep { defined &{$_} } $sym->functions();
  559         661  
  559         1098  
420              
421             # Transform all of the subroutine names
422 25         114 foreach (@{$CONF{xform}}) {
  25         106  
423 16 50       136 SAS::Exception::InvalidOption::Transformer->throw(
424             error => 'Invalid transformer passed to Symbol::Approx::Sub',
425             ) unless defined &$_;
426 16         111 ($sub, @subs) = $_->($sub, @subs);
427             }
428              
429             # Call the subroutine that will look for matches
430             # The matcher returns a list of the _indexes_ that match
431 25         169 my @match_ind;
432 25 100       116 if ($CONF{match}) {
433             SAS::Exception::InvalidOption::Matcher->throw(
434             error => 'Invalid matcher passed to Symbol::Approx::Sub',
435 22 50       48 ) unless defined &{$CONF{match}};
  22         97  
436 22         110 @match_ind = $CONF{match}->($sub, @subs);
437             } else {
438 3         9 @match_ind = (0 .. $#subs);
439             }
440              
441 25         230 @subs = @subs[@match_ind];
442 25         84 @orig = @orig[@match_ind];
443              
444             # If we've got more than one matched subroutine, then call the
445             # chooser to pick one.
446             # Call the matched subroutine using magic goto.
447             # If no match was found, die recreating Perl's usual behaviour.
448 25 100       75 if (@match_ind) {
449 23 100       81 if (@match_ind == 1) {
450 17         58 $sub = "${pkg}::" . $orig[0];
451             } else {
452             SAS::Exception::InvalidOption::Chooser->throw(
453             error => 'Invalid chooser passed to Symbol::Approx::Sub'
454 6 50       16 ) unless defined $CONF{choose};
455 6         24 $sub = "${pkg}::" . $orig[$CONF{choose}->(@subs)];
456             }
457 23 100       106 if ($CONF{suggest}) {
458 1         22 croak "Cannot find subroutine $AUTOLOAD. Did you mean $sub?";
459             } else {
460 22         639 goto &$sub;
461             }
462             } else {
463 2         96 die "REALLY Undefined subroutine $AUTOLOAD called at $c[1] line $c[2]\n";
464             }
465             }
466 17         100 }
467              
468             1;
469             __END__