File Coverage

blib/lib/String/Interpolate/RE.pm
Criterion Covered Total %
statement 60 60 100.0
branch 44 44 100.0
condition 22 24 91.6
subroutine 9 9 100.0
pod n/a
total 135 137 98.5


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 8     8   1543278 use v5.10;
  8         30  
6              
7 8     8   48 use strict;
  8         14  
  8         222  
8 8     8   35 use warnings;
  8         16  
  8         385  
9              
10 8     8   3635 use Exporter::Shiny qw[ strinterp ];
  8         48980  
  8         59  
11              
12             our $VERSION = '0.12';
13              
14             my %Opt = (
15             variable_re => qr/\w+/,
16             raiseundef => !!0,
17             emptyundef => !!0,
18             useenv => !!1,
19             format => !!0,
20             recurse => !!0,
21             recurse_limit => 0,
22             recurse_fail_limit => 100,
23             fallback => undef,
24             );
25             my %Defaults = map { $_ => $Opt{$_} } grep { defined $Opt{$_} } keys %Opt;
26              
27              
28             *strinterp = _mk_strinterp( \%Defaults );
29              
30             sub _croak {
31 14     14   106 require Carp;
32 14         2435 goto &Carp::croak;
33             }
34              
35             sub _generate_strinterp {
36              
37 9     9   298362 my ( undef, undef, $args ) = @_;
38              
39             return \&strinterp
40 9 100 100     81 if !defined $args || !defined $args->{opts};
41              
42 3         27 my %opt = %Defaults;
43 3         7 $opt{ lc $_ } = $args->{opts}{$_} foreach keys %{ $args->{opts} };
  3         18  
44 3 100       24 if ( my @bad = grep !exists $Opt{$_}, keys %opt ) {
45 1         7 _croak( 'unrecognized option(s): ' . join( ', ', @bad ) );
46             }
47              
48             _croak( q{'fallback' option must be a coderef} )
49 2 100 66     16 if exists $opt{fallback} && ref( $opt{fallback} ) ne 'CODE';
50              
51 1         3 return _mk_strinterp( \%opt );
52             }
53              
54             sub _mk_strinterp {
55              
56 9     9   20 my $default_opt = shift;
57              
58             return sub {
59              
60 54     54   1541860 my ( $text, $var, $opts ) = @_;
61              
62 54 100       177 $var = {} unless defined $var;
63              
64 54         353 my %opt = %$default_opt;
65              
66 54 100       159 if ( defined $opts ) {
67 31         141 $opt{ lc $_ } = $opts->{$_} foreach keys %$opts;
68 31 100       181 if ( my @bad = grep !exists $Opt{$_}, keys %opt ) {
69 1         7 _croak( 'unrecognized option(s): ' . join( ', ', @bad ) );
70             }
71             _croak( q{'fallback' option must be a coderef} )
72 30 100 100     112 if exists $opt{fallback} && ref( $opt{fallback} ) ne 'CODE';
73             }
74              
75 52 100       126 my $fmt = $opt{format} ? ':([^}]+)' : '()';
76              
77 52         106 $opt{track} = {};
78 52         149 $opt{loop} = 0;
79 52         102 $opt{fmt} = $fmt;
80              
81 52         144 _strinterp( $text, $var, \%opt );
82              
83 42         307 return $text;
84 9         69 };
85             }
86              
87             sub _strinterp {
88              
89 97     97   135 my $var = $_[1];
90 97         118 my $opt = $_[2];
91 97         137 my $fmt = $opt->{fmt};
92 97         125 my $re = $opt->{variable_re};
93              
94             # The following code pulls things out of the hash to reduce the
95             # number of hash lookups in the code in the RE. Unfortunately, iqt
96             # doesn't seem to make much of a difference, but it does clean
97             # that code up a bit.
98              
99             my ( $useenv, $raiseundef, $recurse, $fallback, $emptyundef, $track,
100             $recurse_limit, $recurse_fail_limit )
101 97         303 = @{$opt}{
102 97         155 qw( useenv raiseundef recurse fallback emptyundef track
103             recurse_limit recurse_fail_limit
104             ) };
105              
106 97         139 my $rloop = \( $opt->{loop} );
107 97         177 my $is_code = 'CODE' eq ref $var;
108              
109 97         2002 $_[0] =~ s{
110             \$ # find a literal dollar sign
111             ( # followed by either
112             [{] ($re)(?:$fmt)? [}] # a variable name in curly brackets ($2)
113             # and an optional sprintf format
114             | # or
115             (\w+) # a bareword ($3)
116             )
117             }{
118 111 100       325 my $t = defined $4 ? $4 : $2;
119              
120             my $user_value
121             = $is_code ? $var->( $t )
122 111 100       300 : exists $var->{$t} ? $var->{$t}
    100          
    100          
123             : $fallback ? $fallback->( $t )
124             : undef;
125              
126             #<<<
127             my $v =
128             # user provided?
129             defined $user_value ? $user_value
130              
131             # maybe in the environment
132 111 100 100     335 : $useenv && exists $ENV{$t} ? $ENV{$t}
    100          
    100          
133              
134             # undefined: throw an error ?
135             : $raiseundef ? _croak( "undefined variable: $t\n" )
136              
137             # undefined
138             : undef;
139             #>>>
140              
141 108 100 66     288 if ( $recurse && defined $v ) {
142              
143             RECURSE:
144             {
145 55         50 _croak( "circular interpolation loop detected with repeated interpolation of <\$$t>\n" )
146 55 100       130 if $track->{$t}++;
147              
148 51         68 my $loop = ++$$rloop;
149              
150 51 100 100     84 last RECURSE if $recurse_limit && $loop > $recurse_limit;
151              
152 48 100 100     120 _croak( "recursion fail-safe limit ($recurse_fail_limit) reached at interpolation of <\$$t>\n" )
153             if $recurse_fail_limit && $loop > $recurse_fail_limit;
154              
155 45         82 _strinterp( $v, $_[1], $_[2] );
156             }
157              
158 32         45 delete $track->{$t};
159 32         35 --$$rloop;
160             }
161              
162             # if not defined:
163             # if emptyundef, replace with an empty string
164             # otherwise, just put it back into the string
165 85 100 100     430 !defined $v
    100          
    100          
166             ? ( $emptyundef ? '' : '$' . $1 )
167              
168             # no format? return as is
169             : !defined $3 || $3 eq '' ? $v
170              
171             # format it
172             : sprintf( $3, $v )
173              
174             ;
175             }egx;
176             }
177              
178             1;
179              
180             #
181             # This file is part of String-Interpolate-RE
182             #
183             # This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
184             #
185             # This is free software, licensed under:
186             #
187             # The GNU General Public License, Version 3, June 2007
188             #
189              
190             __END__