make sure we still properly decode the part name
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
059c085b 3use Moose;
068c0898 4use Class::MOP;
10954d1d 5with 'MooseX::Emulate::Class::Accessor::Fast';
059c085b 6
a2f2cde9 7use Catalyst::Exception;
f05af9ba 8use Catalyst::Utils;
fbcc39ad 9use Catalyst::Action;
b7aebc12 10use Catalyst::ActionContainer;
b96f127f 11use Catalyst::DispatchType::Default;
bcccee4e 12use Catalyst::DispatchType::Index;
39fc2ce1 13use Catalyst::Utils;
87b85407 14use Text::SimpleTable;
1abd6db7 15use Tree::Simple;
16use Tree::Simple::Visitor::FindByPath;
e7399d8b 17use Class::Load qw(load_class try_load_class);
0ca510f0 18use Encode 2.21 'decode_utf8';
1abd6db7 19
792d99b4 20use namespace::clean -except => 'meta';
6f3df815 21
c41cfce3 22# Refactoring note:
23# do these belong as package vars or should we build these via a builder method?
24# See Catalyst-Plugin-Server for them being added to, which should be much less ugly.
25
6d030e6f 26# Preload these action types
9c1fc6d6 27our @PRELOAD = qw/Index Path/;
1abd6db7 28
2d1d8f91 29# Postload these action types
61a9002d 30our @POSTLOAD = qw/Default/;
2d1d8f91 31
c41cfce3 32# Note - see back-compat methods at end of file.
792d99b4 33has _tree => (is => 'rw', builder => '_build__tree');
5b8ac6cc 34has dispatch_types => (is => 'rw', default => sub { [] }, required => 1, lazy => 1);
c41cfce3 35has _registered_dispatch_types => (is => 'rw', default => sub { {} }, required => 1, lazy => 1);
36has _method_action_class => (is => 'rw', default => 'Catalyst::Action');
37has _action_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
38has _container_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
7ffc9d9d 39
11ff9b94 40my %dispatch_types = ( pre => \@PRELOAD, post => \@POSTLOAD );
41foreach my $type (keys %dispatch_types) {
42 has $type . "load_dispatch_types" => (
43 is => 'rw', required => 1, lazy => 1, default => sub { $dispatch_types{$type} },
44 traits => ['MooseX::Emulate::Class::Accessor::Fast::Meta::Role::Attribute'], # List assignment is CAF style
45 );
46}
083ee5d9 47
1abd6db7 48=head1 NAME
49
9c053379 50Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 51
52=head1 SYNOPSIS
53
54See L<Catalyst>.
55
56=head1 DESCRIPTION
57
4ab87e27 58This is the class that maps public urls to actions in your Catalyst
59application based on the attributes you set.
60
1abd6db7 61=head1 METHODS
62
b0ad47c1 63=head2 new
4ab87e27 64
65Construct a new dispatcher.
66
e7bb8d33 67=cut
68
792d99b4 69sub _build__tree {
70 my ($self) = @_;
9e81ba44 71
068c0898 72 my $container =
059c085b 73 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
a13e21ab 74
792d99b4 75 return Tree::Simple->new($container, Tree::Simple->ROOT);
e7bb8d33 76}
77
78=head2 $self->preload_dispatch_types
79
80An arrayref of pre-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
88=head2 $self->postload_dispatch_types
89
90An arrayref of post-loaded dispatchtype classes
91
92Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
93To use a custom class outside the regular C<Catalyst> namespace, prefix
94it with a C<+>, like so:
95
96 +My::Dispatch::Type
97
b5ecfcf0 98=head2 $self->dispatch($c)
1abd6db7 99
4ab87e27 100Delegate the dispatch to the action that matched the url, or return a
101message about unknown resource
102
1abd6db7 103=cut
104
105sub dispatch {
fbcc39ad 106 my ( $self, $c ) = @_;
e63bdf38 107 if ( my $action = $c->action ) {
108 $c->forward( join( '/', '', $action->namespace, '_DISPATCH' ) );
fbcc39ad 109 }
fbcc39ad 110 else {
1abd6db7 111 my $path = $c->req->path;
0ca510f0 112 $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
113 $path = decode_utf8($path);
114
1abd6db7 115 my $error = $path
116 ? qq/Unknown resource "$path"/
117 : "No default action defined";
118 $c->log->error($error) if $c->debug;
119 $c->error($error);
120 }
121}
122
2f381252 123# $self->_command2action( $c, $command [, \@arguments ] )
b456f8f3 124# $self->_command2action( $c, $command [, \@captures, \@arguments ] )
125# Search for an action, from the command and returns C<($action, $args, $captures)> on
2f381252 126# success. Returns C<(0)> on error.
1abd6db7 127
2f381252 128sub _command2action {
e72f8f51 129 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 130
1abd6db7 131 unless ($command) {
2f381252 132 $c->log->debug('Nothing to go to') if $c->debug;
1abd6db7 133 return 0;
134 }
99fe1710 135
b456f8f3 136 my (@args, @captures);
137
138 if ( ref( $extra_params[-2] ) eq 'ARRAY' ) {
e1e81442 139 @captures = @{ splice @extra_params, -2, 1 };
b456f8f3 140 }
068c0898 141
e72f8f51 142 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
143 @args = @{ pop @extra_params }
144 } else {
2f381252 145 # this is a copy, it may take some abuse from
146 # ->_invoke_as_path if the path had trailing parts
e72f8f51 147 @args = @{ $c->request->arguments };
148 }
149
150 my $action;
151
2f381252 152 # go to a string path ("/foo/bar/gorch")
e31b525c 153 # or action object
7e95ba12 154 if (blessed($command) && $command->isa('Catalyst::Action')) {
e31b525c 155 $action = $command;
156 }
157 else {
158 $action = $self->_invoke_as_path( $c, "$command", \@args );
159 }
99fe1710 160
76ac74ec 161 # go to a component ( "View::Foo" or $c->component("...")
2f381252 162 # - a path or an object)
e72f8f51 163 unless ($action) {
164 my $method = @extra_params ? $extra_params[0] : "process";
165 $action = $self->_invoke_as_component( $c, $command, $method );
166 }
99fe1710 167
b456f8f3 168 return $action, \@args, \@captures;
2f381252 169}
170
ae0e35ee 171=head2 $self->visit( $c, $command [, \@arguments ] )
2f381252 172
173Documented in L<Catalyst>
174
175=cut
176
ae0e35ee 177sub visit {
2f381252 178 my $self = shift;
ae0e35ee 179 $self->_do_visit('visit', @_);
180}
181
182sub _do_visit {
183 my $self = shift;
184 my $opname = shift;
2f381252 185 my ( $c, $command ) = @_;
b456f8f3 186 my ( $action, $args, $captures ) = $self->_command2action(@_);
ae0e35ee 187 my $error = qq/Couldn't $opname("$command"): /;
2f381252 188
ae0e35ee 189 if (!$action) {
3ea37672 190 $error .= qq/Couldn't $opname to command "$command": /
191 .qq/Invalid action or component./;
ae0e35ee 192 }
193 elsif (!defined $action->namespace) {
194 $error .= qq/Action has no namespace: cannot $opname() to a plain /
382d317c 195 .qq/method or component, must be an :Action of some sort./
ae0e35ee 196 }
197 elsif (!$action->class->can('_DISPATCH')) {
198 $error .= qq/Action cannot _DISPATCH. /
199 .qq/Did you try to $opname() a non-controller action?/;
200 }
201 else {
202 $error = q();
203 }
204
205 if($error) {
2f381252 206 $c->error($error);
207 $c->log->debug($error) if $c->debug;
208 return 0;
209 }
210
52f71256 211 $action = $self->expand_action($action);
212
2f381252 213 local $c->request->{arguments} = $args;
b456f8f3 214 local $c->request->{captures} = $captures;
ae0e35ee 215 local $c->{namespace} = $action->{'namespace'};
216 local $c->{action} = $action;
217
2f381252 218 $self->dispatch($c);
ae0e35ee 219}
220
221=head2 $self->go( $c, $command [, \@arguments ] )
222
223Documented in L<Catalyst>
224
225=cut
2f381252 226
ae0e35ee 227sub go {
228 my $self = shift;
229 $self->_do_visit('go', @_);
f87b7c21 230 Catalyst::Exception::Go->throw;
2f381252 231}
232
233=head2 $self->forward( $c, $command [, \@arguments ] )
234
235Documented in L<Catalyst>
236
237=cut
238
239sub forward {
240 my $self = shift;
6f3df815 241 no warnings 'recursion';
3ea37672 242 $self->_do_forward(forward => @_);
243}
244
245sub _do_forward {
246 my $self = shift;
247 my $opname = shift;
2f381252 248 my ( $c, $command ) = @_;
b456f8f3 249 my ( $action, $args, $captures ) = $self->_command2action(@_);
99fe1710 250
3ea37672 251 if (!$action) {
252 my $error .= qq/Couldn't $opname to command "$command": /
253 .qq/Invalid action or component./;
e540158b 254 $c->error($error);
255 $c->log->debug($error) if $c->debug;
256 return 0;
257 }
bd7d2e94 258
059c085b 259
12f0342e 260 local $c->request->{arguments} = $args;
6f3df815 261 no warnings 'recursion';
b8f669f3 262 $action->dispatch( $c );
3ea37672 263
1abd6db7 264 return $c->state;
265}
266
3ea37672 267=head2 $self->detach( $c, $command [, \@arguments ] )
268
269Documented in L<Catalyst>
270
271=cut
272
273sub detach {
274 my ( $self, $c, $command, @args ) = @_;
275 $self->_do_forward(detach => $c, $command, @args ) if $command;
f87b7c21 276 Catalyst::Exception::Detach->throw;
3ea37672 277}
278
adb53907 279sub _action_rel2abs {
e540158b 280 my ( $self, $c, $path ) = @_;
281
282 unless ( $path =~ m#^/# ) {
283 my $namespace = $c->stack->[-1]->namespace;
284 $path = "$namespace/$path";
285 }
286
287 $path =~ s#^/##;
288 return $path;
adb53907 289}
290
291sub _invoke_as_path {
e540158b 292 my ( $self, $c, $rel_path, $args ) = @_;
293
e540158b 294 my $path = $self->_action_rel2abs( $c, $rel_path );
295
296 my ( $tail, @extra_args );
297 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
298 { # allow $path to be empty
299 if ( my $action = $c->get_action( $tail, $path ) ) {
300 push @$args, @extra_args;
301 return $action;
302 }
303 else {
304 return
305 unless $path
306 ; # if a match on the global namespace failed then the whole lookup failed
307 }
308
309 unshift @extra_args, $tail;
310 }
adb53907 311}
312
02298d3a 313sub _find_component {
e540158b 314 my ( $self, $c, $component ) = @_;
adb53907 315
02298d3a 316 # fugly, why doesn't ->component('MyApp') work?
317 return $c if ($component eq blessed($c));
318
319 return blessed($component)
320 ? $component
321 : $c->component($component);
adb53907 322}
323
324sub _invoke_as_component {
02298d3a 325 my ( $self, $c, $component_or_class, $method ) = @_;
e540158b 326
02298d3a 327 my $component = $self->_find_component($c, $component_or_class);
328 my $component_class = blessed $component || return 0;
e540158b 329
02298d3a 330 if (my $code = $component_class->can('action_for')) {
331 my $possible_action = $component->$code($method);
84c28acb 332 return $possible_action if $possible_action;
333 }
334
02298d3a 335 if ( my $code = $component_class->can($method) ) {
c41cfce3 336 return $self->_method_action_class->new(
e540158b 337 {
338 name => $method,
339 code => $code,
02298d3a 340 reverse => "$component_class->$method",
341 class => $component_class,
e540158b 342 namespace => Catalyst::Utils::class2prefix(
df960201 343 $component_class, ref($c)->config->{case_sensitive}
e540158b 344 ),
345 }
346 );
347 }
348 else {
349 my $error =
02298d3a 350 qq/Couldn't forward to "$component_class". Does not implement "$method"/;
e540158b 351 $c->error($error);
352 $c->log->debug($error)
353 if $c->debug;
354 return 0;
355 }
adb53907 356}
357
b5ecfcf0 358=head2 $self->prepare_action($c)
fbcc39ad 359
4ab87e27 360Find an dispatch type that matches $c->req->path, and set args from it.
361
fbcc39ad 362=cut
363
364sub prepare_action {
365 my ( $self, $c ) = @_;
e63bdf38 366 my $req = $c->req;
367 my $path = $req->path;
368 my @path = split /\//, $req->path;
369 $req->args( \my @args );
fbcc39ad 370
61a9002d 371 unshift( @path, '' ); # Root action
78d760bb 372
b96f127f 373 DESCEND: while (@path) {
fbcc39ad 374 $path = join '/', @path;
5299fff8 375 $path =~ s#^/+##;
78d760bb 376
22f3a8dd 377 # Check out dispatch types to see if any will handle the path at
378 # this level
379
a2aac3b8 380 foreach my $type ( @{ $self->dispatch_types } ) {
2633d7dc 381 last DESCEND if $type->match( $c, $path );
66e28e3f 382 }
b96f127f 383
22f3a8dd 384 # If not, move the last part path to args
4082e678 385 my $arg = pop(@path);
386 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
387 unshift @args, $arg;
fbcc39ad 388 }
389
e63bdf38 390 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
66d7ad40 391
0ca510f0 392 if($c->debug && defined $req->match && length $req->match) {
393 my $match = $req->match;
394 $match =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
395 $match = decode_utf8($match);
396 $c->log->debug( 'Path is "' . $match . '"' )
397 }
e3a13771 398
0ca510f0 399 $c->log->debug( 'Arguments are "' . join( '/', map { decode_utf8 $_ } @args ) . '"' )
fbcc39ad 400 if ( $c->debug && @args );
401}
402
b5ecfcf0 403=head2 $self->get_action( $action, $namespace )
1abd6db7 404
4ab87e27 405returns a named action from a given namespace.
406
1abd6db7 407=cut
408
409sub get_action {
bcd1002b 410 my ( $self, $name, $namespace ) = @_;
79a3189a 411 return unless $name;
3d0d6d21 412
2f381252 413 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
99fe1710 414
c41cfce3 415 return $self->_action_hash->{"${namespace}/${name}"};
1abd6db7 416}
417
b0ad47c1 418=head2 $self->get_action_by_path( $path );
068c0898 419
ab990582 420Returns the named action by its full private path.
3d0d6d21 421
068c0898 422=cut
3d0d6d21 423
424sub get_action_by_path {
425 my ( $self, $path ) = @_;
ea0e58d9 426 $path =~ s/^\///;
28928de9 427 $path = "/$path" unless $path =~ /\//;
c41cfce3 428 $self->_action_hash->{$path};
3d0d6d21 429}
430
b5ecfcf0 431=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 432
433=cut
434
435sub get_actions {
436 my ( $self, $c, $action, $namespace ) = @_;
437 return [] unless $action;
3d0d6d21 438
28928de9 439 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 440
441 my @match = $self->get_containers($namespace);
442
684d10ed 443 return map { $_->get_action($action) } @match;
a9dc674c 444}
445
b5ecfcf0 446=head2 $self->get_containers( $namespace )
cfd04b0c 447
4ab87e27 448Return all the action containers for a given namespace, inclusive
449
cfd04b0c 450=cut
451
452sub get_containers {
453 my ( $self, $namespace ) = @_;
a13e21ab 454 $namespace ||= '';
455 $namespace = '' if $namespace eq '/';
cfd04b0c 456
a13e21ab 457 my @containers;
cfd04b0c 458
7f23827b 459 if ( length $namespace ) {
460 do {
c41cfce3 461 push @containers, $self->_container_hash->{$namespace};
7f23827b 462 } while ( $namespace =~ s#/[^/]+$## );
463 }
90ce41ba 464
c41cfce3 465 return reverse grep { defined } @containers, $self->_container_hash->{''};
cfd04b0c 466}
467
ea0e58d9 468=head2 $self->uri_for_action($action, \@captures)
469
470Takes a Catalyst::Action object and action parameters and returns a URI
471part such that if $c->req->path were this URI part, this action would be
472dispatched to with $c->req->captures set to the supplied arrayref.
473
474If the action object is not available for external dispatch or the dispatcher
475cannot determine an appropriate URI, this method will return undef.
476
477=cut
478
479sub uri_for_action {
480 my ( $self, $action, $captures) = @_;
481 $captures ||= [];
a2aac3b8 482 foreach my $dispatch_type ( @{ $self->dispatch_types } ) {
ea0e58d9 483 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 484 return( $uri eq '' ? '/' : $uri )
485 if defined($uri);
ea0e58d9 486 }
487 return undef;
488}
489
8f5a2bd9 490=head2 expand_action
ae0e35ee 491
492expand an action into a full representation of the dispatch.
493mostly useful for chained, other actions will just return a
494single action.
495
496=cut
497
52f71256 498sub expand_action {
499 my ($self, $action) = @_;
500
a2aac3b8 501 foreach my $dispatch_type (@{ $self->dispatch_types }) {
52f71256 502 my $expanded = $dispatch_type->expand_action($action);
503 return $expanded if $expanded;
504 }
505
506 return $action;
507}
508
b5ecfcf0 509=head2 $self->register( $c, $action )
aad72cc9 510
4ab87e27 511Make sure all required dispatch types for this action are loaded, then
512pass the action to our dispatch types so they can register it if required.
513Also, set up the tree with the action containers.
514
aad72cc9 515=cut
516
79a3189a 517sub register {
518 my ( $self, $c, $action ) = @_;
519
c41cfce3 520 my $registered = $self->_registered_dispatch_types;
694d15f1 521
694d15f1 522 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 523 next if $key eq 'Private';
694d15f1 524 my $class = "Catalyst::DispatchType::$key";
525 unless ( $registered->{$class} ) {
c41cfce3 526 # FIXME - Some error checking and re-throwing needed here, as
527 # we eat exceptions loading dispatch types.
e7399d8b 528 # see also try_load_class
529 eval { load_class($class) };
5070f111 530 my $load_failed = $@;
6931f972 531 $self->_check_deprecated_dispatch_type( $key, $load_failed );
5070f111 532 push( @{ $self->dispatch_types }, $class->new ) unless $load_failed;
694d15f1 533 $registered->{$class} = 1;
534 }
535 }
536
a2aac3b8 537 my @dtypes = @{ $self->dispatch_types };
1315d253 538 my @normal_dtypes;
539 my @low_precedence_dtypes;
540
7b442de5 541 for my $type ( @dtypes ) {
c4586fd0 542 if ($type->_is_low_precedence) {
1315d253 543 push @low_precedence_dtypes, $type;
544 } else {
545 push @normal_dtypes, $type;
546 }
547 }
548
694d15f1 549 # Pass the action to our dispatch types so they can register it if reqd.
1315d253 550 my $was_registered = 0;
551 foreach my $type ( @normal_dtypes ) {
552 $was_registered = 1 if $type->register( $c, $action );
553 }
554
555 if (not $was_registered) {
556 foreach my $type ( @low_precedence_dtypes ) {
557 $type->register( $c, $action );
558 }
694d15f1 559 }
560
79a3189a 561 my $namespace = $action->namespace;
a13e21ab 562 my $name = $action->name;
c7116517 563
ad5e4650 564 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 565
566 # Set the method value
a13e21ab 567 $container->add_action($action);
c7116517 568
c41cfce3 569 $self->_action_hash->{"$namespace/$name"} = $action;
570 $self->_container_hash->{$namespace} = $container;
15e9b5dd 571}
572
ad5e4650 573sub _find_or_create_action_container {
a13e21ab 574 my ( $self, $namespace ) = @_;
575
c41cfce3 576 my $tree ||= $self->_tree;
99fe1710 577
a13e21ab 578 return $tree->getNodeValue unless $namespace;
78d760bb 579
a13e21ab 580 my @namespace = split '/', $namespace;
581 return $self->_find_or_create_namespace_node( $tree, @namespace )
582 ->getNodeValue;
8505565b 583}
90ce41ba 584
8505565b 585sub _find_or_create_namespace_node {
a13e21ab 586 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 587
a13e21ab 588 return $parent unless $part;
8505565b 589
a13e21ab 590 my $child =
591 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 592
a13e21ab 593 unless ($child) {
594 my $container = Catalyst::ActionContainer->new($part);
595 $parent->addChild( $child = Tree::Simple->new($container) );
596 }
99fe1710 597
a13e21ab 598 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 599}
600
4ab87e27 601=head2 $self->setup_actions( $class, $context )
602
965f3e35 603Loads all of the pre-load dispatch types, registers their actions and then
604loads all of the post-load dispatch types, and iterates over the tree of
8f59bbe2 605actions, displaying the debug information if appropriate.
1abd6db7 606
607=cut
608
609sub setup_actions {
11bd4e3e 610 my ( $self, $c ) = @_;
99fe1710 611
9e81ba44 612 my @classes =
ad5e4650 613 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
c41cfce3 614 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 615
49070d25 616 foreach my $comp ( values %{ $c->components } ) {
617 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 618 }
e494bd6b 619
ad5e4650 620 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 621
11bd4e3e 622 return unless $c->debug;
2eb2c42f 623 $self->_display_action_tables($c);
624}
625
626sub _display_action_tables {
627 my ($self, $c) = @_;
99fe1710 628
48d435ba 629 my $avail_width = Catalyst::Utils::term_width() - 12;
630 my $col1_width = ($avail_width * .25) < 20 ? 20 : int($avail_width * .25);
631 my $col2_width = ($avail_width * .50) < 36 ? 36 : int($avail_width * .50);
632 my $col3_width = $avail_width - $col1_width - $col2_width;
684d10ed 633 my $privates = Text::SimpleTable->new(
48d435ba 634 [ $col1_width, 'Private' ], [ $col2_width, 'Class' ], [ $col3_width, 'Method' ]
684d10ed 635 );
99fe1710 636
87b85407 637 my $has_private = 0;
1abd6db7 638 my $walker = sub {
639 my ( $walker, $parent, $prefix ) = @_;
640 $prefix .= $parent->getNodeValue || '';
641 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 642 my $node = $parent->getNodeValue->actions;
99fe1710 643
78d760bb 644 for my $action ( keys %{$node} ) {
b7aebc12 645 my $action_obj = $node->{$action};
b0bb11ec 646 next
647 if ( ( $action =~ /^_.*/ )
648 && ( !$c->config->{show_internal_actions} ) );
684d10ed 649 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 650 $has_private = 1;
1abd6db7 651 }
99fe1710 652
1abd6db7 653 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
654 };
99fe1710 655
c41cfce3 656 $walker->( $walker, $self->_tree, '' );
1cf0345b 657 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
658 if $has_private;
99fe1710 659
a9cbd748 660 # List all public actions
a2aac3b8 661 $_->list($c) for @{ $self->dispatch_types };
1abd6db7 662}
663
ad5e4650 664sub _load_dispatch_types {
9e81ba44 665 my ( $self, @types ) = @_;
666
667 my @loaded;
9e81ba44 668 # Preload action types
669 for my $type (@types) {
5d8129e9 670 # first param is undef because we cannot get the appclass
671 my $class = Catalyst::Utils::resolve_namespace(undef, 'Catalyst::DispatchType', $type);
2efad04b 672
e7399d8b 673 my ($success, $error) = try_load_class($class);
674 Catalyst::Exception->throw( message => $error ) if not $success;
a2aac3b8 675 push @{ $self->dispatch_types }, $class->new;
9e81ba44 676
677 push @loaded, $class;
678 }
679
a13e21ab 680 return @loaded;
9e81ba44 681}
682
e995c634 683=head2 $self->dispatch_type( $type )
684
685Get the DispatchType object of the relevant type, i.e. passing C<$type> of
686C<Chained> would return a L<Catalyst::DispatchType::Chained> object (assuming
b0ad47c1 687of course it's being used.)
e995c634 688
689=cut
690
7ffc9d9d 691sub dispatch_type {
692 my ($self, $name) = @_;
196932de 693
5d8129e9 694 # first param is undef because we cannot get the appclass
695 $name = Catalyst::Utils::resolve_namespace(undef, 'Catalyst::DispatchType', $name);
7ffc9d9d 696
a2aac3b8 697 for (@{ $self->dispatch_types }) {
7ffc9d9d 698 return $_ if ref($_) eq $name;
699 }
700 return undef;
701}
702
6931f972 703sub _check_deprecated_dispatch_type {
5070f111 704 my ($self, $key, $load_failed) = @_;
705
706 return unless $key =~ /^(Local)?Regexp?/;
707
708 # TODO: Should these throw an exception rather than just warning?
709 if ($load_failed) {
710 warn( "Attempt to use deprecated $key dispatch type.\n"
711 . " Use Chained methods or install the standalone\n"
712 . " Catalyst::DispatchType::Regex if necessary.\n" );
713 } elsif ( !defined $Catalyst::DispatchType::Regex::VERSION
28054612 714 || $Catalyst::DispatchType::Regex::VERSION le '5.90020' ) {
5070f111 715 # We loaded the old core version of the Regex module this will break
716 warn( "The $key DispatchType has been removed from Catalyst core.\n"
717 . " An old version of the core Catalyst::DispatchType::Regex\n"
718 . " has been loaded and will likely fail. Please remove\n"
8503ab9b 719 . " $INC{'Catalyst/DispatchType/Regex.pm'}\n"
5070f111 720 . " and use Chained methods or install the standalone\n"
721 . " Catalyst::DispatchType::Regex if necessary.\n" );
722 }
723}
724
c41cfce3 725use Moose;
726
727# 5.70 backwards compatibility hacks.
728
729# Various plugins (e.g. Plugin::Server and Plugin::Authorization::ACL)
730# need the methods here which *should* be private..
731
2efad04b 732# You should be able to use get_actions or get_containers appropriately
733# instead of relying on these methods which expose implementation details
734# of the dispatcher..
735#
736# IRC backlog included below, please come ask if this doesn't work for you.
737#
738# <@t0m> 5.80, the state of. There are things in the dispatcher which have
739# been deprecated, that we yell at anyone for using, which there isn't
740# a good alternative for yet..
741# <@mst> er, get_actions/get_containers provides that doesn't it?
742# <@mst> DispatchTypes are loaded on demand anyway
743# <@t0m> I'm thinking of things like _tree which is aliased to 'tree' with
744# warnings otherwise shit breaks.. We're issuing warnings about the
745# correct set of things which you shouldn't be calling..
746# <@mst> right
747# <@mst> basically, I don't see there's a need for a replacement for anything
748# <@mst> it was never a good idea to call ->tree
749# <@mst> nothingmuch was the only one who did AFAIK
750# <@mst> and he admitted it was a hack ;)
751
c41cfce3 752# See also t/lib/TestApp/Plugin/AddDispatchTypes.pm
753
754# Alias _method_name to method_name, add a before modifier to warn..
b0ad47c1 755foreach my $public_method_name (qw/
756 tree
b0ad47c1 757 registered_dispatch_types
758 method_action_class
759 action_hash
c41cfce3 760 container_hash
761 /) {
762 my $private_method_name = '_' . $public_method_name;
763 my $meta = __PACKAGE__->meta; # Calling meta method here fine as we happen at compile time.
764 $meta->add_method($public_method_name, $meta->get_method($private_method_name));
765 {
766 my %package_hash; # Only warn once per method, per package. These are infrequent enough that
767 # I haven't provided a way to disable them, patches welcome.
768 $meta->add_before_method_modifier($public_method_name, sub {
3cd3bc6a 769 my $class = caller(2);
770 chomp($class);
b3f7d10b 771 $package_hash{$class}++ || do {
3cd3bc6a 772 warn("Class $class is calling the deprecated method\n"
773 . " Catalyst::Dispatcher::$public_method_name,\n"
dacd8b0e 774 . " this will be removed in Catalyst 5.9\n");
c41cfce3 775 };
776 });
777 }
778}
779# End 5.70 backwards compatibility hacks.
780
e5ecd5bc 781__PACKAGE__->meta->make_immutable;
782
059c085b 783=head2 meta
784
785Provided by Moose
786
2f381252 787=head1 AUTHORS
1abd6db7 788
2f381252 789Catalyst Contributors, see Catalyst.pm
1abd6db7 790
791=head1 COPYRIGHT
792
536bee89 793This library is free software. You can redistribute it and/or modify it under
1abd6db7 794the same terms as Perl itself.
795
796=cut
797
7981;