File Coverage

blib/lib/String/Expando.pm
Criterion Covered Total %
statement 90 131 68.7
branch 34 86 39.5
condition 17 40 42.5
subroutine 14 20 70.0
pod 5 10 50.0
total 160 287 55.7


line stmt bran cond sub pod time code
1             package String::Expando;
2              
3 4     4   5471 use strict;
  4         8  
  4         120  
4 4     4   19 use warnings;
  4         7  
  4         112  
5              
6 4     4   20 use vars qw($VERSION);
  4         8  
  4         7718  
7              
8             $VERSION = '0.08';
9              
10             sub new {
11 3     3 1 1231 my $cls = shift;
12 3         9 my $self = bless { @_ }, $cls;
13 3         9 $self->init;
14             }
15              
16             sub init {
17 3     3 0 8 my ($self) = @_;
18 3 50       16 if (defined $self->{'expando'}) {
19 0         0 my $rx = qr/$self->{'expando'}/;
20             $self->{'consume_expando'} = sub {
21 0 0   0   0 $_ =~ s/\A$rx// ? (defined $2 ? $2 : $1) : ()
    0          
22             }
23 0         0 }
24             else {
25             $self->{'consume_expando'} ||= sub {
26 79 100   79   428 s{ \A \% ([^%()]*) \( ([^\s()]+) \) }{}x
27             ? ($2, $1)
28             : ()
29 3   50     23 };
30             }
31 3 50       9 if (defined $self->{'literal'}) {
32 0         0 my $rx = qr/$self->{'literal'}/;
33             $self->{'consume_literal'} = sub {
34 0 0   0   0 s/\A$rx// ? ($1) : ()
35             }
36 0         0 }
37             else {
38             $self->{'consume_literal'} ||= sub {
39 47 50   47   239 s{\A(.)}{}s ? ($1) : ()
40             }
41 3   50     17 }
42 3 50       7 if (defined $self->{'escaped_literal'}) {
43 0         0 my $rx = qr/$self->{'escaped_literal'}/;
44             $self->{'consume_escaped_literal'} = sub {
45 0 0   0   0 $_ =~ s/\A$rx// ? ($1) : ()
46             }
47 0         0 }
48             else {
49             $self->{'consume_escaped_literal'} ||= sub {
50 49 100   49   147 s{\A\\(.)}{} ? ($1) : ()
51             }
52 3   50     15 }
53 3 100       8 if (defined $self->{'dot_separator'}) {
54 1         2 my $dot = $self->{'dot_separator'};
55 1         1 my $rx;
56 1 50       3 if (ref($dot) eq '') {
    0          
57 1         16 $rx = qr/\Q$dot\E/;
58             }
59             elsif (ref($dot) eq 'Regexp') {
60 0         0 $rx = $dot;
61             }
62             else {
63 0         0 $rx = qr/$dot/;
64             }
65 1   50     7 my $part_decoder = $self->{'decoder'} || \&decode;
66             $self->{'decoder'} ||= sub {
67 9     9   15 my ($self, $code, $stash) = @_;
68 9         41 my @parts = split $rx, $code;
69 9         18 my $val = $stash;
70 9         13 foreach my $part (@parts) {
71 17         26 $val = $part_decoder->($self, $part, $val);
72 17 50       36 last if !defined $val;
73             }
74 9         19 return $val;
75 1   50     6 };
76             }
77             else {
78 2   50     8 $self->{'decoder'} ||= \&decode;
79             }
80 3   50     16 $self->{'stringify'} ||= \&stringify;
81 3   50     18 $self->{'stash'} ||= {};
82 3   50     17 $self->{'functions'} ||= {};
83 3   100     13 $self->{'default_hash_keys'} ||= [q{}, q{""}, q{'}];
84 3         10 return $self;
85             }
86              
87 0 0   0 1 0 sub stash { @_ > 1 ? $_[0]->{'stash'} = $_[1] : $_[0]->{'stash'} }
88 0 0   0 1 0 sub functions { @_ > 1 ? $_[0]->{'functions'} = $_[1] : $_[0]->{'functions'} }
89 4 50   4 1 11 sub default_hash_keys { @_ > 1 ? $_[0]->{'default_hash_keys'} = $_[1] : $_[0]->{'default_hash_keys'} }
90              
91             sub expand {
92 22     22 0 11462 my ($self, $str, $stash) = @_;
93 22   33     56 $stash ||= $self->{'stash'};
94 22         41 my $mat = $self->{'consume_expando'};
95 22         32 my $lit = $self->{'consume_literal'};
96 22         54 my $esc = $self->{'consume_escaped_literal'};
97 22         31 my $dec = $self->{'decoder'};
98 22         32 my $sfy = $self->{'stringify'};
99 22         32 my $out = '';
100 22         36 local $_ = $str;
101 22         84 while (length $_) {
102 79         96 my $res;
103 79 100 66     127 if (my ($code, $fmt) = $mat->()) {
    100          
    50          
104 17         39 my $val = $dec->($self, $code, $stash);
105 17         34 $res = $sfy->($self, $val);
106 17 50 33     85 $res = sprintf($fmt, $res) if defined $fmt && length $fmt;
107             }
108             elsif (s/\A(\s+)//) {
109 13         26 $res = $1;
110             }
111             elsif (defined ($res = &$esc) || defined ($res = &$lit)) {
112 49         69 1;
113             }
114             else {
115 0         0 my $p = length($str) - length($_);
116 0         0 my $L = substr($str, 0, $p);
117 0         0 my $R = substr($str, $p);
118 0         0 die "Unparseable substring at <*>: $L<*>$R";
119 0         0 $res = $1;
120             }
121 79         178 $out .= $res;
122             }
123 22         95 return $out;
124             }
125              
126             sub decode {
127 25     25 0 43 my ($self, $code, $stash) = @_;
128 25         42 my $sr = ref $stash;
129 25 50       48 return if $sr eq '';
130 25         35 my $val;
131 25         65 eval { $val = $stash->{$code}; 1 }
  22         52  
132             or
133 25 50 66     32 eval { $val = $stash->[$code]; 1 }
  3         8  
  3         10  
134             or die "unable to decode $code given stash type $sr";
135 25         55 return value($val);
136             }
137              
138             sub value {
139 25     25 0 39 my ($val) = @_;
140 25 100       54 $val = $val->() if ref($val) eq 'CODE';
141 25 100       46 return '' if !defined $val;
142 23         48 return $val;
143             }
144              
145             sub old_decode {
146 0     0 0 0 my ($self, $code, $stash) = @_;
147             # XXX Not quite working fancy-dancy decoding follows...
148 0   0     0 my $val = $stash || $self->stash;
149 0         0 my $func = $self->functions;
150 0         0 my $rval = ref($val);
151 0 0       0 $code =~ s/^\.?/./ if $rval eq 'HASH';
152 0   0     0 $func ||= {};
153 0         0 while ($code =~ s{
154             ^
155             (?:
156             \[ (-?\d+) (?: \.\. (-?\d+) )? \]
157             |
158             \. ([^\s.:\[\]\(\)]+)
159             |
160             :: ([^\s.:\[\]\(\)]+)
161             )
162             }{}xg) {
163 0         0 my ($l, $r, $k, $f) = ($1, $2, $3, $4);
164 0 0       0 if (defined $f) {
    0          
    0          
165 0 0       0 die "No such function: $f" if !$func->{$f} ;
166 0         0 $val = $func->{$f}->($val);
167             }
168             elsif ($rval eq 'HASH') {
169 0 0 0     0 die if defined $l or defined $r;
170 0         0 $val = $val->{$k};
171             }
172             elsif ($rval eq 'ARRAY') {
173 0 0       0 die if defined $k;
174 0 0       0 $val = defined $r ? [ @$val[$l..$r] ] : $val->[$l];
175             }
176             else {
177 0         0 die "Can't subval: ref = '$rval'";
178             }
179 0         0 $rval = ref $val;
180             }
181 0 0       0 die if length $code;
182 0 0       0 return join('', @$val) if $rval eq 'ARRAY';
183 0 0       0 return join('', values %$val) if $rval eq 'HASH';
184 0         0 return $val;
185             }
186              
187             sub stringify {
188             # For backward compatibility, we allow $self-less calls...
189 20     20 1 32 my $val = pop;
190 20         31 my ($self) = @_;
191 20 50       37 return '' if !defined $val;
192 20         28 my $r = ref $val;
193 20 100       52 return $val if $r eq '';
194             # ...and we don't recurse if we don't have $self
195 4 50       8 return '' if !$self;
196 4         6 my $sfy = $self->{'stringify'};
197 4 50       8 return join('', map { $sfy->($self, $_) } @$val) if $r eq 'ARRAY';
  0         0  
198 4 50       8 return join('', map { $sfy->($self, $_) } $val->()) if $r eq 'CODE';
  0         0  
199 4 50       9 if ($r eq 'HASH') {
200 4         6 foreach (@{ $self->default_hash_keys }) {
  4         7  
201 9 100       24 return $sfy->($self, $val->{$_}) if defined $val->{$_};
202             }
203             }
204 1         3 return '';
205             }
206              
207             1;
208              
209             =pod
210              
211             =head1 NAME
212              
213             String::Expando - expand %(foo) codes in strings
214              
215             =head1 SYNOPSIS
216              
217             $e = String::Expando->new;
218             print $e->expand('%(foo) %(bar)', { foo => 'Hello', bar => 'world!' }), "\n";
219             print $e->expand(
220             '### %04d(year)-%02d(month)-%02d(day)
221             { year => 2011, month => 3, day => 9 }
222             ), "\n";
223             ### 2011-03-09
224              
225             =head1 METHODS
226              
227             =over 4
228              
229             =item B
230              
231             $e = String::Expando->new;
232             $e = String::Expando->new(
233             # "[% foo %]" -> $stash->{foo}
234             'expando' => qr/\[%\s*([^%]+?)\s*%\]/,
235             # "%%" -> "%"
236             'escaped_literal' => qr/%(%)/,
237             # etc.
238             'literal' => qr/(.)/,
239             );
240             $e = String::Expando->new(
241             # "%[.2f]L" => sprintf('%.2f', $stash->{L})
242             'expando' => qr{
243             (?x)
244             %
245             # Optional format string
246             (?:
247             \[
248             ([^\]]+)
249             \]
250             )?
251             # Stash key
252             ( [A-Za-z0-9] )
253             },
254             'stash' => { A => 1, B => 2, ... },
255             );
256              
257             Create a new expando object. Arguments allowed are as follows.
258              
259             =over 4
260              
261             =item B
262              
263             The hash from which expando values are obtained. An expando C<%(xyz)> expanded
264             using stash C<$h> will yield the value of C<$h->{'xyz'}> (or the empty string,
265             if the value of C<$h->{'xyz'}> is undefined).
266              
267             =item B
268              
269             The regexp (or simple scalar) to use to identify expando codes when parsing the
270             input. It must contain a capture group for what will become the key into the
271             stash. If it contains two capture groups and $2 is defined (and not empty)
272             after matching, the value of $1 will be used with sprintf to produce the final
273             output.
274              
275             The default is:
276              
277             qr/
278             (?x)
279             \%
280             ([^%()]*j
281             \(
282             ([^\s()]+)
283             \)
284             /
285              
286             In other words, C<%(...)> with an optional format string between C<%> and C<(>.
287              
288             =back
289              
290             =item B
291              
292             $h = $e->stash;
293             $e->stash(\%hash);
294              
295             Get or set the stash from which expando values will be obtained.
296              
297             =item B
298              
299             A reference to a function with the signature C<($expando, $k, $stash)> that
300             is called to obtain a value from C<$stash> using code C<$k>.
301              
302             The default is to call C<$expando->decode($code, $stash)>, which returns:
303              
304             =over 4
305              
306             =item *
307              
308             The empty string if C<$stash> is scalar value.
309              
310             =item *
311              
312             =item *
313              
314             C<$stash->{$code}> if C<$stash> is a hash reference and C<$stash->{$k}> is a scalar.
315              
316             =item *
317              
318             C<$stash->[$code]> if C<$stash> is an array
319             reference and C<$stash->[$k]> is a scalar; C<< $stash->{$code}->() >> if
320             C<$stash> is a hash reference and C<$stash->{$k}> is a code reference; or C<<
321             $stash->[$code]->() >> if C<$stash> is an array reference and C<$stash->[$k]>
322             is a code reference.
323              
324             =back
325              
326             =item B B
327              
328             A separator to use in expando codes in order to access values not at the top
329             level of the stash. Decoding happens
330              
331             For example, if B is set to C<.> or C then the expando
332             C expanded using stash C<$h> will yield the same value as the
333             expando C expanded using stash C<< $h->{foo}{bar} >> (or the empty string,
334             if said value is undefined). This may or may not be the same value as
335             C<< $h->{foo}{bar}{baz} >>, depending on the B.
336              
337             For example, if C<$h> is this:
338              
339             {
340             'foo' => {
341             'bar' => sub { return { 'baz' => 123 } },
342             },
343             }
344            
345             Then C will expand to C<123>.
346              
347             By default, no dot separator is defined.
348              
349             =item B
350             =item B
351             =item B
352             =item B
353              
354             When expanding, the result of the expansion
355              
356             =item B
357             =item B
358             =item B
359             =item B
360             =item B
361              
362             A coderef that will be used to stringify an expanded value. The code will be
363             called with two arguments: the String::Expando object and the datum to stringify:
364              
365             $stringify->($expando, $val);
366              
367             =back
368              
369             =cut
370