—package
Kelp::Routes::Pattern;
use
Carp;
use
Kelp::Base;
attr
pattern
=>
sub
{
die
"pattern is required"
};
attr
via
=>
undef
;
attr
name
=>
sub
{
$_
[0]->pattern };
attr
check
=>
sub
{ {} };
attr
defaults
=>
sub
{ {} };
attr
bridge
=> 0;
attr
regex
=>
sub
{
$_
[0]->_build_regex };
attr
named
=>
sub
{ {} };
attr
param
=>
sub
{ [] };
attr
to
=>
undef
;
sub
new {
my
$class
=
shift
;
my
$self
=
$class
->SUPER::new(
@_
);
$self
->{_tokens} = [];
$self
->regex;
# Compile the regex
return
$self
;
}
sub
_rep_regex {
my
(
$self
,
$char
,
$switch
,
$token
) =
@_
;
push
@{
$self
->{_tokens}},
$token
;
my
(
$a
,
$b
,
$r
) = (
"(?<$token>"
,
')'
,
undef
);
for
(
$switch
) {
if
(
$_
eq
':'
||
$_
eq
'?'
) {
$r
=
$a
. (
$self
->check->{
$token
} //
'[^\/]+'
) .
$b
}
if
(
$_
eq
'*'
) {
$r
=
$a
.
'.+'
.
$b
}
}
$char
=
$char
.
'?'
if
$char
eq
'/'
&&
$switch
eq
'?'
;
$r
.=
'?'
if
$switch
eq
'?'
;
return
$char
.
$r
;
}
sub
_build_regex {
my
$self
=
shift
;
return
$self
->pattern
if
ref
$self
->pattern eq
'Regexp'
;
my
$PAT
=
'(.?)([:*?])(\w+)'
;
my
$pattern
=
$self
->pattern;
$pattern
=~ s{
$PAT
}{
$self
->_rep_regex($1, $2, $3)}eg;
$pattern
=~ s/[{}]//g;
$pattern
.=
'/?'
unless
$pattern
=~ m{/$};
$pattern
.=
'$'
unless
$self
->bridge;
return
qr{^$pattern}
;
}
sub
_rep_build {
my
(
$self
,
$switch
,
$token
,
%args
) =
@_
;
my
$rep
=
$args
{
$token
} //
$self
->defaults->{
$token
} //
''
;
if
(
$switch
ne
'?'
&& !
$rep
) {
return
'{?'
.
$token
.
'}'
;
}
my
$check
=
$self
->check->{
$token
};
if
(
$check
&&
$args
{
$token
} !~
$check
) {
return
'{!'
.
$token
.
'}'
;
}
return
$rep
;
}
sub
build {
my
(
$self
,
%args
) =
@_
;
my
$pattern
=
$self
->pattern;
if
(
ref
$pattern
eq
'Regexp'
) {
carp
"Can't build a path for regular expressions"
;
return
;
}
my
$PAT
=
'([:*?])(\w+)'
;
$pattern
=~ s/{?
$PAT
}?/
$self
->_rep_build($1, $2,
%args
)/eg;
if
(
$pattern
=~ /{([!?])(\w+)}/) {
carp $1 eq
'!'
?
"Field $2 doesn't match checks"
:
"Default value for field $2 is missing"
;
return
undef
;
}
return
$pattern
;
}
sub
match {
my
(
$self
,
$path
,
$method
) =
@_
;
return
0
if
(
$self
->via &&
$self
->via ne (
$method
//
''
) );
return
0
unless
my
@matched
=
$path
=~
$self
->regex;
# Initialize the named parameters hash and its default values
my
%named
=
map
{
$_
=> $+{
$_
} }
keys
%+;
for
(
keys
%{
$self
->defaults } ) {
$named
{
$_
} =
$self
->defaults->{
$_
}
unless
exists
$named
{
$_
};
}
$self
->named( \
%named
);
# Initialize the param array, containing the values of the
# named placeholders in the order they appear in the regex.
if
(
my
@tokens
= @{
$self
->{_tokens} } ) {
$self
->param( [
map
{
$named
{
$_
} }
@tokens
] );
}
else
{
$self
->param( \
@matched
);
}
return
1;
}
1;
__END__
=head1 NAME
Kelp::Routes::Pattern - Route patterns for Kelp routes
=head1 SYNOPSIS
my $p = Kelp::Routes::Pattern->new( pattern => '/:name/:place' );
if ( $p->match('/james/london') ) {
%named = %{ $p->named }; # ( name => 'james', place => 'london' )
@param = @{ $p->param }; # ( 'james', 'london' )
}
=head1 DESCRIPTION
This module is needed by L<Kelp::Routes>. It provides matching for
individual route patterns, returning the named placeholders in a hash and an
array.
=head1 ATTRIBUTES
=head2 pattern
The pattern to match against. Each pattern is a string, which may contain named
placeholders. For more information on the types and use of placeholders, look at
L<Kelp::Routes/PLACEHOLDERS>.
my $p = Kelp::Routes::Patters->new( pattern => '/:id/*other' );
...
$p->match('/4/something-else'); # True
=head2 via
Specifies an HTTP method to be matched by the route.
my $p = Kelp::Routes::Patters->new(
pattern => '/:id/*other',
via => 'PUT'
);
$p->match('/4/something-else', 'GET'); # False. Only PUT allowed.
=head2 name
You are encouraged to give each route a name, so you can look it up later when
you build a URL for it.
my $p = Kelp::Routes::Patters->new(
pattern => '/:id/*other',
name => 'other_id'
);
...
say $p->build( 'other_id', id => '100', other => 'something-else' );
# Prints '/100/something-else'
If no name is provided for the route, the C<pattern> is used.
=head2 check
A hashref with placeholder names as keys and regular expressions as values. It
is used to match the values of the placeholders against the provided regular
expressions.
my $p = Kelp::Routes::Patters->new(
pattern => '/:id/*other',
check => { id => qr/\d+/ } # id may only be a didgit
);
$p->match('/4/other'); # True
$p->match('/q/other'); # False
Note: Do not add C<^> at the beginning or C<$> at the end of the regular
expressions, because they are merged into a bigger regex.
=head2 defaults
A hashref with placeholder defaults. This only applies to optional placeholders,
or those prefixed with a question mark. If a default value is provided for any
of them, it will be used in case the placeholder value is missing.
my $p = Kelp::Routes::Patters->new(
pattern => '/:id/?other',
defaults => { other => 'info' }
);
$p->match('/100');
# $p->named will contain { id => 100, other => 'info' }
$p->match('/100/delete');
# $p->named will contain { id => 100, other => 'delete' }
=head2 bridge
A True/False value. Specifies if the route is a bridge. For more information
about bridges, please see L<Kelp::Routes/BRIDGES>
=head2 regex
We recommend that you stick to using patterns, because they are simpler and
easier to read, but if you need to match a really complicated route, then
you can use a regular expression.
my $p = Kelp::Routes::Patters->new( regex => qr{^(\d+)/(\d+)$} );
$p->match('/100/200'); # True. $p->param will be [ 100, 200 ]
After matching, the L</param> array will be initialized with the values of the
captures in the order they appear in the regex.
If you used a regex with named captures, then a hashref L</named> will also be
initialized with the names and values of the named placeholders. In other words,
this hash will be a permanent copy of the C<%+> built-in hash.
my $p = Kelp::Routes::Patters->new( regex => qr{^(?<id>\d+)/(?<line>\d+)$} );
$p->match('/100/200'); # True.
# $p->param will be [ 100, 200 ]
# $p->named will be { id => 100, line => 200 }
If C<regex> is not explicitly given a value it will be built from the
C<pattern>.
=head2 named
A hashref which will be initialized by the L</match> function. After matching,
it will contain placeholder names and values for the matched route.
=head2 param
An arrayref, which will be initialized by the L</match> function. After matching,
it will contain all placeholder values in the order they were specified in the
pattern.
=head2 to
Specifies the route destination. See examples in L<Kelp::Routes>.
=head1 METHODS
=head2 match
C<match( $path, $method )>
Matches an already initialized route against a path and http method. If the match
was successful, this sub will return a true value and the L</named> and L</param>
attributes will be initialized with the names and values of the matched placeholders.
=head2 build
C<build( %args )>
Builds a URL from a pattern.
my $p = Kelp::Routes::Patters->new( pattern => '/:id/:line/:row' );
$p->build( id => 100, line => 5, row => 8 ); # Returns '/100/5/8'
=head1 ACKNOWLEDGEMENTS
This module was inspired by L<Routes::Tiny>.
The concept of bridges was borrowed from L<Mojolicious>
=cut