Create branch register_actions.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
ae29b412 3use Moose;
4use Class::MOP;
5with 'MooseX::Emulate::Class::Accessor::Fast';
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;
e73e3bad 13use Catalyst::Utils;
87b85407 14use Text::SimpleTable;
1abd6db7 15use Tree::Simple;
16use Tree::Simple::Visitor::FindByPath;
fbcc39ad 17
ae29b412 18# Refactoring note:
19# do these belong as package vars or should we build these via a builder method?
20# See Catalyst-Plugin-Server for them being added to, which should be much less ugly.
6d030e6f 21
22# Preload these action types
61a9002d 23our @PRELOAD = qw/Index Path Regex/;
1abd6db7 24
2d1d8f91 25# Postload these action types
61a9002d 26our @POSTLOAD = qw/Default/;
2d1d8f91 27
ae29b412 28# Note - see back-compat methods at end of file.
29has _tree => (is => 'rw');
30has _dispatch_types => (is => 'rw', default => sub { [] }, required => 1, lazy => 1);
31has _registered_dispatch_types => (is => 'rw', default => sub { {} }, required => 1, lazy => 1);
32has _method_action_class => (is => 'rw', default => 'Catalyst::Action');
33has _action_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
34has _container_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
35
36has preload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@PRELOAD] });
37has postload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@POSTLOAD] });
38
39# Wrap accessors so you can assign a list and it will capture a list ref.
40around qw/preload_dispatch_types postload_dispatch_types/ => sub {
41 my $orig = shift;
42 my $self = shift;
43 return $self->$orig([@_]) if (scalar @_ && ref $_[0] ne 'ARRAY');
44 return $self->$orig(@_);
45};
46
47no Moose;
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
76ddf86b 64=head2 new
4ab87e27 65
66Construct a new dispatcher.
67
e7bb8d33 68=cut
69
ae29b412 70sub BUILD {
71 my ($self, $params) = @_;
9e81ba44 72
ae29b412 73 my $container =
74 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
9e81ba44 75
ae29b412 76 $self->_tree( 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 ) = @_;
ae29b412 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;
113 my $error = $path
114 ? qq/Unknown resource "$path"/
115 : "No default action defined";
116 $c->log->error($error) if $c->debug;
117 $c->error($error);
118 }
119}
120
9bfaf006 121# $self->_command2action( $c, $command [, \@arguments ] )
122# Search for an action, from the command and returns C<($action, $args)> on
123# success. Returns C<(0)> on error.
1abd6db7 124
9bfaf006 125sub _command2action {
e72f8f51 126 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 127
1abd6db7 128 unless ($command) {
9bfaf006 129 $c->log->debug('Nothing to go to') if $c->debug;
1abd6db7 130 return 0;
131 }
99fe1710 132
e72f8f51 133 my @args;
ae29b412 134
e72f8f51 135 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
136 @args = @{ pop @extra_params }
137 } else {
9bfaf006 138 # this is a copy, it may take some abuse from
139 # ->_invoke_as_path if the path had trailing parts
e72f8f51 140 @args = @{ $c->request->arguments };
141 }
142
143 my $action;
144
ae29b412 145 # go to a string path ("/foo/bar/gorch")
146 # or action object
147 if (blessed($command) && $command->isa('Catalyst::Action')) {
f9bcc128 148 $action = $command;
149 }
150 else {
f9bcc128 151 $action = $self->_invoke_as_path( $c, "$command", \@args );
152 }
99fe1710 153
9bfaf006 154 # go to a component ( "MyApp::*::Foo" or $c->component("...")
155 # - a path or an object)
e72f8f51 156 unless ($action) {
157 my $method = @extra_params ? $extra_params[0] : "process";
158 $action = $self->_invoke_as_component( $c, $command, $method );
159 }
99fe1710 160
9bfaf006 161 return $action, \@args;
162}
163
f9bcc128 164=head2 $self->visit( $c, $command [, \@arguments ] )
165
166Documented in L<Catalyst>
167
168=cut
169
170sub visit {
171 my $self = shift;
172 $self->_do_visit('visit', @_);
173}
174
175sub _do_visit {
176 my $self = shift;
177 my $opname = shift;
178 my ( $c, $command ) = @_;
179 my ( $action, $args ) = $self->_command2action(@_);
180 my $error = qq/Couldn't $opname("$command"): /;
181
182 if (!$action) {
183 $error .= qq/Couldn't $opname to command "$command": /
184 .qq/Invalid action or component./;
185 }
186 elsif (!defined $action->namespace) {
187 $error .= qq/Action has no namespace: cannot $opname() to a plain /
188 .qq/method or component, must be a :Action or some sort./
189 }
190 elsif (!$action->class->can('_DISPATCH')) {
191 $error .= qq/Action cannot _DISPATCH. /
192 .qq/Did you try to $opname() a non-controller action?/;
193 }
194 else {
195 $error = q();
196 }
197
198 if($error) {
199 $c->error($error);
200 $c->log->debug($error) if $c->debug;
201 return 0;
202 }
203
204 $action = $self->expand_action($action);
205
206 local $c->request->{arguments} = $args;
207 local $c->{namespace} = $action->{'namespace'};
208 local $c->{action} = $action;
209
210 $self->dispatch($c);
211}
212
213=head2 $self->go( $c, $command [, \@arguments ] )
214
215Documented in L<Catalyst>
216
217=cut
218
219sub go {
220 my $self = shift;
221 $self->_do_visit('go', @_);
222 die $Catalyst::GO;
223}
224
9bfaf006 225=head2 $self->forward( $c, $command [, \@arguments ] )
226
227Documented in L<Catalyst>
228
229=cut
230
231sub forward {
232 my $self = shift;
ae29b412 233 $self->_do_forward(forward => @_);
234}
235
236sub _do_forward {
237 my $self = shift;
238 my $opname = shift;
9bfaf006 239 my ( $c, $command ) = @_;
240 my ( $action, $args ) = $self->_command2action(@_);
241
ae29b412 242 if (!$action) {
243 my $error .= qq/Couldn't $opname to command "$command": /
244 .qq/Invalid action or component./;
9bfaf006 245 $c->error($error);
246 $c->log->debug($error) if $c->debug;
247 return 0;
248 }
adb53907 249
ae29b412 250 no warnings 'recursion';
251
9bfaf006 252 local $c->request->{arguments} = $args;
b8f669f3 253 $action->dispatch( $c );
99fe1710 254
1abd6db7 255 return $c->state;
256}
257
ae29b412 258=head2 $self->detach( $c, $command [, \@arguments ] )
259
260Documented in L<Catalyst>
261
262=cut
263
264sub detach {
265 my ( $self, $c, $command, @args ) = @_;
266 $self->_do_forward(detach => $c, $command, @args ) if $command;
267 die $Catalyst::DETACH;
268}
269
adb53907 270sub _action_rel2abs {
e540158b 271 my ( $self, $c, $path ) = @_;
272
273 unless ( $path =~ m#^/# ) {
274 my $namespace = $c->stack->[-1]->namespace;
275 $path = "$namespace/$path";
276 }
277
278 $path =~ s#^/##;
279 return $path;
adb53907 280}
281
282sub _invoke_as_path {
e540158b 283 my ( $self, $c, $rel_path, $args ) = @_;
284
e540158b 285 my $path = $self->_action_rel2abs( $c, $rel_path );
286
287 my ( $tail, @extra_args );
288 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
289 { # allow $path to be empty
290 if ( my $action = $c->get_action( $tail, $path ) ) {
291 push @$args, @extra_args;
292 return $action;
293 }
294 else {
295 return
296 unless $path
297 ; # if a match on the global namespace failed then the whole lookup failed
298 }
299
300 unshift @extra_args, $tail;
301 }
adb53907 302}
303
304sub _find_component_class {
e540158b 305 my ( $self, $c, $component ) = @_;
adb53907 306
e540158b 307 return ref($component)
308 || ref( $c->component($component) )
309 || $c->component($component);
adb53907 310}
311
312sub _invoke_as_component {
e540158b 313 my ( $self, $c, $component, $method ) = @_;
314
315 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 316
317 if ( my $code = $class->can($method) ) {
ae29b412 318 return $self->_method_action_class->new(
e540158b 319 {
320 name => $method,
321 code => $code,
322 reverse => "$class->$method",
323 class => $class,
324 namespace => Catalyst::Utils::class2prefix(
325 $class, $c->config->{case_sensitive}
326 ),
327 }
328 );
329 }
330 else {
331 my $error =
332 qq/Couldn't forward to "$class". Does not implement "$method"/;
333 $c->error($error);
334 $c->log->debug($error)
335 if $c->debug;
336 return 0;
337 }
adb53907 338}
339
b5ecfcf0 340=head2 $self->prepare_action($c)
fbcc39ad 341
4ab87e27 342Find an dispatch type that matches $c->req->path, and set args from it.
343
fbcc39ad 344=cut
345
346sub prepare_action {
347 my ( $self, $c ) = @_;
ae29b412 348 my $req = $c->req;
349 my $path = $req->path;
350 my @path = split /\//, $req->path;
351 $req->args( \my @args );
fbcc39ad 352
61a9002d 353 unshift( @path, '' ); # Root action
78d760bb 354
b96f127f 355 DESCEND: while (@path) {
fbcc39ad 356 $path = join '/', @path;
61a9002d 357 $path =~ s#^/##;
fbcc39ad 358
61a9002d 359 $path = '' if $path eq '/'; # Root action
78d760bb 360
22f3a8dd 361 # Check out dispatch types to see if any will handle the path at
362 # this level
363
ae29b412 364 foreach my $type ( @{ $self->_dispatch_types } ) {
2633d7dc 365 last DESCEND if $type->match( $c, $path );
66e28e3f 366 }
b96f127f 367
22f3a8dd 368 # If not, move the last part path to args
4082e678 369 my $arg = pop(@path);
370 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
371 unshift @args, $arg;
fbcc39ad 372 }
373
ae29b412 374 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
66d7ad40 375
ae29b412 376 $c->log->debug( 'Path is "' . $req->match . '"' )
377 if ( $c->debug && defined $req->match && length $req->match );
e3a13771 378
fbcc39ad 379 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
380 if ( $c->debug && @args );
381}
382
b5ecfcf0 383=head2 $self->get_action( $action, $namespace )
1abd6db7 384
4ab87e27 385returns a named action from a given namespace.
386
1abd6db7 387=cut
388
389sub get_action {
bcd1002b 390 my ( $self, $name, $namespace ) = @_;
79a3189a 391 return unless $name;
3d0d6d21 392
53119b78 393 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
99fe1710 394
ae29b412 395 return $self->_action_hash->{"${namespace}/${name}"};
1abd6db7 396}
397
34d28dfd 398=head2 $self->get_action_by_path( $path );
ae29b412 399
34d28dfd 400Returns the named action by its full path.
3d0d6d21 401
ae29b412 402=cut
3d0d6d21 403
404sub get_action_by_path {
405 my ( $self, $path ) = @_;
ea0e58d9 406 $path =~ s/^\///;
28928de9 407 $path = "/$path" unless $path =~ /\//;
ae29b412 408 $self->_action_hash->{$path};
3d0d6d21 409}
410
b5ecfcf0 411=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 412
413=cut
414
415sub get_actions {
416 my ( $self, $c, $action, $namespace ) = @_;
417 return [] unless $action;
3d0d6d21 418
28928de9 419 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 420
421 my @match = $self->get_containers($namespace);
422
684d10ed 423 return map { $_->get_action($action) } @match;
a9dc674c 424}
425
b5ecfcf0 426=head2 $self->get_containers( $namespace )
cfd04b0c 427
4ab87e27 428Return all the action containers for a given namespace, inclusive
429
cfd04b0c 430=cut
431
432sub get_containers {
433 my ( $self, $namespace ) = @_;
a13e21ab 434 $namespace ||= '';
435 $namespace = '' if $namespace eq '/';
cfd04b0c 436
a13e21ab 437 my @containers;
cfd04b0c 438
7f23827b 439 if ( length $namespace ) {
440 do {
ae29b412 441 push @containers, $self->_container_hash->{$namespace};
7f23827b 442 } while ( $namespace =~ s#/[^/]+$## );
443 }
90ce41ba 444
ae29b412 445 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 446
ae29b412 447 #return (split '/', $namespace); # isnt this more clear?
a13e21ab 448 my @parts = split '/', $namespace;
cfd04b0c 449}
450
ea0e58d9 451=head2 $self->uri_for_action($action, \@captures)
452
453Takes a Catalyst::Action object and action parameters and returns a URI
454part such that if $c->req->path were this URI part, this action would be
455dispatched to with $c->req->captures set to the supplied arrayref.
456
457If the action object is not available for external dispatch or the dispatcher
458cannot determine an appropriate URI, this method will return undef.
459
460=cut
461
462sub uri_for_action {
463 my ( $self, $action, $captures) = @_;
464 $captures ||= [];
ae29b412 465 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 466 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 467 return( $uri eq '' ? '/' : $uri )
468 if defined($uri);
ea0e58d9 469 }
470 return undef;
471}
472
ae29b412 473=head2 expand_action
f9bcc128 474
475expand an action into a full representation of the dispatch.
476mostly useful for chained, other actions will just return a
477single action.
478
479=cut
480
481sub expand_action {
482 my ($self, $action) = @_;
483
ae29b412 484 foreach my $dispatch_type (@{ $self->_dispatch_types }) {
f9bcc128 485 my $expanded = $dispatch_type->expand_action($action);
486 return $expanded if $expanded;
487 }
488
489 return $action;
490}
491
b5ecfcf0 492=head2 $self->register( $c, $action )
aad72cc9 493
4ab87e27 494Make sure all required dispatch types for this action are loaded, then
495pass the action to our dispatch types so they can register it if required.
496Also, set up the tree with the action containers.
497
aad72cc9 498=cut
499
79a3189a 500sub register {
501 my ( $self, $c, $action ) = @_;
502
ae29b412 503 my $registered = $self->_registered_dispatch_types;
694d15f1 504
ae29b412 505 #my $priv = 0; #seems to be unused
694d15f1 506 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 507 next if $key eq 'Private';
694d15f1 508 my $class = "Catalyst::DispatchType::$key";
509 unless ( $registered->{$class} ) {
ae29b412 510 # FIXME - Some error checking and re-throwing needed here, as
511 # we eat exceptions loading dispatch types.
512 eval { Class::MOP::load_class($class) };
513 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 514 $registered->{$class} = 1;
515 }
516 }
517
518 # Pass the action to our dispatch types so they can register it if reqd.
ae29b412 519 foreach my $type ( @{ $self->_dispatch_types } ) {
9a6ecf4f 520 $type->register( $c, $action );
694d15f1 521 }
522
79a3189a 523 my $namespace = $action->namespace;
a13e21ab 524 my $name = $action->name;
c7116517 525
ad5e4650 526 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 527
528 # Set the method value
a13e21ab 529 $container->add_action($action);
c7116517 530
ae29b412 531 $self->_action_hash->{"$namespace/$name"} = $action;
532 $self->_container_hash->{$namespace} = $container;
15e9b5dd 533}
534
ad5e4650 535sub _find_or_create_action_container {
a13e21ab 536 my ( $self, $namespace ) = @_;
537
ae29b412 538 my $tree ||= $self->_tree;
99fe1710 539
a13e21ab 540 return $tree->getNodeValue unless $namespace;
78d760bb 541
a13e21ab 542 my @namespace = split '/', $namespace;
543 return $self->_find_or_create_namespace_node( $tree, @namespace )
544 ->getNodeValue;
8505565b 545}
90ce41ba 546
8505565b 547sub _find_or_create_namespace_node {
a13e21ab 548 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 549
a13e21ab 550 return $parent unless $part;
8505565b 551
a13e21ab 552 my $child =
553 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 554
a13e21ab 555 unless ($child) {
556 my $container = Catalyst::ActionContainer->new($part);
557 $parent->addChild( $child = Tree::Simple->new($container) );
558 }
99fe1710 559
a13e21ab 560 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 561}
562
4ab87e27 563=head2 $self->setup_actions( $class, $context )
564
ae29b412 565Loads all of the preload dispatch types, registers their actions and then
566loads all of the postload dispatch types, and iterates over the tree of
567actions, displaying the debug information if appropriate.
1abd6db7 568
569=cut
570
571sub setup_actions {
11bd4e3e 572 my ( $self, $c ) = @_;
99fe1710 573
9e81ba44 574 my @classes =
ad5e4650 575 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
ae29b412 576 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 577
49070d25 578 foreach my $comp ( values %{ $c->components } ) {
579 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 580 }
e494bd6b 581
ad5e4650 582 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 583
11bd4e3e 584 return unless $c->debug;
99fe1710 585
e73e3bad 586 my $column_width = Catalyst::Utils::term_width() - 20 - 36 - 12;
684d10ed 587 my $privates = Text::SimpleTable->new(
e73e3bad 588 [ 20, 'Private' ], [ 36, 'Class' ], [ $column_width, 'Method' ]
684d10ed 589 );
99fe1710 590
87b85407 591 my $has_private = 0;
1abd6db7 592 my $walker = sub {
593 my ( $walker, $parent, $prefix ) = @_;
594 $prefix .= $parent->getNodeValue || '';
595 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 596 my $node = $parent->getNodeValue->actions;
99fe1710 597
78d760bb 598 for my $action ( keys %{$node} ) {
b7aebc12 599 my $action_obj = $node->{$action};
b0bb11ec 600 next
601 if ( ( $action =~ /^_.*/ )
602 && ( !$c->config->{show_internal_actions} ) );
684d10ed 603 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 604 $has_private = 1;
1abd6db7 605 }
99fe1710 606
1abd6db7 607 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
608 };
99fe1710 609
ae29b412 610 $walker->( $walker, $self->_tree, '' );
1cf0345b 611 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
612 if $has_private;
99fe1710 613
a9cbd748 614 # List all public actions
ae29b412 615 $_->list($c) for @{ $self->_dispatch_types };
1abd6db7 616}
617
ad5e4650 618sub _load_dispatch_types {
9e81ba44 619 my ( $self, @types ) = @_;
620
621 my @loaded;
622
623 # Preload action types
624 for my $type (@types) {
625 my $class =
626 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
ae29b412 627
628 eval { Class::MOP::load_class($class) };
9e81ba44 629 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
630 if $@;
ae29b412 631 push @{ $self->_dispatch_types }, $class->new;
9e81ba44 632
633 push @loaded, $class;
634 }
635
a13e21ab 636 return @loaded;
9e81ba44 637}
638
ae29b412 639use Moose;
640
641# 5.70 backwards compatibility hacks.
642
643# Various plugins (e.g. Plugin::Server and Plugin::Authorization::ACL)
644# need the methods here which *should* be private..
645
646# However we can't really take them away until there is a sane API for
647# building actions and configuring / introspecting the dispatcher.
648# In 5.90, we should build that infrastructure, port the plugins which
649# use it, and then take the crap below away.
650# See also t/lib/TestApp/Plugin/AddDispatchTypes.pm
651
652# Alias _method_name to method_name, add a before modifier to warn..
653foreach my $public_method_name (qw/
654 tree
655 dispatch_types
656 registered_dispatch_types
657 method_action_class
658 action_hash
659 container_hash
660 /) {
661 my $private_method_name = '_' . $public_method_name;
662 my $meta = __PACKAGE__->meta; # Calling meta method here fine as we happen at compile time.
663 $meta->add_method($public_method_name, $meta->get_method($private_method_name));
664 {
665 my %package_hash; # Only warn once per method, per package. These are infrequent enough that
666 # I haven't provided a way to disable them, patches welcome.
667 $meta->add_before_method_modifier($public_method_name, sub {
668 my $class = blessed(shift);
669 $package_hash{$class}++ || do {
670 warn("Class $class is calling the deprecated method Catalyst::Dispatcher::$public_method_name,\n"
671 . "this will be removed in Catalyst 5.9X");
672 };
673 });
674 }
675}
676# End 5.70 backwards compatibility hacks.
677
678no Moose;
679__PACKAGE__->meta->make_immutable;
680
681=head2 meta
682
683Provided by Moose
684
0bf7ab71 685=head1 AUTHORS
1abd6db7 686
0bf7ab71 687Catalyst Contributors, see Catalyst.pm
1abd6db7 688
689=head1 COPYRIGHT
690
691This program is free software, you can redistribute it and/or modify it under
692the same terms as Perl itself.
693
694=cut
695
6961;