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   427 use Mojo::Base -base;
  55         146  
  55         477  
3              
4 55     55   470 use Carp qw(croak);
  55         1328  
  55         5085  
5 55     55   437 use Mojo::DynamicMethods -dispatch;
  55         176  
  55         721  
6 55     55   432 use Mojo::Util;
  55         134  
  55         3347  
7 55     55   32247 use Mojolicious::Routes::Pattern;
  55         223  
  55         494  
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 51 my ($class, $method, $dyn_methods) = @_;
22              
23             return sub {
24 27     27   47 my $self = shift;
        27      
25 27         84 my $dynamic = $dyn_methods->{$self->root}{$method};
26 27 50       111 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         28 };
30             }
31              
32             sub add_child {
33 1009     1009 1 2254 my ($self, $route) = @_;
34 1009         1744 push @{$self->children}, $route->remove->parent($self);
  1009         2789  
35 1009         2567 $route->pattern->types($self->root->types);
36 1009         2802 return $self;
37             }
38              
39 551 100   551 1 3629 sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
40              
41 2     2 1 9 sub delete { shift->_generate_route(DELETE => @_) }
42              
43 14     14 1 1028 sub find { shift->_index->{shift()} }
44              
45 398     398 1 1645 sub get { shift->_generate_route(GET => @_) }
46              
47 1412     1412 1 2922 sub has_custom_name { !!shift->{custom} }
48              
49             sub has_websocket {
50 287     287 1 614 my $self = shift;
51 287 100       2657 return $self->{has_websocket} if exists $self->{has_websocket};
52 98         190 return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
  243         605  
  98         241  
53             }
54              
55 17671 100   17671 1 39102 sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
  16995         35267  
