reverting back to when tests pass. applying changes one by one to find what failed
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
059c085b 3use Moose;
4
a2f2cde9 5use Catalyst::Exception;
f05af9ba 6use Catalyst::Utils;
fbcc39ad 7use Catalyst::Action;
b7aebc12 8use Catalyst::ActionContainer;
b96f127f 9use Catalyst::DispatchType::Default;
bcccee4e 10use Catalyst::DispatchType::Index;
87b85407 11use Text::SimpleTable;
1abd6db7 12use Tree::Simple;
13use Tree::Simple::Visitor::FindByPath;
e72f8f51 14use Scalar::Util ();
1abd6db7 15
fbcc39ad 16# Stringify to class
17use overload '""' => sub { return ref shift }, fallback => 1;
18
6d030e6f 19
20# Preload these action types
61a9002d 21our @PRELOAD = qw/Index Path Regex/;
1abd6db7 22
2d1d8f91 23# Postload these action types
61a9002d 24our @POSTLOAD = qw/Default/;
2d1d8f91 25
059c085b 26has _tree => (is => 'rw');
27has _dispatch_types => (is => 'rw');
28has _registered_dispatch_types => (is => 'rw');
29has _method_action_class => (is => 'rw');
30has _action_container_class => (is => 'rw');
31has preload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@PRELOAD] });
32has postload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@POSTLOAD] });
33has _action_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
34has _container_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
35
36no Moose;
37
1abd6db7 38=head1 NAME
39
9c053379 40Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 41
42=head1 SYNOPSIS
43
44See L<Catalyst>.
45
46=head1 DESCRIPTION
47
4ab87e27 48This is the class that maps public urls to actions in your Catalyst
49application based on the attributes you set.
50
1abd6db7 51=head1 METHODS
52
e8b9f2a9 53=head2 new
4ab87e27 54
55Construct a new dispatcher.
56
e7bb8d33 57=cut
58
059c085b 59sub BUILD {
60 my ($self, $params) = @_;
9e81ba44 61
e8b9f2a9 62 my $container =
059c085b 63 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
a13e21ab 64
059c085b 65 $self->_tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
e7bb8d33 66}
67
68=head2 $self->preload_dispatch_types
69
70An arrayref of pre-loaded dispatchtype classes
71
72Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
73To use a custom class outside the regular C<Catalyst> namespace, prefix
74it with a C<+>, like so:
75
76 +My::Dispatch::Type
77
78=head2 $self->postload_dispatch_types
79
80An arrayref of post-loaded dispatchtype classes
81
82Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
83To use a custom class outside the regular C<Catalyst> namespace, prefix
84it with a C<+>, like so:
85
86 +My::Dispatch::Type
87
b5ecfcf0 88=head2 $self->detach( $c, $command [, \@arguments ] )
6ef62eb2 89
4ab87e27 90Documented in L<Catalyst>
91
6ef62eb2 92=cut
93
94sub detach {
fbcc39ad 95 my ( $self, $c, $command, @args ) = @_;
bd7d2e94 96 $c->forward( $command, @args ) if $command;
fbcc39ad 97 die $Catalyst::DETACH;
6ef62eb2 98}
99
b5ecfcf0 100=head2 $self->dispatch($c)
1abd6db7 101
4ab87e27 102Delegate the dispatch to the action that matched the url, or return a
103message about unknown resource
104
105
1abd6db7 106=cut
107
108sub dispatch {
fbcc39ad 109 my ( $self, $c ) = @_;
66e28e3f 110 if ( $c->action ) {
28591cd7 111 $c->forward( join( '/', '', $c->action->namespace, '_DISPATCH' ) );
fbcc39ad 112 }
113
114 else {
1abd6db7 115 my $path = $c->req->path;
116 my $error = $path
117 ? qq/Unknown resource "$path"/
118 : "No default action defined";
119 $c->log->error($error) if $c->debug;
120 $c->error($error);
121 }
122}
123
b5ecfcf0 124=head2 $self->forward( $c, $command [, \@arguments ] )
1abd6db7 125
4ab87e27 126Documented in L<Catalyst>
127
1abd6db7 128=cut
129
130sub forward {
e72f8f51 131 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 132
1abd6db7 133 unless ($command) {
134 $c->log->debug('Nothing to forward to') if $c->debug;
135 return 0;
136 }
99fe1710 137
e72f8f51 138 my @args;
e8b9f2a9 139
e72f8f51 140 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
141 @args = @{ pop @extra_params }
142 } else {
143 # this is a copy, it may take some abuse from ->_invoke_as_path if the path had trailing parts
144 @args = @{ $c->request->arguments };
145 }
146
147 my $action;
148
149 # forward to a string path ("/foo/bar/gorch") or action object which stringifies to that
150 $action = $self->_invoke_as_path( $c, "$command", \@args );
99fe1710 151
e72f8f51 152 # forward to a component ( "MyApp::*::Foo" or $c->component("...") - a path or an object)
153 unless ($action) {
154 my $method = @extra_params ? $extra_params[0] : "process";
155 $action = $self->_invoke_as_component( $c, $command, $method );
156 }
99fe1710 157
99fe1710 158
e540158b 159 unless ($action) {
160 my $error =
161 qq/Couldn't forward to command "$command": /
162 . qq/Invalid action or component./;
163 $c->error($error);
164 $c->log->debug($error) if $c->debug;
165 return 0;
166 }
bd7d2e94 167
e540158b 168 #push @$args, @_;
adb53907 169
059c085b 170 no warnings 'recursion';
171
e72f8f51 172 local $c->request->{arguments} = \@args;
b8f669f3 173 $action->dispatch( $c );
99fe1710 174
1abd6db7 175 return $c->state;
176}
177
adb53907 178sub _action_rel2abs {
e540158b 179 my ( $self, $c, $path ) = @_;
180
181 unless ( $path =~ m#^/# ) {
182 my $namespace = $c->stack->[-1]->namespace;
183 $path = "$namespace/$path";
184 }
185
186 $path =~ s#^/##;
187 return $path;
adb53907 188}
189
190sub _invoke_as_path {
e540158b 191 my ( $self, $c, $rel_path, $args ) = @_;
192
e540158b 193 my $path = $self->_action_rel2abs( $c, $rel_path );
194
195 my ( $tail, @extra_args );
196 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
197 { # allow $path to be empty
198 if ( my $action = $c->get_action( $tail, $path ) ) {
199 push @$args, @extra_args;
200 return $action;
201 }
202 else {
203 return
204 unless $path
205 ; # if a match on the global namespace failed then the whole lookup failed
206 }
207
208 unshift @extra_args, $tail;
209 }
adb53907 210}
211
212sub _find_component_class {
e540158b 213 my ( $self, $c, $component ) = @_;
adb53907 214
e540158b 215 return ref($component)
216 || ref( $c->component($component) )
217 || $c->component($component);
adb53907 218}
219
220sub _invoke_as_component {
e540158b 221 my ( $self, $c, $component, $method ) = @_;
222
223 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 224
225 if ( my $code = $class->can($method) ) {
059c085b 226 return $self->_method_action_class->new(
e540158b 227 {
228 name => $method,
229 code => $code,
230 reverse => "$class->$method",
231 class => $class,
232 namespace => Catalyst::Utils::class2prefix(
233 $class, $c->config->{case_sensitive}
234 ),
235 }
236 );
237 }
238 else {
239 my $error =
240 qq/Couldn't forward to "$class". Does not implement "$method"/;
241 $c->error($error);
242 $c->log->debug($error)
243 if $c->debug;
244 return 0;
245 }
adb53907 246}
247
b5ecfcf0 248=head2 $self->prepare_action($c)
fbcc39ad 249
4ab87e27 250Find an dispatch type that matches $c->req->path, and set args from it.
251
fbcc39ad 252=cut
253
254sub prepare_action {
255 my ( $self, $c ) = @_;
256 my $path = $c->req->path;
257 my @path = split /\//, $c->req->path;
258 $c->req->args( \my @args );
259
61a9002d 260 unshift( @path, '' ); # Root action
78d760bb 261
b96f127f 262 DESCEND: while (@path) {
fbcc39ad 263 $path = join '/', @path;
61a9002d 264 $path =~ s#^/##;
fbcc39ad 265
61a9002d 266 $path = '' if $path eq '/'; # Root action
78d760bb 267
22f3a8dd 268 # Check out dispatch types to see if any will handle the path at
269 # this level
270
059c085b 271 foreach my $type ( @{ $self->_dispatch_types } ) {
2633d7dc 272 last DESCEND if $type->match( $c, $path );
66e28e3f 273 }
b96f127f 274
22f3a8dd 275 # If not, move the last part path to args
4082e678 276 my $arg = pop(@path);
277 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
278 unshift @args, $arg;
fbcc39ad 279 }
280
cccc8f68 281 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$c->req->captures||[]};
66d7ad40 282
e3a13771 283 $c->log->debug( 'Path is "' . $c->req->match . '"' )
284 if ( $c->debug && $c->req->match );
285
fbcc39ad 286 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
287 if ( $c->debug && @args );
288}
289
b5ecfcf0 290=head2 $self->get_action( $action, $namespace )
1abd6db7 291
4ab87e27 292returns a named action from a given namespace.
293
1abd6db7 294=cut
295
296sub get_action {
bcd1002b 297 my ( $self, $name, $namespace ) = @_;
79a3189a 298 return unless $name;
3d0d6d21 299
28928de9 300 $namespace = join( "/", grep { length } split '/', $namespace || "" );
99fe1710 301
059c085b 302 return $self->_action_hash->{"$namespace/$name"};
1abd6db7 303}
304
e8b9f2a9 305=head2 $self->get_action_by_path( $path );
306
307Returns the named action by its full path.
3d0d6d21 308
e8b9f2a9 309=cut
3d0d6d21 310
311sub get_action_by_path {
312 my ( $self, $path ) = @_;
ea0e58d9 313 $path =~ s/^\///;
28928de9 314 $path = "/$path" unless $path =~ /\//;
059c085b 315 $self->_action_hash->{$path};
3d0d6d21 316}
317
b5ecfcf0 318=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 319
320=cut
321
322sub get_actions {
323 my ( $self, $c, $action, $namespace ) = @_;
324 return [] unless $action;
3d0d6d21 325
28928de9 326 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 327
328 my @match = $self->get_containers($namespace);
329
684d10ed 330 return map { $_->get_action($action) } @match;
a9dc674c 331}
332
b5ecfcf0 333=head2 $self->get_containers( $namespace )
cfd04b0c 334
4ab87e27 335Return all the action containers for a given namespace, inclusive
336
cfd04b0c 337=cut
338
339sub get_containers {
340 my ( $self, $namespace ) = @_;
a13e21ab 341 $namespace ||= '';
342 $namespace = '' if $namespace eq '/';
cfd04b0c 343
a13e21ab 344 my @containers;
cfd04b0c 345
7f23827b 346 if ( length $namespace ) {
347 do {
059c085b 348 push @containers, $self->_container_hash->{$namespace};
7f23827b 349 } while ( $namespace =~ s#/[^/]+$## );
350 }
90ce41ba 351
059c085b 352 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 353
a13e21ab 354 my @parts = split '/', $namespace;
cfd04b0c 355}
356
ea0e58d9 357=head2 $self->uri_for_action($action, \@captures)
358
359Takes a Catalyst::Action object and action parameters and returns a URI
360part such that if $c->req->path were this URI part, this action would be
361dispatched to with $c->req->captures set to the supplied arrayref.
362
363If the action object is not available for external dispatch or the dispatcher
364cannot determine an appropriate URI, this method will return undef.
365
366=cut
367
368sub uri_for_action {
369 my ( $self, $action, $captures) = @_;
370 $captures ||= [];
059c085b 371 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 372 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 373 return( $uri eq '' ? '/' : $uri )
374 if defined($uri);
ea0e58d9 375 }
376 return undef;
377}
378
b5ecfcf0 379=head2 $self->register( $c, $action )
aad72cc9 380
4ab87e27 381Make sure all required dispatch types for this action are loaded, then
382pass the action to our dispatch types so they can register it if required.
383Also, set up the tree with the action containers.
384
aad72cc9 385=cut
386
79a3189a 387sub register {
388 my ( $self, $c, $action ) = @_;
389
059c085b 390 my $registered = $self->_registered_dispatch_types;
694d15f1 391
392 my $priv = 0;
393 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 394 next if $key eq 'Private';
694d15f1 395 my $class = "Catalyst::DispatchType::$key";
396 unless ( $registered->{$class} ) {
e8b9f2a9 397 eval "require $class";
059c085b 398 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 399 $registered->{$class} = 1;
400 }
401 }
402
403 # Pass the action to our dispatch types so they can register it if reqd.
059c085b 404 foreach my $type ( @{ $self->_dispatch_types } ) {
9a6ecf4f 405 $type->register( $c, $action );
694d15f1 406 }
407
79a3189a 408 my $namespace = $action->namespace;
a13e21ab 409 my $name = $action->name;
c7116517 410
ad5e4650 411 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 412
413 # Set the method value
a13e21ab 414 $container->add_action($action);
c7116517 415
059c085b 416 $self->_action_hash->{"$namespace/$name"} = $action;
417 $self->_container_hash->{$namespace} = $container;
15e9b5dd 418}
419
ad5e4650 420sub _find_or_create_action_container {
a13e21ab 421 my ( $self, $namespace ) = @_;
422
059c085b 423 my $tree ||= $self->_tree;
99fe1710 424
a13e21ab 425 return $tree->getNodeValue unless $namespace;
78d760bb 426
a13e21ab 427 my @namespace = split '/', $namespace;
428 return $self->_find_or_create_namespace_node( $tree, @namespace )
429 ->getNodeValue;
8505565b 430}
90ce41ba 431
8505565b 432sub _find_or_create_namespace_node {
a13e21ab 433 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 434
a13e21ab 435 return $parent unless $part;
8505565b 436
a13e21ab 437 my $child =
438 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 439
a13e21ab 440 unless ($child) {
441 my $container = Catalyst::ActionContainer->new($part);
442 $parent->addChild( $child = Tree::Simple->new($container) );
443 }
99fe1710 444
a13e21ab 445 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 446}
447
4ab87e27 448=head2 $self->setup_actions( $class, $context )
449
1abd6db7 450
451=cut
452
453sub setup_actions {
11bd4e3e 454 my ( $self, $c ) = @_;
99fe1710 455
059c085b 456 $self->_dispatch_types( [] );
457 $self->_registered_dispatch_types( {} );
458 $self->_method_action_class('Catalyst::Action');
459 $self->_action_container_class('Catalyst::ActionContainer');
12e28165 460
9e81ba44 461 my @classes =
ad5e4650 462 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
059c085b 463 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 464
49070d25 465 foreach my $comp ( values %{ $c->components } ) {
466 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 467 }
e494bd6b 468
ad5e4650 469 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 470
11bd4e3e 471 return unless $c->debug;
99fe1710 472
684d10ed 473 my $privates = Text::SimpleTable->new(
dbf03873 474 [ 20, 'Private' ],
34d28dfd 475 [ 36, 'Class' ],
dbf03873 476 [ 12, 'Method' ]
684d10ed 477 );
99fe1710 478
87b85407 479 my $has_private = 0;
1abd6db7 480 my $walker = sub {
481 my ( $walker, $parent, $prefix ) = @_;
482 $prefix .= $parent->getNodeValue || '';
483 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 484 my $node = $parent->getNodeValue->actions;
99fe1710 485
78d760bb 486 for my $action ( keys %{$node} ) {
b7aebc12 487 my $action_obj = $node->{$action};
b0bb11ec 488 next
489 if ( ( $action =~ /^_.*/ )
490 && ( !$c->config->{show_internal_actions} ) );
684d10ed 491 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 492 $has_private = 1;
1abd6db7 493 }
99fe1710 494
1abd6db7 495 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
496 };
99fe1710 497
059c085b 498 $walker->( $walker, $self->_tree, '' );
1cf0345b 499 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
500 if $has_private;
99fe1710 501
a9cbd748 502 # List all public actions
059c085b 503 $_->list($c) for @{ $self->_dispatch_types };
1abd6db7 504}
505
ad5e4650 506sub _load_dispatch_types {
9e81ba44 507 my ( $self, @types ) = @_;
508
509 my @loaded;
510
511 # Preload action types
512 for my $type (@types) {
513 my $class =
514 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
e8b9f2a9 515 eval "require $class";
9e81ba44 516 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
517 if $@;
059c085b 518 push @{ $self->_dispatch_types }, $class->new;
9e81ba44 519
520 push @loaded, $class;
521 }
522
a13e21ab 523 return @loaded;
9e81ba44 524}
525
059c085b 526=head2 meta
527
528Provided by Moose
529
1abd6db7 530=head1 AUTHOR
531
532Sebastian Riedel, C<sri@cpan.org>
158c88c0 533Matt S Trout, C<mst@shadowcatsystems.co.uk>
1abd6db7 534
535=head1 COPYRIGHT
536
537This program is free software, you can redistribute it and/or modify it under
538the same terms as Perl itself.
539
540=cut
541
5421;