File Coverage

blib/lib/Symbol/Approx/Sub.pm
Criterion Covered Total %
statement 138 138 100.0
branch 45 48 93.7
condition 2 3 66.6
subroutine 16 16 100.0
pod n/a
total 201 205 98.0


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 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   991737 use strict;
  17         159  
  17         542  
203 17     17   89 use warnings;
  17         38  
  17         825  
204              
205             our ($VERSION, @ISA, $AUTOLOAD);
206              
207 17     17   102 use Devel::Symdump;
  17         34  
  17         413  
208 17     17   9520 use Module::Load;
  17         19913  
  17         109  
209             use Exception::Class (
210 17         190 '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   10267 );
  17         183629  
217              
218             $VERSION = '3.1.3';
219              
220 17     17   25421 use Carp;
  17         34  
  17         2087  
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 24     24   12803 my $class = shift;
241              
242 17     17   136 no strict 'refs'; # WARNING: Deep magic here!
  17         34  
  17         27667  
243              
244 24         51 my %param;
245             my %CONF;
246 24 100       131 %param = @_ if @_;
247              
248 24         85 my %defaults = (
249             xform => 'Text::Soundex',
250             match => 'String::Equal',
251             choose => 'Random',
252             suggest => 0,
253             );
254              
255 24         110 foreach (keys %param) {
256             SAS::Exception::InvalidParameter->throw(
257             error => "Invalid parameter $_\n",
258 32 100       159 ) unless exists $defaults{$_};
259             }
260              
261 23         85 $CONF{xform} = _set_transformer(\%param, $defaults{xform});
262 21         84 $CONF{match} = _set_matcher(\%param, $defaults{match});
263 19         68 $CONF{choose} =_set_chooser(\%param, $defaults{choose});
264              
265 17   66     131 $CONF{suggest} = $param{suggest} // $defaults{suggest};
266              
267             # Now install appropriate AUTOLOAD routine in caller's package
268              
269 17         60 my $pkg = caller(0);
270 17         135 *{"${pkg}::AUTOLOAD"} = _make_AUTOLOAD(%CONF);
  17         21380  
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   55 my ($param, $default) = @_;
284              
285 23 100       68 unless (exists $param->{xform}) {
286 8         35 my $mod = "Symbol::Approx::Sub::$default";
287 8         40 load $mod;
288 8         340 return [\&{"${mod}::transform"}];
  8         60  
289             }
290              
291 15 100       77 unless (defined $param->{xform}) {
292 7         26 return [];
293             }
294              
295 8         24 my $type = ref $param->{xform};
296 8 100       39 if ($type eq 'CODE') {
    100          
    100          
297 1         4 return [$param->{xform}];
298             } elsif ($type eq '') {
299 3         9 my $mod = "Symbol::Approx::Sub::$param->{xform}";
300 3         12 load $mod;
301 3         36 return [\&{"${mod}::transform"}];
  3         25  
302             } elsif ($type eq 'ARRAY') {
303 3         6 my @xforms;
304 3         6 foreach (@{$param->{xform}}) {
  3         9  
305 4         9 my $subtype = ref $_;
306 4 100       15 if ($subtype eq 'CODE') {
    100          
307 2         4 push @xforms, $_;
308             } elsif ($subtype eq '') {
309 1         3 my $mod = "Symbol::Approx::Sub::$_";
310 1         5 load $mod;
311 1         12 push @xforms, \&{"${mod}::transform"};
  1         7  
312             } else {
313 1         3 SAS::Exception::InvalidOption::Transformer->throw(
314             error => 'Invalid transformer passed to Symbol::Approx::Sub'
315             );
316             }
317             }
318 2         22 return \@xforms;
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   54 my ($param, $default) = @_;
335              
336 21 100       427 unless (exists $param->{match}) {
337 11         30 my $mod = "Symbol::Approx::Sub::$default";
338 11         57 load $mod;
339 11         245 return \&{"${mod}::match"};
  11         76  
340             }
341              
342 10 100       32 unless (defined $param->{match}) {
343 1         3 return undef;
344             }
345              
346 9         23 my $type = ref $param->{match};
347 9 100       32 if ($type eq 'CODE') {
    100          
348 6         18 return $param->{match};
349             } elsif ($type eq '') {
350 1         3 my $mod = "Symbol::Approx::Sub::$param->{match}";
351 1         3 load $mod;
352 1         12 return \&{"${mod}::match"};
  1         7  
353             } else {
354 2         15 SAS::Exception::InvalidOption::Matcher->throw(
355             error => 'Invalid matcher passed to Symbol::Approx::Sub'
356             );
357             }
358             }
359              
360             # Work out which chooser to use. The valid options are:
361             # 1/ $param{choose} doesn't exist. Use default chooser.
362             # 2/ $param{choose} is undef. Use default chooser.
363             # 3/ $param{choose} is a reference to a subroutine. Use the
364             # referenced subroutine as the chooser.
365             # 4/ $param{choose} is a scalar. This is the name of a chooser
366             # module which should be loaded.
367             sub _set_chooser {
368 19     19   51 my ($param, $default) = @_;
369              
370 19 100       69 unless (exists $param->{choose}) {
371 14         41 my $mod = "Symbol::Approx::Sub::$default";
372 14         70 load $mod;
373 14         218 return \&{"${mod}::choose"};
  14         110  
374             }
375              
376 5 100       13 unless (defined $param->{choose}) {
377 1         3 my $mod = "Symbol::Approx::Sub::$default";
378 1         4 load $mod;
379 1         20 return \&{"${mod}::choose"};
  1         6  
380             }
381              
382 4         10 my $type = ref $param->{choose};
383 4 100       15 if ($type eq 'CODE') {
    100          
384 1         2 return $param->{choose};
385             } elsif ($type eq '') {
386 1         2 my $mod = "Symbol::Approx::Sub::$param->{choose}";
387 1         5 load $mod;
388 1         67 return \&{"${mod}::choose"};
  1         8  
389             } else {
390 2         16 SAS::Exception::InvalidOption::Chooser->throw(
391             error => 'Invalid chooser passed to Symbol::Approx::Sub',
392             );
393             }
394             }
395              
396             sub _run_xformers {
397 27     27   182 my ($xforms, $sub, @subs) = @_;
398              
399 27         123 foreach (@$xforms) {
400 16 50       139 SAS::Exception::InvalidOption::Transformer->throw(
401             error => 'Invalid transformer passed to Symbol::Approx::Sub',
402             ) unless defined &$_;
403 16         88 ($sub, @subs) = $_->($sub, @subs);
404             }
405              
406 27         323 return ($sub, @subs);
407             }
408              
409             sub _run_matcher {
410 27     27   146 my ($matcher, $sub, @subs) = @_;
411              
412 27         50 my @match_ind;
413 27 100       88 if ($matcher) {
414             SAS::Exception::InvalidOption::Matcher->throw(
415             error => 'Invalid matcher passed to Symbol::Approx::Sub',
416 22 50       40 ) unless defined &{$matcher};
  22         79  
417 22         120 @match_ind = $matcher->($sub, @subs);
418             } else {
419 5         13 @match_ind = (0 .. $#subs);
420             }
421              
422 27         270 return @match_ind;
423             }
424              
425             sub _run_chooser {
426 8     8   28 my ($chooser, @subs) = @_;
427              
428 8 50       23 SAS::Exception::InvalidOption::Chooser->throw(
429             error => 'Invalid chooser passed to Symbol::Approx::Sub'
430             ) unless defined &$chooser;
431 8         28 my $index = $chooser->(@subs);
432              
433 8         32 return $index;
434             }
435              
436             # Create a subroutine which is called when a given subroutine
437             # name can't be found in the current package. In the import subroutine
438             # above, we have already arranged that our calling package will use
439             # the AUTOLOAD created here instead of its own.
440             sub _make_AUTOLOAD {
441 17     17   71 my %CONF = @_;
442              
443             return sub {
444 27     27   6598 my @c = caller(0);
445 27         429 my ($pkg, $sub) = $AUTOLOAD =~ /^(.*)::(.*)$/;
446              
447             # Get a list of all of the subroutines in the current package
448             # using the get_subs function from GlobWalker.pm
449             # Note that we deliberately omit function names that exist
450             # in the %_BARRED hash
451 27         74 my (@subs, @orig);
452 27         34068 my $sym = Devel::Symdump->new($pkg);
453 550         1152 @orig = @subs = grep { ! $_BARRED{$_} }
454 550         1407 map { s/${pkg}:://; $_ }
  550         1033  
455 27         1645 grep { defined &{$_} } $sym->functions();
  567         760  
  567         1257  
456              
457             # Transform all of the subroutine names
458 27         220 ($sub, @subs) = _run_xformers($CONF{xform}, $sub, @subs);
459              
460             # Call the subroutine that will look for matches
461             # The matcher returns a list of the _indexes_ that match
462 27         149 my @match_ind = _run_matcher($CONF{match}, $sub, @subs);
463              
464 27         102 @subs = @subs[@match_ind];
465 27         98 @orig = @orig[@match_ind];
466              
467             # If we've got more than one matched subroutine, then call the
468             # chooser to pick one.
469             # Call the matched subroutine using magic goto.
470             # If no match was found, die recreating Perl's usual behaviour.
471              
472 27 100       174 die "REALLY Undefined subroutine $AUTOLOAD called at $c[1] line $c[2]\n"
473             unless @match_ind;
474              
475 25 100       97 if (@match_ind == 1) {
476 17         61 $sub = "${pkg}::" . $orig[0];
477             } else {
478 8         74 my $index = _run_chooser($CONF{choose}, @subs);
479 8         27 $sub = "${pkg}::" . $orig[$index];
480             }
481 25 100       80 if ($CONF{suggest}) {
482 1         21 croak "Cannot find subroutine $AUTOLOAD. Did you mean $sub?";
483             } else {
484 24         731 goto &$sub;
485             }
486             }
487 17         111 }
488              
489             1;
490             __END__