Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Invoke the Perl string interpolation engine on a string contained in a scalar variable.

by ibm1620 (Monk)
on Jan 15, 2019 at 17:49 UTC ( #1228604=perlquestion: print w/replies, xml ) Need Help??

ibm1620 has asked for the wisdom of the Perl Monks concerning the following question:

I want to be able to take arbitrary lines containing variables that are defined in the program, and interpolate them.
#!/usr/bin/env perl use 5.010; use warnings; use strict; my $var1 = "abel"; my $var2 = "baker"; my $var3 = "charlie"; while (my $line = <DATA>) { chomp $line; say "Before interpolation: $line"; say "After interpolation: " . perform_interpolation($line); say ''; } sub perform_interpolation { my $text = shift; # now what? } __DATA__ I'd like to see this one: $var1. How about \$var3? You shouldn't interpolate \$var3 (but it would be ni +ce if you'd remove the backslash) Try a concatenation: $var1$var2
I've seen String::Interpolate mentioned in my searches, but unless I'm misunderstanding something, I'd have to know in advance what variables I'd be interpolating

What I'm trying to do is create a program template where a comment block right after the shebang line (possibly containing scalar variables like $program or other constants or environment variables) can be rendered to produce a usage statement.

The perform_interpolation() subroutine would be part of the template and wouldn't know specifically what variables the programmer might want to interpolate for the usage statement.

Can String::Interpolate do this? Or is there a simpler way?

  • Comment on Invoke the Perl string interpolation engine on a string contained in a scalar variable.
  • Download Code

Replies are listed 'Best First'.
Re: Invoke the Perl string interpolation engine on a string contained in a scalar variable.
by Corion (Pope) on Jan 15, 2019 at 17:56 UTC

    If you trust your input data (and I mean, really trust it), there is eval.

    Otherwise, whitelisting the variables is the sanest approach, and then basically doing s/\$(\w+)/$1/gee.

    Personally, I wouldn't allow a template to access arbitrary variables in the script and always decouple the template variable names from the inner workings of the program by using a hash, just like String::Interpolate does.

      Since it's just for my own set of tools which I share with a few other colleagues, I trust it.

      But I've tried eval several ways and can't get what I want:

      : my $x = 'foo'; my $y = 'The answer is $x'; my $z; eval {$z=$y}; say $z; # or say eval $y; > The answer is $x
      $x is still uninterpolated.

        In your example $y is a string of characters, but when you eval it with "eval $y", eval expects that string of characters to be Perl code. You need to give eval actual code to eval, not just a string of characters. Do this by wrapping that string of characters in Perl code (in quotes):

        my $x = 'foo'; my $y = 'The answer is $x'; my $z = eval enquote($y); sub enquote { return 'qq{' . shift() . '}'; }

        There's no great reason to have used the enquote sub rather than just inlining the buildup of the Perl-quoted string other than to attempt to make it more clear what the intent is. I could have just done this:

        my $z = eval 'qq{' . $y . '}';

        Or even...

        my $z = eval "qq{$y}";

        In any case, the goal is to turn a raw string of characters into something that eval can reasonably compile as code that when evaluated returns the original string with interpolation in place.


        Dave

        You need to eval a string:

        my($greeting,$target)=('Hello','World'); my $str = '$greeting $target'; $res=eval "qq{$str}"; print $res __END__ Hello World
Re: Invoke the Perl string interpolation engine on a string contained in a scalar variable.
by localshop (Monk) on Jan 19, 2019 at 20:15 UTC
    Back in the days when everybody was building their own template interpolation code I used this little snippet This code interpolates the values from a vars hashref into the text string. I suspect that it can be improved but it worked for me for a long time - any suggestions welcome.
    my $text = 'this is my text with a <%VAR%> to interpolate'; my $vars = { 'VAR' => 'value taken from a data structure' }; $_ = $text; while ( /<%(.+?)%>/g) { #print "$1\n"; if (!defined $vars->{$1} ) { $vars->{$1} = 'undefined'; $vars->{MESSAGE} .= "$1 undefined <br />\n"; ## keep a log of mi +ssing variables } } $text =~ s/<%(.+?)%>/$vars->{$1}/g || warn("unable to substitute $1" +); print qq{$text\n};
      $text =~ s/<%(.+?)%>/$vars->{$1}/g || warn("unable to substitute $1");

      This statement is puzzling. If zero substitutions are done, two warnings are emitted (assuming you have warnings enabled). What is the purpose of printing  $1 when it is undefined?


      Give a man a fish:  <%-{-{-{-<

        Thanks - yes the || warning was a hangover from first attempt that should have been removed. Here's a slightly better reworked version - NB you could probably make this more succinct using a map instead of the while.

        use strict; use warnings; use Data::Dumper; =pod Interpolates source text tags with <%keyname%> syntax with values from + hashref $vars =cut my $text = 'this is my text with a <%VAR%> to ' . 'interpolate and an no specified "<%VAR3%>"'; my $vars = { 'VAR' => 'value taken from a data structure', 'VAR1' => 'Variable 1' }; ## Identify any tags not defined within $vars and append to message while ($text =~ /<%(.+?)%>/g ) { if (!defined $vars->{$1} ) { #$vars->{$1} = " no value specified for variable $1"; #$vars->{$1} = "$1"; $vars->{$1} = ''; ## keep a log of missing variables $vars->{MESSAGE} .= "$1 undefined <br />\n"; } } $text =~ s/<%(.+?)%>/$vars->{$1}/g; print qq{\n\n\n\n$text\n}; print Dumper $vars;

        Thanks - yes the || warning was a hangover from first attempt that should have been removed. Here's a slightly better reworked version - NB you could probably make this more succinct using a map instead of the while.

        use strict; use warnings; use Data::Dumper; =pod Interpolates source text tags with <%keyname%> syntax with values from + hashref $vars =cut my $text = 'this is my text with a <%VAR%> to ' . 'interpolate and an no specified "<%VAR3%>"'; my $vars = { 'VAR' => 'value taken from a data structure', 'VAR1' => 'Variable 1' }; ## Identify any tags not defined within $vars and append to message while ($text =~ /<%(.+?)%>/g ) { if (!defined $vars->{$1} ) { #$vars->{$1} = " no value specified for variable $1"; #$vars->{$1} = "$1"; $vars->{$1} = ''; ## keep a log of missing variables $vars->{MESSAGE} .= "$1 undefined <br />\n"; } } $text =~ s/<%(.+?)%>/$vars->{$1}/g; print qq{\n\n\n\n$text\n}; print Dumper $vars;
Re: Invoke the Perl string interpolation engine on a string contained in a scalar variable.
by Anonymous Monk on Jan 22, 2019 at 00:23 UTC
    If you have control over the format of the lines you want to interpolate into, I strongly suggest using a templating module and a "stash" of variables (such as a hash) rather than scalars which can only really be interpolated by string eval.
    use Mojo::Template; my %vars = ( var1 => 'abel', var2 => 'baker', var3 => 'charlie', ); my $t = Mojo::Template->new(vars => 1); my $rendered = $t->render($text, \%vars);
    There are many other options like Text::Template, Template::Toolkit, and Text::Xslate (which is designed to escape HTML by default, so you will want to configure it for type => 'text').

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1228604]
Front-paged by haukex
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (10)
As of 2019-10-18 13:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?