line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Router::PathInfo::Controller; |
2
|
2
|
|
|
2
|
|
4857
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
113
|
|
3
|
2
|
|
|
2
|
|
11
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
121
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 NAME |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
B provides a mapping PATH_INFO to controllers. |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 SYNOPSIS |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# create instance |
12
|
|
|
|
|
|
|
my $r = Router::PathInfo::Controller->new(); |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# describe connect |
15
|
|
|
|
|
|
|
$r->add_rule(connect => '/foo/:enum(bar|baz)/:any', action => ['some','bar']); |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# prepare arguments (this action to prepare $env hidden from you in the module Router::PathInfo) |
18
|
|
|
|
|
|
|
my $env = {PATH_INFO => '/foo/baz/bar', REQUEST_METHOD => 'GET'}; |
19
|
|
|
|
|
|
|
my @segment = split '/', $env->{PATH_INFO}, -1; |
20
|
|
|
|
|
|
|
shift @segment; |
21
|
|
|
|
|
|
|
$env->{'psgix.tmp.RouterPathInfo'} = { |
22
|
|
|
|
|
|
|
segments => [@segment], |
23
|
|
|
|
|
|
|
depth => scalar @segment |
24
|
|
|
|
|
|
|
}; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# match |
27
|
|
|
|
|
|
|
my $res = $r->match($env); |
28
|
|
|
|
|
|
|
# $res = HASH(0x93d74d8) |
29
|
|
|
|
|
|
|
# 'action' => ARRAY(0x99294e8) |
30
|
|
|
|
|
|
|
# 0 'some' |
31
|
|
|
|
|
|
|
# 1 'bar' |
32
|
|
|
|
|
|
|
# 'segment' => ARRAY(0x93d8038) |
33
|
|
|
|
|
|
|
# 0 'baz' |
34
|
|
|
|
|
|
|
# 1 'bar' |
35
|
|
|
|
|
|
|
# 'type' => 'controller' |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# or $res may by undef |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head1 DESCRIPTION |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
C is used for matching sets of trees. |
42
|
|
|
|
|
|
|
Therefore, search matching is faster and more efficient, |
43
|
|
|
|
|
|
|
than a simple enumeration of regular expressions to search for a suitable result. |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
In the descriptions of 'C' by adding rules, you can use these tokens: |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
:any - match with any segment |
48
|
|
|
|
|
|
|
:re(...some regular expression...) - match with the specified regular expression |
49
|
|
|
|
|
|
|
:enum(...|...) - match with a segment from the set |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
and sub-attribute for rules |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
:name(...) |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
For example |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
'/foo/:name(some_name)bar/:any' |
58
|
|
|
|
|
|
|
'/foo/:re(^\d{4}\w{4}$)/:name(my_token):any' |
59
|
|
|
|
|
|
|
'/:enum(foo|bar|baz)/:re(^\d{4}\w{4}$)/:any' |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
All descriptions of the segments have a certain weight. |
62
|
|
|
|
|
|
|
Thus, the description C<:enum> has the greatest weight, a description of C<:re> weighs even less. Weakest coincidence is C<:any>. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
For all descriptions 'C' using these tokens in the match will be returned to a special key 'C' |
65
|
|
|
|
|
|
|
in which stores a list of all segments C they are responsible. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
An important point: description 'C' dominates over http method. Example: |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
$r->add_rule(connect => '/foo/:any/baz', action => 'one', methods => ['GET','DELETE']); |
70
|
|
|
|
|
|
|
$r->add_rule(connect => '/foo/bar/:any', action => 'two'); |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
for '/foo/bar/baz' with GET -> 'two' |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
In C you can pass any value: object, arrayref, hashref or a scalar. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=head1 METHODS |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=cut |
79
|
|
|
|
|
|
|
|
80
|
2
|
|
|
2
|
|
5455
|
use namespace::autoclean; |
|
2
|
|
|
|
|
45627
|
|
|
2
|
|
|
|
|
12
|
|
81
|
2
|
|
|
2
|
|
112
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
3127
|
|
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
my $http_methods = { |
84
|
|
|
|
|
|
|
GET => 1, |
85
|
|
|
|
|
|
|
POST => 1, |
86
|
|
|
|
|
|
|
PUT => 1, |
87
|
|
|
|
|
|
|
OPTIONS => 1, |
88
|
|
|
|
|
|
|
DELETE => 1, |
89
|
|
|
|
|
|
|
HEAD => 1 |
90
|
|
|
|
|
|
|
}; |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head2 new() |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Simple constructor |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=cut |
97
|
|
|
|
|
|
|
sub new { |
98
|
3
|
|
|
3
|
1
|
1169
|
bless { |
99
|
|
|
|
|
|
|
rule => {}, |
100
|
|
|
|
|
|
|
re_compile => {}, |
101
|
|
|
|
|
|
|
}, shift; |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=head2 add_rule(connect => $describe_connect, action => $action_token[, methods => $arrayref, match_callback => $code_ref]) |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Add your description to match. |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
'C' - arrayref of items GET, POST, PUT, OPTIONS, DELETE, HEAD |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
'C' - coderef is called after match found. It takes two arguments: a match found and heshref passed parameters (see method C). |
111
|
|
|
|
|
|
|
Example: |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
$r->add_rule( |
114
|
|
|
|
|
|
|
connect => '/foo/:enum(bar|baz)/:any', |
115
|
|
|
|
|
|
|
action => ['any thing'], |
116
|
|
|
|
|
|
|
methods => ['POST'], |
117
|
|
|
|
|
|
|
match_callback => sub { |
118
|
|
|
|
|
|
|
my ($match, $env) = @_; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
if ($env->{...} == ..) { |
121
|
|
|
|
|
|
|
# $match->{action}->[0] eq 'any thing' |
122
|
|
|
|
|
|
|
return $match; |
123
|
|
|
|
|
|
|
} else { |
124
|
|
|
|
|
|
|
return { |
125
|
|
|
|
|
|
|
type => 'error', |
126
|
|
|
|
|
|
|
code => 403, |
127
|
|
|
|
|
|
|
desc => 'blah-blah' |
128
|
|
|
|
|
|
|
}; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=cut |
134
|
|
|
|
|
|
|
sub add_rule { |
135
|
7
|
|
|
7
|
1
|
3792
|
my ($self, %args) = @_; |
136
|
|
|
|
|
|
|
|
137
|
7
|
|
|
|
|
20
|
for ( ('connect', 'action') ) { |
138
|
14
|
50
|
|
|
|
55
|
unless ($args{$_}) { |
139
|
0
|
|
|
|
|
0
|
carp "missing '$_'"; |
140
|
0
|
|
|
|
|
0
|
return; |
141
|
|
|
|
|
|
|
}; |
142
|
|
|
|
|
|
|
} |
143
|
7
|
50
|
|
|
|
39
|
$args{methods} = $args{methods} ? [grep {$http_methods->{$_}} (ref $args{methods} eq 'ARRAY' ? @{$args{methods}} : $args{methods})] : []; |
|
3
|
100
|
|
|
|
13
|
|
|
2
|
|
|
|
|
7
|
|
144
|
7
|
100
|
|
|
|
46
|
my @methods = $args{methods}->[0] ? @{$args{methods}} : keys %$http_methods; |
|
2
|
|
|
|
|
8
|
|
145
|
7
|
|
|
|
|
17
|
my $methods_weight = $#methods; |
146
|
|
|
|
|
|
|
|
147
|
7
|
100
|
|
|
|
29
|
my $sub_after_match = $args{match_callback} if ref $args{match_callback} eq 'CODE'; |
148
|
|
|
|
|
|
|
|
149
|
7
|
|
|
|
|
39
|
my @depth = split '/',$args{connect},-1; |
150
|
|
|
|
|
|
|
|
151
|
7
|
|
|
|
|
17
|
my $named_segment = {}; my $i = 0; |
|
7
|
|
|
|
|
11
|
|
152
|
|
|
|
|
|
|
|
153
|
7
|
|
|
|
|
12
|
my $res = []; |
154
|
7
|
|
|
|
|
14
|
for (@methods) { |
155
|
33
|
|
100
|
|
|
183
|
$self->{rule}->{$_}->{$#depth} ||= {}; |
156
|
33
|
|
|
|
|
108
|
push @$res, $self->{rule}->{$_}->{$#depth}; |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
|
159
|
7
|
|
|
|
|
100
|
(my $tmp = $args{connect}) =~ s! |
160
|
|
|
|
|
|
|
(/)(?=/) | # double slash |
161
|
|
|
|
|
|
|
(/$) | # end slash |
162
|
|
|
|
|
|
|
/(:name\(["']?(.*?)["']?\))?:enum\(([^/]+)\)(?= $|/) | # enum |
163
|
|
|
|
|
|
|
/(:name\(["']?(.*?)["']?\))?:re\(([^/]+)\)(?= $|/) | # re |
164
|
|
|
|
|
|
|
/(:name\(["']?(.*?)["']?\))?(:any)(?= $|/) | # any |
165
|
|
|
|
|
|
|
/(:name\(["']?(.*?)["']?\))?([^/]+)(?= $|/) # eq |
166
|
|
|
|
|
|
|
! |
167
|
20
|
50
|
33
|
|
|
175
|
if ($1 or $2) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
168
|
0
|
|
0
|
|
|
0
|
$_->{exactly}->{''} ||= {} for @$res; |
169
|
0
|
|
|
|
|
0
|
$res = [map {$_->{exactly}->{''}} @$res]; |
|
0
|
|
|
|
|
0
|
|
170
|
|
|
|
|
|
|
} elsif ($5) { |
171
|
6
|
|
|
|
|
25
|
my @val = split('\|',$5); |
172
|
6
|
|
|
|
|
22
|
my @tmp; |
173
|
6
|
|
|
|
|
15
|
for my $val (@val) { |
174
|
12
|
|
|
|
|
23
|
for (@$res) { |
175
|
54
|
|
100
|
|
|
187
|
$_->{exactly}->{$val} ||= {}; |
176
|
54
|
|
|
|
|
156
|
push @tmp, $_->{exactly}->{$val}; |
177
|
|
|
|
|
|
|
}; |
178
|
|
|
|
|
|
|
} |
179
|
6
|
|
|
|
|
20
|
$res = [@tmp]; |
180
|
6
|
100
|
|
|
|
41
|
$named_segment->{$i} = $4 if $4; |
181
|
|
|
|
|
|
|
} elsif ($8) { |
182
|
1
|
|
|
|
|
43
|
$self->{re_compile}->{$8} = qr{$8}s; |
183
|
1
|
|
50
|
|
|
59
|
$_->{regexp}->{$8} ||= {} for @$res; |
184
|
1
|
|
|
|
|
3
|
$res = [map {$_->{regexp}->{$8}} @$res]; |
|
12
|
|
|
|
|
30
|
|
185
|
1
|
50
|
|
|
|
9
|
$named_segment->{$i} = $7 if $7; |
186
|
|
|
|
|
|
|
} elsif ($11) { |
187
|
5
|
|
100
|
|
|
139
|
$_->{default}->{''} ||= {} for @$res; |
188
|
5
|
|
|
|
|
10
|
$res = [map {$_->{default}->{''}} @$res]; |
|
42
|
|
|
|
|
70
|
|
189
|
5
|
100
|
|
|
|
24
|
$named_segment->{$i} = $10 if $10; |
190
|
|
|
|
|
|
|
} elsif ($14) { |
191
|
8
|
|
100
|
|
|
164
|
$_->{exactly}->{$14} ||= {} for @$res; |
192
|
8
|
|
|
|
|
18
|
$res = [map {$_->{exactly}->{$14}} @$res]; |
|
39
|
|
|
|
|
88
|
|
193
|
8
|
50
|
|
|
|
30
|
$named_segment->{$i} = $13 if $13; |
194
|
|
|
|
|
|
|
} else { |
195
|
|
|
|
|
|
|
# default as word |
196
|
0
|
|
|
|
|
0
|
croak "cant't resolve connect '$args{connect}'" |
197
|
|
|
|
|
|
|
} |
198
|
20
|
|
|
|
|
142
|
$i++; |
199
|
|
|
|
|
|
|
!gex; |
200
|
|
|
|
|
|
|
|
201
|
7
|
|
|
|
|
17
|
for (@$res) { |
202
|
60
|
50
|
66
|
|
|
162
|
if (not $_->{match} or $_->{match}->[3] >= $methods_weight) { |
203
|
|
|
|
|
|
|
# set only if no match or a match for a more accurate description |
204
|
60
|
100
|
|
|
|
359
|
$_->{match} = [$args{action}, keys %$named_segment ? $named_segment : undef, $sub_after_match, $methods_weight]; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
7
|
|
|
|
|
58
|
return 1; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub _match { |
212
|
22
|
|
|
22
|
|
64
|
my ($self, $reserch, $size_el, @el) = @_; |
213
|
22
|
|
|
|
|
24
|
my $ret; |
214
|
22
|
|
|
|
|
27
|
my $not_exactly = 0; |
215
|
22
|
|
|
|
|
33
|
my $segment = shift @el; |
216
|
22
|
|
|
|
|
24
|
$size_el--; |
217
|
22
|
|
|
|
|
44
|
my $exactly = $reserch->{exactly}->{$segment}; |
218
|
22
|
100
|
|
|
|
51
|
if (defined $exactly) { |
219
|
14
|
100
|
|
|
|
82
|
($ret, $not_exactly) = $size_el ? $self->_match($exactly, $size_el, @el) : $exactly->{match}; |
220
|
14
|
50
|
|
|
|
57
|
return ($ret, $not_exactly) if $ret; |
221
|
|
|
|
|
|
|
}; |
222
|
|
|
|
|
|
|
|
223
|
8
|
100
|
|
|
|
35
|
if ($reserch->{regexp}) { |
224
|
1
|
|
|
|
|
4
|
for (keys %{$reserch->{regexp}}) { |
|
1
|
|
|
|
|
17
|
|
225
|
1
|
50
|
|
|
|
9
|
if ($segment =~ $self->{re_compile}->{$_}) { |
226
|
1
|
50
|
|
|
|
5
|
($ret) = $size_el ? $self->_match($reserch->{regexp}->{$_}, $size_el, @el) : $reserch->{regexp}->{$_}->{match}; |
227
|
1
|
50
|
|
|
|
7
|
return ($ret, 1) if $ret; |
228
|
|
|
|
|
|
|
}; |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
}; |
231
|
|
|
|
|
|
|
|
232
|
7
|
100
|
|
|
|
21
|
if ($reserch->{default}) { |
233
|
5
|
50
|
|
|
|
17
|
($ret) = $size_el ? $self->_match($reserch->{default}->{''}, $size_el, @el) : $reserch->{default}->{''}->{match}; |
234
|
5
|
50
|
|
|
|
35
|
return ($ret, 1) if $ret; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
2
|
|
|
|
|
8
|
return; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=head2 match({REQUEST_METHOD => ..., 'psgix.tmp.RouterPathInfo' => ...}) |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
Search match. See SYNOPSIS. |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
If a match is found, it returns hashref: |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
{ |
247
|
|
|
|
|
|
|
type => 'controller', |
248
|
|
|
|
|
|
|
action => $action, |
249
|
|
|
|
|
|
|
name_segments => $arrayref |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
Otherwise, undef. |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=cut |
255
|
|
|
|
|
|
|
sub match { |
256
|
9
|
|
|
9
|
1
|
2740
|
my $self = shift; |
257
|
9
|
|
|
|
|
16
|
my $env = shift; |
258
|
|
|
|
|
|
|
|
259
|
9
|
|
|
|
|
18
|
my $depth = $env->{'psgix.tmp.RouterPathInfo'}->{depth}; |
260
|
|
|
|
|
|
|
|
261
|
9
|
|
|
|
|
31
|
my ($match, $not_exactly) = $self->_match( |
262
|
|
|
|
|
|
|
$self->{rule}->{$env->{REQUEST_METHOD}}->{$depth}, |
263
|
|
|
|
|
|
|
$depth, |
264
|
9
|
|
|
|
|
33
|
@{$env->{'psgix.tmp.RouterPathInfo'}->{segments}} |
265
|
|
|
|
|
|
|
); |
266
|
|
|
|
|
|
|
|
267
|
9
|
100
|
|
|
|
26
|
if ($match) { |
268
|
7
|
|
|
|
|
22
|
my $ret = { |
269
|
|
|
|
|
|
|
type => 'controller', |
270
|
|
|
|
|
|
|
action => $match->[0] |
271
|
|
|
|
|
|
|
}; |
272
|
7
|
100
|
|
|
|
20
|
unless ($match->[1]) { |
273
|
5
|
|
|
|
|
11
|
$ret->{name_segments} = {}; |
274
|
|
|
|
|
|
|
} else { |
275
|
2
|
|
|
|
|
3
|
$ret->{name_segments}->{$match->[1]->{$_}} = $env->{'psgix.tmp.RouterPathInfo'}->{segments}->[$_] for keys %{$match->[1]}; |
|
2
|
|
|
|
|
22
|
|
276
|
|
|
|
|
|
|
} |
277
|
7
|
100
|
|
|
|
24
|
$ret->{_callback} = $match->[2] if $match->[2]; |
278
|
7
|
|
|
|
|
22
|
return ($not_exactly, $ret); |
279
|
|
|
|
|
|
|
} else { |
280
|
2
|
|
|
|
|
7
|
return; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# if ($match) { |
284
|
|
|
|
|
|
|
# my $ret = { |
285
|
|
|
|
|
|
|
# type => 'controller', |
286
|
|
|
|
|
|
|
# action => $match->[0], |
287
|
|
|
|
|
|
|
# segment => $match->[1] ? [map {$env->{'psgix.tmp.RouterPathInfo'}->{segments}->[$_]} @{$match->[1]}] : [] |
288
|
|
|
|
|
|
|
# }; |
289
|
|
|
|
|
|
|
# if ($match->[2]) { |
290
|
|
|
|
|
|
|
# return ($not_exactly, $match->[2]->($ret,$env)); |
291
|
|
|
|
|
|
|
# } else { |
292
|
|
|
|
|
|
|
# return ($not_exactly, $ret); |
293
|
|
|
|
|
|
|
# } |
294
|
|
|
|
|
|
|
# } else { |
295
|
|
|
|
|
|
|
# return; |
296
|
|
|
|
|
|
|
# } |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=head1 SEE ALSO |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
L, L |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=head1 AUTHOR |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
mr.Rico |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=cut |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
1; |
311
|
|
|
|
|
|
|
__END__ |