add files
|
1 |
package Mojolicious::Routes::Route; |
2 |
use Mojo::Base -base; |
|
3 | ||
4 |
use Carp 'croak'; |
|
5 |
use Mojolicious::Routes::Pattern; |
|
6 |
use Scalar::Util qw(blessed weaken); |
|
7 | ||
8 |
has [qw(inline parent partial)]; |
|
9 |
has 'children' => sub { [] }; |
|
10 |
has pattern => sub { Mojolicious::Routes::Pattern->new }; |
|
11 | ||
12 |
sub AUTOLOAD { |
|
13 |
my $self = shift; |
|
14 | ||
15 |
my ($package, $method) = our $AUTOLOAD =~ /^([\w:]+)::(\w+)$/; |
|
16 |
croak "Undefined subroutine &${package}::$method called" |
|
17 |
unless blessed $self && $self->isa(__PACKAGE__); |
|
18 | ||
19 |
# Call shortcut with current route |
|
20 |
croak qq{Can't locate object method "$method" via package "$package"} |
|
21 |
unless my $shortcut = $self->root->shortcuts->{$method}; |
|
22 |
return $self->$shortcut(@_); |
|
23 |
} |
|
24 | ||
25 |
sub DESTROY { } |
|
26 | ||
27 |
sub new { shift->SUPER::new->parse(@_) } |
|
28 | ||
29 |
sub add_child { |
|
30 |
my ($self, $route) = @_; |
|
31 |
weaken $route->remove->parent($self)->{parent}; |
|
32 |
push @{$self->children}, $route; |
|
33 |
return $self; |
|
34 |
} |
|
35 | ||
36 |
sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) } |
|
37 | ||
38 |
sub bridge { shift->route(@_)->inline(1) } |
|
39 | ||
40 |
sub delete { shift->_generate_route(DELETE => @_) } |
|
41 | ||
42 |
sub detour { shift->partial(1)->to(@_) } |
|
43 | ||
44 |
sub find { |
|
45 |
my ($self, $name) = @_; |
|
46 | ||
47 |
my @children = (@{$self->children}); |
|
48 |
my $candidate; |
|
49 |
while (my $child = shift @children) { |
|
50 | ||
51 |
# Match |
|
52 |
$candidate = $child->has_custom_name ? return $child : $child |
|
53 |
if $child->name eq $name; |
|
54 | ||
55 |
# Search children too |
|
56 |
push @children, @{$child->children}; |
|
57 |
} |
|
58 | ||
59 |
return $candidate; |
|
60 |
} |
|
61 | ||
62 |
sub get { shift->_generate_route(GET => @_) } |
|
63 | ||
64 |
sub has_conditions { |
|
65 |
my $self = shift; |
|
66 |
return 1 if @{$self->over || []}; |
|
67 |
return undef unless my $parent = $self->parent; |
|
68 |
return $parent->has_conditions; |
|
69 |
} |
|
70 | ||
71 |
sub has_custom_name { !!shift->{custom} } |
|
72 | ||
73 |
sub has_websocket { |
|
74 |
my $self = shift; |
|
75 |
return 1 if $self->is_websocket; |
|
76 |
return undef unless my $parent = $self->parent; |
|
77 |
return $parent->is_websocket; |
|
78 |
} |
|
79 | ||
80 |
sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} } |
|
81 | ||
82 |
sub is_websocket { !!shift->{websocket} } |
|
83 | ||
84 |
sub name { |
|
85 |
my $self = shift; |
|
86 |
return $self->{name} unless @_; |
|
87 |
$self->{name} = shift; |
|
88 |
$self->{custom} = 1; |
|
89 |
return $self; |
|
90 |
} |
|
91 | ||
92 |
sub options { shift->_generate_route(OPTIONS => @_) } |
|
93 | ||
94 |
sub over { |
|
95 |
my $self = shift; |
|
96 | ||
97 |
# Routes with conditions can't be cached |
|
98 |
return $self->{over} unless @_; |
|
99 |
my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_]; |
|
100 |
return $self unless @$conditions; |
|
101 |
$self->{over} = $conditions; |
|
102 |
$self->root->cache(0); |
|
103 | ||
104 |
return $self; |
|
105 |
} |
|
106 | ||
107 |
sub parse { |
|
108 |
my $self = shift; |
|
109 |
$self->{name} = $self->pattern->parse(@_)->pattern // ''; |
|
110 |
$self->{name} =~ s/\W+//g; |
|
111 |
return $self; |
|
112 |
} |
|
113 | ||
114 |
sub patch { shift->_generate_route(PATCH => @_) } |
|
115 |
sub post { shift->_generate_route(POST => @_) } |
|
116 |
sub put { shift->_generate_route(PUT => @_) } |
|
117 | ||
118 |
sub remove { |
|
119 |
my $self = shift; |
|
120 |
return $self unless my $parent = $self->parent; |
|
121 |
@{$parent->children} = grep { $_ ne $self } @{$parent->children}; |
|
122 |
return $self->parent(undef); |
|
123 |
} |
|
124 | ||
125 |
sub render { |
|
126 |
my ($self, $path, $values) = @_; |
|
127 | ||
128 |
# Render pattern |
|
129 |
my $prefix = $self->pattern->render($values, !$path); |
|
130 |
$path = "$prefix$path" unless $prefix eq '/'; |
|
131 |
$path ||= '/' unless my $parent = $self->parent; |
|
132 | ||
133 |
# Let parent render |
|
134 |
return $parent ? $parent->render($path, $values) : $path; |
|
135 |
} |
|
136 | ||
137 |
sub root { |
|
138 |
my $root = my $parent = shift; |
|
139 |
$root = $parent while $parent = $parent->parent; |
|
140 |
return $root; |
|
141 |
} |
|
142 | ||
143 |
sub route { |
|
144 |
my $self = shift; |
|
145 |
my $route = $self->add_child($self->new(@_))->children->[-1]; |
|
146 |
my $format = $self->pattern->constraints->{format}; |
|
147 |
$route->pattern->constraints->{format} //= 0 if defined $format && !$format; |
|
148 |
return $route; |
|
149 |
} |
|
150 | ||
151 |
sub to { |
|
152 |
my $self = shift; |
|
153 | ||
154 |
my $pattern = $self->pattern; |
|
155 |
return $pattern->defaults unless @_; |
|
156 |
my ($shortcut, %defaults) = _defaults(@_); |
|
157 | ||
158 |
if ($shortcut) { |
|
159 | ||
160 |
# Application |
|
161 |
if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { |
|
162 |
$defaults{app} = $shortcut; |
|
163 |
} |
|
164 | ||
165 |
# Controller and action |
|
166 |
elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) { |
|
167 |
$defaults{controller} = $1 if defined $1; |
|
168 |
$defaults{action} = $2 if defined $2; |
|
169 |
} |
|
170 |
} |
|
171 | ||
172 |
$pattern->defaults({%{$pattern->defaults}, %defaults}); |
|
173 | ||
174 |
return $self; |
|
175 |
} |
|
176 | ||
177 |
sub to_string { |
|
178 |
my $self = shift; |
|
179 |
my $pattern = $self->parent ? $self->parent->to_string : ''; |
|
180 |
$pattern .= $self->pattern->pattern if $self->pattern->pattern; |
|
181 |
return $pattern; |
|
182 |
} |
|
183 | ||
184 |
sub under { shift->_generate_route(under => @_) } |
|
185 | ||
186 |
sub via { |
|
187 |
my $self = shift; |
|
188 |
return $self->{via} unless @_; |
|
189 |
my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}]; |
|
190 |
$self->{via} = $methods if @$methods; |
|
191 |
return $self; |
|
192 |
} |
|
193 | ||
194 |
sub websocket { |
|
195 |
my $route = shift->get(@_); |
|
196 |
$route->{websocket} = 1; |
|
197 |
return $route; |
|
198 |
} |
|
199 | ||
200 |
sub _defaults { |
|
201 | ||
202 |
# Hash or shortcut (one) |
|
203 |
return ref $_[0] eq 'HASH' ? (undef, %{shift()}) : @_ if @_ == 1; |
|
204 | ||
205 |
# Shortcut and values (odd) |
|
206 |
return shift, @_ if @_ % 2; |
|
207 | ||
208 |
# Shortcut and hash or just values (even) |
|
209 |
return ref $_[1] eq 'HASH' ? (shift, %{shift()}) : (undef, @_); |
|
210 |
} |
|
211 | ||
212 |
sub _generate_route { |
|
213 |
my ($self, $methods, @args) = @_; |
|
214 | ||
215 |
my ($cb, @conditions, @constraints, %defaults, $name, $pattern); |
|
216 |
while (defined(my $arg = shift @args)) { |
|
217 | ||
218 |
# First scalar is the pattern |
|
219 |
if (!ref $arg && !$pattern) { $pattern = $arg } |
|
220 | ||
221 |
# Scalar |
|
222 |
elsif (!ref $arg && @args) { push @conditions, $arg, shift @args } |
|
223 | ||
224 |
# Last scalar is the route name |
|
225 |
elsif (!ref $arg) { $name = $arg } |
|
226 | ||
227 |
# Callback |
|
228 |
elsif (ref $arg eq 'CODE') { $cb = $arg } |
|
229 | ||
230 |
# Constraints |
|
231 |
elsif (ref $arg eq 'ARRAY') { @constraints = @$arg } |
|
232 | ||
233 |
# Defaults |
|
234 |
elsif (ref $arg eq 'HASH') { %defaults = %$arg } |
|
235 |
} |
|
236 | ||
237 |
# Callback |
|
238 |
$defaults{cb} = $cb if $cb; |
|
239 | ||
240 |
# Create bridge or route |
|
241 |
my $route |
|
242 |
= $methods eq 'under' |
|
243 |
? $self->bridge($pattern, @constraints) |
|
244 |
: $self->route($pattern, @constraints)->via($methods); |
|
245 |
$route->over(\@conditions)->to(\%defaults); |
|
246 | ||
247 |
return defined $name ? $route->name($name) : $route; |
|
248 |
} |
|
249 | ||
250 |
1; |
|
251 | ||
252 |
=encoding utf8 |
|
253 | ||
254 |
=head1 NAME |
|
255 | ||
256 |
Mojolicious::Routes::Route - Route |
|
257 | ||
258 |
=head1 SYNOPSIS |
|
259 | ||
260 |
use Mojolicious::Routes::Route; |
|
261 | ||
262 |
my $r = Mojolicious::Routes::Route->new; |
|
263 | ||
264 |
=head1 DESCRIPTION |
|
265 | ||
266 |
L<Mojolicious::Routes::Route> is the route container used by |
|
267 |
L<Mojolicious::Routes>. |
|
268 | ||
269 |
=head1 ATTRIBUTES |
|
270 | ||
271 |
L<Mojolicious::Routes::Route> implements the following attributes. |
|
272 | ||
273 |
=head2 children |
|
274 | ||
275 |
my $children = $r->children; |
|
276 |
$r = $r->children([Mojolicious::Routes::Route->new]); |
|
277 | ||
278 |
The children of this route, used for nesting routes. |
|
279 | ||
280 |
=head2 inline |
|
281 | ||
282 |
my $bool = $r->inline; |
|
283 |
$r = $r->inline($bool); |
|
284 | ||
285 |
Allow L</"bridge"> semantics for this route. |
|
286 | ||
287 |
=head2 parent |
|
288 | ||
289 |
my $parent = $r->parent; |
|
290 |
$r = $r->parent(Mojolicious::Routes::Route->new); |
|
291 | ||
292 |
The parent of this route, used for nesting routes. |
|
293 | ||
294 |
=head2 partial |
|
295 | ||
296 |
my $bool = $r->partial; |
|
297 |
$r = $r->partial($bool); |
|
298 | ||
299 |
Route has no specific end, remaining characters will be captured in C<path>. |
|
300 | ||
301 |
=head2 pattern |
|
302 | ||
303 |
my $pattern = $r->pattern; |
|
304 |
$r = $r->pattern(Mojolicious::Routes::Pattern->new); |
|
305 | ||
306 |
Pattern for this route, defaults to a L<Mojolicious::Routes::Pattern> object. |
|
307 | ||
308 |
=head1 METHODS |
|
309 | ||
310 |
L<Mojolicious::Routes::Route> inherits all methods from L<Mojo::Base> and |
|
311 |
implements the following new ones. |
|
312 | ||
313 |
=head2 new |
|
314 | ||
315 |
my $r = Mojolicious::Routes::Route->new; |
|
316 |
my $r = Mojolicious::Routes::Route->new('/:controller/:action'); |
|
317 | ||
318 |
Construct a new L<Mojolicious::Routes::Route> object and <parse> pattern if |
|
319 |
necessary. |
|
320 | ||
321 |
=head2 add_child |
|
322 | ||
323 |
$r = $r->add_child(Mojolicious::Routes::Route->new); |
|
324 | ||
325 |
Add a new child to this route, it will be automatically removed from its |
|
326 |
current parent if necessary. |
|
327 | ||
328 |
# Reattach route |
|
329 |
$r->add_child($r->find('foo')); |
|
330 | ||
331 |
=head2 any |
|
332 | ||
333 |
my $route = $r->any('/:foo' => sub {...}); |
|
334 |
my $route = $r->any([qw(GET POST)] => '/:foo' => sub {...}); |
|
335 | ||
336 |
Generate route matching any of the listed HTTP request methods or all. See |
|
337 |
also the L<Mojolicious::Lite> tutorial for more argument variations. |
|
338 | ||
339 |
$r->any('/user')->to('user#whatever'); |
|
340 | ||
341 |
=head2 bridge |
|
342 | ||
343 |
my $bridge = $r->bridge; |
|
344 |
my $bridge = $r->bridge('/:action'); |
|
345 |
my $bridge = $r->bridge('/:action', action => qr/\w+/); |
|
346 |
my $bridge = $r->bridge(format => 0); |
|
347 | ||
348 |
Generate bridge route with optional pattern and restrictive placeholders. |
|
349 | ||
350 |
my $auth = $r->bridge('/user')->to('user#auth'); |
|
351 |
$auth->get('/show')->to('#show'); |
|
352 |
$auth->post('/create')->to('#create'); |
|
353 | ||
354 |
=head2 delete |
|
355 | ||
356 |
my $route = $r->delete('/:foo' => sub {...}); |
|
357 | ||
358 |
Generate route matching only DELETE requests. See also the |
|
359 |
L<Mojolicious::Lite> tutorial for more argument variations. |
|
360 | ||
361 |
$r->delete('/user')->to('user#remove'); |
|
362 | ||
363 |
=head2 detour |
|
364 | ||
365 |
$r = $r->detour(action => 'foo'); |
|
366 |
$r = $r->detour('controller#action'); |
|
367 |
$r = $r->detour(Mojolicious->new, foo => 'bar'); |
|
368 |
$r = $r->detour('MyApp', {foo => 'bar'}); |
|
369 | ||
370 |
Set default parameters for this route and allow partial matching to simplify |
|
371 |
application embedding, takes the same arguments as L</"to">. |
|
372 | ||
373 |
=head2 find |
|
374 | ||
375 |
my $route = $r->find('foo'); |
|
376 | ||
377 |
Find child route by name, custom names have precedence over automatically |
|
378 |
generated ones. |
|
379 | ||
380 |
$r->find('show_user')->to(foo => 'bar'); |
|
381 | ||
382 |
=head2 get |
|
383 | ||
384 |
my $route = $r->get('/:foo' => sub {...}); |
|
385 | ||
386 |
Generate route matching only GET requests. See also the L<Mojolicious::Lite> |
|
387 |
tutorial for more argument variations. |
|
388 | ||
389 |
$r->get('/user')->to('user#show'); |
|
390 | ||
391 |
=head2 has_conditions |
|
392 | ||
393 |
my $bool = $r->has_conditions; |
|
394 | ||
395 |
Check if this route has active conditions. |
|
396 | ||
397 |
=head2 has_custom_name |
|
398 | ||
399 |
my $bool = $r->has_custom_name; |
|
400 | ||
401 |
Check if this route has a custom name. |
|
402 | ||
403 |
=head2 has_websocket |
|
404 | ||
405 |
my $bool = $r->has_websocket; |
|
406 | ||
407 |
Check if this route has a WebSocket ancestor. |
|
408 | ||
409 |
=head2 is_endpoint |
|
410 | ||
411 |
my $bool = $r->is_endpoint; |
|
412 | ||
413 |
Check if this route qualifies as an endpoint. |
|
414 | ||
415 |
=head2 is_websocket |
|
416 | ||
417 |
my $bool = $r->is_websocket; |
|
418 | ||
419 |
Check if this route is a WebSocket. |
|
420 | ||
421 |
=head2 name |
|
422 | ||
423 |
my $name = $r->name; |
|
424 |
$r = $r->name('foo'); |
|
425 | ||
426 |
The name of this route, defaults to an automatically generated name based on |
|
427 |
the route pattern. Note that the name C<current> is reserved for referring to |
|
428 |
the current route. |
|
429 | ||
430 |
$r->get('/user')->to('user#show')->name('show_user'); |
|
431 | ||
432 |
=head2 options |
|
433 | ||
434 |
my $route = $r->options('/:foo' => sub {...}); |
|
435 | ||
436 |
Generate route matching only OPTIONS requests. See also the |
|
437 |
L<Mojolicious::Lite> tutorial for more argument variations. |
|
438 | ||
439 |
$r->options('/user')->to('user#overview'); |
|
440 | ||
441 |
=head2 over |
|
442 | ||
443 |
my $over = $r->over; |
|
444 |
$r = $r->over(foo => 1); |
|
445 |
$r = $r->over(foo => 1, bar => {baz => 'yada'}); |
|
446 |
$r = $r->over([foo => 1, bar => {baz => 'yada'}]); |
|
447 | ||
448 |
Activate conditions for this route. Note that this automatically disables the |
|
449 |
routing cache, since conditions are too complex for caching. |
|
450 | ||
451 |
$r->get('/foo')->over(host => qr/mojolicio\.us/)->to('foo#bar'); |
|
452 | ||
453 |
=head2 parse |
|
454 | ||
455 |
$r = $r->parse('/:action'); |
|
456 |
$r = $r->parse('/:action', action => qr/\w+/); |
|
457 |
$r = $r->parse(format => 0); |
|
458 | ||
459 |
Parse pattern. |
|
460 | ||
461 |
=head2 patch |
|
462 | ||
463 |
my $route = $r->patch('/:foo' => sub {...}); |
|
464 | ||
465 |
Generate route matching only PATCH requests. See also the L<Mojolicious::Lite> |
|
466 |
tutorial for more argument variations. |
|
467 | ||
468 |
$r->patch('/user')->to('user#update'); |
|
469 | ||
470 |
=head2 post |
|
471 | ||
472 |
my $route = $r->post('/:foo' => sub {...}); |
|
473 | ||
474 |
Generate route matching only POST requests. See also the L<Mojolicious::Lite> |
|
475 |
tutorial for more argument variations. |
|
476 | ||
477 |
$r->post('/user')->to('user#create'); |
|
478 | ||
479 |
=head2 put |
|
480 | ||
481 |
my $route = $r->put('/:foo' => sub {...}); |
|
482 | ||
483 |
Generate route matching only PUT requests. See also the L<Mojolicious::Lite> |
|
484 |
tutorial for more argument variations. |
|
485 | ||
486 |
$r->put('/user')->to('user#replace'); |
|
487 | ||
488 |
=head2 remove |
|
489 | ||
490 |
$r = $r->remove; |
|
491 | ||
492 |
Remove route from parent. |
|
493 | ||
494 |
# Remove route completely |
|
495 |
$r->find('foo')->remove; |
|
496 | ||
497 |
# Reattach route to new parent |
|
498 |
$r->route('/foo')->add_child($r->find('bar')->remove); |
|
499 | ||
500 |
=head2 render |
|
501 | ||
502 |
my $path = $r->render($suffix); |
|
503 |
my $path = $r->render($suffix, {foo => 'bar'}); |
|
504 | ||
505 |
Render route with parameters into a path. |
|
506 | ||
507 |
=head2 root |
|
508 | ||
509 |
my $root = $r->root; |
|
510 | ||
511 |
The L<Mojolicious::Routes> object this route is an descendent of. |
|
512 | ||
513 |
$r->root->cache(0); |
|
514 | ||
515 |
=head2 route |
|
516 | ||
517 |
my $route = $r->route; |
|
518 |
my $route = $r->route('/:action'); |
|
519 |
my $route = $r->route('/:action', action => qr/\w+/); |
|
520 |
my $route = $r->route(format => 0); |
|
521 | ||
522 |
Generate route matching all HTTP request methods with optional pattern and |
|
523 |
restrictive placeholders. |
|
524 | ||
525 |
=head2 to |
|
526 | ||
527 |
my $defaults = $r->to; |
|
528 |
$r = $r->to(action => 'foo'); |
|
529 |
$r = $r->to({action => 'foo'}); |
|
530 |
$r = $r->to('controller#action'); |
|
531 |
$r = $r->to('controller#action', foo => 'bar'); |
|
532 |
$r = $r->to('controller#action', {foo => 'bar'}); |
|
533 |
$r = $r->to(Mojolicious->new); |
|
534 |
$r = $r->to(Mojolicious->new, foo => 'bar'); |
|
535 |
$r = $r->to(Mojolicious->new, {foo => 'bar'}); |
|
536 |
$r = $r->to('MyApp'); |
|
537 |
$r = $r->to('MyApp', foo => 'bar'); |
|
538 |
$r = $r->to('MyApp', {foo => 'bar'}); |
|
539 | ||
540 |
Set default parameters for this route. |
|
541 | ||
542 |
=head2 to_string |
|
543 | ||
544 |
my $str = $r->to_string; |
|
545 | ||
546 |
Stringify the whole route. |
|
547 | ||
548 |
=head2 under |
|
549 | ||
550 |
my $bridge = $r->under(sub {...}); |
|
551 |
my $bridge = $r->under('/:foo'); |
|
552 | ||
553 |
Generate bridge route. See also the L<Mojolicious::Lite> tutorial for more |
|
554 |
argument variations. |
|
555 | ||
556 |
my $auth = $r->under('/user')->to('user#auth'); |
|
557 |
$auth->get('/show')->to('#show'); |
|
558 |
$auth->post('/create')->to('#create'); |
|
559 | ||
560 |
=head2 via |
|
561 | ||
562 |
my $methods = $r->via; |
|
563 |
$r = $r->via('GET'); |
|
564 |
$r = $r->via(qw(GET POST)); |
|
565 |
$r = $r->via([qw(GET POST)]); |
|
566 | ||
567 |
Restrict HTTP methods this route is allowed to handle, defaults to no |
|
568 |
restrictions. |
|
569 | ||
570 |
$r->route('/foo')->via(qw(GET POST))->to('foo#bar'); |
|
571 | ||
572 |
=head2 websocket |
|
573 | ||
574 |
my $ws = $r->websocket('/:foo' => sub {...}); |
|
575 | ||
576 |
Generate route matching only WebSocket handshakes. See also the |
|
577 |
L<Mojolicious::Lite> tutorial for more argument variations. |
|
578 | ||
579 |
$r->websocket('/echo')->to('example#echo'); |
|
580 | ||
581 |
=head1 SHORTCUTS |
|
582 | ||
583 |
In addition to the attributes and methods above you can also call shortcuts |
|
584 |
on L<Mojolicious::Routes::Route> objects. |
|
585 | ||
586 |
$r->root->add_shortcut(firefox => sub { |
|
587 |
my ($r, $path) = @_; |
|
588 |
$r->get($path, agent => qr/Firefox/); |
|
589 |
}); |
|
590 | ||
591 |
$r->firefox('/welcome')->to('firefox#welcome'); |
|
592 |
$r->firefox('/bye')->to('firefox#bye); |
|
593 | ||
594 |
=head1 SEE ALSO |
|
595 | ||
596 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
597 | ||
598 |
=cut |