File Coverage

blib/lib/String/Expando.pm
Criterion Covered Total %
statement 47 81 58.0
branch 14 58 24.1
condition 9 29 31.0
subroutine 9 16 56.2
pod 2 7 28.5
total 81 191 42.4


line stmt bran cond sub pod time code
1             package String::Expando;
2              
3 2     2   2652 use strict;
  2         18  
  2         59  
4 2     2   12 use warnings;
  2         6  
  2         75  
5              
6 2     2   13 use vars qw($VERSION);
  2         5  
  2         2774  
7              
8             $VERSION = '0.05';
9              
10             sub new {
11 1     1 1 453 my $cls = shift;
12 1         3 my $self = bless { @_ }, $cls;
13 1         3 $self->init;
14             }
15              
16             sub init {
17 1     1 0 3 my ($self) = @_;
18 1 50       6 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 20 100   20   85 m{ \G \% ([^%()]*) \( ([^\s()]+) \) }xgc
27             ? ($2, $1)
28             : ()
29 1   50     11 };
30             }
31 1 50       4 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 16 50   16   68 m{ \G (.) }xgc ? ($1) : ()
40             }
41 1   50     7 }
42 1 50       3 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 1   50     7 }
53 1   50     6 $self->{'decoder'} ||= \&decode;
54 1   50     6 $self->{'stash'} ||= {};
55 1   50     7 $self->{'functions'} ||= {};
56 1         4 return $self;
57             }
58              
59 0 0   0 1 0 sub stash { @_ > 1 ? $_[0]->{'stash'} = $_[1] : $_[0]->{'stash'} }
60 0 0   0 0 0 sub functions { @_ > 1 ? $_[0]->{'functions'} = $_[1] : $_[0]->{'functions'} }
61              
62             sub expand {
63 8     8 0 4089 my ($self, $str, $stash) = @_;
64 8   33     20 $stash ||= $self->{'stash'};
65 8         16 my $mat = $self->{'consume_expando'};
66 8         10 my $lit = $self->{'consume_literal'};
67 8         11 my $esc = $self->{'consume_escaped_literal'};
68 8         10 my $dec = $self->{'decoder'};
69 8         12 my $out = '';
70 8         11 local $_ = $str;
71 8         22 pos($_) = 0;
72 8         25 while (pos($_) < length($_)) {
73 20         35 my $res;
74 20 100 33     32 if (my ($code, $fmt) = $mat->()) {
    50          
75 4         10 $res = $dec->($self, $code, $stash);
76 4 100       12 $res = '' if !defined $res;
77 4 50 33     18 $res = sprintf($fmt, $res) if defined $fmt && length $fmt;
78             }
79             elsif (!defined ($res = &$lit)
80             && !defined ($res = &$esc)) {
81 0         0 die "Unparseable: $_";
82             }
83 20         44 $out .= $res;
84             }
85 8         39 return $out;
86             }
87              
88             sub decode {
89 4     4 0 8 my ($self, $code, $stash) = @_;
90 4         8 my $val = $stash->{$code};
91 4 50       11 $val = &$val if ref($val) eq 'CODE';
92 4 50       9 $val = join('', @$val) if ref($val) eq 'ARRAY';
93 4         8 return $val;
94             }
95              
96             sub old_decode {
97 0     0 0   my ($self, $code, $stash) = @_;
98             # XXX Not quite working fancy-dancy decoding follows...
99 0   0       my $val = $stash || $self->stash;
100 0           my $func = $self->functions;
101 0           my $rval = ref($val);
102 0 0         $code =~ s/^\.?/./ if $rval eq 'HASH';
103 0   0       $func ||= {};
104 0           while ($code =~ s{
105             ^
106             (?:
107             \[ (-?\d+) (?: \.\. (-?\d+) )? \]
108             |
109             \. ([^\s.:\[\]\(\)]+)
110             |
111             :: ([^\s.:\[\]\(\)]+)
112             )
113             }{}xg) {
114 0           my ($l, $r, $k, $f) = ($1, $2, $3, $4);
115 0 0         if (defined $f) {
    0          
    0          
116 0 0         die "No such function: $f" if !$func->{$f} ;
117 0           $val = $func->{$f}->($val);
118             }
119             elsif ($rval eq 'HASH') {
120 0 0 0       die if defined $l or defined $r;
121 0           $val = $val->{$k};
122             }
123             elsif ($rval eq 'ARRAY') {
124 0 0         die if defined $k;
125 0 0         $val = defined $r ? [ @$val[$l..$r] ] : $val->[$l];
126             }
127             else {
128 0           die "Can't subval: ref = '$rval'";
129             }
130 0           $rval = ref $val;
131             }
132 0 0         die if length $code;
133 0 0         return join('', @$val) if $rval eq 'ARRAY';
134 0 0         return join('', values %$val) if $rval eq 'HASH';
135 0           return $val;
136             }
137              
138             1;
139              
140             =pod
141              
142             =head1 NAME
143              
144             String::Expando - expand %(foo) codes in strings
145              
146             =head1 SYNOPSIS
147              
148             $e = String::Expando->new;
149             print $e->expand('%(foo) %(bar)', { foo => 'Hello', bar => 'world!' }), "\n";
150             print $e->expand(
151             '### %04d(year)-%02d(month)-%02d(day)
152             { year => 2011, month => 3, day => 9 }
153             ), "\n";
154             ### 2011-03-09
155              
156             =head1 METHODS
157              
158             =over 4
159              
160             =item B
161              
162             $e = String::Expando->new;
163             $e = String::Expando->new(
164             # "[% foo %]" -> $stash->{foo}
165             'expando' => qr/\[%\s*([^%]+?)\s*%\]/,
166             # "%%" -> "%"
167             'escaped_literal' => qr/%(%)/,
168             # etc.
169             'literal' => qr/(.)/,
170             );
171             $e = String::Expando->new(
172             # "%[.2f]L" => sprintf('%.2f', $stash->{L})
173             'expando' => qr{
174             (?x)
175             %
176             # Optional format string
177             (?:
178             \[
179             ([^\]]+)
180             \]
181             )?
182             # Stash key
183             ( [A-Za-z0-9] )
184             },
185             'stash' => { A => 1, B => 2, ... },
186             );
187              
188             Create a new expando object. Arguments allowed are as follows.
189              
190             =over 4
191              
192             =item B
193              
194             The hash from which expando values are obtained. An expando C<%(xyz)> expanded
195             using stash C<$h> will yield the value of C<$h->{'xyz'}> (or the empty string,
196             if the value of C<$h->{'xyz'}> is undefined).
197              
198             =item B
199              
200             The regexp (or simple scalar) to use to identify expando codes when parsing the
201             input. It must contain a capture group for what will become the key into the
202             stash. If it contains two capture groups and $2 is defined (and not empty)
203             after matching, the value of $1 will be used with sprintf to produce the final
204             output.
205              
206             The default is:
207              
208             qr/
209             (?x)
210             \%
211             ([^%()]*j
212             \(
213             ([^\s()]+)
214             \)
215             /
216              
217             In other words, C<%(...)> with an optional format string between C<%> and C<(>.
218              
219             =back
220              
221             =item B
222              
223             $h = $e->stash;
224             $e->stash(\%hash);
225              
226             Get or set the stash from which expando values will be obtained.
227              
228             =back
229              
230             =cut
231