File Coverage

blib/lib/String/Interpolate/Shell.pm
Criterion Covered Total %
statement 55 61 90.1
branch 34 46 73.9
condition 5 8 62.5
subroutine 10 10 100.0
pod 1 1 100.0
total 105 126 83.3


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 2011 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of String-Interpolate-Shell
6             #
7             # String-Interpolate-Shell is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 3 of the License, or (at
10             # your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22             package String::Interpolate::Shell;
23              
24 3     3   93679 use strict;
  3         9  
  3         115  
25 3     3   17 use warnings;
  3         5  
  3         121  
26              
27 3     3   4459 use Text::Balanced qw[ extract_bracketed extract_multiple extract_quotelike];
  3         100399  
  3         542  
28 3     3   5269 use Params::Check qw[ check ];
  3         23142  
  3         275  
29 3     3   36 use Carp;
  3         9  
  3         539  
30              
31 3     3   19 use base 'Exporter';
  3         7  
  3         11946  
32              
33             our @EXPORT_OK = qw[ strinterp ];
34              
35             our $VERSION = 0.01;
36              
37             sub _extract {
38              
39             extract_multiple( $_[0],
40             [
41             qr/\s+/,
42             qr/\\(\\)/,
43             qr/\\(\$)/,
44             { V => qr/\$(\w+)/ },
45 30     30   3842 { B => sub { (extract_bracketed( $_[0], '{}', qr/\$/ ))[0] } },
46 21     21   302 ] );
47             }
48              
49             sub _handle_undef {
50              
51 13     13   28 my ( $var, $q, $attr, $rep ) = @_;
52              
53             ## no critic(ProhibitAccessOfPrivateData)
54              
55 13 100       82 return $var->{$q} if defined $var->{$q};
56              
57 5 100       213 carp( sprintf( $attr->{undef_message}, $rep) )
58             if $attr->{undef_verbosity} eq 'warn';
59              
60 5 100       553 croak( sprintf( $attr->{undef_message}, $rep) )
61             if $attr->{undef_verbosity} eq 'fatal';
62              
63 3 100       19 return $rep if $attr->{undef_value} eq 'ignore';
64             }
65              
66             sub strinterp{
67              
68 21     21 1 11703 my ( $text, $var, $attr ) = @_;
69              
70             ## no critic(ProhibitAccessOfPrivateData)
71 21 50 100     286 $attr = check( {
72             undef_value => { allow => [ qw[ ignore remove ] ],
73             default => 'ignore' },
74             undef_verbosity => { allow => [ qw[ silent warn fatal ] ],
75             default => 'silent' },
76             undef_message => { default => "undefined variable: %s\n" },
77             }, $attr || {} )
78             or croak( "error parsing arguments: ", Params::Check::last_error() );
79              
80 21         1734 my @matches;
81              
82 21         50 for my $matchstr ( _extract($text ) ) {
83              
84 25         2997 my $ref = ref $matchstr;
85              
86 25 100       87 if ( 'B' eq $ref ) {
    100          
    50          
87              
88             # remove enclosing brackets
89 13         44 my $match = substr( $$matchstr, 1,-1 );
90              
91             # see if there's a 'shell' modifier expression
92 13         80 my ( $ind, $q, $modf, $rest ) = $match =~ /^(!)?(\w+)(:[-?=+~:])?(.*)/;
93              
94             # if there's no modifier but there is trailing cruft, it's an error
95 13 50 66     59 die( "unrecognizeable variable name: \${$match}\n")
96             if ! defined $modf && $rest ne '';
97              
98             # if indirect flag is set, expand variable
99 13 100       30 if ( defined $ind ) {
100              
101 1 50       5 if ( defined $var->{$q} ) {
102 1         3 $q = $var->{$q};
103             }
104             else
105             {
106 0         0 push @matches, _handle_undef( $attr, '$' . $$matchstr );
107 0         0 next;
108             }
109             }
110              
111              
112 13 100       63 if ( ! defined $modf ) {
    100          
    100          
    50          
    100          
    100          
    50          
113              
114 4         16 push @matches, _handle_undef( $var, $q, $attr, '$' . $$matchstr );
115             }
116              
117             elsif ( ':?' eq $modf ) {
118              
119 2         6 local $attr->{undef_verbosity} = 'fatal';
120 2         5 local $attr->{undef_message} = $rest;
121              
122 2         9 push @matches, _handle_undef( $var, $q, $attr, '$' . $$matchstr );
123              
124             }
125             elsif ( ':-' eq $modf ) {
126              
127 1 50       6 push @matches, defined $var->{$q} ? $var->{$q} : strinterp( $rest, $var, $attr );
128              
129             }
130              
131             elsif ( ':=' eq $modf ) {
132              
133              
134 0 0       0 $var->{$q} = strinterp( $rest, $var, $attr )
135             unless defined $var->{$q};
136 0         0 push @matches, $var->{$q};
137              
138             }
139              
140             elsif ( ':+' eq $modf ) {
141              
142 2 50       14 push @matches, strinterp( $rest, $var, $attr )
143             if defined $var->{$q};
144              
145             }
146              
147             elsif ( '::' eq $modf ) {
148              
149 1         4 push @matches, sprintf( $rest, _handle_undef( $var, $q, $attr, '$' . $$matchstr ) );
150              
151             }
152              
153             elsif ( ':~' eq $modf ) {
154              
155 3         11 my ( $expr, $xtra, $op ) = (extract_quotelike( $rest ))[0,1,3];
156 3 50 33     478 die( "unable to parse variable substitution command: $rest\n" )
157             if $xtra !~ /^\s*$/ or $op !~ /^(s|tr|y)$/;
158              
159 3         9 my $t = $var->{$q};
160             ## no critic(ProhibitStringyEval)
161 3         317 eval "\$t =~ $expr";
162 3 50       15 die $@ if $@;
163              
164 3         17 push @matches, $t;
165              
166             }
167              
168 0         0 else { die( "internal error" ) }
169              
170             }
171             elsif ( 'V' eq $ref ) {
172              
173 6         11 my $q = $$matchstr;
174              
175 6         23 push @matches, _handle_undef( $var, $q, $attr, "\$$q" );
176              
177             }
178              
179             elsif ( $ref ) {
180              
181 0         0 push @matches, $$matchstr;
182              
183             }
184             else {
185              
186 6         12 push @matches, $matchstr;
187              
188             }
189              
190              
191             }
192              
193 19         252 return join('', @matches);
194              
195             }
196              
197             1;
198              
199             __END__