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
|
|
|
|
|
|
|
|