converting the engines. i had to add use NEXT to some of the test files to make it...
[catagits/Catalyst-Runtime.git] / lib / Catalyst / DispatchType / Chained.pm
1 package Catalyst::DispatchType::Chained;
2
3 use Moose;
4 extends 'Catalyst::DispatchType';
5
6 #use strict;
7 #use base qw/Catalyst::DispatchType/;
8 use Text::SimpleTable;
9 use Catalyst::ActionChain;
10 use URI;
11
12 has _endpoints => (
13                    is => 'rw',
14                    isa => 'ArrayRef',
15                    required => 1,
16                    default => sub{ [] },
17                   );
18
19 has _actions => (
20                  is => 'rw',
21                  isa => 'HashRef',
22                  required => 1,
23                  default => sub{ {} },
24                 );
25
26 has _children_of => (
27                      is => 'rw',
28                      isa => 'HashRef',
29                      required => 1,
30                      default => sub{ {} },
31                     );
32
33 # please don't perltidy this. hairy code within.
34
35 =head1 NAME
36
37 Catalyst::DispatchType::Chained - Path Part DispatchType
38
39 =head1 SYNOPSIS
40
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   }
52
53 =head1 DESCRIPTION
54
55 See L</USAGE>.
56
57 =head1 METHODS
58
59 =head2 $self->list($c)
60
61 Debug output for Path Part dispatch points
62
63 =cut
64
65 sub list {
66     my ( $self, $c ) = @_;
67
68     return unless $self->_endpoints;
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 }
76                            @{ $self->_endpoints }
77                   ) {
78         my $args = $endpoint->attributes->{Args}->[0];
79         my @parts = (defined($args) ? (("*") x $args) : '...');
80         my @parents = ();
81         my $parent = "DUMMY";
82         my $curr = $endpoint;
83         while ($curr) {
84             if (my $cap = $curr->attributes->{CaptureArgs}) {
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             }
91             $parent = $curr->attributes->{Chained}->[0];
92             $curr = $self->_actions->{$parent};
93             unshift(@parents, $curr) if $curr;
94         }
95         next ENDPOINT unless $parent eq '/'; # skip dangling action
96         my @rows;
97         foreach my $p (@parents) {
98             my $name = "/${p}";
99             if (my $cap = $p->attributes->{CaptureArgs}) {
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;
110     }
111
112     $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
113 }
114
115 =head2 $self->match( $c, $path )
116
117 Calls C<recurse_match> to see if a chain matches the C<$path>.
118
119 =cut
120
121 sub match {
122     my ( $self, $c, $path ) = @_;
123
124     return 0 if @{$c->req->args};
125
126     my @parts = split('/', $path);
127
128     my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
129     push @{$c->req->args}, @$parts if $parts && @$parts;
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
146 Recursive search for a matching chain.
147
148 =cut
149
150 sub recurse_match {
151     my ( $self, $c, $parent, $path_parts ) = @_;
152     my $children = $self->_children_of->{$parent};
153     return () unless $children;
154     my $best_action;
155     my @captures;
156     TRY: foreach my $try_part (sort { length($b) <=> length($a) }
157                                    keys %$children) {
158                                # $b then $a to try longest part first
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
164                                 @parts, 0, scalar(@{[split('/', $try_part)]})
165                               ))); # @{[]} to avoid split to @_
166         }
167         my @try_actions = @{$children->{$try_part}};
168         TRY_ACTION: foreach my $action (@try_actions) {
169             if (my $capture_attr = $action->attributes->{CaptureArgs}) {
170
171                 # Short-circuit if not enough remaining parts
172                 next TRY_ACTION unless @parts >= $capture_attr->[0];
173
174                 my @captures;
175                 my @parts = @parts; # localise
176
177                 # strip CaptureArgs into list
178                 push(@captures, splice(@parts, 0, $capture_attr->[0]));
179
180                 # try the remaining parts against children of this action
181                 my ($actions, $captures, $action_parts) = $self->recurse_match(
182                                              $c, '/'.$action->reverse, \@parts
183                                            );
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 {
193                 {
194                     local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
195                     next TRY_ACTION unless $action->match($c);
196                 }
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
202                 # OR No parts and this expects 0
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}}   ||
208                     (!@parts && $args_attr eq 0)){
209                     $best_action = {
210                         actions => [ $action ],
211                         captures=> [],
212                         parts   => \@parts
213                     }
214                 }
215             }
216         }
217     }
218     return @$best_action{qw/actions captures parts/} if $best_action;
219     return ();
220 }
221
222 =head2 $self->register( $c, $action )
223
224 Calls register_path for every Path attribute for the given $action.
225
226 =cut
227
228 sub register {
229     my ( $self, $c, $action ) = @_;
230
231     my @chained_attr = @{ $action->attributes->{Chained} || [] };
232
233     return 0 unless @chained_attr;
234
235     if (@chained_attr > 2) {
236         Catalyst::Exception->throw(
237           "Multiple Chained attributes not supported registering ${action}"
238         );
239     }
240
241     my $parent = $chained_attr[0];
242
243     if (defined($parent) && length($parent)) {
244         if ($parent eq '.') {
245             $parent = '/'.$action->namespace;
246         } elsif ($parent !~ m/^\//) {
247             if ($action->namespace) {
248                 $parent = '/'.join('/', $action->namespace, $parent);
249             } else {
250                 $parent = '/'.$parent; # special case namespace '' (root)
251             }
252         }
253     } else {
254         $parent = '/'
255     }
256
257     $action->attributes->{Chained} = [ $parent ];
258
259     my $children = ($self->_children_of->{$parent} ||= {});
260
261     my @path_part = @{ $action->attributes->{PathPart} || [] };
262
263     my $part = $action->name;
264
265     if (@path_part == 1 && defined $path_part[0]) {
266         $part = $path_part[0];
267     } elsif (@path_part > 1) {
268         Catalyst::Exception->throw(
269           "Multiple PathPart attributes not supported registering ${action}"
270         );
271     }
272
273     if ($part =~ m(^/)) {
274         Catalyst::Exception->throw(
275           "Absolute parameters to PathPart not allowed registering ${action}"
276         );
277     }
278
279     $action->attributes->{PartPath} = [ $part ];
280
281     unshift(@{ $children->{$part} ||= [] }, $action);
282
283     $self->_actions->{'/'.$action->reverse} = $action;
284
285     unless ($action->attributes->{CaptureArgs}) {
286         unshift(@{ $self->_endpoints }, $action);
287     }
288
289     return 1;
290 }
291
292 =head2 $self->uri_for_action($action, $captures)
293
294 Get the URI part for the action, using C<$captures> to fill
295 the capturing parts.
296
297 =cut
298
299 sub uri_for_action {
300     my ( $self, $action, $captures ) = @_;
301
302     return undef unless ($action->attributes->{Chained}
303                            && !$action->attributes->{CaptureArgs});
304
305     my @parts = ();
306     my @captures = @$captures;
307     my $parent = "DUMMY";
308     my $curr = $action;
309     while ($curr) {
310         if (my $cap = $curr->attributes->{CaptureArgs}) {
311             return undef unless @captures >= $cap->[0]; # not enough captures
312             if ($cap->[0]) {
313                 unshift(@parts, splice(@captures, -$cap->[0]));
314             }
315         }
316         if (my $pp = $curr->attributes->{PartPath}) {
317             unshift(@parts, $pp->[0])
318                 if (defined($pp->[0]) && length($pp->[0]));
319         }
320         $parent = $curr->attributes->{Chained}->[0];
321         $curr = $self->_actions->{$parent};
322     }
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);
329
330 }
331
332 =head1 USAGE
333
334 =head2 Introduction
335
336 The C<Chained> attribute allows you to chain public path parts together
337 by their private names. A chain part's path can be specified with
338 C<PathPart> and can be declared to expect an arbitrary number of
339 arguments. The endpoint of the chain specifies how many arguments it
340 gets through the C<Args> attribute. C<:Args(0)> would be none at all,
341 C<:Args> without an integer would be unlimited. The path parts that
342 aren't endpoints are using C<CaptureArgs> to specify how many parameters
343 they expect to receive. As an example setup:
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
365 The debug output provides a separate table for chained actions, showing
366 the whole chain as it would match and the actions it contains. Here's an
367 example of the startup output with our actions above:
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
379 As you can see, Catalyst only deals with chains as whole paths and
380 builds one for each endpoint, which are the actions with C<:Chained> but
381 without C<:CaptureArgs>.
382
383 Let's assume this application gets a request at the path
384 C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
385 to the C<hello> action and pass the value C<23> as an argument to it
386 after the context. It does so because we have previously used
387 C<:CaptureArgs(1)> to declare that it has one path part after itself as
388 its argument. We told Catalyst that this is the beginning of the chain
389 by specifying C<:Chained('/')>. Also note that instead of saying
390 C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
391 defaults to the name of the action.
392
393 After C<hello> has run, Catalyst goes on to dispatch to the C<world>
394 action. This is the last action to be called: Catalyst knows this is an
395 endpoint because we did not specify a C<:CaptureArgs>
396 attribute. Nevertheless we specify that this action expects an argument,
397 but at this point we're using C<:Args(1)> to do that. We could also have
398 said C<:Args> or left it out altogether, which would mean this action
399 would get all arguments that are there. This action's C<:Chained>
400 attribute says C<hello> and tells Catalyst that the C<hello> action in
401 the current controller is its parent.
402
403 With this we have built a chain consisting of two public path parts.
404 C<hello> captures one part of the path as its argument, and also
405 specifies the path root as its parent. So this part is
406 C</hello/$arg>. The next part is the endpoint C<world>, expecting one
407 argument. It sums up to the path part C<world/$arg>. This leads to a
408 complete chain of C</hello/$arg/world/$arg> which is matched against the
409 requested paths.
410
411 This example application would, if run and called by e.g.
412 C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
413 value C<arg_sum> to "23". The C<world> action would then append "World!"
414 to C<message> and add C<12> to the stash's C<arg_sum> value.  For the
415 sake of simplicity no view is shown. Instead we just put the values of
416 the stash into our body. So the output would look like:
417
418   Hello World!
419   35
420
421 And our test server would have given us this debugging output for the
422 request:
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
437 What would be common uses of this dispatch technique? It gives the
438 possibility to split up logic that contains steps that each depend on
439 each other. An example would be, for example, a wiki path like
440 C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
441 these 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 ) = @_;
451       #  use the page object in the stash to get at its
452       #  revision with number $revision_id
453   }
454
455   sub view : PathPart Chained('rev') Args(0) {
456       my ( $self, $c ) = @_;
457       #  display the revision in our stash. Another option
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
464 It would now be possible to add other endpoints, for example C<restore>
465 to restore this specific revision as the current state.
466
467 You don't have to put all the chained actions in one controller. The
468 specification of the parent through C<:Chained> also takes an absolute
469 action path as its argument. Just specify it with a leading C</>.
470
471 If you want, for example, to have actions for the public paths
472 C</foo/12/edit> and C</foo/12>, just specify two actions with
473 C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
474 path needs a C<:CaptureArgs(1)> attribute and a endpoint with
475 C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
476 the action just a C<:Args(1)> to mark it as endpoint. This sums up to
477 this 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
490 Here's a more detailed specification of the attributes belonging to
491 C<:Chained>:
492
493 =head2 Attributes
494
495 =over 8
496
497 =item PathPart
498
499 Sets the name of this part of the chain. If it is specified without
500 arguments, it takes the name of the action as default. So basically
501 C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
502 This can also contain slashes to bind to a deeper level. An action
503 with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
504 C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
505 effect as using C<:PathPart>, it would default to the action name.
506
507 =item Chained
508
509 Has to be specified for every child in the chain. Possible values are
510 absolute and relative private action paths, with the relatives pointing
511 to the current controller, or a single slash C</> to tell Catalyst that
512 this is the root of a chain. The attribute C<:Chained> without arguments
513 also defaults to the C</> behavior.
514
515 Because you can specify an absolute path to the parent action, it
516 doesn't matter to Catalyst where that parent is located. So, if your
517 design requests it, you can redispatch a chain through any controller or
518 namespace you want.
519
520 Another interesting possibility gives C<:Chained('.')>, which chains
521 itself to an action with the path of the current controller's namespace.
522 For 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
530 This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
531 as the argument to Chained here chains the C<baz> action to an action
532 with the path of the current controller namespace, namely
533 C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
534 chain comes out as the end product.
535
536 =item CaptureArgs
537
538 Must be specified for every part of the chain that is not an
539 endpoint. With this attribute Catalyst knows how many of the following
540 parts of the path (separated by C</>) this action wants to capture as
541 its arguments. If it doesn't expect any, just specify
542 C<:CaptureArgs(0)>.  The captures get passed to the action's C<@_> right
543 after the context, but you can also find them as array references in
544 C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
545 level of the action in the chain that captured the parts of the path.
546
547 An action that is part of a chain (that is, one that has a C<:Chained>
548 attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
549 as a chain end.
550
551 =item Args
552
553 By default, endpoints receive the rest of the arguments in the path. You
554 can tell Catalyst through C<:Args> explicitly how many arguments your
555 endpoint expects, just like you can with C<:CaptureArgs>. Note that this
556 also affects whether this chain is invoked on a request. A chain with an
557 endpoint specifying one argument will only match if exactly one argument
558 exists in the path.
559
560 You can specify an exact number of arguments like C<:Args(3)>, including
561 C<0>. If you just say C<:Args> without any arguments, it is the same as
562 leaving it out altogether: The chain is matched regardless of the number
563 of path parts after the endpoint.
564
565 Just as with C<:CaptureArgs>, the arguments get passed to the action in
566 C<@_> after the context object. They can also be reached through
567 C<$c-E<gt>request-E<gt>arguments>.
568
569 =back
570
571 =head2 Auto actions, dispatching and forwarding
572
573 Note that the list of C<auto> actions called depends on the private path
574 of the endpoint of the chain, not on the chained actions way. The
575 C<auto> actions will be run before the chain dispatching begins. In
576 every other aspect, C<auto> actions behave as documented.
577
578 The C<forward>ing to other actions does just what you would expect. But if
579 you C<detach> out of a chain, the rest of the chain will not get called
580 after the C<detach>.
581
582 =head1 AUTHOR
583
584 Matt S Trout <mst@shadowcatsystems.co.uk>
585
586 =head1 COPYRIGHT
587
588 This program is free software, you can redistribute it and/or modify it under
589 the same terms as Perl itself.
590
591 =cut
592
593 1;