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