Skip to content

Commit

Permalink
deprecate placeholder quoting with "(placeholder)" in favor of "<plac…
Browse files Browse the repository at this point in the history
…eholder>"
  • Loading branch information
kraih committed Apr 9, 2018
1 parent 291f958 commit dd09c15
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 46 deletions.
4 changes: 3 additions & 1 deletion Changes
@@ -1,5 +1,7 @@

7.75 2018-04-08
7.75 2018-04-09
- Deprecated placeholder quoting with "(placeholder)" in favor of
"<placeholder>".

7.74 2018-04-06
- Improved unknown placeholder types to match nothing in
Expand Down
28 changes: 14 additions & 14 deletions lib/Mojolicious/Guides/Routing.pod
Expand Up @@ -103,20 +103,20 @@ expression C<([^/.]+)>.
/sebastian23/hello -> /:name/hello -> {name => 'sebastian23'}
/sebastian 23/hello -> /:name/hello -> {name => 'sebastian 23'}

All placeholders can be surrounded by parentheses to separate them from the
surrounding text.
All placeholders can be surrounded by C<E<lt>> and C<E<gt>> to separate them
from the surrounding text.

/hello -> /(:name)hello -> undef
/sebastian/23hello -> /(:name)hello -> undef
/sebastian.23hello -> /(:name)hello -> undef
/sebastianhello -> /(:name)hello -> {name => 'sebastian'}
/sebastian23hello -> /(:name)hello -> {name => 'sebastian23'}
/sebastian 23hello -> /(:name)hello -> {name => 'sebastian 23'}
/hello -> /<:name>hello -> undef
/sebastian/23hello -> /<:name>hello -> undef
/sebastian.23hello -> /<:name>hello -> undef
/sebastianhello -> /<:name>hello -> {name => 'sebastian'}
/sebastian23hello -> /<:name>hello -> {name => 'sebastian23'}
/sebastian 23hello -> /<:name>hello -> {name => 'sebastian 23'}

The colon prefix is optional for standard placeholders that are surrounded by
parentheses.
C<E<lt>> and C<E<gt>>.

/i♥mojolicious -> /(one)♥(two) -> {one => 'i', two => 'mojolicious'}
/i♥mojolicious -> /<one>♥<two> -> {one => 'i', two => 'mojolicious'}

=head2 Relaxed placeholders

Expand Down Expand Up @@ -538,24 +538,24 @@ them into placeholder types with L<Mojolicious::Routes/"add_type">.
# /fry -> undef
# /bender -> {controller => 'foo', action => 'bar', name => 'bender'}
# /leela -> {controller => 'foo', action => 'bar', name => 'leela'}
$r->get('/(name:futurama_name)')->to('foo#bar');
$r->get('/<name:futurama_name>')->to('foo#bar');

Placeholder types work just like restrictive placeholders, they are just
reusable with the C<(placeholder:type)> notation.
reusable with the C<E<lt>placeholder:typeE<gt>> notation.

# A type adjusting the regular expression
$r->add_type(upper => qr/[A-Z]+/);

# /user/ROOT -> {controller => 'users', action => 'show', name => 'ROOT'}
# /user/root -> undef
# /user/23 -> undef
$r->get('/user/(name:upper)')->to('users#show');
$r->get('/user/<name:upper>')->to('users#show');

Some types like C<num> are used so commonly that they are available by default.

# /article/12 -> {controller => 'article', action => 'show', id => 12}
# /article/test -> undef
$r->get('/article/(id:num)')->to('articles#show');
$r->get('/article/<id:num>')->to('articles#show');

For a full list of available placeholder types see also
L<Mojolicious::Routes/"TYPES">.
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojolicious/Guides/Tutorial.pod
Expand Up @@ -361,7 +361,7 @@ L<Mojolicious::Controller/"param">.

