use Catalyst::ActionChain;
use Catalyst::Utils;
use URI;
+use Scalar::Util ();
has _endpoints => (
is => 'rw',
=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 $avail_width = Catalyst::Utils::term_width() - 9;
+ my $col1_width = ($avail_width * .50) < 35 ? 35 : int($avail_width * .50);
+ my $col2_width = $avail_width - $col1_width;
my $paths = Text::SimpleTable->new(
- [ 35, 'Path Spec' ], [ $column_width, 'Private' ],
+ [ $col1_width, 'Path Spec' ], [ $col2_width, 'Private' ],
);
my $has_unattached_actions;
my $unattached_actions = Text::SimpleTable->new(
- [ 35, 'Private' ], [ 36, 'Missing parent' ],
+ [ $col1_width, 'Private' ], [ $col2_width, 'Missing parent' ],
);
ENDPOINT: foreach my $endpoint (
}
if ($parent ne '/') {
$has_unattached_actions = 1;
- $unattached_actions->row('/'.$parents[0]->reverse, $parent);
+ $unattached_actions->row('/' . ($parents[0] || $endpoint)->reverse, $parent);
next ENDPOINT;
}
my @rows;
push(@rows, [ '', $name ]);
}
push(@rows, [ '', (@rows ? "=> " : '')."/${endpoint}" ]);
- $rows[0][0] = join('/', '', @parts);
+ $rows[0][0] = join('/', '', @parts) || '/';
$paths->row(@$_) for @rows;
}
my @parts = split('/', $path);
my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
- push @{$request->args}, @$parts if $parts && @$parts;
+
+ if ($parts && @$parts) {
+ for my $arg (@$parts) {
+ $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
+ push @{$request->args}, $arg;
+ }
+ }
return 0 unless $chain;
# 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
$self->_actions->{'/'.$action->reverse} = $action;
+ if (exists $action->attributes->{Args}) {
+ my $args = $action->attributes->{Args}->[0];
+ if (defined($args) and not (
+ Scalar::Util::looks_like_number($args) and
+ int($args) == $args
+ )) {
+ require Data::Dumper;
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Indent = 0;
+ $args = Data::Dumper::Dumper($args);
+ Catalyst::Exception->throw(
+ "Invalid Args($args) for action " . $action->reverse() .
+ " (use 'Args' or 'Args(<number>)'"
+ );
+ }
+ }
+
unless ($action->attributes->{CaptureArgs}) {
unshift(@{ $self->_endpoints }, $action);
}
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
+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.
return Catalyst::ActionChain->from_chain([reverse @chain]);
}
+=head2 $self->splice_captures_from( $c, $action, $args )
+
+Calculates the number of capture args for the given action,
+splices off the front of the supplied args, and pushes them back
+on the args list wrapped in an array ref
+
+=cut
+
+sub splice_captures_from {
+ my ($self, $c, $action, $args) = @_; my $attrs = $action->attributes;
+
+ return 0 unless ($attrs->{Chained});
+
+ if ($attrs->{CaptureArgs}) {
+ $c->log->debug( 'Action '.$action->reverse.' is a midpoint' )
+ if ($c->debug);
+ return 1;
+ }
+
+ my @captures = ();
+ my @chain = @{ $self->expand_action( $action )->chain }; pop @chain;
+
+ # Now start from the root of the chain, populate captures
+ for my $num_caps (map { $_->attributes->{CaptureArgs}->[0] } @chain) {
+ if ($num_caps > scalar @{ $args }) {
+ $c->log->debug( 'Action '.$action->reverse.' insufficient args' )
+ if ($c->debug);
+ return 1;
+ }
+
+ push @captures, splice @{ $args }, 0, $num_caps;
+ }
+
+ if ( defined $attrs->{Args}->[0]
+ and defined $args->[ $attrs->{Args}->[0] ]) {
+ $c->log->debug( 'Action '.$action->reverse.' too many args' )
+ if ($c->debug);
+ }
+
+ unshift @{ $args }, \@captures if (defined $captures[0]);
+
+ return 1;
+}
+
__PACKAGE__->meta->make_immutable;
=head1 USAGE
'-----------------------+------------------------------'
...
-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
=head1 COPYRIGHT
-This program is free software, you can redistribute it and/or modify it under
+This library is free software. You can redistribute it and/or modify it under
the same terms as Perl itself.
=cut