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