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.006_000;
202 17     17   940427 use strict;
  17         166  
  17         500  
203 17     17   92 use warnings;
  17         32  
  17         806  
204              
205             our ($VERSION, @ISA, $AUTOLOAD);
206              
207 17     17   97 use Devel::Symdump;
  17         31  
  17         349  
208 17     17   8361 use Module::Load;
  17         18829  
  17         102  
209             use Exception::Class (
210 17         172 '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   9230 );
  17         173289  
217              
218             $VERSION = '3.1.1';
219              
220 17     17   25064 use Carp;
  17         37  
  17         2053  
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   10895 my $class = shift;
241              
242 17     17   132 no strict 'refs'; # WARNING: Deep magic here!
  17         31  
  17         24690  
243              
244 23         55 my %param;
245             my %CONF;
246 23 100       126 %param = @_ if @_;
247              
248 23         79 my %defaults = (
249             xform => 'Text::Soundex',
250             match => 'String::Equal',
251             choose => 'Random',
252             suggest => 0,
253             );
254              
255 23         84 foreach (keys %param) {
256             SAS::Exception::InvalidParameter->throw(
257             error => "Invalid parameter $_\n",
258 31 50       139 ) unless exists $defaults{$_};
259             }
260              
261 23         108 _set_transformer(\%param, \%CONF, $defaults{xform});
262 21         92 _set_matcher(\%param, \%CONF, $defaults{match});
263 19         77 _set_chooser(\%param, \%CONF, $defaults{choose});
264              
265 17   66     121 $CONF{suggest} = $param{suggest} // $defaults{suggest};
266              
267             # Now install appropriate AUTOLOAD routine in caller's package
268              
269 17         53 my $pkg = caller(0);
270 17         139 *{"${pkg}::AUTOLOAD"} = _make_AUTOLOAD(%CONF);
  17         20618  
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   75 my ($param, $CONF, $default) = @_;
284              
285 23 100       69 unless (exists $param->{xform}) {
286 8         32 my $mod = "Symbol::Approx::Sub::$default";
287 8         39 load $mod;
288 8         344 $CONF->{xform} = [\&{"${mod}::transform"}];
  8         51  
289 8         22 return;
290             }
291              
292 15 100       67 unless (defined $param->{xform}) {
293 7         24 $CONF->{xform} = [];
294 7         20 return;
295             }
296              
297 8         31 my $type = ref $param->{xform};
298 8 100       41 if ($type eq 'CODE') {
    100          
    100          
299 1         4 $CONF->{xform} = [$param->{xform}];
300             } elsif ($type eq '') {
301 3         9 my $mod = "Symbol::Approx::Sub::$param->{xform}";
302 3         13 load $mod;
303 3         45 $CONF->{xform} = [\&{"${mod}::transform"}];
  3         52  
304             } elsif ($type eq 'ARRAY') {
305 3         5 foreach (@{$param->{xform}}) {
  3         9  
306 4         9 my $type = ref $_;
307 4 100       12 if ($type eq 'CODE') {
    100          
308 2         3 push @{$CONF->{xform}}, $_;
  2         5  
309             } elsif ($type eq '') {
310 1         3 my $mod = "Symbol::Approx::Sub::$_";
311 1         4 load $mod;
312 1         12 push @{$CONF->{xform}}, \&{"${mod}::transform"};
  1         5  
  1         8  
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   56 my ($param, $CONF, $default) = @_;
335              
336 21 100       71 unless (exists $param->{match}) {
337 11         32 my $mod = "Symbol::Approx::Sub::$default";
338 11         58 load $mod;
339 11         189 $CONF->{match} = \&{"${mod}::match"};
  11         75  
340 11         30 return;
341             }
342              
343 10 100       28 unless (defined $param->{match}) {
344 1         2 $CONF->{match} = undef;
345 1         2 return;
346             }
347              
348 9         43 my $type = ref $param->{match};
349 9 100       38 if ($type eq 'CODE') {
    100          
350 6         26 $CONF->{match} = $param->{match};
351             } elsif ($type eq '') {
352 1         2 my $mod = "Symbol::Approx::Sub::$param->{match}";
353 1         4 load $mod;
354 1         13 $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   51 my ($param, $CONF, $default) = @_;
371              
372 19 100       74 unless (exists $param->{choose}) {
373 14         53 my $mod = "Symbol::Approx::Sub::$default";
374 14         91 load $mod;
375 14         279 $CONF->{choose} = \&{"${mod}::choose"};
  14         92  
376 14         41 return;
377             }
378              
379 5 100       15 unless (defined $param->{choose}) {
380 1         3 my $mod = "Symbol::Approx::Sub::$default";
381 1         4 load $mod;
382 1         11 $CONF->{choose} = \&{"${mod}::choose"};
  1         7  
383 1         2 return;
384             }
385              
386 4         11 my $type = ref $param->{choose};
387 4 100       16 if ($type eq 'CODE') {
    100          
388 1         2 $CONF->{choose} = $param->{choose};
389             } elsif ($type eq '') {
390 1         3 my $mod = "Symbol::Approx::Sub::$param->{choose}";
391 1         3 load $mod;
392 1         65 $CONF->{choose} = \&{"${mod}::choose"};
  1         7  
393             } else {
394 2         16 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   64 my %CONF = @_;
406              
407             return sub {
408 26     26   6719 my @c = caller(0);
409 26         461 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 26         86 my (@subs, @orig);
416 26         32889 my $sym = Devel::Symdump->new($pkg);
417 546         1138 @orig = @subs = grep { ! $_BARRED{$_} }
418 546         1410 map { s/${pkg}:://; $_ }
  546         1032  
419 26         1577 grep { defined &{$_} } $sym->functions();
  563         753  
  563         1259  
420              
421             # Transform all of the subroutine names
422 26         122 foreach (@{$CONF{xform}}) {
  26         133  
423 16 50       147 SAS::Exception::InvalidOption::Transformer->throw(
424             error => 'Invalid transformer passed to Symbol::Approx::Sub',
425             ) unless defined &$_;
426 16         120 ($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 26         188 my @match_ind;
432 26 100       128 if ($CONF{match}) {
433             SAS::Exception::InvalidOption::Matcher->throw(
434             error => 'Invalid matcher passed to Symbol::Approx::Sub',
435 22 50       43 ) unless defined &{$CONF{match}};
  22         106  
436 22         122 @match_ind = $CONF{match}->($sub, @subs);
437             } else {
438 4         10 @match_ind = (0 .. $#subs);
439             }
440              
441 26         248 @subs = @subs[@match_ind];
442 26         96 @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 26 100       79 if (@match_ind) {
449 24 100       106 if (@match_ind == 1) {
450 17         56 $sub = "${pkg}::" . $orig[0];
451             } else {
452             SAS::Exception::InvalidOption::Chooser->throw(
453             error => 'Invalid chooser passed to Symbol::Approx::Sub'
454 7 50       19 ) unless defined $CONF{choose};
455 7         36 $sub = "${pkg}::" . $orig[$CONF{choose}->(@subs)];
456             }
457 24 100       107 if ($CONF{suggest}) {
458 1         23 croak "Cannot find subroutine $AUTOLOAD. Did you mean $sub?";
459             } else {
460 23         740 goto &$sub;
461             }
462             } else {
463 2         84 die "REALLY Undefined subroutine $AUTOLOAD called at $c[1] line $c[2]\n";
464             }
465             }
466 17         117 }
467              
468             1;
469             __END__