Recipe 6.20 Matching Abbreviations
6.20.1 Problem
Suppose you had a list of
commands, such as "send",
"abort", "list", and
"edit". The user types one in, but you don't want
to make them type out the whole thing.
6.20.2 Solution
Use the following technique if all strings start with different
characters, or to arrange matches so one takes precedence over
another, as "SEND" has precedence over
"STOP" here:
chomp($answer = <>);
if ("SEND" =~ /^\Q$answer/i) { print "Action is send\n" }
elsif ("STOP" =~ /^\Q$answer/i) { print "Action is stop\n" }
elsif ("ABORT" =~ /^\Q$answer/i) { print "Action is abort\n" }
elsif ("LIST" =~ /^\Q$answer/i) { print "Action is list\n" }
elsif ("EDIT" =~ /^\Q$answer/i) { print "Action is edit\n" }
Or use the Text::Abbrev module:
use Text::Abbrev;
$href = abbrev qw(send abort list edit);
for (print "Action: "; <>; print "Action: ") {
chomp;
my $action = $href->{ lc($_) };
print "Action is $action\n";
}
6.20.3 Discussion
The first technique exchanges the typical operand order of a match.
Normally you have a variable on the left side of the match and a
known pattern on the right side. We might try to decide which action
the user wanted us to take by saying $answer
=~ /^ABORT/i, which is true if
$answer begins with the string
"ABORT". It matches regardless of whether
$answer has anything after
"ABORT", so "ABORT
LATER" would still match. Handling abbreviations
generally requires quite a bit of ugliness:
$answer =~
/^A(B(O(R(T)?)?)?)?$/i.
Compare the classic variable =~
/pattern/ with "ABORT"
=~ /^\Q$answer/i. The
\Q escapes characters that would otherwise be
treated specially: that way your program won't blow up if the user
enters an invalid pattern. When the user enters something like
"ab", the expanded match becomes
"ABORT" =~
/^ab/i after variable substitution and
metaquoting. This matches.
The standard
Text::Abbrev module takes a different approach. You supply a list of
words, and the abbrev( ) function returns a
reference to a hash whose keys are all unambiguous abbreviations and
whose values are the fully expanded strings. So if
$href were created as in the Solution example,
$href->{"a"} would return the string
"abort".
This technique is commonly used to call a function based on the name
of the string the user types in. Although it's possible to implement
this using symbolic references, as in:
$name = 'send';
&$name($message);
$name->($message); # alternate, simpler syntax
that's scary because it lets the user run any function whose name
they know (or can guess), not just those we want to make available to
them. It also runs afoul of that pesky use
strict 'refs' pragma.
Here's a partial program that creates a hash in which the key is the
command name and the value is a reference to the function to call for
that command:
# assumes that &invoke_editor, &deliver_message,
# $file and $PAGER are defined somewhere else.
use Text::Abbrev;
my($href, %actions, $errors);
%actions = (
"edit" => \&invoke_editor,
"send" => \&deliver_message,
"list" => sub { system($PAGER, $file) },
"abort" => sub {
print "See ya!\n";
exit;
},
"" => sub {
print "Unknown command: $cmd\n";
$errors++;
},
);
$href = abbrev(keys %actions);
for (print "Action: "; my $choice = <>; print "Action: ") {
$choice =~ s/^\s+//; # trim leading white space
$choice =~ s/\s+$//; # trim trailing white space
next unless $choice;
$actions->{ $href->{ lc($choice) } }->( );
}
If you're not into long expressions or need practice typing, that
last statement could have been written:
$abbreviation = lc($_);
$expansion = $href->{$abbreviation};
$coderef = $actions->{$expansion};
$coderef->( );
6.20.4 See Also
The documentation for the standard Text::Abbrev module; interpolation
is explained in the "Scalar Value Constructors" section of
perldata(1), and in the "String Literals"
section of Chapter 2 of Programming
Perl
|