56              
57 220     220 1 1140 sub is_reserved { !!$RESERVED{$_[1]} }
58              
59 2597     2597 1 24326 sub is_websocket { !!shift->{websocket} }
60              
61             sub methods {
62 3842     3842 1 7834 my $self = shift;
63 3842 100       12623 return $self->{methods} unless @_;
64 996 100       1629 my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
  996         4340  
65 996 100       3011 $self->{methods} = $methods if @$methods;
66 996         1780 return $self;
67             }
68              
69             sub name {
70 1699     1699 1 2953 my $self = shift;
71 1699 100       7255 return $self->{name} unless @_;
72 66         275 @$self{qw(name custom)} = (shift, 1);
73 66         261 return $self;
74             }
75              
76 2     2 1 18 sub options { shift->_generate_route(OPTIONS => @_) }
77              
78             sub parse {
79 1008     1008 1 1646 my $self = shift;
80 1008   100     2553 $self->{name} = $self->pattern->parse(@_)->unparsed // '';
81 1008         6597 $self->{name} =~ s/\W+//g;
82 1008         4065 return $self;
83             }
84              
85 3     3 1 14 sub patch { shift->_generate_route(PATCH => @_) }
86 24     24 1 116 sub post { shift->_generate_route(POST => @_) }
87 10     10 1 42 sub put { shift->_generate_route(PUT => @_) }
88              
89             sub remove {
90 1011     1011 1 1805 my $self = shift;
91 1011 100       2681 return $self unless my $parent = $self->parent;
92 2         4 @{$parent->children} = grep { $_ ne $self } @{$parent->children};
  2         4  
  4         13  
  2         6  
93 2         5 return $self->parent(undef);
94             }
95              
96             sub render {
97 290     290 1 979 my ($self, $values) = @_;
98 290   100     615 my $path = join '', map { $_->pattern->render($values, !@{$_->children} && !$_->partial) } @{$self->_chain};
  667         1703  
  290         1022  
99 290   100     2032 return $path || '/';
100             }
101              
102 1058     1058 1 2695 sub root { shift->_chain->[0] }
103              
104             sub requires {
105 3474     3474 1 15150 my $self = shift;
106              
107             # Routes with conditions can't be cached
108 3474 100       12414 return $self->{requires} unless @_;
109 1014 100       2909 my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
110 1014 100       3764 return $self unless @$conditions;
111 21         65 $self->{requires} = $conditions;
112 21         72 $self->root->cache->max_keys(0);
113              
114 21         100 return $self;
115             }
116              
117             sub suggested_method {
118 48     48 1 111 my $self = shift;
119              
120 48         93 my %via;
121 48         114 for my $route (@{$self->_chain}) {
  48         197  
122 98 100 100     179 next unless my @via = @{$route->methods // []};
  98         341  
123 31 100       132 %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
  46         227  
  2         4  
124             }
125              
126 48 100 100     325 return 'POST' if $via{POST} && !$via{GET};
127 40 100 100     324 return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
128             }
129              
130             sub to {
131 1523     1523 1 2384 my $self = shift;
132              
133 1523         3352 my $pattern = $self->pattern;
134 1523 100       3127 return $pattern->defaults unless @_;
135 1521         7765 my ($shortcut, %defaults) = Mojo::Util::_options(@_);
136              
137 1521 100       3465 if ($shortcut) {
138              
139             # Application
140 379 100 100     3821 if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { $defaults{app} = $shortcut }
  5 50       19  
141              
142             # Controller and action
143             elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
144 374 100       1656 $defaults{controller} = $1 if defined $1;
145 374 100       1206 $defaults{action} = $2 if defined $2;
146             }
147             }
148              
149 1521         3483 @{$pattern->defaults}{keys %defaults} = values %defaults;
  1521         3440  
150              
151 1521         4501 return $self;
152             }
153              
154             sub to_string {
155 5   100 5 1 12 join '', map { $_->pattern->unparsed // '' } @{shift->_chain};
  12         25  
  5         15  
156             }
157              
158 17     17 1 66 sub under { shift->_generate_route(under => @_) }
159              
160             sub websocket {
161 36     36 1 133 my $route = shift->get(@_);
162 36         107 $route->{websocket} = 1;
163 36         114 return $route;
164             }
165              
166             sub _chain {
167 1499     1499   4419 my @chain = (my $parent = shift);
168 1499         3937 unshift @chain, $parent while $parent = $parent->parent;
169 1499         6084 return \@chain;
170             }
171              
172             sub _generate_route {
173 1007     1007   3152 my ($self, $methods, @args) = @_;
174              
175 1007         1815 my (@conditions, @constraints, %defaults, $name, $pattern);
176 1007         3207 while (defined(my $arg = shift @args)) {
177              
178             # First scalar is the pattern
179 1529 100 100     8314 if (!ref $arg && !$pattern) { $pattern = $arg }
  956 100 100     3113  
    100          
    100          
    100          
    50          
180              
181             # Scalar
182 16         71 elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
183              
184             # Last scalar is the route name
185 50         183 elsif (!ref $arg) { $name = $arg }
186              
187             # Callback
188 311         1307 elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
189              
190             # Constraints
191 76         275 elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
192              
193             # Defaults
194 120         674 elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
195             }
196              
197 1007         2923 my $route = $self->_route($pattern, @constraints)->requires(\@conditions)->to(\%defaults);
198 1007 100       4516 $methods eq 'under' ? $route->inline(1) : $route->methods($methods);
199              
200 1007 100       5375 return defined $name ? $route->name($name) : $route;
201             }
202              
203             sub _index {
204 30     30   71 my $self = shift;
205              
206 30         65 my (%auto, %custom);
207 30         61 my @children = (@{$self->children});
  30         136  
208 30         143 while (my $child = shift @children) {
209 1293 100 66     2106 if ($child->has_custom_name) { $custom{$child->name} ||= $child }
  89         190  
210 1204   66     1820 else { $auto{$child->name} ||= $child }
211 1293         1794 push @children, @{$child->children};
  1293         2178  
212             }
213              
214 30         1071 return {%auto, %custom};
215             }
216              
217             sub _route {
218 1007     1007   1763 my $self = shift;
219              
220 1007         4023 my $route = $self->add_child(__PACKAGE__->new->parse(@_))->children->[-1];
221 1007         2461 my $new_pattern = $route->pattern;
222 0         0 croak qq{Route pattern "@{[$new_pattern->unparsed]}" contains a reserved stash value}
223 1007 50       1555 if grep { $self->is_reserved($_) } @{$new_pattern->placeholders};
  103         573  
  1007         2531  
224              
225 1007         2741 my $old_pattern = $self->pattern;
226 1007         2367 my $constraints = $old_pattern->constraints;
227 1007 100 66     2655 $new_pattern->constraints->{format} //= $constraints->{format} if exists $constraints->{format};
228 1007         2439 my $defaults = $old_pattern->defaults;
229 1007 100 66     2572 $new_pattern->defaults->{format} //= $defaults->{format} if exists $defaults->{format};
230              
231 1007         3480 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