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