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