File Coverage

blib/lib/String/Expando.pm
Criterion Covered Total %
statement 88 126 69.8
branch 30 84 35.7
condition 16 40 40.0
subroutine 13 20 65.0
pod 5 10 50.0
total 152 280 54.2


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