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