File Coverage

blib/lib/Mojolicious/Routes/Route.pm
Criterion Covered Total %
statement 146 149 97.9
branch 64 68 94.1
condition 33 37 89.1
subroutine 38 38 100.0
pod 26 27 96.3
total 307 319 96.2


line stmt bran cond sub pod time code
1             package Mojolicious::Routes::Route;
2 55     55   453 use Mojo::Base -base;
  55         163  
  55         513  
3              
4 55     55   450 use Carp qw(croak);
  55         152  
  55         4094  
5 55     55   453 use Mojo::DynamicMethods -dispatch;
  55         147  
  55         500  
6 55     55   459 use Mojo::Util;
  55         171  
  55         3081  
7 55     55   32986 use Mojolicious::Routes::Pattern;
  55         207  
  55         446  
8              
9             has [qw(inline partial)];
10             has 'children' => sub { [] };
11             has parent => undef, weak => 1;
12             has pattern => sub { Mojolicious::Routes::Pattern->new };
13              
14             # Reserved stash values
15             my %RESERVED = map { $_ => 1 } (
16             qw(action app cb controller data extends format handler inline json layout namespace path status template text),
17             qw(variant)
18             );
19              
20             sub BUILD_DYNAMIC {
21 3     3 0 12 my ($class, $method, $dyn_methods) = @_;
22              
23             return sub {
24 27     27   51 my $self = shift;
        27      
25 27         74 my $dynamic = $dyn_methods->{$self->root}{$method};
26 27 50       112 return $self->$dynamic(@_) if $dynamic;
27 0         0 my $package = ref($self);
28 0         0 croak qq{Can't locate object method "$method" via package "$package"};
29 3         26 };
30             }
31              
32             sub add_child {
33 1009     1009 1 3848 my ($self, $route) = @_;
34 1009         1780 push @{$self->children}, $route->remove->parent($self);
  1009         5669  
35 1009         2822 $route->pattern->types($self->root->types);
36 1009         3120 return $self;
37             }
38              
39 551 100   551 1 4772 sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
40              
41 2     2 1 6 sub delete { shift->_generate_route(DELETE => @_) }
42              
43 14     14 1 2778 sub find { shift->_index->{shift()} }
44              
45 398     398 1 1354 sub get { shift->_generate_route(GET => @_) }
46              
47 1412     1412 1 3374 sub has_custom_name { !!shift->{custom} }
48              
49             sub has_websocket {
50 287     287 1 511 my $self = shift;
51 287 100       2031 return $self->{has_websocket} if exists $self->{has_websocket};
52 98         162 return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
  243         465  
  98         244  
53             }
54              
55 17671 100   17671 1 35888 sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
  16995         33278  
56              
57 220     220 1 1159 sub is_reserved { !!$RESERVED{$_[1]} }
58              
59 2597     2597 1 9642 sub is_websocket { !!shift->{websocket} }
60              
61             sub methods {
62 3842     3842 1 7701 my $self = shift;
63 3842 100       12061 return $self->{methods} unless @_;
64 996 100       1536 my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
  996         4171  
65 996 100       3169 $self->{methods} = $methods if @$methods;
66 996         2187 return $self;
67             }
68              
69             sub name {
70 1699     1699 1 3130 my $self = shift;
71 1699 100       7869 return $self->{name} unless @_;
72 66         242 @$self{qw(name custom)} = (shift, 1);
73 66         265 return $self;
74             }
75              
76 2     2 1 8 sub options { shift->_generate_route(OPTIONS => @_) }
77              
78             sub parse {
79 1008     1008 1 1667 my $self = shift;
80 1008   100     2681 $self->{name} = $self->pattern->parse(@_)->unparsed // '';
81 1008         6169 $self->{name} =~ s/\W+//g;
82 1008         3390 return $self;
83             }
84              
85 3     3 1 10 sub patch { shift->_generate_route(PATCH => @_) }
86 24     24 1 213 sub post { shift->_generate_route(POST => @_) }
87 10     10 1 38 sub put { shift->_generate_route(PUT => @_) }
88              
89             sub remove {
90 1011     1011 1 1751 my $self = shift;
91 1011 100       2748 return $self unless my $parent = $self->parent;
92 2         5 @{$parent->children} = grep { $_ ne $self } @{$parent->children};
  2         7  
  4         16  
  2         7  
93 2         6 return $self->parent(undef);
94             }
95              
96             sub render {
97 290     290 1 698 my ($self, $values) = @_;
98 290   100     618 my $path = join '', map { $_->pattern->render($values, !@{$_->children} && !$_->partial) } @{$self->_chain};
  667         1445  
  290         792  
99 290   100     1728 return $path || '/';
100             }
101              
102 1058     1058 1 3810 sub root { shift->_chain->[0] }
103              
104             sub requires {
105 3474     3474 1 12683 my $self = shift;
106              
107             # Routes with conditions can't be cached
108 3474 100       11311 return $self->{requires} unless @_;
109 1014 100       18836 my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
110 1014 100       3534 return $self unless @$conditions;
111 21         56 $self->{requires} = $conditions;
112 21         63 $self->root->cache->max_keys(0);
113              
114 21         74 return $self;
115             }
116              
117             sub suggested_method {
118 48     48 1 105 my $self = shift;
119              
120 48         132 my %via;
121 48         94 for my $route (@{$self->_chain}) {
  48         180  
122 98 100 100     176 next unless my @via = @{$route->methods // []};
  98         281  
123 31 100       122 %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
  46         212  
  2         6  
124             }
125              
126 48 100 100     305 return 'POST' if $via{POST} && !$via{GET};
127 40 100 100     349 return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
128             }
129              
130             sub to {
131 1523     1523 1 2458 my $self = shift;
132              
133 1523         3243 my $pattern = $self->pattern;
134 1523 100       3352 return $pattern->defaults unless @_;
135 1521         4677 my ($shortcut, %defaults) = Mojo::Util::_options(@_);
136              
137 1521 100       4002 if ($shortcut) {
138              
139             # Application
140 379 100 100     3883 if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { $defaults{app} = $shortcut }
  5 50       11  
141              
142             # Controller and action
143             elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
144 374 100       1824 $defaults{controller} = $1 if defined $1;
145 374 100       1496 $defaults{action} = $2 if defined $2;
146             }
147             }
148              
149 1521         3441 @{$pattern->defaults}{keys %defaults} = values %defaults;
  1521         3938  
150              
151 1521         4644 return $self;
152             }
153              
154             sub to_string {
155 5   100 5 1 14 join '', map { $_->pattern->unparsed // '' } @{shift->_chain};
  12         30  
  5         15  
156             }
157              
158 17     17 1 60 sub under { shift->_generate_route(under => @_) }
159              
160             sub websocket {
161 36     36 1 147 my $route = shift->get(@_);
162 36         98 $route->{websocket} = 1;
163 36         107 return $route;
164             }
165              
166             sub _chain {
167 1499     1499   4227 my @chain = (my $parent = shift);
168 1499         4420 unshift @chain, $parent while $parent = $parent->parent;
169 1499         5892 return \@chain;
170             }
171              
172             sub _generate_route {
173 1007     1007   4965 my ($self, $methods, @args) = @_;
174              
175 1007         1886 my (@conditions, @constraints, %defaults, $name, $pattern);
176 1007         2970 while (defined(my $arg = shift @args)) {
177              
178             # First scalar is the pattern
179 1529 100 100     7293 if (!ref $arg && !$pattern) { $pattern = $arg }
  956 100 100     2773  
    100          
    100          
    100          
    50          
180              
181             # Scalar
182 16         60 elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
183              
184             # Last scalar is the route name
185 50         151 elsif (!ref $arg) { $name = $arg }
186              
187             # Callback
188 311         1075 elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
189              
190             # Constraints
191 76         395 elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
192              
193             # Defaults
194 120         687 elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
195             }
196              
197 1007         2963 my $route = $self->_route($pattern, @constraints)->requires(\@conditions)->to(\%defaults);
198 1007 100       4212 $methods eq 'under' ? $route->inline(1) : $route->methods($methods);
199              
200 1007 100       17253 return defined $name ? $route->name($name) : $route;
201             }
202              
203             sub _index {
204 30     30   68 my $self = shift;
205              
206 30         67 my (%auto, %custom);
207 30         55 my @children = (@{$self->children});
  30         133  
208 30         153 while (my $child = shift @children) {
209 1293 100 66     2483 if ($child->has_custom_name) { $custom{$child->name} ||= $child }
  89         217  
210 1204   66     2327 else { $auto{$child->name} ||= $child }
211 1293         3145 push @children, @{$child->children};
  1293         2551  
212             }
213              
214 30         1108 return {%auto, %custom};
215             }
216              
217             sub _route {
218 1007     1007   1775 my $self = shift;
219              
220 1007         3850 my $route = $self->add_child(__PACKAGE__->new->parse(@_))->children->[-1];
221 1007         2520 my $new_pattern = $route->pattern;
222 0         0 croak qq{Route pattern "@{[$new_pattern->unparsed]}" contains a reserved stash value}
223 1007 50       1663 if grep { $self->is_reserved($_) } @{$new_pattern->placeholders};
  103         413  
  1007         2291  
224              
225 1007         2730 my $old_pattern = $self->pattern;
226 1007         2471 my $constraints = $old_pattern->constraints;
227 1007 100 66     2557 $new_pattern->constraints->{format} //= $constraints->{format} if exists $constraints->{format};
228 1007         2428 my $defaults = $old_pattern->defaults;
229 1007 100 66     2380 $new_pattern->defaults->{format} //= $defaults->{format} if exists $defaults->{format};
230              
231 1007         3264 return $route;
232             }
233              
234             1;
235              
236             =encoding utf8
237              
238             =head1 NAME
239              
240             Mojolicious::Routes::Route - Route
241              
242             =head1 SYNOPSIS
243              
244             use Mojolicious::Routes::Route;
245              
246             my $r = Mojolicious::Routes::Route->new;
247              
248             =head1 DESCRIPTION
249              
250             L is the route container used by L.
251              
252             =head1 ATTRIBUTES
253              
254             L implements the following attributes.
255              
256             =head2 children
257              
258             my $children = $r->children;
259             $r = $r->children([Mojolicious::Routes::Route->new]);
260              
261             The children of this route, used for nesting routes.
262              
263             =head2 inline
264              
265             my $bool = $r->inline;
266             $r = $r->inline($bool);
267              
268             Allow L semantics for this route.
269              
270             =head2 parent
271              
272             my $parent = $r->parent;
273             $r = $r->parent(Mojolicious::Routes::Route->new);
274              
275             The parent of this route, usually a L object. Note that this attribute is weakened.
276              
277             =head2 partial
278              
279             my $bool = $r->partial;
280             $r = $r->partial($bool);
281              
282             Route has no specific end, remaining characters will be captured in C.
283              
284             =head2 pattern
285              
286             my $pattern = $r->pattern;
287             $r = $r->pattern(Mojolicious::Routes::Pattern->new);
288              
289             Pattern for this route, defaults to a L object.
290              
291             =head1 METHODS
292              
293             L inherits all methods from L and implements the following new ones.
294              
295             =head2 add_child
296              
297             $r = $r->add_child(Mojolicious::Routes::Route->new);
298              
299             Add a child to this route, it will be automatically removed from its current parent if necessary.
300              
301             # Reattach route
302             $r->add_child($r->find('foo'));
303              
304             =head2 any
305              
306             my $route = $r->any;
307             my $route = $r->any('/:foo');
308             my $route = $r->any('/:foo' => sub ($c) {...});
309             my $route = $r->any('/:foo' => sub ($c) {...} => 'name');
310             my $route = $r->any('/:foo' => {foo => 'bar'} => sub ($c) {...});
311             my $route = $r->any('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
312             my $route = $r->any('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
313             my $route = $r->any(['GET', 'POST'] => '/:foo' => sub ($c) {...});
314             my $route = $r->any(['GET', 'POST'] => '/:foo' => [foo => qr/\w+/]);
315              
316             Generate L object matching any of the listed HTTP request methods or all.
317              
318             # Route with pattern and destination
319             $r->any('/user')->to('user#whatever');
320              
321             All arguments are optional, but some have to appear in a certain order, like the two supported array reference values,
322             which contain the HTTP methods to match and restrictive placeholders.
323              
324             # Route with HTTP methods, pattern, restrictive placeholders and destination
325             $r->any(['DELETE', 'PUT'] => '/:foo' => [foo => qr/\w+/])->to('foo#bar');
326              
327             There are also two supported string values, containing the route pattern and the route name, defaulting to the pattern
328             C and a name based on the pattern.
329              
330             # Route with pattern, name and destination
331             $r->any('/:foo' => 'foo_route')->to('foo#bar');
332              
333             An arbitrary number of key/value pairs in between the route pattern and name can be used to specify route conditions.
334              
335             # Route with pattern, condition and destination
336             $r->any('/' => (agent => qr/Firefox/))->to('foo#bar');
337              
338             A hash reference is used to specify optional placeholders and default values for the stash.
339              
340             # Route with pattern, optional placeholder and destination
341             $r->any('/:foo' => {foo => 'bar'})->to('foo#bar');
342              
343             And a code reference can be used to specify a C value to be merged into the default values for the stash.
344              
345             # Route with pattern and a closure as destination
346             $r->any('/:foo' => sub ($c) {
347             $c->render(text => 'Hello World!');
348             });
349              
350             See L and L for more information.
351              
352             =head2 delete
353              
354             my $route = $r->delete;
355             my $route = $r->delete('/:foo');
356             my $route = $r->delete('/:foo' => sub ($c) {...});
357             my $route = $r->delete('/:foo' => sub ($c) {...} => 'name');
358             my $route = $r->delete('/:foo' => {foo => 'bar'} => sub ($c) {...});
359             my $route = $r->delete('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
360             my $route = $r->delete('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
361              
362             Generate L object matching only C requests, takes the same arguments as L
363             (except for the HTTP methods to match, which are implied). See L and
364             L for more information.
365              
366             # Route with destination
367             $r->delete('/user')->to('user#remove');
368              
369             =head2 find
370              
371             my $route = $r->find('foo');
372              
373             Find child route by name, custom names have precedence over automatically generated ones.
374              
375             # Change default parameters of a named route
376             $r->find('show_user')->to(foo => 'bar');
377              
378             =head2 get
379              
380             my $route = $r->get;
381             my $route = $r->get('/:foo');
382             my $route = $r->get('/:foo' => sub ($c) {...});
383             my $route = $r->get('/:foo' => sub ($c) {...} => 'name');
384             my $route = $r->get('/:foo' => {foo => 'bar'} => sub ($c) {...});
385             my $route = $r->get('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
386             my $route = $r->get('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
387              
388             Generate L object matching only C requests, takes the same arguments as L
389             (except for the HTTP methods to match, which are implied). See L and
390             L for more information.
391              
392             # Route with destination
393             $r->get('/user')->to('user#show');
394              
395             =head2 has_custom_name
396              
397             my $bool = $r->has_custom_name;
398              
399             Check if this route has a custom name.
400              
401             =head2 has_websocket
402              
403             my $bool = $r->has_websocket;
404              
405             Check if this route has a WebSocket ancestor and cache the result for future checks.
406              
407             =head2 is_endpoint
408              
409             my $bool = $r->is_endpoint;
410              
411             Check if this route qualifies as an endpoint.
412              
413             =head2 is_reserved
414              
415             my $bool = $r->is_reserved('controller');
416              
417             Check if string is a reserved stash value.
418              
419             =head2 is_websocket
420              
421             my $bool = $r->is_websocket;
422              
423             Check if this route is a WebSocket.
424              
425             =head2 methods
426              
427             my $methods = $r->methods;
428             $r = $r->methods('GET');
429             $r = $r->methods('GET', 'POST');
430             $r = $r->methods(['GET', 'POST']);
431              
432             Restrict HTTP methods this route is allowed to handle, defaults to no restrictions.
433              
434             # Route with two methods and destination
435             $r->any('/foo')->methods('GET', 'POST')->to('foo#bar');
436              
437             =head2 name
438              
439             my $name = $r->name;
440             $r = $r->name('foo');
441              
442             The name of this route, defaults to an automatically generated name based on the route pattern. Note that the name
443             C is reserved for referring to the current route.
444              
445             # Route with destination and custom name
446             $r->get('/user')->to('user#show')->name('show_user');
447              
448             =head2 options
449              
450             my $route = $r->options;
451             my $route = $r->options('/:foo');
452             my $route = $r->options('/:foo' => sub ($c) {...});
453             my $route = $r->options('/:foo' => sub ($c) {...} => 'name');
454             my $route = $r->options('/:foo' => {foo => 'bar'} => sub ($c) {...});
455             my $route = $r->options('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
456             my $route = $r->options('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
457              
458             Generate L object matching only C requests, takes the same arguments as L
459             (except for the HTTP methods to match, which are implied). See L and
460             L for more information.
461              
462             # Route with destination
463             $r->options('/user')->to('user#overview');
464              
465             =head2 parse
466              
467             $r = $r->parse('/user/:id');
468             $r = $r->parse('/user/:id', id => qr/\d+/);
469             $r = $r->parse(format => ['json', 'yaml']);
470              
471             Parse pattern.
472              
473             =head2 patch
474              
475             my $route = $r->patch;
476             my $route = $r->patch('/:foo');
477             my $route = $r->patch('/:foo' => sub ($c) {...});
478             my $route = $r->patch('/:foo' => sub ($c) {...} => 'name');
479             my $route = $r->patch('/:foo' => {foo => 'bar'} => sub ($c) {...});
480             my $route = $r->patch('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
481             my $route = $r->patch('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
482              
483             Generate L object matching only C requests, takes the same arguments as L
484             (except for the HTTP methods to match, which are implied). See L and
485             L for more information.
486              
487             # Route with destination
488             $r->patch('/user')->to('user#update');
489              
490             =head2 post
491              
492             my $route = $r->post;
493             my $route = $r->post('/:foo');
494             my $route = $r->post('/:foo' => sub ($c) {...});
495             my $route = $r->post('/:foo' => sub ($c) {...} => 'name');
496             my $route = $r->post('/:foo' => {foo => 'bar'} => sub ($c) {...});
497             my $route = $r->post('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
498             my $route = $r->post('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
499              
500             Generate L object matching only C requests, takes the same arguments as L
501             (except for the HTTP methods to match, which are implied). See L and
502             L for more information.
503              
504             # Route with destination
505             $r->post('/user')->to('user#create');
506              
507             =head2 put
508              
509             my $route = $r->put;
510             my $route = $r->put('/:foo');
511             my $route = $r->put('/:foo' => sub ($c) {...});
512             my $route = $r->put('/:foo' => sub ($c) {...} => 'name');
513             my $route = $r->put('/:foo' => {foo => 'bar'} => sub ($c) {...});
514             my $route = $r->put('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
515             my $route = $r->put('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
516              
517             Generate L object matching only C requests, takes the same arguments as L
518             (except for the HTTP methods to match, which are implied). See L and
519             L for more information.
520              
521             # Route with destination
522             $r->put('/user')->to('user#replace');
523              
524             =head2 remove
525              
526             $r = $r->remove;
527              
528             Remove route from parent.
529              
530             # Remove route completely
531             $r->find('foo')->remove;
532              
533             # Reattach route to new parent
534             $r->any('/foo')->add_child($r->find('bar')->remove);
535              
536             =head2 render
537              
538             my $path = $r->render({foo => 'bar'});
539              
540             Render route with parameters into a path.
541              
542             =head2 root
543              
544             my $root = $r->root;
545              
546             The L object this route is a descendant of.
547              
548             =head2 requires
549              
550             my $requires = $r->requires;
551             $r = $r->requires(foo => 1);
552             $r = $r->requires(foo => 1, bar => {baz => 'yada'});
553             $r = $r->requires([foo => 1, bar => {baz => 'yada'}]);
554              
555             Activate conditions for this route. Note that this automatically disables the routing cache, since conditions are too
556             complex for caching.
557              
558             # Route with condition and destination
559             $r->get('/foo')->requires(host => qr/mojolicious\.org/)->to('foo#bar');
560              
561             =head2 suggested_method
562              
563             my $method = $r->suggested_method;
564              
565             Suggested HTTP method for reaching this route, C and C are preferred.
566              
567             =head2 to
568              
569             my $defaults = $r->to;
570             $r = $r->to(action => 'foo');
571             $r = $r->to({action => 'foo'});
572             $r = $r->to('controller#action');
573             $r = $r->to('controller#action', foo => 'bar');
574             $r = $r->to('controller#action', {foo => 'bar'});
575             $r = $r->to(Mojolicious->new);
576             $r = $r->to(Mojolicious->new, foo => 'bar');
577             $r = $r->to(Mojolicious->new, {foo => 'bar'});
578             $r = $r->to('MyApp');
579             $r = $r->to('MyApp', foo => 'bar');
580             $r = $r->to('MyApp', {foo => 'bar'});
581              
582             Set default parameters for this route.
583              
584             =head2 to_string
585              
586             my $str = $r->to_string;
587              
588             Stringify the whole route.
589              
590             =head2 under
591              
592             my $route = $r->under(sub ($c) {...});
593             my $route = $r->under('/:foo' => sub ($c) {...});
594             my $route = $r->under('/:foo' => {foo => 'bar'});
595             my $route = $r->under('/:foo' => [foo => qr/\w+/]);
596             my $route = $r->under('/:foo' => (agent => qr/Firefox/));
597             my $route = $r->under([format => ['json', 'yaml']]);
598              
599             Generate L object for a nested route with its own intermediate destination, takes the same
600             arguments as L (except for the HTTP methods to match, which are not available). See
601             L and L for more information.
602              
603             # Longer version
604             $r->any('/:foo' => sub ($c) {...})->inline(1);
605              
606             # Intermediate destination and prefix shared between two routes
607             my $auth = $r->under('/user')->to('user#auth');
608             $auth->get('/show')->to('#show');
609             $auth->post('/create')->to('#create');
610              
611             =head2 websocket
612              
613             my $route = $r->websocket;
614             my $route = $r->websocket('/:foo');
615             my $route = $r->websocket('/:foo' => sub ($c) {...});
616             my $route = $r->websocket('/:foo' => sub ($c) {...} => 'name');
617             my $route = $r->websocket('/:foo' => {foo => 'bar'} => sub ($c) {...});
618             my $route = $r->websocket('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
619             my $route = $r->websocket('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
620              
621             Generate L object matching only WebSocket handshakes, takes the same arguments as L
622             (except for the HTTP methods to match, which are implied). See L and
623             L for more information.
624              
625             # Route with destination
626             $r->websocket('/echo')->to('example#echo');
627              
628             =head1 SHORTCUTS
629              
630             In addition to the L and L above you can also call shortcuts provided by L on
631             L objects.
632              
633             # Add a "firefox" shortcut
634             $r->root->add_shortcut(firefox => sub ($r, $path) {
635             $r->get($path, agent => qr/Firefox/);
636             });
637              
638             # Use "firefox" shortcut to generate routes
639             $r->firefox('/welcome')->to('firefox#welcome');
640             $r->firefox('/bye')->to('firefox#bye');
641              
642             =head1 SEE ALSO
643              
644             L, L, L.
645              
646             =cut