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