use Moose;
extends 'Catalyst::DispatchType';
-#use strict;
-#use base qw/Catalyst::DispatchType/;
use Text::SimpleTable;
use Catalyst::ActionChain;
+use Catalyst::Utils;
use URI;
has _endpoints => (
default => sub{ {} },
);
+no Moose;
+
# please don't perltidy this. hairy code within.
=head1 NAME
=head1 SYNOPSIS
+Path part matching, allowing several actions to sequentially take care of processing a request:
+
# root action - captures one argument after it
sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
my ( $self, $c, $foo_arg ) = @_;
=head1 DESCRIPTION
-See L</USAGE>.
+Dispatch type managing default behaviour. For more information on
+dispatch types, see:
+
+=over 4
+
+=item * L<Catalyst::Manual::Intro> for how they affect application authors
+
+=item * L<Catalyst::DispatchType> for implementation information.
+
+=back
=head1 METHODS
return unless $self->_endpoints;
+ my $column_width = Catalyst::Utils::term_width() - 35 - 9;
my $paths = Text::SimpleTable->new(
- [ 35, 'Path Spec' ], [ 36, 'Private' ]
- );
+ [ 35, 'Path Spec' ], [ $column_width, 'Private' ],
+ );
+
+ my $has_unattached_actions;
+ my $unattached_actions = Text::SimpleTable->new(
+ [ 35, 'Private' ], [ $column_width, 'Missing parent' ],
+ );
ENDPOINT: foreach my $endpoint (
sort { $a->reverse cmp $b->reverse }
$curr = $self->_actions->{$parent};
unshift(@parents, $curr) if $curr;
}
- next ENDPOINT unless $parent eq '/'; # skip dangling action
+ if ($parent ne '/') {
+ $has_unattached_actions = 1;
+ $unattached_actions->row('/'.$parents[0]->reverse, $parent);
+ next ENDPOINT;
+ }
my @rows;
foreach my $p (@parents) {
my $name = "/${p}";
}
$c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
+ $c->log->debug( "Unattached Chained actions:\n", $unattached_actions->draw . "\n" )
+ if $has_unattached_actions;
}
=head2 $self->match( $c, $path )
sub match {
my ( $self, $c, $path ) = @_;
- return 0 if @{$c->req->args};
+ my $request = $c->request;
+ return 0 if @{$request->args};
my @parts = split('/', $path);
my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
- push @{$c->req->args}, @$parts if $parts && @$parts;
+ push @{$request->args}, @$parts if $parts && @$parts;
return 0 unless $chain;
my $action = Catalyst::ActionChain->from_chain($chain);
- $c->req->action("/${action}");
- $c->req->match("/${action}");
- $c->req->captures($captures);
+ $request->action("/${action}");
+ $request->match("/${action}");
+ $request->captures($captures);
$c->action($action);
$c->namespace( $action->namespace );
my ($actions, $captures, $action_parts) = $self->recurse_match(
$c, '/'.$action->reverse, \@parts
);
- if ($actions && (!$best_action || $#$action_parts < $#{$best_action->{parts}})){
+ # No best action currently
+ # OR The action has less parts
+ # OR The action has equal parts but less captured data (ergo more defined)
+ if ($actions &&
+ (!$best_action ||
+ $#$action_parts < $#{$best_action->{parts}} ||
+ ($#$action_parts == $#{$best_action->{parts}} &&
+ $#$captures < $#{$best_action->{captures}}))){
$best_action = {
actions => [ $action, @$actions ],
captures=> [ @captures, @$captures ],
# No best action currently
# OR This one matches with fewer parts left than the current best action,
# And therefore is a better match
- # OR No parts and this expects 0
+ # OR No parts and this expects 0
# The current best action might also be Args(0),
# but we couldn't chose between then anyway so we'll take the last seen
return 0 unless @chained_attr;
- if (@chained_attr > 2) {
+ if (@chained_attr > 1) {
Catalyst::Exception->throw(
"Multiple Chained attributes not supported registering ${action}"
);
}
+ my $chained_to = $chained_attr[0];
- my $parent = $chained_attr[0];
-
- if (defined($parent) && length($parent)) {
- if ($parent eq '.') {
- $parent = '/'.$action->namespace;
- } elsif ($parent !~ m/^\//) {
- if ($action->namespace) {
- $parent = '/'.join('/', $action->namespace, $parent);
- } else {
- $parent = '/'.$parent; # special case namespace '' (root)
- }
- }
- } else {
- $parent = '/'
- }
+ Catalyst::Exception->throw(
+ "Actions cannot chain to themselves registering /${action}"
+ ) if ($chained_to eq '/' . $action);
- $action->attributes->{Chained} = [ $parent ];
-
- my $children = ($self->_children_of->{$parent} ||= {});
+ my $children = ($self->_children_of->{ $chained_to } ||= {});
my @path_part = @{ $action->attributes->{PathPart} || [] };
$part = $path_part[0];
} elsif (@path_part > 1) {
Catalyst::Exception->throw(
- "Multiple PathPart attributes not supported registering ${action}"
+ "Multiple PathPart attributes not supported registering " . $action->reverse()
);
}
if ($part =~ m(^/)) {
Catalyst::Exception->throw(
- "Absolute parameters to PathPart not allowed registering ${action}"
+ "Absolute parameters to PathPart not allowed registering " . $action->reverse()
);
}
return undef if @captures; # fail for too many captures
return join('/', '', @parts);
+
+}
+
+=head2 $c->expand_action($action)
+
+Return a list of actions that represents a chained action. See
+L<Catalyst::Dispatcher> for more info. You probably want to
+use the expand_action it provides rather than this directly.
+=cut
+
+sub expand_action {
+ my ($self, $action) = @_;
+
+ return unless $action->attributes && $action->attributes->{Chained};
+
+ my @chain;
+ my $curr = $action;
+
+ while ($curr) {
+ push @chain, $curr;
+ my $parent = $curr->attributes->{Chained}->[0];
+ $curr = $self->_actions->{$parent};
+ }
+
+ return Catalyst::ActionChain->from_chain([reverse @chain]);
}
+__PACKAGE__->meta->make_immutable;
+
=head1 USAGE
=head2 Introduction
'-----------------------+------------------------------'
...
-Here's a more detailed specification of the attributes belonging to
+Here's a more detailed specification of the attributes belonging to
C<:Chained>:
=head2 Attributes
C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
effect as using C<:PathPart>, it would default to the action name.
+=item PathPrefix
+
+Sets PathPart to the path_prefix of the current controller.
+
=item Chained
Has to be specified for every child in the chain. Possible values are
-absolute and relative private action paths, with the relatives pointing
-to the current controller, or a single slash C</> to tell Catalyst that
-this is the root of a chain. The attribute C<:Chained> without arguments
-also defaults to the C</> behavior.
+absolute and relative private action paths or a single slash C</> to
+tell Catalyst that this is the root of a chain. The attribute
+C<:Chained> without arguments also defaults to the C</> behavior.
+Relative action paths may use C<../> to refer to actions in parent
+controllers.
Because you can specify an absolute path to the parent action, it
doesn't matter to Catalyst where that parent is located. So, if your
C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
chain comes out as the end product.
+=item ChainedParent
+
+Chains an action to another action with the same name in the parent
+controller. For Example:
+
+ # in MyApp::Controller::Foo
+ sub bar : Chained CaptureArgs(1) { ... }
+
+ # in MyApp::Controller::Foo::Moo
+ sub bar : ChainedParent Args(1) { ... }
+
+This builds a chain like C</bar/*/bar/*>.
+
=item CaptureArgs
Must be specified for every part of the chain that is not an
you C<detach> out of a chain, the rest of the chain will not get called
after the C<detach>.
-=head1 AUTHOR
+=head1 AUTHORS
-Matt S Trout <mst@shadowcatsystems.co.uk>
+Catalyst Contributors, see Catalyst.pm
=head1 COPYRIGHT