# /testsomething/foo
# /test123something/foo
get '/(:bar)something/foo' => sub {
get '/<:bar>something/foo' => sub {
my $c = shift;
my $bar = $c->param('bar');
$c->render(text => "Our :bar placeholder matched $bar");
Expand All @@ -370,7 +370,7 @@ L<Mojolicious::Controller/"param">.
app->start;

To separate them from the surrounding text, you can surround your placeholders
with parentheses, which also makes the colon prefix optional.
with C<E<lt>> and C<E<gt>>, which also makes the colon prefix optional.

=head2 Relaxed Placeholders

Expand Down
2 changes: 1 addition & 1 deletion lib/Mojolicious/Routes.pm
Expand Up @@ -229,7 +229,7 @@ These placeholder types are available by default.
=head2 num
$r->get('/article/(id:num)');
$r->get('/article/<id:num>');
Placeholder value needs to be a non-fractional number, similar to the regular
expression C<([0-9]+)>.
Expand Down
20 changes: 13 additions & 7 deletions lib/Mojolicious/Routes/Pattern.pm
Expand Up @@ -2,12 +2,13 @@ package Mojolicious::Routes::Pattern;
use Mojo::Base -base;

use Carp 'croak';
use Mojo::Util 'deprecated';

has [qw(constraints defaults types)] => sub { {} };
has [qw(placeholder_start type_start)] => ':';
has [qw(placeholders tree)] => sub { [] };
has quote_end => ')';
has quote_start => '(';
has quote_end => '>';
has quote_start => '<';
has [qw(regex unparsed)];
has relaxed_start => '#';
has wildcard_start => '*';
Expand Down Expand Up @@ -163,6 +164,11 @@ sub _compile_req {
sub _tokenize {
my ($self, $pattern) = @_;

# DEPRECATED!
deprecated 'Placeholder quoting with "(placeholder)" is DEPRECATED'
. ' in favor of "<placeholder>"'
if $pattern =~ tr/()/<>/;

my $quote_end = $self->quote_end;
my $quote_start = $self->quote_start;
my $start = $self->placeholder_start;
Expand Down Expand Up @@ -268,16 +274,16 @@ Placeholder names.
=head2 quote_end
my $end = $pattern->quote_end;
$pattern = $pattern->quote_end(']');
$pattern = $pattern->quote_end('}');
Character indicating the end of a quoted placeholder, defaults to C<)>.
Character indicating the end of a quoted placeholder, defaults to C<E<gt>>.
=head2 quote_start
my $start = $pattern->quote_start;
$pattern = $pattern->quote_start('[');
$pattern = $pattern->quote_start('{');
Character indicating the start of a quoted placeholder, defaults to C<(>.
Character indicating the start of a quoted placeholder, defaults to C<E<lt>>.
=head2 regex
Expand Down Expand Up @@ -318,7 +324,7 @@ Placeholder types.
=head2 unparsed
my $unparsed = $pattern->unparsed;
$pattern = $pattern->unparsed('/(foo)/(bar)');
$pattern = $pattern->unparsed('/:foo/:bar');
Raw unparsed pattern.
Expand Down
2 changes: 1 addition & 1 deletion t/mojolicious/dispatch.t
Expand Up @@ -111,7 +111,7 @@ my $d = $c->app->routes;
ok $d, 'initialized';
$d->namespaces(['Test']);
$d->route('/')->over([])->to(controller => 'foo', action => 'home');
$d->route('/foo/(capture)')->to(controller => 'foo', action => 'bar');
$d->route('/foo/<capture>')->to(controller => 'foo', action => 'bar');

# Cache
$c = $app->build_controller;
Expand Down
2 changes: 1 addition & 1 deletion t/mojolicious/lib/MojoliciousTest.pm
Expand Up @@ -156,7 +156,7 @@ sub startup {
$r->route('/rss.xml')->to('foo#bar', format => 'rss');

# /*/* (the default route)
$r->route('/(controller)/(action)')->to(action => 'index');
$r->route('/<controller>/<action>')->to(action => 'index');

# /just/some/template (embedded template)
$r->route('/just/some/template')->to(template => 'just/some/template');
Expand Down
6 changes: 3 additions & 3 deletions t/mojolicious/lite_app.t
Expand Up @@ -240,7 +240,7 @@ get '/foo_relaxed/#test' => sub {
$c->render(text => $c->stash('test') . ($c->req->headers->dnt ? 1 : 0));
};

get '/foo_wildcard/(*test)' => sub {
get '/foo_wildcard/<*test>' => sub {
my $c = shift;
$c->render(text => $c->stash('test'));
};
Expand Down Expand Up @@ -410,15 +410,15 @@ get '/default/:text' => (default => 23) => sub {
$c->render(text => "works $default $test");
};

get '/foo/(bar:num)/baz' => sub {
get '/foo/<bar:num>/baz' => sub {
my $c = shift;
$c->render(text => $c->param('bar'));
};

# Custom placeholder type
app->routes->add_type(my_num => qr/[5-9]+/);

get '/type/(test:my_num)' => {inline => '%= $test'};
get '/type/<test:my_num>' => {inline => '%= $test'};

# Redirect condition
app->routes->add_condition(
Expand Down
22 changes: 11 additions & 11 deletions t/mojolicious/pattern.t
Expand Up @@ -12,7 +12,7 @@ is $pattern->tree->[0][1], '/test/123', 'optimized pattern';

# Normal pattern with text, placeholders and a default value
$pattern
= Mojolicious::Routes::Pattern->new->parse('/test/(controller)/:action');
= Mojolicious::Routes::Pattern->new->parse('/test/<controller>/:action');
$pattern->defaults({action => 'index'});
is_deeply $pattern->match('/test/foo/bar', 1),
{controller => 'foo', action => 'bar'}, 'right structure';
Expand All @@ -24,7 +24,7 @@ ok !$pattern->match('/test/'), 'no result';
is $pattern->render({controller => 'foo'}), '/test/foo', 'right result';

# Optional placeholder in the middle
$pattern = Mojolicious::Routes::Pattern->new('/test(name)123');
$pattern = Mojolicious::Routes::Pattern->new('/test<name>123');
$pattern->defaults({name => 'foo'});
is_deeply $pattern->match('/test123', 1), {name => 'foo'}, 'right structure';
is_deeply $pattern->match('/testbar123', 1), {name => 'bar'}, 'right structure';
Expand Down Expand Up @@ -69,7 +69,7 @@ is $pattern->render, '', 'right result';
is $pattern->render({format => 'txt'}, 1), '.txt', 'right result';

# Regex in pattern
$pattern = Mojolicious::Routes::Pattern->new('/test/(controller)/:action/(id)',
$pattern = Mojolicious::Routes::Pattern->new('/test/<controller>/:action/<id>',
id => '\d+');
$pattern->defaults({action => 'index', id => 1});
is_deeply $pattern->match('/test/foo/bar/203'),
Expand All @@ -80,7 +80,7 @@ is $pattern->render({controller => 'zzz', action => 'index', id => 13}),
is $pattern->render({controller => 'zzz'}), '/test/zzz', 'right result';

# Quoted placeholders
$pattern = Mojolicious::Routes::Pattern->new('/(:controller)test/(action)');
$pattern = Mojolicious::Routes::Pattern->new('/<:controller>test/<action>');
$pattern->defaults({action => 'index'});
is_deeply $pattern->match('/footest/bar'),
{controller => 'foo', action => 'bar'}, 'right structure';
Expand All @@ -94,14 +94,14 @@ is_deeply $pattern->match('/test/foo.bar/baz'),
{controller => 'foo.bar', action => 'baz'}, 'right structure';
is $pattern->render({controller => 'foo.bar', action => 'baz'}),
'/test/foo.bar/baz', 'right result';
$pattern = Mojolicious::Routes::Pattern->new('/test/(#groovy)');
$pattern = Mojolicious::Routes::Pattern->new('/test/<#groovy>');
is_deeply $pattern->match('/test/foo.bar'), {groovy => 'foo.bar'},
'right structure';
is $pattern->defaults->{format}, undef, 'no value';
is $pattern->render({groovy => 'foo.bar'}), '/test/foo.bar', 'right result';

# Wildcard
$pattern = Mojolicious::Routes::Pattern->new('/test/(:controller)/(*action)');
$pattern = Mojolicious::Routes::Pattern->new('/test/<:controller>/<*action>');
is_deeply $pattern->match('/test/foo/bar.baz/yada'),
{controller => 'foo', action => 'bar.baz/yada'}, 'right structure';
is $pattern->render({controller => 'foo', action => 'bar.baz/yada'}),
Expand All @@ -123,7 +123,7 @@ is_deeply $pattern->match('/test(test)(\Qtest\E)('),
is $pattern->render({test => '23'}), '/23', 'right result';

# Regex in pattern
$pattern = Mojolicious::Routes::Pattern->new('/.+(:test)');
$pattern = Mojolicious::Routes::Pattern->new('/.+<:test>');
is_deeply $pattern->match('/.+test'), {test => 'test'}, 'right structure';
is $pattern->render({test => '23'}), '/.+23', 'right result';

Expand Down Expand Up @@ -223,11 +223,11 @@ $result = $pattern->match('/foo/bar', 1);
is_deeply $result, {'' => 'foo', '0' => 'bar'}, 'right structure';
is $pattern->render($result, 1), '/foo/bar', 'right result';
is $pattern->render({'' => 'bar', '0' => 'baz'}, 1), '/bar/baz', 'right result';
$pattern = Mojolicious::Routes::Pattern->new('/(:)test/(0)');
$pattern = Mojolicious::Routes::Pattern->new('/<:>test/<0>');
$result = $pattern->match('/footest/bar', 1);
is_deeply $result, {'' => 'foo', '0' => 'bar'}, 'right structure';
is $pattern->render($result, 1), '/footest/bar', 'right result';
$pattern = Mojolicious::Routes::Pattern->new('/()test');
$pattern = Mojolicious::Routes::Pattern->new('/<>test');
$result = $pattern->match('/footest', 1);
is_deeply $result, {'' => 'foo'}, 'right structure';
is $pattern->render($result, 1), '/footest', 'right result';
Expand Down Expand Up @@ -258,7 +258,7 @@ $result = $pattern->match('/', 1);
is_deeply $result, {format => 'txt'}, 'right structure';

# Unicode
$pattern = Mojolicious::Routes::Pattern->new('/(one)♥(two)');
$pattern = Mojolicious::Routes::Pattern->new('/<one>♥<two>');
$result = $pattern->match('/i♥mojolicious');
is_deeply $result, {one => 'i', two => 'mojolicious'}, 'right structure';
is $pattern->render($result, 1), '/i♥mojolicious', 'right result';
Expand All @@ -273,7 +273,7 @@ is $pattern->render($result, 1), '/second', 'right result';

# Placeholder types
$pattern = Mojolicious::Routes::Pattern->new->types({num => qr/\d+/})
->parse('/foo/(bar:num)/baz');
->parse('/foo/<bar:num>/baz');
$result = $pattern->match('/foo/23/baz', 1);
is_deeply $result, {'bar' => 23}, 'right structure';
is $pattern->render($result, 1), '/foo/23/baz', 'right result';
Expand Down
10 changes: 5 additions & 5 deletions t/mojolicious/routes.t
Expand Up @@ -54,7 +54,7 @@ $test->route('/edit')->to(action => 'edit')->name('test_edit');
$r->route('/:controller/testedit')->to(action => 'testedit');

# /*/test/delete/*
$test->route('/delete/(id)', id => qr/\d+/)->to(action => 'delete', id => 23);
$test->route('/delete/<id>', id => qr/\d+/)->to(action => 'delete', id => 23);

# /test2
my $test2 = $r->route('/test2/')->inline(1)->to(controller => 'test2');
Expand All @@ -75,14 +75,14 @@ $test2->route('/baz')->to('just#works');
$r->route('/')->to(controller => 'hello', action => 'world');

# /wildcards/1/*
$r->route('/wildcards/1/(*wildcard)', wildcard => qr/(?:.*)/)
$r->route('/wildcards/1/<*wildcard>', wildcard => qr/(?:.*)/)
->to(controller => 'wild', action => 'card');

# /wildcards/2/*
$r->route('/wildcards/2/*wildcard')->to(controller => 'card', action => 'wild');

# /wildcards/3/*/foo
$r->route('/wildcards/3/(*wildcard)/foo')
$r->route('/wildcards/3/<*wildcard>/foo')
->to(controller => 'very', action => 'dangerous');

# /wildcards/4/*/foo
Expand Down Expand Up @@ -122,7 +122,7 @@ $r->route('/format7', format => [qw(foo foobar)])->to('perl#rocks');
# /type/23
# /type/24
$r->add_type(my_num => [23, 24]);
$r->route('/type/(id:my_num)')->to('foo#bar');
$r->route('/type/<id:my_num>')->to('foo#bar');

# /articles/1/edit
# /articles/1/delete
Expand Down Expand Up @@ -969,7 +969,7 @@ is $m->path_for->{path}, '/custom_pattern/a_123_b/c.123/d/123', 'right path';

# Unknown placeholder type (matches nothing)
$r = Mojolicious::Routes->new;
$r->get('/(foo:does_not_exist)');
$r->get('/<foo:does_not_exist>');
$m = Mojolicious::Routes::Match->new(root => $r);
$m->find($c => {method => 'GET', path => '/'});
is_deeply $m->stack, [], 'empty stack';
Expand Down

0 comments on commit dd09c15

Please sign in to comment.