line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Plack::Middleware::ReviseEnv; |
2
|
|
|
|
|
|
|
|
3
|
7
|
|
|
7
|
|
5404
|
use strict; |
|
7
|
|
|
|
|
9
|
|
|
7
|
|
|
|
|
192
|
|
4
|
7
|
|
|
7
|
|
25
|
use warnings; |
|
7
|
|
|
|
|
7
|
|
|
7
|
|
|
|
|
201
|
|
5
|
7
|
|
|
7
|
|
32
|
use Carp qw< confess >; |
|
7
|
|
|
|
|
7
|
|
|
7
|
|
|
|
|
440
|
|
6
|
7
|
|
|
7
|
|
3265
|
use English qw< -no_match_vars >; |
|
7
|
|
|
|
|
5144
|
|
|
7
|
|
|
|
|
30
|
|
7
|
|
|
|
|
|
|
{ our $VERSION = '0.004'; } |
8
|
|
|
|
|
|
|
|
9
|
7
|
|
|
7
|
|
2476
|
use parent 'Plack::Middleware'; |
|
7
|
|
|
|
|
241
|
|
|
7
|
|
|
|
|
45
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
sub call { |
12
|
8
|
|
|
8
|
1
|
98503
|
my ($self, $env) = @_; |
13
|
8
|
|
|
|
|
37
|
my %vars = (env => $env, ENV => \%ENV); |
14
|
|
|
|
|
|
|
REVISOR: |
15
|
8
|
50
|
|
|
|
9
|
for my $revisor (@{$self->{revisors} || []}) { |
|
8
|
|
|
|
|
70
|
|
16
|
|
|
|
|
|
|
my ($key, $value) = map { |
17
|
103
|
|
|
|
|
85
|
my $retval = $revisor->{$_}; |
|
206
|
|
|
|
|
187
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# if array reference, there's more work to do |
20
|
206
|
100
|
|
|
|
257
|
if (ref $retval) { |
21
|
191
|
|
|
|
|
119
|
my $all_defs = 1; |
22
|
311
|
100
|
|
|
|
451
|
my @parts = grep { defined($_) ? 1 : ($all_defs = 0) } map { |
23
|
191
|
|
|
|
|
161
|
(!ref($_)) ? $_ |
24
|
|
|
|
|
|
|
: exists($vars{$_->{src}}{$_->{key}}) |
25
|
|
|
|
|
|
|
? $vars{$_->{src}}{$_->{key}} |
26
|
311
|
100
|
|
|
|
574
|
: undef; |
|
|
100
|
|
|
|
|
|
27
|
|
|
|
|
|
|
} @$retval; |
28
|
|
|
|
|
|
|
|
29
|
191
|
100
|
100
|
|
|
516
|
$retval = ($revisor->{require_all} && (!$all_defs)) |
30
|
|
|
|
|
|
|
? undef |
31
|
|
|
|
|
|
|
: join '', @parts; |
32
|
|
|
|
|
|
|
} ## end if (defined $retval) |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# last chance to have a say on $retval... |
35
|
|
|
|
|
|
|
$retval = $revisor->{'default_' . $_} |
36
|
|
|
|
|
|
|
if (!defined($retval)) |
37
|
206
|
100
|
66
|
|
|
417
|
|| ((length($retval) == 0) && $revisor->{empty_as_default}); |
|
|
|
66
|
|
|
|
|
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# save for next iteration, if so requested |
40
|
206
|
100
|
|
|
|
297
|
$revisor->{$_} = $retval if $revisor->{cache}; |
41
|
|
|
|
|
|
|
|
42
|
206
|
|
|
|
|
302
|
$retval; |
43
|
|
|
|
|
|
|
} qw< key value >; |
44
|
|
|
|
|
|
|
|
45
|
103
|
50
|
|
|
|
147
|
next unless defined $key; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
$env->{$key} = $value |
48
|
103
|
100
|
100
|
|
|
245
|
if $revisor->{override} || (!exists($env->{$key})); |
49
|
103
|
100
|
|
|
|
173
|
delete $env->{$key} unless defined $value; |
50
|
|
|
|
|
|
|
} ## end REVISOR: for my $revisor (@{$self...}) |
51
|
|
|
|
|
|
|
|
52
|
8
|
|
|
|
|
69
|
return $self->app()->($env); |
53
|
|
|
|
|
|
|
} ## end sub call |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# Initialization code, this is executed once at application startup |
56
|
|
|
|
|
|
|
# so we are more relaxed about *not* calling too many subs |
57
|
|
|
|
|
|
|
sub prepare_app { |
58
|
6
|
|
|
6
|
1
|
543
|
my ($self) = @_; |
59
|
6
|
|
|
|
|
13
|
$self->normalize_input_structure(); # reorganize internally |
60
|
6
|
|
|
|
|
6
|
my @inputs = @{delete $self->{revisors}}; # we will consume @inputs |
|
6
|
|
|
|
|
38
|
|
61
|
6
|
|
|
|
|
22
|
my @revisors; |
62
|
|
|
|
|
|
|
|
63
|
6
|
|
|
|
|
17
|
while (@inputs) { |
64
|
93
|
|
|
|
|
80
|
my $spec = shift @inputs; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# allow for key => value or \%spec |
67
|
93
|
100
|
|
|
|
127
|
if (!ref($spec)) { |
68
|
60
|
50
|
|
|
|
84
|
confess "stray revisor '$spec'" unless @inputs; |
69
|
60
|
|
|
|
|
69
|
(my $key, $spec) = ($spec, shift @inputs); |
70
|
60
|
100
|
|
|
|
112
|
$spec = {value => $spec} unless ref($spec) eq 'HASH'; |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# override key only if not already present. The external key |
73
|
|
|
|
|
|
|
# can then be used for ordering revisors also in the hash |
74
|
|
|
|
|
|
|
# scenario |
75
|
60
|
100
|
|
|
|
105
|
$spec->{key} = $key unless defined $spec->{key}; |
76
|
|
|
|
|
|
|
} ## end if (!ref($spec)) |
77
|
|
|
|
|
|
|
|
78
|
93
|
|
|
|
|
112
|
push @revisors, $self->generate_revisor($spec); |
79
|
|
|
|
|
|
|
} ## end while (@inputs) |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# if we arrived here, it's safe |
82
|
6
|
|
|
|
|
17
|
$self->{revisors} = \@revisors; |
83
|
|
|
|
|
|
|
|
84
|
6
|
|
|
|
|
21
|
return $self; |
85
|
|
|
|
|
|
|
} ## end sub prepare_app |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub generate_revisor { |
88
|
93
|
|
|
93
|
1
|
81
|
my ($self, $spec) = @_; |
89
|
93
|
50
|
|
|
|
122
|
confess "one spec has no (defined) key" unless defined $spec->{key}; |
90
|
|
|
|
|
|
|
|
91
|
93
|
|
|
|
|
94
|
my $opts = $self->{opts}; |
92
|
93
|
100
|
|
|
|
118
|
my $start = defined($spec->{start}) ? $spec->{start} : $opts->{start}; |
93
|
93
|
50
|
|
|
|
110
|
confess "start sequence cannot be empty" unless length $start; |
94
|
|
|
|
|
|
|
|
95
|
93
|
100
|
|
|
|
109
|
my $stop = defined($spec->{stop}) ? $spec->{stop} : $opts->{stop}; |
96
|
93
|
50
|
|
|
|
115
|
confess "stop sequence cannot be empty" unless length $stop; |
97
|
|
|
|
|
|
|
|
98
|
93
|
100
|
|
|
|
104
|
my $esc = defined($spec->{esc}) ? $spec->{esc} : $opts->{esc}; |
99
|
93
|
50
|
|
|
|
117
|
confess "escape sequence cannot be empty" unless length $esc; |
100
|
93
|
50
|
|
|
|
136
|
confess "escape sequence cannot start with a space, sorry" |
101
|
|
|
|
|
|
|
if substr($esc, 0, 1) eq ' '; |
102
|
93
|
50
|
33
|
|
|
280
|
confess "escape sequence cannot be equal to start or stop sequence" |
103
|
|
|
|
|
|
|
if ($esc eq $start) || ($esc eq $stop); |
104
|
|
|
|
|
|
|
|
105
|
93
|
|
|
|
|
287
|
my %m = %$spec; |
106
|
93
|
100
|
|
|
|
170
|
$m{override} = 1 unless exists $m{override}; |
107
|
93
|
|
|
|
|
114
|
$m{key} = $self->parse_template($m{key}, $start, $stop, $esc); |
108
|
93
|
|
|
|
|
113
|
$m{value} = $self->parse_template($m{value}, $start, $stop, $esc); |
109
|
93
|
100
|
|
|
|
182
|
$m{cache} = $opts->{cache} unless exists $m{cache}; |
110
|
|
|
|
|
|
|
|
111
|
93
|
|
|
|
|
225
|
return \%m; |
112
|
|
|
|
|
|
|
} ## end sub generate_revisor |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub parse_template { |
115
|
186
|
|
|
186
|
1
|
184
|
my ($self, $template, $start, $stop, $esc) = @_; |
116
|
186
|
100
|
|
|
|
252
|
return undef unless defined $template; |
117
|
177
|
|
|
|
|
130
|
my $pos = 0; |
118
|
177
|
|
|
|
|
99
|
my $len = length $template; |
119
|
177
|
|
|
|
|
115
|
my @chunks; |
120
|
|
|
|
|
|
|
CHUNK: |
121
|
177
|
|
|
|
|
250
|
while ($pos < $len) { |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# find start, if any |
124
|
197
|
|
|
|
|
197
|
my $i = $self->escaped_index($template, $start, $esc, $pos); |
125
|
197
|
100
|
|
|
|
313
|
my $text = substr $template, $pos, ($i < 0 ? $len : $i) - $pos; |
126
|
197
|
|
|
|
|
216
|
push @chunks, $self->unescape($text, $esc); |
127
|
197
|
100
|
|
|
|
303
|
last CHUNK if $i < 0; # nothing more left to search |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# advance position marker immediately after start sequence |
130
|
93
|
|
|
|
|
71
|
$pos = $i + length $start; |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# start sequence found, let's look for the stop |
133
|
93
|
|
|
|
|
99
|
$i = $self->escaped_index($template, $stop, $esc, $pos); |
134
|
93
|
50
|
|
|
|
123
|
confess "unclosed start sequence in '$template'" if $i < 0; |
135
|
|
|
|
|
|
|
|
136
|
93
|
|
|
|
|
102
|
my $chunk = substr $template, $pos, $i - $pos; |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# trim intelligently, then unescape |
139
|
93
|
|
|
|
|
104
|
$chunk = $self->unescape($self->escaped_trim($chunk, $esc), $esc); |
140
|
|
|
|
|
|
|
|
141
|
93
|
|
|
|
|
148
|
my ($src, $key) = split /:/, $chunk, 2; |
142
|
93
|
50
|
66
|
|
|
250
|
confess "invalid source '$src' in chunk '$chunk'" |
143
|
|
|
|
|
|
|
if ($src ne 'env') && ($src ne 'ENV'); |
144
|
93
|
50
|
|
|
|
122
|
confess "no key in chunk '$chunk'" unless defined $key; |
145
|
93
|
|
|
|
|
174
|
push @chunks, {src => $src, key => $key}; |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# advance position marker for next iteration |
148
|
93
|
|
|
|
|
153
|
$pos = $i + length $stop; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
} ## end CHUNK: while ($pos < $len) |
151
|
|
|
|
|
|
|
|
152
|
177
|
|
|
|
|
242
|
return \@chunks; |
153
|
|
|
|
|
|
|
} ## end sub parse_template |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
sub unescape { |
156
|
290
|
|
|
290
|
1
|
243
|
my ($self, $str, $esc) = @_; |
157
|
290
|
|
|
|
|
556
|
$str =~ s{\Q$esc\E(.)}{$1}gmxs; |
158
|
290
|
|
|
|
|
370
|
return $str; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub escaped_trim { |
162
|
93
|
|
|
93
|
1
|
85
|
my ($self, $str, $esc) = @_; |
163
|
93
|
|
|
|
|
206
|
$str =~ s{\A\s+}{}mxs; # trimming the initial part is easy |
164
|
|
|
|
|
|
|
|
165
|
93
|
|
|
|
|
70
|
my $pos = 0; |
166
|
93
|
|
|
|
|
71
|
while ('necessary') { |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# find next un-escaped space |
169
|
94
|
|
|
|
|
97
|
my $i = $self->escaped_index($str, ' ', $esc, $pos); |
170
|
94
|
100
|
|
|
|
124
|
last if $i < 0; # no further spaces... nothing to trim |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# now look for escapes after that, because we're interested only |
173
|
|
|
|
|
|
|
# in un-escaped spaces at the end of $str |
174
|
84
|
|
|
|
|
65
|
my $e = index $str, $esc, $i + 1; |
175
|
|
|
|
|
|
|
|
176
|
84
|
100
|
|
|
|
109
|
if ($e < 0) { # no escapes past last space found |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# Now we split our string at $i, which represents the first |
179
|
|
|
|
|
|
|
# space character that is not escaped and has no escapes after it. |
180
|
|
|
|
|
|
|
# The string before it MUST NOT be subject to trimming, the part |
181
|
|
|
|
|
|
|
# from $i on is safe to trim. |
182
|
83
|
|
|
|
|
100
|
my $keep = substr $str, 0, $i, ''; |
183
|
83
|
|
|
|
|
155
|
$str =~ s{\s+\z}{}mxs; |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# merge the two parts back and we're good to go |
186
|
83
|
|
|
|
|
159
|
return $keep . $str; |
187
|
|
|
|
|
|
|
} ## end if ($e < 0) |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# we found an escape sequence after the last space we found, we have |
190
|
|
|
|
|
|
|
# to look further past this escape sequence and the char it escapes |
191
|
1
|
|
|
|
|
2
|
$pos = $e + length($esc) + 1; |
192
|
|
|
|
|
|
|
} ## end while ('necessary') |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# no trailing spaces to be trimmed found, $str is fine |
195
|
10
|
|
|
|
|
26
|
return $str; |
196
|
|
|
|
|
|
|
} ## end sub escaped_trim |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
sub escaped_index { |
199
|
384
|
|
|
384
|
1
|
314
|
my ($self, $str, $delimiter, $escaper, $pos) = @_; |
200
|
|
|
|
|
|
|
|
201
|
384
|
|
|
|
|
225
|
my $len = length $str; |
202
|
384
|
|
|
|
|
448
|
while ($pos < $len) { |
203
|
396
|
|
|
|
|
338
|
my $dpos = index $str, $delimiter, $pos; # next delimiter |
204
|
396
|
|
|
|
|
275
|
my $epos = index $str, $escaper, $pos; # next escaper |
205
|
396
|
100
|
100
|
|
|
1136
|
return $dpos |
|
|
|
100
|
|
|
|
|
206
|
|
|
|
|
|
|
if ($dpos < 0) # didn't find it |
207
|
|
|
|
|
|
|
|| ($epos < 0) # nothing escaped at all |
208
|
|
|
|
|
|
|
|| ($dpos < $epos); # nothing escaped before it |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# there's an escaper occurrence *before* a delimiter, so we have |
211
|
|
|
|
|
|
|
# to honor the escaping and restart the quest past the escaped char |
212
|
14
|
|
|
|
|
22
|
$pos = $epos + length($escaper) + 1; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
} ## end while ($pos < $len) |
215
|
|
|
|
|
|
|
|
216
|
2
|
50
|
|
|
|
6
|
return -1 if $pos == $len; |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# we got past the end of the string, there's an escaper at the end |
219
|
0
|
|
|
|
|
0
|
confess "stray escaping in '$str'"; |
220
|
|
|
|
|
|
|
} ## end sub escaped_index |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
sub normalize_input_structure { |
223
|
6
|
|
|
6
|
1
|
7
|
my ($self) = @_; |
224
|
|
|
|
|
|
|
|
225
|
6
|
|
|
|
|
189
|
my $app = delete $self->{app}; # temporarily remove these keys |
226
|
6
|
|
100
|
|
|
37
|
my $opts = delete($self->{opts}) || {}; |
227
|
6
|
|
50
|
|
|
27
|
$opts->{start} ||= '[%'; |
228
|
6
|
|
50
|
|
|
32
|
$opts->{stop} ||= '%]'; |
229
|
6
|
|
50
|
|
|
21
|
$opts->{esc} ||= '\\'; |
230
|
6
|
100
|
|
|
|
16
|
$opts->{cache} = 1 unless exists $opts->{cache}; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
my $revisors = exists($self->{revisors}) |
233
|
|
|
|
|
|
|
? delete($self->{revisors}) # just take it |
234
|
6
|
100
|
|
|
|
18
|
: __exhaust_hash($self); # or move stuff out of $self |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Fun fact: __exhaust_hash($self) could have been written as: |
237
|
|
|
|
|
|
|
# |
238
|
|
|
|
|
|
|
# { (@{[]}, %$self) = %$self } |
239
|
|
|
|
|
|
|
# |
240
|
|
|
|
|
|
|
# but let's avoid being too "clever" for readability's sake... |
241
|
|
|
|
|
|
|
|
242
|
6
|
50
|
|
|
|
18
|
if (scalar keys %$self > 0) { |
243
|
0
|
|
|
|
|
0
|
my @keys = __stringified_list(keys %$self); |
244
|
0
|
|
|
|
|
0
|
confess "stray keys found: @keys"; |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
6
|
100
|
|
|
|
38
|
$revisors = [map { $_ => $revisors->{$_} } sort keys %$revisors] |
|
40
|
|
|
|
|
47
|
|
248
|
|
|
|
|
|
|
if ref($revisors) eq 'HASH'; |
249
|
|
|
|
|
|
|
|
250
|
6
|
|
|
|
|
19
|
%$self = ( |
251
|
|
|
|
|
|
|
app => $app, |
252
|
|
|
|
|
|
|
revisors => $revisors, |
253
|
|
|
|
|
|
|
opts => $opts, |
254
|
|
|
|
|
|
|
); |
255
|
6
|
|
|
|
|
9
|
return $self; |
256
|
|
|
|
|
|
|
} ## end sub normalize_input_structure |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# _PRIVATE_ convenience functions |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub __stringified_list { |
261
|
|
|
|
|
|
|
return map { |
262
|
0
|
0
|
|
0
|
|
0
|
if (defined(my $v = $_)) { |
|
0
|
|
|
|
|
0
|
|
263
|
0
|
|
|
|
|
0
|
$v =~ s{([\\'])}{\\$1}gmxs; |
264
|
0
|
|
|
|
|
0
|
"'$v'"; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
else { |
267
|
0
|
|
|
|
|
0
|
'undef'; |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
} @_; |
270
|
|
|
|
|
|
|
} ## end sub __stringified_list |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub __exhaust_hash { |
273
|
1
|
|
|
1
|
|
1
|
my ($target) = @_; |
274
|
1
|
|
|
|
|
7
|
my $retval = {%$target}; |
275
|
1
|
|
|
|
|
4
|
%$target = (); |
276
|
1
|
|
|
|
|
1
|
return $retval; |
277
|
|
|
|
|
|
|
} ## end sub __exhaust_hash |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
1; |