add chained to the list of successful conversions :)
[catagits/Catalyst-Runtime.git] / lib / Catalyst / DispatchType / Chained.pm
CommitLineData
5882c86e 1package Catalyst::DispatchType::Chained;
141459fa 2
3c0186f2 3use Moose;
e8b9f2a9 4extends 'Catalyst::DispatchType';
5
6#use strict;
7#use base qw/Catalyst::DispatchType/;
141459fa 8use Text::SimpleTable;
9use Catalyst::ActionChain;
10use URI;
11
be5cb4e4 12has _endpoints => (
13 is => 'rw',
14 isa => 'ArrayRef',
15 required => 1,
16 default => sub{ [] },
17 );
18
19has _actions => (
20 is => 'rw',
21 isa => 'HashRef',
22 required => 1,
23 default => sub{ {} },
24 );
25
26has _children_of => (
27 is => 'rw',
28 isa => 'HashRef',
29 required => 1,
30 default => sub{ {} },
31 );
32
792b40ac 33# please don't perltidy this. hairy code within.
34
141459fa 35=head1 NAME
36
5882c86e 37Catalyst::DispatchType::Chained - Path Part DispatchType
141459fa 38
39=head1 SYNOPSIS
40
05a90578 41 # root action - captures one argument after it
42 sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
43 my ( $self, $c, $foo_arg ) = @_;
44 ...
45 }
46
47 # child action endpoint - takes one argument
48 sub bar : Chained('foo_setup') Args(1) {
49 my ( $self, $c, $bar_arg ) = @_;
50 ...
51 }
141459fa 52
53=head1 DESCRIPTION
54
05a90578 55See L</USAGE>.
56
141459fa 57=head1 METHODS
58
59=head2 $self->list($c)
60
61Debug output for Path Part dispatch points
62
141459fa 63=cut
64
792b40ac 65sub list {
66 my ( $self, $c ) = @_;
67
be5cb4e4 68 return unless $self->_endpoints;
792b40ac 69
70 my $paths = Text::SimpleTable->new(
71 [ 35, 'Path Spec' ], [ 36, 'Private' ]
72 );
73
74 ENDPOINT: foreach my $endpoint (
75 sort { $a->reverse cmp $b->reverse }
be5cb4e4 76 @{ $self->_endpoints }
792b40ac 77 ) {
78 my $args = $endpoint->attributes->{Args}->[0];
79 my @parts = (defined($args) ? (("*") x $args) : '...');
d34667c3 80 my @parents = ();
792b40ac 81 my $parent = "DUMMY";
82 my $curr = $endpoint;
83 while ($curr) {
1c34f703 84 if (my $cap = $curr->attributes->{CaptureArgs}) {
792b40ac 85 unshift(@parts, (("*") x $cap->[0]));
86 }
87 if (my $pp = $curr->attributes->{PartPath}) {
88 unshift(@parts, $pp->[0])
89 if (defined $pp->[0] && length $pp->[0]);
90 }
5882c86e 91 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 92 $curr = $self->_actions->{$parent};
d34667c3 93 unshift(@parents, $curr) if $curr;
792b40ac 94 }
95 next ENDPOINT unless $parent eq '/'; # skip dangling action
d34667c3 96 my @rows;
97 foreach my $p (@parents) {
98 my $name = "/${p}";
1c34f703 99 if (my $cap = $p->attributes->{CaptureArgs}) {
d34667c3 100 $name .= ' ('.$cap->[0].')';
101 }
102 unless ($p eq $parents[0]) {
103 $name = "-> ${name}";
104 }
105 push(@rows, [ '', $name ]);
106 }
107 push(@rows, [ '', (@rows ? "=> " : '')."/${endpoint}" ]);
108 $rows[0][0] = join('/', '', @parts);
109 $paths->row(@$_) for @rows;
792b40ac 110 }
111
1cf0345b 112 $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
792b40ac 113}
141459fa 114
115=head2 $self->match( $c, $path )
116
05a90578 117Calls C<recurse_match> to see if a chain matches the C<$path>.
141459fa 118
119=cut
120
121sub match {
122 my ( $self, $c, $path ) = @_;
123
124 return 0 if @{$c->req->args};
125
126 my @parts = split('/', $path);
127
6365b527 128 my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
6b495723 129 push @{$c->req->args}, @$parts if $parts && @$parts;
141459fa 130
131 return 0 unless $chain;
132
133 my $action = Catalyst::ActionChain->from_chain($chain);
134
135 $c->req->action("/${action}");
136 $c->req->match("/${action}");
137 $c->req->captures($captures);
138 $c->action($action);
139 $c->namespace( $action->namespace );
140
141 return 1;
142}
143
144=head2 $self->recurse_match( $c, $parent, \@path_parts )
145
05a90578 146Recursive search for a matching chain.
141459fa 147
148=cut
149
150sub recurse_match {
151 my ( $self, $c, $parent, $path_parts ) = @_;
be5cb4e4 152 my $children = $self->_children_of->{$parent};
141459fa 153 return () unless $children;
6b495723 154 my $best_action;
141459fa 155 my @captures;
1b04b972 156 TRY: foreach my $try_part (sort { length($b) <=> length($a) }
cdc97b63 157 keys %$children) {
1b04b972 158 # $b then $a to try longest part first
141459fa 159 my @parts = @$path_parts;
160 if (length $try_part) { # test and strip PathPart
161 next TRY unless
162 ($try_part eq join('/', # assemble equal number of parts
163 splice( # and strip them off @parts as well
792b40ac 164 @parts, 0, scalar(@{[split('/', $try_part)]})
165 ))); # @{[]} to avoid split to @_
141459fa 166 }
167 my @try_actions = @{$children->{$try_part}};
168 TRY_ACTION: foreach my $action (@try_actions) {
1c34f703 169 if (my $capture_attr = $action->attributes->{CaptureArgs}) {
f505df49 170
171 # Short-circuit if not enough remaining parts
172 next TRY_ACTION unless @parts >= $capture_attr->[0];
173
141459fa 174 my @captures;
175 my @parts = @parts; # localise
7a7ac23c 176
1c34f703 177 # strip CaptureArgs into list
7a7ac23c 178 push(@captures, splice(@parts, 0, $capture_attr->[0]));
179
141459fa 180 # try the remaining parts against children of this action
6365b527 181 my ($actions, $captures, $action_parts) = $self->recurse_match(
141459fa 182 $c, '/'.$action->reverse, \@parts
183 );
6b495723 184 if ($actions && (!$best_action || $#$action_parts < $#{$best_action->{parts}})){
185 $best_action = {
186 actions => [ $action, @$actions ],
187 captures=> [ @captures, @$captures ],
188 parts => $action_parts
189 };
190 }
191 }
192 else {
7a7ac23c 193 {
194 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
195 next TRY_ACTION unless $action->match($c);
196 }
953c176d 197 my $args_attr = $action->attributes->{Args}->[0];
198
199 # No best action currently
200 # OR This one matches with fewer parts left than the current best action,
201 # And therefore is a better match
3c0186f2 202 # OR No parts and this expects 0
953c176d 203 # The current best action might also be Args(0),
204 # but we couldn't chose between then anyway so we'll take the last seen
205
206 if (!$best_action ||
207 @parts < @{$best_action->{parts}} ||
a8194217 208 (!@parts && $args_attr eq 0)){
6b495723 209 $best_action = {
210 actions => [ $action ],
211 captures=> [],
212 parts => \@parts
6b495723 213 }
953c176d 214 }
141459fa 215 }
216 }
217 }
953c176d 218 return @$best_action{qw/actions captures parts/} if $best_action;
141459fa 219 return ();
220}
221
222=head2 $self->register( $c, $action )
223
05a90578 224Calls register_path for every Path attribute for the given $action.
141459fa 225
226=cut
227
228sub register {
229 my ( $self, $c, $action ) = @_;
230
1dc8af44 231 my @chained_attr = @{ $action->attributes->{Chained} || [] };
141459fa 232
1dc8af44 233 return 0 unless @chained_attr;
141459fa 234
1dc8af44 235 if (@chained_attr > 2) {
141459fa 236 Catalyst::Exception->throw(
5882c86e 237 "Multiple Chained attributes not supported registering ${action}"
141459fa 238 );
239 }
240
1dc8af44 241 my $parent = $chained_attr[0];
141459fa 242
243 if (defined($parent) && length($parent)) {
1dc8af44 244 if ($parent eq '.') {
245 $parent = '/'.$action->namespace;
246 } elsif ($parent !~ m/^\//) {
7f64ae17 247 if ($action->namespace) {
248 $parent = '/'.join('/', $action->namespace, $parent);
249 } else {
250 $parent = '/'.$parent; # special case namespace '' (root)
251 }
141459fa 252 }
253 } else {
1dc8af44 254 $parent = '/'
141459fa 255 }
256
5882c86e 257 $action->attributes->{Chained} = [ $parent ];
792b40ac 258
be5cb4e4 259 my $children = ($self->_children_of->{$parent} ||= {});
141459fa 260
261 my @path_part = @{ $action->attributes->{PathPart} || [] };
262
09461385 263 my $part = $action->name;
141459fa 264
09461385 265 if (@path_part == 1 && defined $path_part[0]) {
266 $part = $path_part[0];
141459fa 267 } elsif (@path_part > 1) {
268 Catalyst::Exception->throw(
269 "Multiple PathPart attributes not supported registering ${action}"
270 );
271 }
272
8a6a6581 273 if ($part =~ m(^/)) {
274 Catalyst::Exception->throw(
275 "Absolute parameters to PathPart not allowed registering ${action}"
276 );
277 }
278
792b40ac 279 $action->attributes->{PartPath} = [ $part ];
280
141459fa 281 unshift(@{ $children->{$part} ||= [] }, $action);
282
be5cb4e4 283 $self->_actions->{'/'.$action->reverse} = $action;
792b40ac 284
1c34f703 285 unless ($action->attributes->{CaptureArgs}) {
be5cb4e4 286 unshift(@{ $self->_endpoints }, $action);
792b40ac 287 }
288
289 return 1;
141459fa 290}
291
292=head2 $self->uri_for_action($action, $captures)
293
05a90578 294Get the URI part for the action, using C<$captures> to fill
295the capturing parts.
141459fa 296
297=cut
298
299sub uri_for_action {
300 my ( $self, $action, $captures ) = @_;
301
5882c86e 302 return undef unless ($action->attributes->{Chained}
8b13f357 303 && !$action->attributes->{CaptureArgs});
792b40ac 304
305 my @parts = ();
306 my @captures = @$captures;
307 my $parent = "DUMMY";
308 my $curr = $action;
309 while ($curr) {
1c34f703 310 if (my $cap = $curr->attributes->{CaptureArgs}) {
792b40ac 311 return undef unless @captures >= $cap->[0]; # not enough captures
8b13f357 312 if ($cap->[0]) {
313 unshift(@parts, splice(@captures, -$cap->[0]));
314 }
792b40ac 315 }
316 if (my $pp = $curr->attributes->{PartPath}) {
317 unshift(@parts, $pp->[0])
8b13f357 318 if (defined($pp->[0]) && length($pp->[0]));
792b40ac 319 }
5882c86e 320 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 321 $curr = $self->_actions->{$parent};
141459fa 322 }
792b40ac 323
324 return undef unless $parent eq '/'; # fail for dangling action
325
326 return undef if @captures; # fail for too many captures
327
328 return join('/', '', @parts);
3c0186f2 329
141459fa 330}
331
05a90578 332=head1 USAGE
333
334=head2 Introduction
335
336The C<Chained> attribute allows you to chain public path parts together
67869327 337by their private names. A chain part's path can be specified with
338C<PathPart> and can be declared to expect an arbitrary number of
339arguments. The endpoint of the chain specifies how many arguments it
340gets through the C<Args> attribute. C<:Args(0)> would be none at all,
341C<:Args> without an integer would be unlimited. The path parts that
342aren't endpoints are using C<CaptureArgs> to specify how many parameters
343they expect to receive. As an example setup:
05a90578 344
345 package MyApp::Controller::Greeting;
346 use base qw/ Catalyst::Controller /;
347
348 # this is the beginning of our chain
349 sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
350 my ( $self, $c, $integer ) = @_;
351 $c->stash->{ message } = "Hello ";
352 $c->stash->{ arg_sum } = $integer;
353 }
354
355 # this is our endpoint, because it has no :CaptureArgs
356 sub world : PathPart('world') Chained('hello') Args(1) {
357 my ( $self, $c, $integer ) = @_;
358 $c->stash->{ message } .= "World!";
359 $c->stash->{ arg_sum } += $integer;
360
361 $c->response->body( join "<br/>\n" =>
362 $c->stash->{ message }, $c->stash->{ arg_sum } );
363 }
364
365The debug output provides a separate table for chained actions, showing
67869327 366the whole chain as it would match and the actions it contains. Here's an
367example of the startup output with our actions above:
05a90578 368
369 ...
370 [debug] Loaded Path Part actions:
371 .-----------------------+------------------------------.
372 | Path Spec | Private |
373 +-----------------------+------------------------------+
374 | /hello/*/world/* | /greeting/hello (1) |
375 | | => /greeting/world |
376 '-----------------------+------------------------------'
377 ...
378
67869327 379As you can see, Catalyst only deals with chains as whole paths and
380builds one for each endpoint, which are the actions with C<:Chained> but
381without C<:CaptureArgs>.
05a90578 382
383Let's assume this application gets a request at the path
67869327 384C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
385to the C<hello> action and pass the value C<23> as an argument to it
386after the context. It does so because we have previously used
387C<:CaptureArgs(1)> to declare that it has one path part after itself as
388its argument. We told Catalyst that this is the beginning of the chain
389by specifying C<:Chained('/')>. Also note that instead of saying
390C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
391defaults to the name of the action.
05a90578 392
393After C<hello> has run, Catalyst goes on to dispatch to the C<world>
67869327 394action. This is the last action to be called: Catalyst knows this is an
395endpoint because we did not specify a C<:CaptureArgs>
396attribute. Nevertheless we specify that this action expects an argument,
397but at this point we're using C<:Args(1)> to do that. We could also have
398said C<:Args> or left it out altogether, which would mean this action
399would get all arguments that are there. This action's C<:Chained>
400attribute says C<hello> and tells Catalyst that the C<hello> action in
401the current controller is its parent.
05a90578 402
403With this we have built a chain consisting of two public path parts.
67869327 404C<hello> captures one part of the path as its argument, and also
405specifies the path root as its parent. So this part is
406C</hello/$arg>. The next part is the endpoint C<world>, expecting one
407argument. It sums up to the path part C<world/$arg>. This leads to a
408complete chain of C</hello/$arg/world/$arg> which is matched against the
409requested paths.
410
411This example application would, if run and called by e.g.
412C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
413value C<arg_sum> to "23". The C<world> action would then append "World!"
414to C<message> and add C<12> to the stash's C<arg_sum> value. For the
415sake of simplicity no view is shown. Instead we just put the values of
416the stash into our body. So the output would look like:
05a90578 417
418 Hello World!
419 35
420
67869327 421And our test server would have given us this debugging output for the
05a90578 422request:
423
424 ...
425 [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
426 [debug] Path is "/greeting/world"
427 [debug] Arguments are "12"
428 [info] Request took 0.164113s (6.093/s)
429 .------------------------------------------+-----------.
430 | Action | Time |
431 +------------------------------------------+-----------+
432 | /greeting/hello | 0.000029s |
433 | /greeting/world | 0.000024s |
434 '------------------------------------------+-----------'
435 ...
436
67869327 437What would be common uses of this dispatch technique? It gives the
438possibility to split up logic that contains steps that each depend on
439each other. An example would be, for example, a wiki path like
05a90578 440C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
441these actions:
442
443 sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
444 my ( $self, $c, $page_name ) = @_;
445 # load the page named $page_name and put the object
446 # into the stash
447 }
448
449 sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
450 my ( $self, $c, $revision_id ) = @_;
67869327 451 # use the page object in the stash to get at its
05a90578 452 # revision with number $revision_id
453 }
454
455 sub view : PathPart Chained('rev') Args(0) {
456 my ( $self, $c ) = @_;
67869327 457 # display the revision in our stash. Another option
05a90578 458 # would be to forward a compatible object to the action
459 # that displays the default wiki pages, unless we want
460 # a different interface here, for example restore
461 # functionality.
462 }
463
67869327 464It would now be possible to add other endpoints, for example C<restore>
465to restore this specific revision as the current state.
05a90578 466
67869327 467You don't have to put all the chained actions in one controller. The
468specification of the parent through C<:Chained> also takes an absolute
469action path as its argument. Just specify it with a leading C</>.
05a90578 470
471If you want, for example, to have actions for the public paths
67869327 472C</foo/12/edit> and C</foo/12>, just specify two actions with
05a90578 473C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
67869327 474path needs a C<:CaptureArgs(1)> attribute and a endpoint with
05a90578 475C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
476the action just a C<:Args(1)> to mark it as endpoint. This sums up to
477this debugging output:
478
479 ...
480 [debug] Loaded Path Part actions:
481 .-----------------------+------------------------------.
482 | Path Spec | Private |
483 +-----------------------+------------------------------+
484 | /foo/* | /controller/foo_view |
485 | /foo/*/edit | /controller/foo_load (1) |
486 | | => /controller/edit |
487 '-----------------------+------------------------------'
488 ...
489
3c0186f2 490Here's a more detailed specification of the attributes belonging to
05a90578 491C<:Chained>:
492
493=head2 Attributes
494
495=over 8
496
497=item PathPart
498
499Sets the name of this part of the chain. If it is specified without
500arguments, it takes the name of the action as default. So basically
501C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
502This can also contain slashes to bind to a deeper level. An action
503with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
504C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
505effect as using C<:PathPart>, it would default to the action name.
506
507=item Chained
508
509Has to be specified for every child in the chain. Possible values are
510absolute and relative private action paths, with the relatives pointing
511to the current controller, or a single slash C</> to tell Catalyst that
83784422 512this is the root of a chain. The attribute C<:Chained> without arguments
67869327 513also defaults to the C</> behavior.
05a90578 514
67869327 515Because you can specify an absolute path to the parent action, it
516doesn't matter to Catalyst where that parent is located. So, if your
517design requests it, you can redispatch a chain through any controller or
518namespace you want.
05a90578 519
520Another interesting possibility gives C<:Chained('.')>, which chains
67869327 521itself to an action with the path of the current controller's namespace.
05a90578 522For example:
523
524 # in MyApp::Controller::Foo
525 sub bar : Chained CaptureArgs(1) { ... }
526
527 # in MyApp::Controller::Foo::Bar
528 sub baz : Chained('.') Args(1) { ... }
529
530This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
67869327 531as the argument to Chained here chains the C<baz> action to an action
532with the path of the current controller namespace, namely
533C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
534chain comes out as the end product.
05a90578 535
536=item CaptureArgs
537
67869327 538Must be specified for every part of the chain that is not an
05a90578 539endpoint. With this attribute Catalyst knows how many of the following
67869327 540parts of the path (separated by C</>) this action wants to capture as
541its arguments. If it doesn't expect any, just specify
542C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
543after the context, but you can also find them as array references in
05a90578 544C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
545level of the action in the chain that captured the parts of the path.
546
67869327 547An action that is part of a chain (that is, one that has a C<:Chained>
548attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
549as a chain end.
05a90578 550
551=item Args
552
553By default, endpoints receive the rest of the arguments in the path. You
554can tell Catalyst through C<:Args> explicitly how many arguments your
555endpoint expects, just like you can with C<:CaptureArgs>. Note that this
67869327 556also affects whether this chain is invoked on a request. A chain with an
05a90578 557endpoint specifying one argument will only match if exactly one argument
558exists in the path.
559
560You can specify an exact number of arguments like C<:Args(3)>, including
561C<0>. If you just say C<:Args> without any arguments, it is the same as
67869327 562leaving it out altogether: The chain is matched regardless of the number
05a90578 563of path parts after the endpoint.
564
67869327 565Just as with C<:CaptureArgs>, the arguments get passed to the action in
05a90578 566C<@_> after the context object. They can also be reached through
567C<$c-E<gt>request-E<gt>arguments>.
568
569=back
570
67869327 571=head2 Auto actions, dispatching and forwarding
05a90578 572
573Note that the list of C<auto> actions called depends on the private path
67869327 574of the endpoint of the chain, not on the chained actions way. The
575C<auto> actions will be run before the chain dispatching begins. In
576every other aspect, C<auto> actions behave as documented.
05a90578 577
578The C<forward>ing to other actions does just what you would expect. But if
579you C<detach> out of a chain, the rest of the chain will not get called
67869327 580after the C<detach>.
05a90578 581
141459fa 582=head1 AUTHOR
583
792b40ac 584Matt S Trout <mst@shadowcatsystems.co.uk>
141459fa 585
586=head1 COPYRIGHT
587
588This program is free software, you can redistribute it and/or modify it under
589the same terms as Perl itself.
590
591=cut
592
5931;