line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Struct::Path::PerlStyle; |
2
|
|
|
|
|
|
|
|
3
|
8
|
|
|
8
|
|
272654
|
use 5.010; |
|
8
|
|
|
|
|
79
|
|
4
|
8
|
|
|
8
|
|
44
|
use strict; |
|
8
|
|
|
|
|
17
|
|
|
8
|
|
|
|
|
218
|
|
5
|
8
|
|
|
8
|
|
50
|
use warnings FATAL => 'all'; |
|
8
|
|
|
|
|
17
|
|
|
8
|
|
|
|
|
370
|
|
6
|
8
|
|
|
8
|
|
3772
|
use parent 'Exporter'; |
|
8
|
|
|
|
|
2248
|
|
|
8
|
|
|
|
|
40
|
|
7
|
8
|
|
|
8
|
|
4470
|
use utf8; |
|
8
|
|
|
|
|
134
|
|
|
8
|
|
|
|
|
43
|
|
8
|
|
|
|
|
|
|
|
9
|
8
|
|
|
8
|
|
252
|
use Carp 'croak'; |
|
8
|
|
|
|
|
20
|
|
|
8
|
|
|
|
|
364
|
|
10
|
8
|
|
|
8
|
|
4145
|
use Safe; |
|
8
|
|
|
|
|
292833
|
|
|
8
|
|
|
|
|
521
|
|
11
|
8
|
|
|
8
|
|
5485
|
use Text::Balanced qw(extract_bracketed extract_quotelike); |
|
8
|
|
|
|
|
136926
|
|
|
8
|
|
|
|
|
751
|
|
12
|
8
|
|
|
8
|
|
66
|
use re qw(is_regexp regexp_pattern); |
|
8
|
|
|
|
|
17
|
|
|
8
|
|
|
|
|
3279
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
require Struct::Path::PerlStyle::Functions; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
17
|
|
|
|
|
|
|
path2str |
18
|
|
|
|
|
|
|
str2path |
19
|
|
|
|
|
|
|
); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=encoding utf8 |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 NAME |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
Struct::Path::PerlStyle - Perl-style syntax frontend for L. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=begin html |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=end html |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=head1 VERSION |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
Version 0.93 |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=cut |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
our $VERSION = '0.93'; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=head1 SYNOPSIS |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
use Struct::Path qw(path); |
46
|
|
|
|
|
|
|
use Struct::Path::PerlStyle qw(path2str str2path); |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
my $nested = { |
49
|
|
|
|
|
|
|
a => { |
50
|
|
|
|
|
|
|
b => ["B0", "B1", "B2"], |
51
|
|
|
|
|
|
|
c => ["C0", "C1"], |
52
|
|
|
|
|
|
|
d => {}, |
53
|
|
|
|
|
|
|
}, |
54
|
|
|
|
|
|
|
}; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
my @found = path($nested, str2path('{a}{}[0,2]'), deref => 1, paths => 1); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
while (@found) { |
59
|
|
|
|
|
|
|
my $path = shift @found; |
60
|
|
|
|
|
|
|
my $data = shift @found; |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
print "path '" . path2str($path) . "' refer to '$data'\n"; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# path '{a}{b}[0]' refer to 'B0' |
66
|
|
|
|
|
|
|
# path '{a}{b}[2]' refer to 'B2' |
67
|
|
|
|
|
|
|
# path '{a}{c}[0]' refer to 'C0' |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head1 EXPORT |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
Nothing is exported by default. |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head1 PATH SYNTAX |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Path is a sequence of 'steps', each represents nested level in the structure. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head2 Hashes |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Like in perl hash keys should be specified using curly brackets |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
{} # all values from a's subhash |
82
|
|
|
|
|
|
|
{foo} # value for 'foo' key |
83
|
|
|
|
|
|
|
{foo,bar} # slicing: 'foo' and 'bar' values |
84
|
|
|
|
|
|
|
{"space inside"} # key must be quoted unless it is a simple word |
85
|
|
|
|
|
|
|
{"multi\nline"} # special characters interpolated when double quoted |
86
|
|
|
|
|
|
|
{/pattern/mods} # keys regexp match |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=head2 Arrays |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
Square brackets used for array indexes specification |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
[] # all array items |
93
|
|
|
|
|
|
|
[9] # 9-th element |
94
|
|
|
|
|
|
|
[0,1,2,5] # slicing: 0, 1, 2 and 5 array items |
95
|
|
|
|
|
|
|
[0..2,5] # same, but using ranges |
96
|
|
|
|
|
|
|
[9..0] # descending ranges allowed |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head2 Hooks |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Expressions enclosed in parenthesis treated as hooks and evaluated using |
101
|
|
|
|
|
|
|
L compartment. Almost all perl operators and core functions available, |
102
|
|
|
|
|
|
|
see L for more info. Some path related functions provided by |
103
|
|
|
|
|
|
|
L. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
[](/pattern/mods) # match array values by regular expression |
106
|
|
|
|
|
|
|
[]{foo}(eq "bar" && BACK) # select hashes which have pair 'foo' => 'bar' |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
There are two global variables available whithin safe compartment: C<$_> which |
109
|
|
|
|
|
|
|
refers to value and C<%_> which provides current path via key C (in |
110
|
|
|
|
|
|
|
L notation) and structure levels refs stack via key C. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=head2 Aliases |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
String in angle brackets is an alias - shortcut mapped into sequence of |
115
|
|
|
|
|
|
|
steps. Aliases resolved iteratively, so alias may also refer into path with |
116
|
|
|
|
|
|
|
another aliases. |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Aliases may be defined via global variable |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
$Struct::Path::PerlStyle::ALIASES = { |
121
|
|
|
|
|
|
|
foo => '{some}{long}{path}', |
122
|
|
|
|
|
|
|
bar => '{and}{few}{steps}{more}' |
123
|
|
|
|
|
|
|
}; |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
and then |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
# expands to '{some}{long}{path}{and}{few}{steps}{more}' |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
or as option for C: |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
str2path('', {aliases => {foo => '{long}{path}'}}); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head1 SUBROUTINES |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=cut |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
our $ALIASES; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
my %ESCP = ( |
140
|
|
|
|
|
|
|
'"' => '\"', |
141
|
|
|
|
|
|
|
"\a" => '\a', |
142
|
|
|
|
|
|
|
"\b" => '\b', |
143
|
|
|
|
|
|
|
"\t" => '\t', |
144
|
|
|
|
|
|
|
"\n" => '\n', |
145
|
|
|
|
|
|
|
"\f" => '\f', |
146
|
|
|
|
|
|
|
"\r" => '\r', |
147
|
|
|
|
|
|
|
"\e" => '\e', |
148
|
|
|
|
|
|
|
); |
149
|
|
|
|
|
|
|
my $ESCP = join('', sort keys %ESCP); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
my %INTP = map { $ESCP{$_} => $_ } keys %ESCP; # swap keys <-> values |
152
|
|
|
|
|
|
|
my $INTP = join('|', map { "\Q$_\E" } sort keys %INTP); |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
# $_ will be substituted (if omitted) as first arg if placed on start of |
155
|
|
|
|
|
|
|
# hook expression |
156
|
|
|
|
|
|
|
my $COMPL_OPS = join('|', ( |
157
|
|
|
|
|
|
|
'==', |
158
|
|
|
|
|
|
|
'!=', |
159
|
|
|
|
|
|
|
'=~', |
160
|
|
|
|
|
|
|
'!~', |
161
|
|
|
|
|
|
|
'eq', |
162
|
|
|
|
|
|
|
'ne', |
163
|
|
|
|
|
|
|
'<', |
164
|
|
|
|
|
|
|
'>', |
165
|
|
|
|
|
|
|
'<=', |
166
|
|
|
|
|
|
|
'>=', |
167
|
|
|
|
|
|
|
'lt', |
168
|
|
|
|
|
|
|
'gt', |
169
|
|
|
|
|
|
|
'le', |
170
|
|
|
|
|
|
|
'ge', |
171
|
|
|
|
|
|
|
'~~', |
172
|
|
|
|
|
|
|
)); |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
my $HASH_KEY_CHARS = qr/[\p{Alnum}_\.\-\+]/; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
our $HOOK_STRICT = 1; |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
my $SAFE = Safe->new; |
179
|
|
|
|
|
|
|
$SAFE->share_from( |
180
|
|
|
|
|
|
|
'Struct::Path::PerlStyle::Functions', |
181
|
|
|
|
|
|
|
\@Struct::Path::PerlStyle::Functions::EXPORT_OK |
182
|
|
|
|
|
|
|
); |
183
|
|
|
|
|
|
|
$SAFE->deny('warn'); |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
my $QR_MAP = { |
186
|
|
|
|
|
|
|
'' => sub { qr/$_[0]/ }, |
187
|
|
|
|
|
|
|
i => sub { qr/$_[0]/i }, |
188
|
|
|
|
|
|
|
m => sub { qr/$_[0]/m }, |
189
|
|
|
|
|
|
|
s => sub { qr/$_[0]/s }, |
190
|
|
|
|
|
|
|
x => sub { qr/$_[0]/x }, |
191
|
|
|
|
|
|
|
im => sub { qr/$_[0]/im }, |
192
|
|
|
|
|
|
|
is => sub { qr/$_[0]/is }, |
193
|
|
|
|
|
|
|
ix => sub { qr/$_[0]/ix }, |
194
|
|
|
|
|
|
|
ms => sub { qr/$_[0]/ms }, |
195
|
|
|
|
|
|
|
mx => sub { qr/$_[0]/mx }, |
196
|
|
|
|
|
|
|
sx => sub { qr/$_[0]/sx }, |
197
|
|
|
|
|
|
|
ims => sub { qr/$_[0]/ims }, |
198
|
|
|
|
|
|
|
imx => sub { qr/$_[0]/imx }, |
199
|
|
|
|
|
|
|
isx => sub { qr/$_[0]/isx }, |
200
|
|
|
|
|
|
|
msx => sub { qr/$_[0]/msx }, |
201
|
|
|
|
|
|
|
imsx => sub { qr/$_[0]/imsx }, |
202
|
|
|
|
|
|
|
}; |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head2 str2path |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Convert perl-style string to L path structure |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
$struct = str2path($string); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=cut |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
sub _push_hash { |
213
|
75
|
|
|
75
|
|
163
|
my ($steps, $text) = @_; |
214
|
75
|
|
|
|
|
126
|
my ($body, $delim, $mods, %step, $token, $type); |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# extract_quotelike fails to parse bare zero as a string |
217
|
75
|
100
|
|
|
|
163
|
push @{$step{K}}, $text if $text eq '0'; |
|
1
|
|
|
|
|
3
|
|
218
|
|
|
|
|
|
|
|
219
|
75
|
|
|
|
|
154
|
while ($text) { |
220
|
142
|
|
|
|
|
379
|
($token, $text, $type, $delim, $body, $mods) = |
221
|
|
|
|
|
|
|
(extract_quotelike($text))[0,1,3,4,5,10]; |
222
|
|
|
|
|
|
|
|
223
|
142
|
100
|
100
|
|
|
10836
|
if (not defined $delim) { # bareword |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
224
|
57
|
100
|
|
|
|
406
|
push @{$step{K}}, $token = $1 |
|
55
|
|
|
|
|
698
|
|
225
|
|
|
|
|
|
|
if ($text =~ s/^\s*($HASH_KEY_CHARS+)//); |
226
|
|
|
|
|
|
|
} elsif (!$type and $delim eq '"') { |
227
|
39
|
|
|
|
|
197
|
$body =~ s/($INTP)/$INTP{$1}/gs; # interpolate |
228
|
39
|
|
|
|
|
65
|
push @{$step{K}}, $body; |
|
39
|
|
|
|
|
105
|
|
229
|
|
|
|
|
|
|
} elsif (!$type and $delim eq "'") { |
230
|
11
|
|
|
|
|
15
|
push @{$step{K}}, $body; |
|
11
|
|
|
|
|
29
|
|
231
|
|
|
|
|
|
|
} elsif ($delim eq '/' and !$type or $type eq 'm') { |
232
|
33
|
|
|
|
|
119
|
$mods = join('', sort(split('', $mods))); |
233
|
33
|
|
|
|
|
59
|
eval { push @{$step{K}}, $QR_MAP->{$mods}->($body) }; |
|
33
|
|
|
|
|
49
|
|
|
33
|
|
|
|
|
130
|
|
234
|
33
|
100
|
|
|
|
93
|
if ($@) { |
235
|
3
|
|
|
|
|
18
|
(my $err = $@) =~ s/ at .+//s; |
236
|
3
|
|
|
|
|
8
|
croak "Step #" . scalar @{$steps} . " $err"; |
|
3
|
|
|
|
|
335
|
|
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
} else { # things like qr, qw and so on |
239
|
2
|
|
|
|
|
6
|
substr($text, 0, 0, $token); |
240
|
2
|
|
|
|
|
5
|
undef $token; |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
139
|
100
|
|
|
|
708
|
croak "Unsupported key '$text', step #" . @{$steps} |
|
4
|
|
|
|
|
545
|
|
244
|
|
|
|
|
|
|
if (!defined $token); |
245
|
|
|
|
|
|
|
|
246
|
135
|
|
|
|
|
271
|
$text =~ s/^\s+//; # discard trailing spaces |
247
|
|
|
|
|
|
|
|
248
|
135
|
100
|
|
|
|
308
|
if ($text ne '') { |
249
|
78
|
100
|
|
|
|
266
|
if ($text =~ s/^,//) { |
250
|
71
|
100
|
|
|
|
197
|
croak "Trailing delimiter at step #" . @{$steps} |
|
2
|
|
|
|
|
217
|
|
251
|
|
|
|
|
|
|
if ($text eq ''); |
252
|
|
|
|
|
|
|
} else { |
253
|
7
|
|
|
|
|
17
|
croak "Delimiter expected before '$text', step #" . @{$steps}; |
|
7
|
|
|
|
|
775
|
|
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
59
|
|
|
|
|
99
|
push @{$steps}, \%step; |
|
59
|
|
|
|
|
213
|
|
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
sub _push_hook { |
262
|
31
|
|
|
31
|
|
71
|
my ($steps, $text) = @_; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# substitute default value if omitted |
265
|
31
|
|
|
|
|
287
|
$text =~ s/^\s*($COMPL_OPS)/\$_ $1/; |
266
|
|
|
|
|
|
|
|
267
|
31
|
|
|
|
|
105
|
my $hook = 'sub {' . |
268
|
|
|
|
|
|
|
'$^W = 0; ' . |
269
|
|
|
|
|
|
|
'local %_ = ("path", $_[0], "refs", $_[1]); ' . |
270
|
|
|
|
|
|
|
'local $_ = (ref $_[1] eq "ARRAY" and @{$_[1]}) ? ${$_[1]->[-1]} : undef; ' . |
271
|
|
|
|
|
|
|
$text . |
272
|
|
|
|
|
|
|
'}'; |
273
|
|
|
|
|
|
|
|
274
|
31
|
|
|
2
|
|
370
|
open (local *STDERR,'>', \(my $stderr)); # catch compilation errors |
|
2
|
|
|
|
|
14
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
17
|
|
275
|
|
|
|
|
|
|
|
276
|
31
|
100
|
|
|
|
1637
|
unless ($hook = $SAFE->reval($hook, $HOOK_STRICT)) { |
277
|
10
|
100
|
|
|
|
6365
|
if ($stderr) { |
278
|
1
|
|
|
|
|
7
|
$stderr =~ s/ at \(eval \d+\) .+//s; |
279
|
1
|
|
|
|
|
4
|
$stderr = " ($stderr)"; |
280
|
|
|
|
|
|
|
} else { |
281
|
9
|
|
|
|
|
19
|
$stderr = ""; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
10
|
|
|
|
|
31
|
(my $err = $@) =~ s/ at \(eval \d+\) .+//s; |
285
|
10
|
|
|
|
|
26
|
croak "Failed to eval hook '$text': $err, step #" . @{$steps} . $stderr; |
|
10
|
|
|
|
|
1359
|
|
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
21
|
|
|
|
|
14207
|
push @{$steps}, $hook; |
|
21
|
|
|
|
|
195
|
|
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
sub _push_list { |
292
|
57
|
|
|
57
|
|
124
|
my ($steps, $text) = @_; |
293
|
57
|
|
|
|
|
91
|
my (@range, @step); |
294
|
|
|
|
|
|
|
|
295
|
57
|
|
|
|
|
215
|
for my $i (split /\s*,\s*/, $text, -1) { |
296
|
|
|
|
|
|
|
@range = grep { |
297
|
69
|
100
|
|
|
|
211
|
croak "Incorrect array index '$i', step #" . @{$steps} |
|
7
|
|
|
|
|
837
|
|
298
|
80
|
100
|
|
|
|
145
|
unless (eval { $_ == int($_) }); |
|
80
|
|
|
|
|
411
|
|
299
|
|
|
|
|
|
|
} ($i =~ /^\s*(-?\d+)\s*\.\.\s*(-?\d+)\s*$/) ? ($1, $2) : $i; |
300
|
|
|
|
|
|
|
|
301
|
62
|
100
|
|
|
|
231
|
push @step, $range[0] < $range[-1] |
302
|
|
|
|
|
|
|
? $range[0] .. $range[-1] |
303
|
|
|
|
|
|
|
: reverse $range[-1] .. $range[0]; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
50
|
|
|
|
|
90
|
push @{$steps}, \@step; |
|
50
|
|
|
|
|
171
|
|
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
sub str2path($;$) { |
310
|
116
|
|
|
116
|
1
|
69976
|
my ($path, $opts) = @_; |
311
|
|
|
|
|
|
|
|
312
|
116
|
100
|
|
|
|
485
|
croak "Undefined path passed" unless (defined $path); |
313
|
|
|
|
|
|
|
|
314
|
115
|
100
|
|
|
|
312
|
local $ALIASES = $opts->{aliases} if (exists $opts->{aliases}); |
315
|
|
|
|
|
|
|
|
316
|
115
|
|
|
|
|
201
|
my (@steps, $step, $type); |
317
|
|
|
|
|
|
|
|
318
|
115
|
|
|
|
|
246
|
while ($path) { |
319
|
|
|
|
|
|
|
# separated match: to be able to have another brackets inside; |
320
|
|
|
|
|
|
|
# currently mostly for hooks, for example: '( $x > $y )' |
321
|
182
|
|
|
|
|
370
|
for ('{"}', '["]', '(")', '<">') { |
322
|
358
|
|
|
|
|
950
|
($step, $path) = extract_bracketed($path, $_, ''); |
323
|
358
|
100
|
|
|
|
39361
|
last if ($step); |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
182
|
100
|
|
|
|
1381
|
croak "Unsupported thing in the path, step #" . @steps . ": '$path'" |
327
|
|
|
|
|
|
|
unless ($step); |
328
|
|
|
|
|
|
|
|
329
|
173
|
|
|
|
|
398
|
$type = substr $step, 0, 1, ''; # remove leading bracket |
330
|
173
|
|
|
|
|
317
|
substr $step, -1, 1, ''; # remove trailing bracket |
331
|
|
|
|
|
|
|
|
332
|
173
|
100
|
|
|
|
472
|
if ($type eq '{') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
333
|
75
|
|
|
|
|
196
|
_push_hash(\@steps, $step); |
334
|
|
|
|
|
|
|
} elsif ($type eq '[') { |
335
|
57
|
|
|
|
|
145
|
_push_list(\@steps, $step); |
336
|
|
|
|
|
|
|
} elsif ($type eq '(') { |
337
|
31
|
|
|
|
|
80
|
_push_hook(\@steps, $step); |
338
|
|
|
|
|
|
|
} else { # <> |
339
|
10
|
100
|
|
|
|
315
|
croak "Unknown alias '$step'" unless (exists $ALIASES->{$step}); |
340
|
|
|
|
|
|
|
|
341
|
8
|
|
|
|
|
18
|
substr $path, 0, 0, $ALIASES->{$step}; |
342
|
8
|
|
|
|
|
11
|
redo; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
71
|
|
|
|
|
399
|
return \@steps; |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=head2 path2str |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
Convert L path structure to perl-style string |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
$string = path2str($struct); |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=cut |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
sub path2str($) { |
358
|
47
|
|
|
47
|
1
|
35393
|
my $path = shift; |
359
|
|
|
|
|
|
|
|
360
|
47
|
100
|
|
|
|
266
|
croak "Arrayref expected for path" unless (ref $path eq 'ARRAY'); |
361
|
46
|
|
|
|
|
79
|
my $out = ''; |
362
|
46
|
|
|
|
|
85
|
my $sc = 0; # step counter |
363
|
|
|
|
|
|
|
|
364
|
46
|
|
|
|
|
69
|
for my $step (@{$path}) { |
|
46
|
|
|
|
|
115
|
|
365
|
67
|
|
|
|
|
103
|
my @items; |
366
|
|
|
|
|
|
|
|
367
|
67
|
100
|
|
|
|
183
|
if (ref $step eq 'ARRAY') { |
|
|
100
|
|
|
|
|
|
368
|
27
|
|
|
|
|
41
|
for my $i (@{$step}) { |
|
27
|
|
|
|
|
74
|
|
369
|
|
|
|
|
|
|
croak "Incorrect array index '" . ($i // 'undef') . "', step #$sc" |
370
|
65
|
100
|
100
|
|
|
95
|
unless (eval { int($i) == $i }); |
|
65
|
|
|
|
|
480
|
|
371
|
62
|
100
|
100
|
|
|
279
|
if (@items and ( |
|
|
|
100
|
|
|
|
|
372
|
|
|
|
|
|
|
$items[-1][0] < $i and $items[-1][-1] == $i - 1 or # ascending |
373
|
|
|
|
|
|
|
$items[-1][0] > $i and $items[-1][-1] == $i + 1 # descending |
374
|
|
|
|
|
|
|
)) { |
375
|
30
|
|
|
|
|
59
|
$items[-1][1] = $i; # update range |
376
|
|
|
|
|
|
|
} else { |
377
|
32
|
|
|
|
|
66
|
push @items, [$i]; # new range |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
24
|
|
|
|
|
44
|
for (@{items}) { |
382
|
|
|
|
|
|
|
$_ = abs($_->[0] - $_->[-1]) < 2 |
383
|
32
|
100
|
|
|
|
85
|
? join(',', @{$_}) |
|
21
|
|
|
|
|
60
|
|
384
|
|
|
|
|
|
|
: "$_->[0]..$_->[-1]" |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
24
|
|
|
|
|
58
|
$out .= "[" . join(",", @{items}) . "]"; |
388
|
|
|
|
|
|
|
} elsif (ref $step eq 'HASH') { |
389
|
39
|
|
|
|
|
53
|
my $keys; |
390
|
|
|
|
|
|
|
|
391
|
39
|
100
|
|
|
|
82
|
if (exists $step->{K}) { |
|
|
100
|
|
|
|
|
|
392
|
|
|
|
|
|
|
croak "Unsupported hash keys definition, step #$sc" |
393
|
36
|
100
|
|
|
|
193
|
unless (ref $step->{K} eq 'ARRAY'); |
394
|
|
|
|
|
|
|
croak "Unsupported hash definition (extra keys), step #$sc" |
395
|
35
|
100
|
|
|
|
52
|
if (keys %{$step} > 1); |
|
35
|
|
|
|
|
189
|
|
396
|
34
|
|
|
|
|
59
|
$keys = $step->{K}; |
397
|
3
|
|
|
|
|
11
|
} elsif (keys %{$step}) { |
398
|
1
|
|
|
|
|
189
|
croak "Unsupported hash definition (unknown keys), step #$sc"; |
399
|
|
|
|
|
|
|
} else { |
400
|
2
|
|
|
|
|
4
|
$keys = []; |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
36
|
|
|
|
|
59
|
for my $k (@{$keys}) { |
|
36
|
|
|
|
|
64
|
|
404
|
84
|
100
|
100
|
|
|
734
|
if (is_regexp($k)) { |
|
|
100
|
|
|
|
|
|
405
|
15
|
|
|
|
|
50
|
my ($patt, $mods) = regexp_pattern($k); |
406
|
15
|
|
|
|
|
35
|
$mods =~ s/[dlu]//g; # for Perl's internal use (c) perlre |
407
|
15
|
|
|
|
|
46
|
push @items, "/$patt/$mods"; |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
} elsif (defined $k and ref $k eq '') { |
410
|
67
|
|
|
|
|
111
|
push @items, $k; |
411
|
|
|
|
|
|
|
|
412
|
67
|
100
|
|
|
|
400
|
unless ($k =~ /^$HASH_KEY_CHARS+$/) { |
413
|
33
|
|
|
|
|
141
|
$items[-1] =~ s/([\Q$ESCP\E])/$ESCP{$1}/gs; # escape |
414
|
33
|
|
|
|
|
92
|
$items[-1] = qq("$items[-1]"); # quote |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
} else { |
417
|
2
|
|
100
|
|
|
210
|
croak "Unsupported hash key type '" . |
418
|
|
|
|
|
|
|
(ref($k) || 'undef') . "', step #$sc" |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
|
422
|
34
|
|
|
|
|
126
|
$out .= "{" . join(",", @items) . "}"; |
423
|
|
|
|
|
|
|
} else { |
424
|
1
|
|
|
|
|
101
|
croak "Unsupported thing in the path, step #$sc"; |
425
|
|
|
|
|
|
|
} |
426
|
58
|
|
|
|
|
123
|
$sc++; |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
37
|
|
|
|
|
101
|
return $out; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=head1 AUTHOR |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
Michael Samoglyadov, C<< >> |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=head1 BUGS |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
439
|
|
|
|
|
|
|
C, or through the web interface at |
440
|
|
|
|
|
|
|
L. I |
441
|
|
|
|
|
|
|
will be notified, and then you'll automatically be notified of progress on |
442
|
|
|
|
|
|
|
your bug as I make changes. |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=head1 SUPPORT |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
perldoc Struct::Path::PerlStyle |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
You can also look for information at: |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=over 4 |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
L |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
L |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=item * CPAN Ratings |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
L |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=item * Search CPAN |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
L |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=back |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=head1 SEE ALSO |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
L, L, L |
475
|
|
|
|
|
|
|
L, L, L |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
Copyright 2016-2019 Michael Samoglyadov. |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
482
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
483
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
See L for more information. |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=cut |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
1; # End of Struct::Path::PerlStyle |