I just want to thank you - being able to read this thread has saved me hours of work. Or cost me hours. Depends on how you look at it. I spent about 3 hours trying to get a similar version to work, and it finally does now. Hopefully this gives me such a simplified API to doing what I am trying to do that it will save me (and my cow-orkers) enough time to justify that... Even if it doesn't, it's a cool API to work with. :-)
Here it is - I've co-opted the colon for a shell-like experience. However, if you want to provide a function that maps ".." or "," or whatever you want, you can go ahead. My requirements state that () and {} are both valid token markers, but you can do whatever you want with it. (I'm hoping to remove the ambiguity later, but I'm stuck with it for now.)
Also - in my case, it's possible to come up with duplication (partly because of the shell-likeness, if you're using it), so I came up with a method to remove duplicates while preserving order. That order part isn't well-tested, so anyone noticing anything amiss there would be appreciated :-)
=item expand_string
Expands a string to possibly a set of strings.
Inputs:
=over 4
=item *
String to expand.
=item *
Function used to look up the variable. The function will be passed th
+e variable
to be interpolated in using $_. The function should return a list of
+replacement
values.
The function may set any return value to undef to signify that the var
+iable should be left intact
and unexpanded. An empty string signifies removal of the variable in
+its entirety.
And returning an array will cause the whole expand_string function to
+return each
possible item as an array. This allows you to pass in a single string
+ to expand into
a plethora of output strings all at once.
The default function is below. It is recommended that the given funct
+ion call the
default function in cases where it cannot resolve the variable. The d
+efault function
will die with an error if it cannot resolve the variable which may be
+caught with eval.
=back
The variable may have modifiers:
(variable:+foo)
This means that if variable is non-zero length, use "foo" instead. If
+ the variable
expands to nothing (or undef), use that instead (blank, or leave the v
+ariable unchanged
as appropriate).
(variable:-foo)
This means that if variable is zero length (or undef), use "foo" inste
+ad.
(variable:/blah/baz)
(variable:|blah|baz)
This means to expand variable, and, if successful, modify the value be
+fore replacement
using "s/blah/baz/g". Regular expressions are allowed. Two forms are
+ permitted in
case one is using the special character you need. Note that the ) or
+} characters are
not permitted in here.
=cut
sub expand_string
{
my $self = shift;
my $string = shift;
my $func = shift || sub { $self->expand_variable(@_) };
local $_;
my $varre = qr/
([^:]*?) # variable name
(?::(.*?))? # special instructions
/x;
my @s = split /
(?: # either
(\()$varre(\)) # parenthesis variable
)
| # or...
(?:
(\{)$varre(\}) # brace variable
)/x, $string;
my $output;
my $replace_sub;
$replace_sub = sub {
return '' if @_ == 0;
return @_ if @_ == 1;
my ($pre, $open, $var, $alternate, $close, @post) =
@_[0, defined $_[1] ? (1..4) : (5..8), 9..$#_];
map {
if ($alternate)
{
my ($cmd, $alt) = $alternate =~ /^(.)(.*)$/;
if ($cmd eq '+')
{
if (defined and length)
{
$_ = $alt;
}
}
elsif ($cmd eq '-')
{
unless (defined and length)
{
$_ = $alt;
}
}
elsif ($cmd eq '/' or $cmd eq '|')
{
my ($re,$replace) = split /$cmd/, $alt;
eval "s$cmd$re$cmd$replace$cmd";
die $@ if $@;
}
}
if (not defined)
{
$_ = join '', $open, $var, $alternate ? (':',$alternat
+e) : (), $close;
}
my $this = $pre . $_;
map { $this . $_ } $replace_sub->(@post);
} do { local $_ = $var; ($func->()) };
};
my %rc;
{
my $i = 0;
$rc{$_} ||= ++$i foreach $replace_sub->(@s);
}
my @r = sort { $rc{$a} <=> $rc{$b} } keys %rc;
if (wantarray)
{
@r;
}
elsif (scalar @r == 1)
{
$r[0];
}
else
{
\@r;
}
}
=item expand_variable
Expands $_ to the values that it cares about. Returns an array of
values for this variable.
=cut
sub expand_variable
{
my $self = shift;
# just a dummy for now.
qw(A B);
# could be:
# map { /^(.*)\.\.(.*)/ ? $1..$2 : $_ } split /,/
# or something like that, for example.
}
Update: Code fixes - missed @_==0 case, allow scalar return if expansion does not give multiple return values. Remember - this code can do more than just turn a simple scalar into a list (although that's where the thread started). It can perform arbitrary replacements which may be one-to-one. The expand_variable routine can return a single string which causes the whole thing to return a single string.