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