File Coverage

blib/lib/String/Interpolate/RE.pm
Criterion Covered Total %
statement 50 50 100.0
branch 32 32 100.0
condition 18 21 85.7
subroutine 8 8 100.0
pod n/a
total 108 111 97.3


line stmt bran cond sub pod time code
1             package String::Interpolate::RE;
2              
3             # ABSTRACT: interpolate variables into strings using regular expressions
4              
5 5     5   1122920 use strict;
  5         36  
  5         147  
6 5     5   26 use warnings;
  5         11  
  5         113  
7 5     5   23 use Carp;
  5         10  
  5         292  
8              
9 5     5   2174 use Exporter::Shiny qw[ strinterp ];
  5         20962  
  5         32  
10              
11             our $VERSION = '0.09';
12              
13             ## no critic (ProhibitAccessOfPrivateData)
14              
15             my %Opt = (
16             variable_re => qr/\w+/,
17             raiseundef => 0,
18             emptyundef => 0,
19             useenv => 1,
20             format => 0,
21             recurse => 0,
22             recurse_limit => 0,
23             recurse_fail_limit => 100,
24             );
25              
26             my $default_strinterp;
27              
28             sub _generate_strinterp {
29              
30 6     6   739 my ( $me, $name, $args ) = @_;
31              
32 6 100 100     44 if ( ! defined $args || ! defined $args->{opts}) {
33 5   33     24 return $default_strinterp || _mk_strinterp( \%Opt );
34             }
35              
36 1         7 my %opt = %Opt;
37 1         2 $opt{lc $_} = $args->{opts}{$_} foreach keys %{$args->{opts}};
  1         7  
38 1         3 return _mk_strinterp( \%opt );
39             }
40              
41             sub _mk_strinterp {
42              
43 6     6   13 my $default_opt = shift;
44              
45             return sub {
46              
47 48     48   28052 my ( $text, $var, $opts ) = @_;
48              
49 48 100       140 $var = {} unless defined $var;
50              
51 48         260 my %opt = %$default_opt;
52              
53 48 100       140 if ( defined $opts ) {
54 26         136 $opt{ lc $_ } = $opts->{$_} foreach keys %$opts;
55             }
56             ## use critic
57              
58 48 100       136 my $fmt = $opt{format} ? ':([^}]+)' : '()';
59              
60 48         98 $opt{track} = {};
61 48         82 $opt{loop} = 0;
62 48         83 $opt{fmt} = $fmt;
63              
64 48         153 _strinterp( $text, $var, \%opt );
65              
66 38         264 return $text;
67             }
68 6         61 }
69              
70             sub _strinterp {
71              
72 93     93   150 my $var = $_[1];
73 93         136 my $opt = $_[2];
74 93         148 my $fmt = $opt->{fmt};
75 93         159 my $re = $opt->{variable_re};
76              
77 93         1045 $_[0] =~ s{
78             \$ # find a literal dollar sign
79             ( # followed by either
80             \{ ($re)(?:$fmt)? \} # a variable name in curly brackets ($2)
81             # and an optional sprintf format
82             | # or
83             (\w+) # a bareword ($3)
84             )
85             }{
86 99 100       325 my $t = defined $4 ? $4 : $2;
87              
88 99 100       269 my $user_value = 'CODE' eq ref $var ? $var->($t) : $var->{$t};
89              
90             my $v =
91             # user provided?
92             defined $user_value ? $user_value
93              
94             # maybe in the environment
95             : $opt->{useenv} && exists $ENV{$t} ? $ENV{$t}
96              
97             # undefined: throw an error?
98             : $opt->{raiseundef} ? croak( "undefined variable: $t\n" )
99              
100             # undefined: replace with ''?
101 99 100 100     905 : $opt->{emptyundef} ? ''
    100          
    100          
    100          
102              
103             # undefined
104             : undef
105              
106             ;
107              
108 96 100 66     296 if ( $opt->{recurse} && defined $v ) {
109              
110              
111             RECURSE:
112             {
113              
114 55         80 croak(
115             "circular interpolation loop detected with repeated interpolation of <\$$t>\n"
116 55 100       640 ) if $opt->{track}{$t}++;
117              
118 51         68 ++$opt->{loop};
119              
120 51 100 100     140 last RECURSE if $opt->{recurse_limit} && $opt->{loop} > $opt->{recurse_limit};
121              
122             croak(
123             "recursion fail-safe limit ($opt->{recurse_fail_limit}) reached at interpolation of <\$$t>\n"
124 48 100 100     723 ) if $opt->{recurse_fail_limit} && $opt->{loop} > $opt->{recurse_fail_limit};
125              
126 45         106 _strinterp( $v, $_[1], $_[2] );
127              
128             }
129              
130 32         68 delete $opt->{track}{$t};
131 32         41 --$opt->{loop};
132             }
133              
134             # if not defined, just put it back into the string
135 73 100 100     412 ! defined $v ? '$' . $1
    100          
136              
137             # no format? return as is
138             : ! defined $3 || $3 eq '' ? $v
139              
140             # format it
141             : sprintf( $3, $v)
142              
143             ;
144              
145             }egx;
146             }
147              
148             1;
149              
150             #
151             # This file is part of String-Interpolate-RE
152             #
153             # This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
154             #
155             # This is free software, licensed under:
156             #
157             # The GNU General Public License, Version 3, June 2007
158             #
159              
160             =pod
161              
162             =head1 NAME
163              
164             String::Interpolate::RE - interpolate variables into strings using regular expressions
165              
166             =head1 VERSION
167              
168             version 0.09
169              
170             =head1 SYNOPSIS
171              
172             # default formulation
173             use String::Interpolate::RE qw( strinterp );
174              
175             $str = strinterp( "${Var1} $Var2", $vars, \%opts );
176              
177             # import with different default options.
178             use String::Interpolate::RE strinterp => { opts => { useENV => 0 } };
179              
180             =head1 DESCRIPTION
181              
182             This module interpolates variables into strings using regular
183             expression matching rather than Perl's built-in interpolation
184             mechanism and thus hopefully does not suffer from the security
185             problems inherent in using B to interpolate into strings of
186             suspect ancestry.
187              
188             =head2 Changing the default option values
189              
190             The default values for L's options were not all well
191             thought out. B uses L,
192             allowing a version of L with saner defaults to be
193             exported. Simply specify them when importing:
194              
195             use String::Interpolate::RE strinterp => { opts => { useENV => 0 } };
196              
197             The subroutine may be renamed using the C<-as> option:
198              
199             use String::Interpolate::RE strinterp => { -as => strinterp_noenv,
200             opts => { useENV => 0 } };
201              
202             strinterp_noenv( ... );
203              
204             =head1 INTERFACE
205              
206             =over
207              
208             =item strinterp
209              
210             $str = strinterp( $template );
211             $str = strinterp( $template, $vars );
212             $str = strinterp( $template, $vars, \%opts );
213              
214             Interpolate variables into a template string, returning the
215             resultant string. The template string is scanned for tokens of the
216             form
217              
218             $VAR
219             ${VAR}
220              
221             where C is composed of one or more word characters (as defined by
222             the C<\w> Perl regular expression pattern). C is resolved using
223             the optional C<$vars> argument, which may either by a hashref (in
224             which case C must be a key), or a function reference (which is
225             passed C as its only argument and must return the value).
226              
227             If the value returned for C is defined, it will be interpolated
228             into the string at that point. By default, variables which are not
229             defined are by default left as is in the string.
230              
231             The C<%opts> parameter may be used to modify the behavior of this
232             function. The following (case insensitive) keys are recognized:
233              
234             =over
235              
236             =item format I
237              
238             If this flag is true, the template string may provide a C
239             compatible format which will be used to generate the interpolated
240             value. The format should be appended to the variable name with
241             an intervening C<:> character, e.g.
242              
243             ${VAR:fmt}
244              
245             For example,
246              
247             %var = ( foo => 3 );
248             print strinterp( '${foo:%03d}', \%var, { format => 1 } );
249              
250             would result in
251              
252             003
253              
254             =item raiseundef I
255              
256             If true, a variable which has not been defined will result in an
257             exception being raised. This defaults to false.
258              
259             =item emptyundef I
260              
261             If true, a variable which has not been defined will be replaced with
262             the empty string. This defaults to false.
263              
264             =item useENV I
265              
266             If true, the C<%ENV> hash will be searched for variables which are not
267             defined in the passed C<%var> hash. This defaults to true.
268              
269             =item recurse I
270              
271             If true, derived values are themselves scanned for variables to
272             interpolate. To specify a limit to the number of levels of recursions
273             to attempt, set the C option. Circular dependencies
274             are caught, but just to be safe there's a limit of recursion levels
275             specified by C, beyond which an exception is
276             thrown.
277              
278             For example,
279              
280             my %var = ( a => '$b', b => '$c', c => 'd' );
281             strinterp( '$a', \%var ) => '$b'
282             strinterp( '$a', \%var, { recurse => 1 } ) => 'd'
283             strinterp( '$a', \%var, { recurse => 1, recurse_limit => 1 } ) => '$c'
284              
285             strinterp( '$a', { a => '$b', b => '$a' } , { recurse => 1 }
286             recursive interpolation loop detected with repeated
287             interpolation of $a
288              
289             =item recurse_limit I
290              
291             The number of recursion levels to descend when recursing into a
292             variable's value before stopping. The default is C<0>, which means no
293             limit.
294              
295             =item recurse_fail_limit I
296              
297             The number of recursion levels to descend when recursing into a
298             variable's value before giving up and croaking. The default is C<100>.
299             Setting this to C<0> means no limit.
300              
301             =item variable_re I
302              
303             This specifies the regular expression (created with the C
304             operator) which will match a variable name. It defaults to
305             C. Don't use C<:>, C<{>, or C<}> in the regex, or things may
306             break.
307              
308             =back
309              
310             =back
311              
312             =head1 DIAGNOSTICS
313              
314             =over
315              
316             =item C<< undefined variable: %s >>
317              
318             This string is thrown if the C option is set and the
319             variable C<%s> is not defined.
320              
321             =item C<< recursive interpolation loop detected with repeated interpolation of <%s> >>
322              
323             When resolving nested interpolated values (with the C option
324             true ) a circular loop was found.
325              
326             =item C<< recursion fail-safe limit (%d) reached at interpolation of <%s> >>
327              
328             The recursion fail safe limit (C) was reached while
329             interpolating nested variable values (with the C option true ).
330              
331             =back
332              
333             =head1 BUGS AND LIMITATIONS
334              
335             You can make new bug reports, and view existing ones, through the
336             web interface at L.
337              
338             =head1 AUTHOR
339              
340             Diab Jerius
341              
342             =head1 COPYRIGHT AND LICENSE
343              
344             This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
345              
346             This is free software, licensed under:
347              
348             The GNU General Public License, Version 3, June 2007
349              
350             =cut
351              
352             __END__