Skip to content

Commit

Permalink
updated Mojolicious::Routes::Pattern with new terms
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Jun 25, 2012
1 parent 7a33fad commit a074d4a
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 89 deletions.
3 changes: 3 additions & 0 deletions Changes
Expand Up @@ -10,6 +10,9 @@
Mojo::Template.
- Renamed Mojo::CookieJar to Mojo::UserAgent::CookieJar.
- Renamed Mojo::Command to Mojolicious::Command.
- Renamed format, reqs, symbol_start and symbols attributes in
Mojolicious::Routes::Pattern to format_regex, constraints,
placeholder_start and placeholders.
- Merged get_all_data and get_data methods from Mojo::Command into data
method in Mojo::Loader.
- Moved class_to_file and class_to_path methods from Mojo::Command to
Expand Down
5 changes: 3 additions & 2 deletions lib/Mojolicious/Command/routes.pm
Expand Up @@ -72,8 +72,9 @@ sub _draw {
my $pattern = $node->[1]->pattern;
$pattern->match('/', $node->[1]->is_endpoint);
my $regex = (regexp_pattern $pattern->regex)[0];
my $format = (regexp_pattern $pattern->format || '')[0];
my $optional = !$pattern->reqs->{format} || $pattern->defaults->{format};
my $format = (regexp_pattern $pattern->format_regex || '')[0];
my $optional = !$pattern->constraints->{format}
|| $pattern->defaults->{format};
$format .= '?' if $format && $optional;
push @parts, $format ? "$regex$format" : $regex if $verbose;

Expand Down
2 changes: 1 addition & 1 deletion lib/Mojolicious/Routes/Match.pm
Expand Up @@ -140,7 +140,7 @@ sub path_for {
= defined $captures->{format}
? $captures->{format}
: $pattern->defaults->{format}
if $pattern->reqs->{format};
if $pattern->constraints->{format};

# Render
my $path = $endpoint->render('', \%values);
Expand Down
140 changes: 70 additions & 70 deletions lib/Mojolicious/Routes/Pattern.pm
@@ -1,13 +1,13 @@
package Mojolicious::Routes::Pattern;
use Mojo::Base -base;

has [qw(defaults reqs)] => sub { {} };
has [qw(format pattern regex)];
has quote_end => ')';
has quote_start => '(';
has relaxed_start => '#';
has symbol_start => ':';
has [qw(symbols tree)] => sub { [] };
has [qw(constraints defaults)] => sub { {} };
has [qw(format_regex pattern regex)];
has placeholder_start => ':';
has [qw(placeholders tree)] => sub { [] };
has quote_end => ')';
has quote_start => '(';
has relaxed_start => '#';
has wildcard_start => '*';

# "This is the worst kind of discrimination. The kind against me!"
Expand All @@ -26,8 +26,8 @@ sub parse {
my $pattern = @_ % 2 ? (shift || '/') : '/';
$pattern = "/$pattern" unless $pattern =~ m!^/!;

# Requirements
$self->reqs({@_});
# Constraints
$self->constraints({@_});

# Tokenize
return $pattern eq '/' ? $self : $self->pattern($pattern)->_tokenize;
Expand Down Expand Up @@ -56,8 +56,8 @@ sub render {
$optional = 0;
}

# Relaxed, symbol or wildcard
elsif ($op ~~ [qw(relaxed symbol wildcard)]) {
# Placeholder, relaxed or wildcard
elsif ($op ~~ [qw(placeholder relaxed wildcard)]) {
my $name = $token->[1];
$rendered = $values->{$name} // '';
my $default = $self->defaults->{$name};
Expand All @@ -78,25 +78,26 @@ sub shape_match {

# Compile on demand
my $regex = $self->regex || $self->_compile;
my $format = $detect ? ($self->format || $self->_compile_format) : undef;
my $format
= $detect ? ($self->format_regex || $self->_compile_format) : undef;

# Match
return unless my @captures = $$pathref =~ $regex;
$$pathref =~ s/($regex)//;

# Merge captures
my $result = {%{$self->defaults}};
for my $symbol (@{$self->symbols}) {
for my $placeholder (@{$self->placeholders}) {
last unless @captures;
my $capture = shift @captures;
$result->{$symbol} = $capture if defined $capture;
$result->{$placeholder} = $capture if defined $capture;
}

# Format
my $req = $self->reqs->{format};
return $result if !$detect || defined $req && !$req;
my $constraint = $self->constraints->{format};
return $result if !$detect || defined $constraint && !$constraint;
if ($$pathref =~ s!^/?$format!!) { $result->{format} = $1 }
elsif ($req) { return unless $result->{format} }
elsif ($constraint) { return unless $result->{format} }

return $result;
}
Expand All @@ -106,9 +107,9 @@ sub _compile {

# Compile tree to regex
my $block = my $regex = '';
my $reqs = $self->reqs;
my $optional = 1;
my $defaults = $self->defaults;
my $constraints = $self->constraints;
my $optional = 1;
my $defaults = $self->defaults;
for my $token (reverse @{$self->tree}) {
my $op = $token->[0];
my $compiled = '';
Expand All @@ -129,23 +130,23 @@ sub _compile {
$optional = 0;
}

# Symbol
elsif ($op ~~ [qw(relaxed symbol wildcard)]) {
# Placeholder
elsif ($op ~~ [qw(placeholder relaxed wildcard)]) {
my $name = $token->[1];
unshift @{$self->symbols}, $name;
unshift @{$self->placeholders}, $name;

# Relaxed
if ($op eq 'relaxed') { $compiled = '([^\/]+)' }
# Placeholder
if ($op eq 'placeholder') { $compiled = '([^\/\.]+)' }

# Symbol
elsif ($op eq 'symbol') { $compiled = '([^\/\.]+)' }
# Relaxed
elsif ($op eq 'relaxed') { $compiled = '([^\/]+)' }

# Wildcard
elsif ($op eq 'wildcard') { $compiled = '(.+)' }

# Custom regex
my $req = $reqs->{$name};
$compiled = _compile_req($req) if $req;
my $constraint = $constraints->{$name};
$compiled = _compile_req($constraint) if $constraint;

# Optional placeholder
$optional = 0 unless exists $defaults->{$name};
Expand All @@ -167,14 +168,13 @@ sub _compile_format {
my $self = shift;

# Default regex
my $reqs = $self->reqs;
return $self->format(qr!\.([^/]+)$!)->format
if !exists $reqs->{format} && $reqs->{format};
my $c = $self->constraints;
return $self->format_regex(qr!\.([^/]+)$!)->format_regex
if !exists $c->{format} && $c->{format};

# Compile custom regex
my $regex
= defined $reqs->{format} ? _compile_req($reqs->{format}) : '([^/]+)';
return $self->format(qr!\.$regex$!)->format;
my $regex = defined $c->{format} ? _compile_req($c->{format}) : '([^/]+)';
return $self->format_regex(qr!\.$regex$!)->format_regex;
}

# "Interesting... Oh no wait, the other thing, tedious."
Expand All @@ -190,8 +190,8 @@ sub _tokenize {
# Token
my $quote_end = $self->quote_end;
my $quote_start = $self->quote_start;
my $placeholder = $self->placeholder_start;
my $relaxed = $self->relaxed_start;
my $symbol = $self->symbol_start;
my $wildcard = $self->wildcard_start;

# Parse the pattern character wise
Expand All @@ -201,24 +201,24 @@ sub _tokenize {
while (length(my $char = substr $pattern, 0, 1, '')) {

# Inside a placeholder
my $placeholder = $state ~~ [qw(relaxed symbol wildcard)];
my $inside = $state ~~ [qw(placeholder relaxed wildcard)];

# Quote start
if ($char eq $quote_start) {
$quoted = 1;
$state = 'symbol';
push @tree, ['symbol', ''];
$state = 'placeholder';
push @tree, ['placeholder', ''];
}

# Symbol start
elsif ($char eq $symbol) {
push @tree, ['symbol', ''] if $state ne 'symbol';
$state = 'symbol';
# Placeholder start
elsif ($char eq $placeholder) {
push @tree, ['placeholder', ''] if $state ne 'placeholder';
$state = 'placeholder';
}

# Relaxed or wildcard start (upgrade when quoted)
elsif ($char ~~ [$relaxed, $wildcard]) {
push @tree, ['symbol', ''] unless $quoted;
push @tree, ['placeholder', ''] unless $quoted;
$tree[-1]->[0] = $state = $char eq $relaxed ? 'relaxed' : 'wildcard';
}

Expand All @@ -234,8 +234,8 @@ sub _tokenize {
$state = 'text';
}

# Relaxed, symbol or wildcard
elsif ($placeholder && $char =~ /\w/) { $tree[-1]->[-1] .= $char }
# Placeholder, relaxed or wildcard
elsif ($inside && $char =~ /\w/) { $tree[-1]->[-1] .= $char }

# Text
else {
Expand Down Expand Up @@ -277,17 +277,24 @@ L<Mojolicious::Routes::Pattern> is the core of L<Mojolicious::Routes>.
L<Mojolicious::Routes::Pattern> implements the following attributes.
=head2 C<constraints>
my $constraints = $pattern->constraints;
$pattern = $pattern->constraints({foo => qr/\w+/});
Regex constraints.
=head2 C<defaults>
my $defaults = $pattern->defaults;
$pattern = $pattern->defaults({foo => 'bar'});
Default parameters.
=head2 C<format>
=head2 C<format_regex>
my $regex = $pattern->format;
$pattern = $pattern->format($regex);
my $regex = $pattern->format_regex;
$pattern = $pattern->format_regex($regex);
Compiled regex for format matching.
Expand All @@ -298,6 +305,20 @@ Compiled regex for format matching.
Raw unparsed pattern.
=head2 C<placeholder_start>
my $placeholder = $pattern->placeholder_start;
$pattern = $pattern->placeholder_start(':');
Character indicating a placeholder, defaults to C<:>.
=head2 C<placeholders>
my $placeholders = $pattern->placeholders;
$pattern = $pattern->placeholders(['foo', 'bar']);
Placeholder names.
=head2 C<quote_end>
my $quote = $pattern->quote_end;
Expand Down Expand Up @@ -326,27 +347,6 @@ Pattern in compiled regex form.
Character indicating a relaxed placeholder, defaults to C<#>.
=head2 C<reqs>
my $reqs = $pattern->reqs;
$pattern = $pattern->reqs({foo => qr/\w+/});
Regex constraints.
=head2 C<symbol_start>
my $symbol = $pattern->symbol_start;
$pattern = $pattern->symbol_start(':');
Character indicating a placeholder, defaults to C<:>.
=head2 C<symbols>
my $symbols = $pattern->symbols;
$pattern = $pattern->symbols(['foo', 'bar']);
Placeholder names.
=head2 C<tree>
my $tree = $pattern->tree;
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojolicious/Routes/Route.pm
Expand Up @@ -160,8 +160,8 @@ sub root {
sub route {
my $self = shift;
my $route = $self->add_child($self->new(@_))->children->[-1];
my $format = $self->pattern->reqs->{format};
$route->pattern->reqs->{format} //= 0 if defined $format && !$format;
my $format = $self->pattern->constraints->{format};
$route->pattern->constraints->{format} //= 0 if defined $format && !$format;
return $route;
}

Expand Down
26 changes: 13 additions & 13 deletions t/mojolicious/pattern.t
Expand Up @@ -6,7 +6,7 @@ use Test::More tests => 90;
use Mojo::ByteStream 'b';
use Mojolicious::Routes::Pattern;

# Normal pattern with text, symbols and a default value
# Normal pattern with text, placeholders and a default value
my $pattern = Mojolicious::Routes::Pattern->new('/test/(controller)/:action');
$pattern->defaults({action => 'index'});
my $result = $pattern->match('/test/foo/bar', 1);
Expand Down Expand Up @@ -45,7 +45,7 @@ is $pattern->render({controller => 'zzz', action => 'index', id => 13}),
'/test/zzz/index/13', 'right result';
is $pattern->render({controller => 'zzz'}), '/test/zzz', 'right result';

# Quoted symbol
# Quoted placeholders
$pattern = Mojolicious::Routes::Pattern->new('/(:controller)test/(action)');
$pattern->defaults({action => 'index'});
$result = $pattern->match('/footest/bar');
Expand Down Expand Up @@ -126,20 +126,20 @@ is $pattern->render({test => $value}), "/$value", 'right result';
# Format detection
$pattern = Mojolicious::Routes::Pattern->new('/test');
$pattern->defaults({action => 'index'});
ok !$pattern->regex, 'no regex';
ok !$pattern->format, 'no format regex';
ok !$pattern->regex, 'no regex';
ok !$pattern->format_regex, 'no format regex';
$result = $pattern->match('/test.xml', 1);
ok $pattern->regex, 'regex has been compiled on demand';
ok $pattern->format, 'format regex has been compiled on demand';
ok $pattern->regex, 'regex has been compiled on demand';
ok $pattern->format_regex, 'format regex has been compiled on demand';
is $result->{action}, 'index', 'right value';
is $result->{format}, 'xml', 'right value';
$pattern = Mojolicious::Routes::Pattern->new('/test.json');
$pattern->defaults({action => 'index'});
ok !$pattern->regex, 'no regex';
ok !$pattern->format, 'no format regex';
ok !$pattern->regex, 'no regex';
ok !$pattern->format_regex, 'no format regex';
$result = $pattern->match('/test.json');
ok $pattern->regex, 'regex has been compiled on demand';
ok !$pattern->format, 'no format regex';
ok !$pattern->format_regex, 'no format regex';
is $result->{action}, 'index', 'right value';
is $result->{format}, undef, 'no value';
$result = $pattern->match('/test.json', 1);
Expand All @@ -153,11 +153,11 @@ is $result, undef, 'no result';
# Formats without detection
$pattern = Mojolicious::Routes::Pattern->new('/test');
$pattern->defaults({action => 'index'});
ok !$pattern->regex, 'no regex';
ok !$pattern->format, 'no format regex';
ok !$pattern->regex, 'no regex';
ok !$pattern->format_regex, 'no format regex';
$result = $pattern->match('/test.xml');
ok $pattern->regex, 'regex has been compiled on demand';
ok !$pattern->format, 'no format regex';
ok !$pattern->format_regex, 'no format regex';
is $result, undef, 'no result';
$result = $pattern->match('/test');
is $result->{action}, 'index', 'right value';
Expand All @@ -173,7 +173,7 @@ is $result, undef, 'no result';

# Special pattern for disabling format detection
$pattern = Mojolicious::Routes::Pattern->new(format => 0);
is $pattern->reqs->{format}, 0, 'right value';
is $pattern->constraints->{format}, 0, 'right value';
$pattern->defaults({action => 'index'});
$result = $pattern->match('/', 1);
is $result->{action}, 'index', 'right value';
Expand Down
2 changes: 1 addition & 1 deletion t/mojolicious/routes.t
Expand Up @@ -208,7 +208,7 @@ is $r->find('0')->to_string, '/0', 'right pattern';
is $r->find('test_edit')->to_string, '/:controller/test/edit', 'right pattern';
is $r->find('articles_delete')->to_string, '/articles/:id/delete',
'right pattern';
is $r->find('nodetect')->pattern->reqs->{format}, 0, 'right value';
is $r->find('nodetect')->pattern->constraints->{format}, 0, 'right value';
is $r->find('nodetect')->to->{controller}, 'foo', 'right controller';

# Null route
Expand Down

0 comments on commit a074d4a

Please sign in to comment.