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