line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Kelp::Routes::Pattern; |
2
|
|
|
|
|
|
|
|
3
|
37
|
|
|
37
|
|
209218
|
use Carp; |
|
37
|
|
|
|
|
150
|
|
|
37
|
|
|
|
|
2164
|
|
4
|
|
|
|
|
|
|
|
5
|
37
|
|
|
37
|
|
1047
|
use Kelp::Base; |
|
37
|
|
|
|
|
79
|
|
|
37
|
|
|
|
|
229
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
attr pattern => sub { die "pattern is required" }; |
8
|
|
|
|
|
|
|
attr via => undef; |
9
|
|
|
|
|
|
|
attr method => sub { $_[0]->via }; |
10
|
|
|
|
|
|
|
attr name => sub { $_[0]->pattern }; |
11
|
|
|
|
|
|
|
attr check => sub { {} }; |
12
|
|
|
|
|
|
|
attr defaults => sub { {} }; |
13
|
|
|
|
|
|
|
attr bridge => 0; |
14
|
|
|
|
|
|
|
attr regex => sub { $_[0]->_build_regex }; |
15
|
|
|
|
|
|
|
attr named => sub { {} }; |
16
|
|
|
|
|
|
|
attr param => sub { [] }; |
17
|
|
|
|
|
|
|
attr to => undef; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub new { |
21
|
214
|
|
|
214
|
0
|
4092
|
my $class = shift; |
22
|
214
|
|
|
|
|
754
|
my $self = $class->SUPER::new(@_); |
23
|
214
|
|
|
|
|
555
|
$self->{_tokens} = []; |
24
|
214
|
|
|
|
|
614
|
$self->regex; # Compile the regex |
25
|
214
|
|
|
|
|
794
|
return $self; |
26
|
|
|
|
|
|
|
} |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub _rep_regex { |
29
|
96
|
|
|
96
|
|
395
|
my ( $self, $char, $switch, $token ) = @_; |
30
|
|
|
|
|
|
|
|
31
|
96
|
|
|
|
|
202
|
push @{$self->{_tokens}}, $token; |
|
96
|
|
|
|
|
223
|
|
32
|
|
|
|
|
|
|
|
33
|
96
|
|
|
|
|
256
|
my ( $a, $b, $r ) = ( "(?<$token>", ')', undef ); |
34
|
96
|
|
|
|
|
188
|
for ($switch) { |
35
|
96
|
100
|
100
|
|
|
268
|
if ( $_ eq ':' || $_ eq '?' ) { |
36
|
92
|
|
100
|
|
|
275
|
$r = $a . ( $self->check->{$token} // '[^\/]+' ) . $b |
37
|
|
|
|
|
|
|
} |
38
|
96
|
100
|
|
|
|
309
|
if ( $_ eq '*' ) { |
39
|
4
|
|
|
|
|
12
|
$r = $a . '.+' . $b |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
96
|
100
|
100
|
|
|
354
|
$char = $char . '?' if $char eq '/' && $switch eq '?'; |
44
|
96
|
100
|
|
|
|
210
|
$r .= '?' if $switch eq '?'; |
45
|
|
|
|
|
|
|
|
46
|
96
|
|
|
|
|
429
|
return $char . $r; |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub _build_regex { |
50
|
214
|
|
|
214
|
|
349
|
my $self = shift; |
51
|
214
|
100
|
|
|
|
546
|
return $self->pattern if ref $self->pattern eq 'Regexp'; |
52
|
|
|
|
|
|
|
|
53
|
210
|
|
|
|
|
442
|
my $PAT = '(.?)([:*?])(\w+)'; |
54
|
210
|
|
|
|
|
436
|
my $pattern = $self->pattern; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# Curly braces and brackets are only used for separation. |
57
|
|
|
|
|
|
|
# We replace all of them with \0, then convert the pattern |
58
|
|
|
|
|
|
|
# into a regular expression. This way if the regular expression |
59
|
|
|
|
|
|
|
# contains curlies, they won't be removed. |
60
|
210
|
|
|
|
|
713
|
$pattern =~ s/[{}]/\0/g; |
61
|
210
|
|
|
|
|
1935
|
$pattern =~ s{$PAT}{$self->_rep_regex($1, $2, $3)}eg; |
|
96
|
|
|
|
|
280
|
|
62
|
210
|
|
|
|
|
549
|
$pattern =~ s/\0//g; |
63
|
210
|
100
|
|
|
|
688
|
$pattern .= '/?' unless $pattern =~ m{/$}; |
64
|
210
|
100
|
|
|
|
580
|
$pattern .= '$' unless $self->bridge; |
65
|
|
|
|
|
|
|
|
66
|
210
|
|
|
|
|
4435
|
return qr{^$pattern}; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub _rep_build { |
70
|
65
|
|
|
65
|
|
249
|
my ( $self, $switch, $token, %args ) = @_; |
71
|
65
|
|
100
|
|
|
214
|
my $rep = $args{$token} // $self->defaults->{$token} // ''; |
|
|
|
100
|
|
|
|
|
72
|
65
|
100
|
100
|
|
|
219
|
if ($switch ne '?' && !$rep) { |
73
|
12
|
|
|
|
|
66
|
return '{?' . $token . '}'; |
74
|
|
|
|
|
|
|
} |
75
|
53
|
|
|
|
|
115
|
my $check = $self->check->{$token}; |
76
|
53
|
100
|
100
|
|
|
166
|
if ( $check && $args{$token} !~ $check ) { |
77
|
2
|
|
|
|
|
13
|
return '{!' . $token . '}'; |
78
|
|
|
|
|
|
|
} |
79
|
51
|
|
|
|
|
205
|
return $rep; |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub build { |
83
|
37
|
|
|
37
|
1
|
147
|
my ( $self, %args ) = @_; |
84
|
|
|
|
|
|
|
|
85
|
37
|
|
|
|
|
90
|
my $pattern = $self->pattern; |
86
|
37
|
50
|
|
|
|
95
|
if ( ref $pattern eq 'Regexp' ) { |
87
|
0
|
|
|
|
|
0
|
carp "Can't build a path for regular expressions"; |
88
|
0
|
|
|
|
|
0
|
return; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
37
|
|
|
|
|
73
|
my $PAT = '([:*?])(\w+)'; |
92
|
37
|
|
|
|
|
334
|
$pattern =~ s/{?$PAT}?/$self->_rep_build($1, $2, %args)/eg; |
|
65
|
|
|
|
|
185
|
|
93
|
37
|
100
|
|
|
|
155
|
if ($pattern =~ /{([!?])(\w+)}/) { |
94
|
13
|
100
|
|
|
|
1300
|
carp $1 eq '!' |
95
|
|
|
|
|
|
|
? "Field $2 doesn't match checks" |
96
|
|
|
|
|
|
|
: "Default value for field $2 is missing"; |
97
|
13
|
|
|
|
|
626
|
return; |
98
|
|
|
|
|
|
|
} |
99
|
24
|
|
|
|
|
136
|
return $pattern; |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub match { |
103
|
1206
|
|
|
1206
|
1
|
10778
|
my ( $self, $path, $method ) = @_; |
104
|
1206
|
100
|
100
|
|
|
2310
|
return 0 if ( $self->method && $self->method ne ( $method // '' ) ); |
|
|
|
100
|
|
|
|
|
105
|
1132
|
100
|
|
|
|
2283
|
return 0 unless my @matched = $path =~ $self->regex; |
106
|
|
|
|
|
|
|
|
107
|
282
|
100
|
|
|
|
1007
|
@matched = () unless $#+; # were there any captures? see perlvar @+ |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Initialize the named parameters hash and its default values |
110
|
282
|
|
|
8
|
|
1800
|
my %named = map { $_ => $+{$_} } keys %+; |
|
187
|
|
|
|
|
1241
|
|
|
8
|
|
|
|
|
13482
|
|
|
8
|
|
|
|
|
2951
|
|
|
8
|
|
|
|
|
1855
|
|
111
|
282
|
|
|
|
|
536
|
for ( keys %{ $self->defaults } ) { |
|
282
|
|
|
|
|
774
|
|
112
|
4
|
100
|
|
|
|
32
|
$named{$_} = $self->defaults->{$_} unless exists $named{$_}; |
113
|
|
|
|
|
|
|
} |
114
|
282
|
|
|
|
|
955
|
$self->named( \%named ); |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Initialize the param array, containing the values of the |
117
|
|
|
|
|
|
|
# named placeholders in the order they appear in the regex. |
118
|
282
|
100
|
|
|
|
433
|
if ( my @tokens = @{ $self->{_tokens} } ) { |
|
282
|
|
|
|
|
879
|
|
119
|
116
|
|
|
|
|
204
|
$self->param( [ map { $named{$_} } @tokens ] ); |
|
197
|
|
|
|
|
567
|
|
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
else { |
122
|
166
|
100
|
|
|
|
528
|
$self->param( [ map { $_ eq '' ? undef : $_ } @matched] ); |
|
13
|
|
|
|
|
47
|
|
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
282
|
|
|
|
|
1282
|
return 1; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
1; |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
__END__ |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head1 NAME |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
Kelp::Routes::Pattern - Route patterns for Kelp routes |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=head1 SYNOPSIS |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
my $p = Kelp::Routes::Pattern->new( pattern => '/:name/:place' ); |
139
|
|
|
|
|
|
|
if ( $p->match('/james/london') ) { |
140
|
|
|
|
|
|
|
%named = %{ $p->named }; # ( name => 'james', place => 'london' ) |
141
|
|
|
|
|
|
|
@param = @{ $p->param }; # ( 'james', 'london' ) |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=head1 DESCRIPTION |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
This module is needed by L<Kelp::Routes>. It provides matching for |
147
|
|
|
|
|
|
|
individual route patterns, returning the named placeholders in a hash and an |
148
|
|
|
|
|
|
|
array. |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=head2 pattern |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
The pattern to match against. Each pattern is a string, which may contain named |
155
|
|
|
|
|
|
|
placeholders. For more information on the types and use of placeholders, look at |
156
|
|
|
|
|
|
|
L<Kelp::Routes/PLACEHOLDERS>. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( pattern => '/:id/*other' ); |
159
|
|
|
|
|
|
|
... |
160
|
|
|
|
|
|
|
$p->match('/4/something-else'); # True |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=head2 method |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
Specifies an HTTP method to be matched by the route. |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( |
167
|
|
|
|
|
|
|
pattern => '/:id/*other', |
168
|
|
|
|
|
|
|
method => 'PUT' |
169
|
|
|
|
|
|
|
); |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
$p->match('/4/something-else', 'GET'); # False. Only PUT allowed. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=head2 name |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
You are encouraged to give each route a name, so you can look it up later when |
176
|
|
|
|
|
|
|
you build a URL for it. |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( |
179
|
|
|
|
|
|
|
pattern => '/:id/*other', |
180
|
|
|
|
|
|
|
name => 'other_id' |
181
|
|
|
|
|
|
|
); |
182
|
|
|
|
|
|
|
... |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
say $p->build( 'other_id', id => '100', other => 'something-else' ); |
185
|
|
|
|
|
|
|
# Prints '/100/something-else' |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
If no name is provided for the route, the C<pattern> is used. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head2 check |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
A hashref with placeholder names as keys and regular expressions as values. It |
192
|
|
|
|
|
|
|
is used to match the values of the placeholders against the provided regular |
193
|
|
|
|
|
|
|
expressions. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( |
196
|
|
|
|
|
|
|
pattern => '/:id/*other', |
197
|
|
|
|
|
|
|
check => { id => qr/\d+/ } # id may only be a didgit |
198
|
|
|
|
|
|
|
); |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
$p->match('/4/other'); # True |
201
|
|
|
|
|
|
|
$p->match('/q/other'); # False |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
Note: Do not add C<^> at the beginning or C<$> at the end of the regular |
204
|
|
|
|
|
|
|
expressions, because they are merged into a bigger regex. |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=head2 defaults |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
A hashref with placeholder defaults. This only applies to optional placeholders, |
209
|
|
|
|
|
|
|
or those prefixed with a question mark. If a default value is provided for any |
210
|
|
|
|
|
|
|
of them, it will be used in case the placeholder value is missing. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( |
213
|
|
|
|
|
|
|
pattern => '/:id/?other', |
214
|
|
|
|
|
|
|
defaults => { other => 'info' } |
215
|
|
|
|
|
|
|
); |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
$p->match('/100'); |
218
|
|
|
|
|
|
|
# $p->named will contain { id => 100, other => 'info' } |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
$p->match('/100/delete'); |
221
|
|
|
|
|
|
|
# $p->named will contain { id => 100, other => 'delete' } |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head2 bridge |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
A True/False value. Specifies if the route is a bridge. For more information |
226
|
|
|
|
|
|
|
about bridges, please see L<Kelp::Routes/BRIDGES> |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=head2 regex |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
We recommend that you stick to using patterns, because they are simpler and |
231
|
|
|
|
|
|
|
easier to read, but if you need to match a really complicated route, then |
232
|
|
|
|
|
|
|
you can use a regular expression. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( regex => qr{^(\d+)/(\d+)$} ); |
235
|
|
|
|
|
|
|
$p->match('/100/200'); # True. $p->param will be [ 100, 200 ] |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
After matching, the L</param> array will be initialized with the values of the |
238
|
|
|
|
|
|
|
captures in the order they appear in the regex. |
239
|
|
|
|
|
|
|
If you used a regex with named captures, then a hashref L</named> will also be |
240
|
|
|
|
|
|
|
initialized with the names and values of the named placeholders. In other words, |
241
|
|
|
|
|
|
|
this hash will be a permanent copy of the C<%+> built-in hash. |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( regex => qr{^(?<id>\d+)/(?<line>\d+)$} ); |
244
|
|
|
|
|
|
|
$p->match('/100/200'); # True. |
245
|
|
|
|
|
|
|
# $p->param will be [ 100, 200 ] |
246
|
|
|
|
|
|
|
# $p->named will be { id => 100, line => 200 } |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
If C<regex> is not explicitly given a value it will be built from the |
249
|
|
|
|
|
|
|
C<pattern>. |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head2 named |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
A hashref which will be initialized by the L</match> function. After matching, |
254
|
|
|
|
|
|
|
it will contain placeholder names and values for the matched route. |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=head2 param |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
An arrayref, which will be initialized by the L</match> function. After matching, |
259
|
|
|
|
|
|
|
it will contain all placeholder values in the order they were specified in the |
260
|
|
|
|
|
|
|
pattern. |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=head2 to |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
Specifies the route destination. See examples in L<Kelp::Routes>. |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=head1 METHODS |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=head2 match |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
C<match( $path, $method )> |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
Matches an already initialized route against a path and http method. If the match |
273
|
|
|
|
|
|
|
was successful, this sub will return a true value and the L</named> and L</param> |
274
|
|
|
|
|
|
|
attributes will be initialized with the names and values of the matched placeholders. |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=head2 build |
277
|
|
|
|
|
|
|
C<build( %args )> |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Builds a URL from a pattern. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
my $p = Kelp::Routes::Patters->new( pattern => '/:id/:line/:row' ); |
282
|
|
|
|
|
|
|
$p->build( id => 100, line => 5, row => 8 ); # Returns '/100/5/8' |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
This module was inspired by L<Routes::Tiny>. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
The concept of bridges was borrowed from L<Mojolicious> |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=cut |