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