| Mojolicious documentation | Contained in the Mojolicious distribution. |
Mojolicious::Routes::Pattern - Routes Pattern
use Mojolicious::Routes::Pattern;
Mojolicious::Routes::Pattern is a container for routes pattern which are used to match paths against.
Mojolicious::Routes::Pattern implements the following attributes.
defaults my $defaults = $pattern->defaults;
$pattern = $pattern->defaults({foo => 'bar'});
Default parameters.
pattern my $pattern = $pattern->pattern;
$pattern = $pattern->pattern('/(foo)/(bar)');
Raw unparsed pattern.
quote_end my $quote = $pattern->quote_end;
$pattern = $pattern->quote_end(']');
Character indicating the end of a quoted placeholder, defaults to ).
quote_start my $quote = $pattern->quote_start;
$pattern = $pattern->quote_start('[');
Character indicating the start of a quoted placeholder, defaults to (.
regexmy $regex = $pattern->regex; $pattern = $pattern->regex(qr/\/foo/);
Pattern in compiled regex form.
relaxed_start my $relaxed = $pattern->relaxed_start;
$pattern = $pattern->relaxed_start('*');
Character indicating a relaxed placeholder, defaults to ..
reqs my $reqs = $pattern->reqs;
$pattern = $pattern->reqs({foo => qr/\w+/});
Regex constraints.
symbol_start my $symbol = $pattern->symbol_start;
$pattern = $pattern->symbol_start(':');
Character indicating a placeholder, defaults to :.
symbolsmy $symbols = $pattern->symbols; $pattern = $pattern->symbols(['foo', 'bar']);
Placeholder names.
treemy $tree = $pattern->tree; $pattern = $pattern->tree([ ... ]);
Pattern in parsed form.
wildcard_start my $wildcard = $pattern->wildcard_start;
$pattern = $pattern->wildcard_start('*');
Character indicating the start of a wildcard placeholder, defaults to *.
Mojolicious::Routes::Pattern inherits all methods from Mojo::Base and implements the following ones.
new my $pattern = Mojolicious::Routes::Pattern->new('/:controller/:action',
action => qr/\w+/
);
Construct a new pattern object.
match my $result = $pattern->match('/foo/bar');
Match pattern against a path.
parse $pattern = $pattern->parse('/:controller/:action', action => qr/\w+/);
Parse a raw pattern.
render my $path = $pattern->render({action => 'foo'});
Render pattern into a path with parameters.
shape_matchmy $result = $pattern->shape_match(\$path);
Match pattern against a path and remove matching parts.
Mojolicious, Mojolicious::Guides, http://mojolicio.us.
| Mojolicious documentation | Contained in the Mojolicious distribution. |
package Mojolicious::Routes::Pattern; use Mojo::Base -base; has defaults => sub { {} }; has quote_end => ')'; has quote_start => '('; has relaxed_start => '.'; has reqs => sub { {} }; has symbol_start => ':'; has symbols => sub { [] }; has tree => sub { [] }; has wildcard_start => '*'; has [qw/format pattern regex/]; # "This is the worst kind of discrimination. The kind against me!" sub new { my $self = shift->SUPER::new(); $self->parse(@_); $self; } sub match { my ($self, $path) = @_; my $result = $self->shape_match(\$path); return $result if !$path || $path eq '/'; undef; } sub parse { my $self = shift; my $pattern = shift; # Make sure we have a viable pattern return $self if !defined $pattern || $pattern eq '/'; $pattern = "/$pattern" unless $pattern =~ /^\//; # Format if ($pattern =~ /\.([^\/\)]+)$/) { $self->format($1) } # Requirements my $reqs = ref $_[0] eq 'HASH' ? $_[0] : {@_}; $self->reqs($reqs); # Tokenize $self->pattern($pattern); $self->_tokenize; $self; } sub render { my ($self, $values) = @_; # Merge values with defaults $values ||= {}; $values = {%{$self->defaults}, %$values}; # Turn pattern into path my $string = ''; my $optional = 1; for my $token (reverse @{$self->tree}) { my $op = $token->[0]; my $rendered = ''; # Slash if ($op eq 'slash') { $rendered = '/' unless $optional; } # Text elsif ($op eq 'text') { $rendered = $token->[1]; $optional = 0; } # Relaxed, symbol or wildcard elsif ($op eq 'relaxed' || $op eq 'symbol' || $op eq 'wildcard') { my $name = $token->[1]; $rendered = $values->{$name}; $rendered = '' unless defined $rendered; my $default = $self->defaults->{$name}; $default = '' unless defined $default; $optional = 0 unless $default eq $rendered; $rendered = '' if $optional && $default eq $rendered; } $string = "$rendered$string"; } $string || '/'; } sub shape_match { my ($self, $pathref) = @_; # Compile on demand my $regex; $regex = $self->_compile unless $regex = $self->regex; # Match if (my @captures = $$pathref =~ $regex) { $$pathref =~ s/$regex//; # Merge captures my $result = {%{$self->defaults}}; for my $symbol (@{$self->symbols}) { last unless @captures; # Merge my $capture = shift @captures; $result->{$symbol} = $capture if defined $capture; } return $result; } undef; } sub _compile { my $self = shift; # Compile tree to regular expression my $block = ''; my $regex = ''; my $optional = 1; for my $token (reverse @{$self->tree}) { my $op = $token->[0]; my $compiled = ''; # Slash if ($op eq 'slash') { # Full block $block = $optional ? "(?:/$block)?" : "/$block"; $regex = "$block$regex"; $block = ''; next; } # Text elsif ($op eq 'text') { $compiled = quotemeta $token->[1]; $optional = 0; } # Symbol elsif ($op eq 'relaxed' || $op eq 'symbol' || $op eq 'wildcard') { my $name = $token->[1]; unshift @{$self->symbols}, $name; # Relaxed if ($op eq 'relaxed') { $compiled = '([^\/]+)' } # Symbol elsif ($op eq 'symbol') { $compiled = '([^\/\.]+)' } # Wildcard elsif ($op eq 'wildcard') { $compiled = '(.+)' } # Custom regex my $req = $self->reqs->{$name}; $compiled = "($req)" if $req; # Optional placeholder $optional = 0 unless exists $self->defaults->{$name}; $compiled .= '?' if $optional; } # Add to block $block = "$compiled$block"; } # Not rooted with a slash $regex = "$block$regex" if $block; $regex = qr/^$regex/; $self->regex($regex); $regex; } sub _tokenize { my $self = shift; # Token my $quote_end = $self->quote_end; my $quote_start = $self->quote_start; my $relaxed_start = $self->relaxed_start; my $symbol_start = $self->symbol_start; my $wildcard_start = $self->wildcard_start; # Parse the pattern character wise my $pattern = $self->pattern; my $tree = []; my $state = 'text'; my $quoted = 0; while (length(my $char = substr $pattern, 0, 1, '')) { # Inside a symbol my $symbol = 0; $symbol = 1 if $state eq 'relaxed' || $state eq 'symbol' || $state eq 'wildcard'; # Quote start if ($char eq $quote_start) { $quoted = 1; $state = 'symbol'; push @$tree, ['symbol', '']; next; } # Symbol start if ($char eq $symbol_start) { push @$tree, ['symbol', ''] if $state ne 'symbol'; $state = 'symbol'; next; } # Relaxed start (needs to be quoted) if ($quoted && $char eq $relaxed_start && $state eq 'symbol') { $state = 'relaxed'; $tree->[-1]->[0] = 'relaxed'; next; } # Wildcard start (upgrade when quoted) if ($char eq $wildcard_start) { push @$tree, ['symbol', ''] unless $quoted; $state = 'wildcard'; $tree->[-1]->[0] = 'wildcard'; next; } # Quote end if ($char eq $quote_end) { $quoted = 0; $state = 'text'; next; } # Slash if ($char eq '/') { push @$tree, ['slash']; $state = 'text'; next; } # Relaxed, symbol or wildcard elsif ($symbol && $char =~ /\w/) { $tree->[-1]->[-1] .= $char; next; } # Text else { $state = 'text'; # New text element unless ($tree->[-1]->[0] eq 'text') { push @$tree, ['text', $char]; next; } # More text $tree->[-1]->[-1] .= $char; } } $self->tree($tree); $self; } 1; __END__