Move Controller::ActionRole's functionality into the core.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Controller.pm
CommitLineData
5ee249f2 1package Catalyst::Controller;
2
ae29b412 3use Moose;
eff60019 4use Class::MOP;
5use String::RewritePrefix;
a58af53d 6use Moose::Util qw/find_meta/;
eff60019 7use List::Util qw/first/;
4f4ab5b4 8use List::MoreUtils qw/uniq/;
ae29b412 9use namespace::clean -except => 'meta';
10
ba545c13 11BEGIN { extends qw/Catalyst::Component MooseX::MethodAttributes::Inheritable/; }
234763d4 12
cf37d21a 13use MooseX::MethodAttributes;
234763d4 14use Catalyst::Exception;
15use Catalyst::Utils;
ae29b412 16
17with 'Catalyst::Component::ApplicationAttribute';
18
aea897b2 19has path_prefix => (
20 is => 'rw',
21 isa => 'Str',
22 init_arg => 'path',
23 predicate => 'has_path_prefix',
24);
25
26has action_namespace => (
27 is => 'rw',
28 isa => 'Str',
29 init_arg => 'namespace',
30 predicate => 'has_action_namespace',
31);
32
33has actions => (
34 accessor => '_controller_actions',
35 isa => 'HashRef',
36 init_arg => undef,
37);
ae29b412 38
eff60019 39has _action_role_args => (
40 traits => [qw(Array)],
41 isa => 'ArrayRef[Str]',
42 init_arg => 'action_roles',
43 default => sub { [] },
44 handles => {
45 _action_role_args => 'elements',
46 },
47);
48
49has _action_roles => (
50 traits => [qw(Array)],
51 isa => 'ArrayRef[RoleName]',
52 init_arg => undef,
53 lazy => 1,
54 builder => '_build__action_roles',
55 handles => {
56 _action_roles => 'elements',
57 },
58);
59
bf7c9c87 60has action_args => (is => 'ro');
61
bdd6684e 62# ->config(actions => { '*' => ...
63has _all_actions_attributes => (
64 is => 'ro',
65 isa => 'HashRef',
66 init_arg => undef,
67 lazy => 1,
68 builder => '_build__all_actions_attributes',
69);
70
7f22a5aa 71sub BUILD {
72 my ($self, $args) = @_;
ae29b412 73 my $action = delete $args->{action} || {};
74 my $actions = delete $args->{actions} || {};
7f22a5aa 75 my $attr_value = $self->merge_config_hashes($actions, $action);
76 $self->_controller_actions($attr_value);
5ee249f2 77
bdd6684e 78 # trigger lazy builder
79 $self->_all_actions_attributes;
eff60019 80 $self->_action_roles;
81}
82
83sub _build__action_roles {
84 my $self = shift;
85 my @roles = $self->_expand_role_shortname($self->_action_role_args);
86 Class::MOP::load_class($_) for @roles;
87 return \@roles;
bdd6684e 88}
d0e5dfb5 89
bdd6684e 90sub _build__all_actions_attributes {
91 my ($self) = @_;
92 delete $self->_controller_actions->{'*'} || {};
93}
d0e5dfb5 94
5ee249f2 95=head1 NAME
96
97Catalyst::Controller - Catalyst Controller base class
98
99=head1 SYNOPSIS
100
234763d4 101 package MyApp::Controller::Search
a269e0c2 102 use base qw/Catalyst::Controller/;
234763d4 103
27ae4114 104 sub foo : Local {
85d9fce6 105 my ($self,$c,@args) = @_;
27ae4114 106 ...
234763d4 107 } # Dispatches to /search/foo
5ee249f2 108
109=head1 DESCRIPTION
110
a269e0c2 111Controllers are where the actions in the Catalyst framework
112reside. Each action is represented by a function with an attribute to
113identify what kind of action it is. See the L<Catalyst::Dispatcher>
114for more info about how Catalyst dispatches to actions.
234763d4 115
116=cut
117
ae29b412 118#I think both of these could be attributes. doesn't really seem like they need
119#to ble class data. i think that attributes +default would work just fine
eff60019 120__PACKAGE__->mk_classdata($_) for qw/_dispatch_steps _action_class _action_role_prefix/;
234763d4 121
122__PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] );
7b41db70 123__PACKAGE__->_action_class('Catalyst::Action');
eff60019 124__PACKAGE__->_action_role_prefix([ 'Catalyst::ActionRole::' ]);
234763d4 125
234763d4 126
127sub _DISPATCH : Private {
128 my ( $self, $c ) = @_;
129
130 foreach my $disp ( @{ $self->_dispatch_steps } ) {
131 last unless $c->forward($disp);
132 }
133
134 $c->forward('_END');
135}
136
137sub _BEGIN : Private {
138 my ( $self, $c ) = @_;
139 my $begin = ( $c->get_actions( 'begin', $c->namespace ) )[-1];
140 return 1 unless $begin;
141 $begin->dispatch( $c );
142 return !@{ $c->error };
143}
144
145sub _AUTO : Private {
146 my ( $self, $c ) = @_;
147 my @auto = $c->get_actions( 'auto', $c->namespace );
148 foreach my $auto (@auto) {
149 $auto->dispatch( $c );
150 return 0 unless $c->state;
151 }
152 return 1;
153}
154
155sub _ACTION : Private {
156 my ( $self, $c ) = @_;
157 if ( ref $c->action
158 && $c->action->can('execute')
53119b78 159 && defined $c->req->action )
234763d4 160 {
161 $c->action->dispatch( $c );
162 }
163 return !@{ $c->error };
164}
165
166sub _END : Private {
167 my ( $self, $c ) = @_;
168 my $end = ( $c->get_actions( 'end', $c->namespace ) )[-1];
169 return 1 unless $end;
170 $end->dispatch( $c );
171 return !@{ $c->error };
172}
173
234763d4 174sub action_for {
175 my ( $self, $name ) = @_;
176 my $app = ($self->isa('Catalyst') ? $self : $self->_application);
177 return $app->dispatcher->get_action($name, $self->action_namespace);
178}
179
27ae4114 180#my opinion is that this whole sub really should be a builder method, not
ae29b412 181#something that happens on every call. Anyone else disagree?? -- groditi
182## -- apparently this is all just waiting for app/ctx split
183around action_namespace => sub {
184 my $orig = shift;
234763d4 185 my ( $self, $c ) = @_;
ae29b412 186
df960201 187 my $class = ref($self) || $self;
188 my $appclass = ref($c) || $c;
ae29b412 189 if( ref($self) ){
190 return $self->$orig if $self->has_action_namespace;
191 } else {
df960201 192 return $class->config->{namespace} if exists $class->config->{namespace};
234763d4 193 }
234763d4 194
ae29b412 195 my $case_s;
196 if( $c ){
df960201 197 $case_s = $appclass->config->{case_sensitive};
ae29b412 198 } else {
199 if ($self->isa('Catalyst')) {
df960201 200 $case_s = $class->config->{case_sensitive};
ae29b412 201 } else {
202 if (ref $self) {
df960201 203 $case_s = ref($self->_application)->config->{case_sensitive};
ae29b412 204 } else {
205 confess("Can't figure out case_sensitive setting");
206 }
207 }
234763d4 208 }
ae29b412 209
8f6cebb2 210 my $namespace = Catalyst::Utils::class2prefix($self->catalyst_component_name, $case_s) || '';
ae29b412 211 $self->$orig($namespace) if ref($self);
212 return $namespace;
213};
214
215#Once again, this is probably better written as a builder method
216around path_prefix => sub {
217 my $orig = shift;
218 my $self = shift;
219 if( ref($self) ){
220 return $self->$orig if $self->has_path_prefix;
221 } else {
222 return $self->config->{path} if exists $self->config->{path};
223 }
224 my $namespace = $self->action_namespace(@_);
225 $self->$orig($namespace) if ref($self);
226 return $namespace;
227};
234763d4 228
9ab7d83d 229sub get_action_methods {
230 my $self = shift;
2bf074ab 231 my $meta = find_meta($self) || confess("No metaclass setup for $self");
69048792 232 confess(
233 sprintf "Metaclass %s for %s cannot support register_actions.",
234 ref $meta, $meta->name,
235 ) unless $meta->can('get_nearest_methods_with_attributes');
cf37d21a 236 my @methods = $meta->get_nearest_methods_with_attributes;
fa649eb7 237
238 # actions specified via config are also action_methods
239 push(
240 @methods,
241 map {
d0e78355 242 $meta->find_method_by_name($_)
e87273a4 243 || confess( sprintf 'Action "%s" is not available from controller %s',
244 $_, ref $self )
bdd6684e 245 } keys %{ $self->_controller_actions }
fa649eb7 246 ) if ( ref $self );
4f4ab5b4 247 return uniq @methods;
9ab7d83d 248}
234763d4 249
fa649eb7 250
234763d4 251sub register_actions {
252 my ( $self, $c ) = @_;
9ab7d83d 253 $self->register_action_methods( $c, $self->get_action_methods );
254}
255
256sub register_action_methods {
257 my ( $self, $c, @methods ) = @_;
8f6cebb2 258 my $class = $self->catalyst_component_name;
ae29b412 259 #this is still not correct for some reason.
234763d4 260 my $namespace = $self->action_namespace($c);
234763d4 261
f3c5b1c9 262 # FIXME - fugly
a202886b 263 if (!blessed($self) && $self eq $c && scalar(@methods)) {
f3c5b1c9 264 my @really_bad_methods = grep { ! /^_(DISPATCH|BEGIN|AUTO|ACTION|END)$/ } map { $_->name } @methods;
265 if (scalar(@really_bad_methods)) {
266 $c->log->warn("Action methods (" . join(', ', @really_bad_methods) . ") found defined in your application class, $self. This is deprecated, please move them into a Root controller.");
267 }
a202886b 268 }
d2598ac8 269
ba545c13 270 foreach my $method (@methods) {
271 my $name = $method->name;
d0f30dbc 272 # Horrible hack! All method metaclasses should have an attributes
273 # method, core Moose bug - see r13354.
10e970e4 274 my $attributes = $method->can('attributes') ? $method->attributes : [];
ba545c13 275 my $attrs = $self->_parse_attrs( $c, $name, @{ $attributes } );
234763d4 276 if ( $attrs->{Private} && ( keys %$attrs > 1 ) ) {
277 $c->log->debug( 'Bad action definition "'
ba545c13 278 . join( ' ', @{ $attributes } )
279 . qq/" for "$class->$name"/ )
234763d4 280 if $c->debug;
281 next;
282 }
bc677969 283 my $reverse = $namespace ? "${namespace}/${name}" : $name;
234763d4 284 my $action = $self->create_action(
ba545c13 285 name => $name,
286 code => $method->body,
234763d4 287 reverse => $reverse,
288 namespace => $namespace,
289 class => $class,
290 attributes => $attrs,
291 );
292
293 $c->dispatcher->register( $c, $action );
294 }
295}
296
eff60019 297sub _apply_action_class_roles {
298 my ($self, $class, @roles) = @_;
299
300 Class::MOP::load_class($_) for @roles;
301 my $meta = Moose::Meta::Class->initialize($class)->create_anon_class(
302 superclasses => [$class],
303 roles => \@roles,
304 cache => 1,
305 );
306 $meta->add_method(meta => sub { $meta });
307
308 return $meta->name;
309}
310
f0a9b791 311sub action_class {
7b41db70 312 my $self = shift;
313 my %args = @_;
234763d4 314
315 my $class = (exists $args{attributes}{ActionClass}
f0a9b791 316 ? $args{attributes}{ActionClass}[0]
317 : $self->_action_class);
318
ae29b412 319 Class::MOP::load_class($class);
f0a9b791 320 return $class;
321}
322
323sub create_action {
324 my $self = shift;
325 my %args = @_;
a7e955ae 326
f0a9b791 327 my $class = $self->action_class(%args);
eff60019 328
329 load_class($class);
330 Moose->init_meta(for_class => $class)
331 unless Class::MOP::does_metaclass_exist($class);
332
333 unless ($args{name} =~ /^_(DISPATCH|BEGIN|AUTO|ACTION|END)$/) {
334 my @roles = $self->gather_action_roles(%args);
335 $class = $self->_apply_action_class_roles($class, @roles) if @roles;
336 }
337
bf7c9c87 338 my $action_args = (
339 ref($self)
340 ? $self->action_args
341 : $self->config->{action_args}
342 );
f0a9b791 343
a7e955ae 344 my %extra_args = (
345 %{ $action_args->{'*'} || {} },
346 %{ $action_args->{ $args{name} } || {} },
347 );
348
349 return $class->new({ %extra_args, %args });
234763d4 350}
351
eff60019 352sub gather_action_roles {
353 my ($self, %args) = @_;
354
355 return (
356 (blessed $self ? $self->_action_roles : ()),
357 @{ $args{attributes}->{Does} || [] },
358 );
359}
360
234763d4 361sub _parse_attrs {
362 my ( $self, $c, $name, @attrs ) = @_;
363
364 my %raw_attributes;
365
366 foreach my $attr (@attrs) {
367
368 # Parse out :Foo(bar) into Foo => bar etc (and arrayify)
369
370 if ( my ( $key, $value ) = ( $attr =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ ) )
371 {
372
373 if ( defined $value ) {
374 ( $value =~ s/^'(.*)'$/$1/ ) || ( $value =~ s/^"(.*)"/$1/ );
375 }
376 push( @{ $raw_attributes{$key} }, $value );
377 }
378 }
379
bdd6684e 380 my ($actions_config, $all_actions_config);
ae29b412 381 if( ref($self) ) {
bdd6684e 382 $actions_config = $self->_controller_actions;
383 # No, you're not getting actions => { '*' => ... } with actions in MyApp.
384 $all_actions_config = $self->_all_actions_attributes;
ae29b412 385 } else {
386 my $cfg = $self->config;
bdd6684e 387 $actions_config = $self->merge_config_hashes($cfg->{actions}, $cfg->{action});
388 $all_actions_config = {};
234763d4 389 }
390
ed9d06b6 391 %raw_attributes = (
392 %raw_attributes,
e95b2b49 393 # Note we deep copy array refs here to stop crapping on config
394 # when attributes are parsed. RT#65463
395 exists $actions_config->{$name} ? map { ref($_) eq 'ARRAY' ? [ @$_ ] : $_ } %{ $actions_config->{$name } } : (),
ed9d06b6 396 );
ae29b412 397
ed9d06b6 398 # Private actions with additional attributes will raise a warning and then
399 # be ignored. Adding '*' arguments to the default _DISPATCH / etc. methods,
400 # which are Private, will prevent those from being registered. They should
401 # probably be turned into :Actions instead, or we might want to otherwise
402 # disambiguate between those built-in internal actions and user-level
403 # Private ones.
bdd6684e 404 %raw_attributes = (%{ $all_actions_config }, %raw_attributes)
405 unless $raw_attributes{Private};
ae29b412 406
234763d4 407 my %final_attributes;
408
409 foreach my $key (keys %raw_attributes) {
410
411 my $raw = $raw_attributes{$key};
412
413 foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {
414
415 my $meth = "_parse_${key}_attr";
ae29b412 416 if ( my $code = $self->can($meth) ) {
417 ( $key, $value ) = $self->$code( $c, $name, $value );
234763d4 418 }
419 push( @{ $final_attributes{$key} }, $value );
420 }
421 }
422
423 return \%final_attributes;
424}
425
426sub _parse_Global_attr {
427 my ( $self, $c, $name, $value ) = @_;
428 return $self->_parse_Path_attr( $c, $name, "/$name" );
429}
430
431sub _parse_Absolute_attr { shift->_parse_Global_attr(@_); }
432
433sub _parse_Local_attr {
434 my ( $self, $c, $name, $value ) = @_;
435 return $self->_parse_Path_attr( $c, $name, $name );
436}
437
438sub _parse_Relative_attr { shift->_parse_Local_attr(@_); }
439
440sub _parse_Path_attr {
441 my ( $self, $c, $name, $value ) = @_;
53119b78 442 $value = '' if !defined $value;
234763d4 443 if ( $value =~ m!^/! ) {
444 return ( 'Path', $value );
445 }
446 elsif ( length $value ) {
447 return ( 'Path', join( '/', $self->path_prefix($c), $value ) );
448 }
449 else {
450 return ( 'Path', $self->path_prefix($c) );
451 }
452}
453
454sub _parse_Regex_attr {
455 my ( $self, $c, $name, $value ) = @_;
456 return ( 'Regex', $value );
457}
458
459sub _parse_Regexp_attr { shift->_parse_Regex_attr(@_); }
460
461sub _parse_LocalRegex_attr {
462 my ( $self, $c, $name, $value ) = @_;
463 unless ( $value =~ s/^\^// ) { $value = "(?:.*?)$value"; }
19c01ee1 464
465 my $prefix = $self->path_prefix( $c );
466 $prefix .= '/' if length( $prefix );
27ae4114 467
19c01ee1 468 return ( 'Regex', "^${prefix}${value}" );
234763d4 469}
470
471sub _parse_LocalRegexp_attr { shift->_parse_LocalRegex_attr(@_); }
472
f3107403 473sub _parse_Chained_attr {
474 my ($self, $c, $name, $value) = @_;
475
476 if (defined($value) && length($value)) {
477 if ($value eq '.') {
478 $value = '/'.$self->action_namespace($c);
fb56008f 479 } elsif (my ($rel, $rest) = $value =~ /^((?:\.{2}\/)+)(.*)$/) {
eb270c30 480 my @parts = split '/', $self->action_namespace($c);
fb56008f 481 my @levels = split '/', $rel;
482
483 $value = '/'.join('/', @parts[0 .. $#parts - @levels], $rest);
f3107403 484 } elsif ($value !~ m/^\//) {
485 my $action_ns = $self->action_namespace($c);
486
487 if ($action_ns) {
488 $value = '/'.join('/', $action_ns, $value);
489 } else {
490 $value = '/'.$value; # special case namespace '' (root)
491 }
492 }
493 } else {
494 $value = '/'
495 }
496
497 return Chained => $value;
498}
499
9356b981 500sub _parse_ChainedParent_attr {
501 my ($self, $c, $name, $value) = @_;
502 return $self->_parse_Chained_attr($c, $name, '../'.$name);
503}
504
e5d2cfdb 505sub _parse_PathPrefix_attr {
02825551 506 my ( $self, $c ) = @_;
507 return PathPart => $self->path_prefix($c);
e5d2cfdb 508}
509
234763d4 510sub _parse_ActionClass_attr {
511 my ( $self, $c, $name, $value ) = @_;
5d8129e9 512 my $appname = $self->_application;
513 $value = Catalyst::Utils::resolve_namespace($appname . '::Action', $self->_action_class, $value);
234763d4 514 return ( 'ActionClass', $value );
515}
516
9287719b 517sub _parse_MyAction_attr {
518 my ( $self, $c, $name, $value ) = @_;
519
520 my $appclass = Catalyst::Utils::class2appclass($self);
521 $value = "${appclass}::Action::${value}";
234763d4 522
9287719b 523 return ( 'ActionClass', $value );
524}
234763d4 525
eff60019 526sub _parse_Does_attr {
527 my ($self, $app, $name, $value) = @_;
528 return Does => $self->_expand_role_shortname($value);
529}
530
531sub _expand_role_shortname {
532 my ($self, @shortnames) = @_;
533 my $app = $self->_application;
534
535 my $prefix = $self->can('_action_role_prefix') ? $self->_action_role_prefix : ['Catalyst::ActionRole::'];
536 my @prefixes = (qq{${app}::ActionRole::}, @$prefix);
537
538 return String::RewritePrefix->rewrite(
539 { '' => sub {
540 my $loaded = Class::MOP::load_first_existing_class(
541 map { "$_$_[0]" } @prefixes
542 );
543 return first { $loaded =~ /^$_/ }
544 sort { length $b <=> length $a } @prefixes;
545 },
546 '~' => $prefixes[0],
547 '+' => '' },
548 @shortnames,
549 );
550}
551
ae29b412 552__PACKAGE__->meta->make_immutable;
553
234763d4 5541;
555
556__END__
557
558=head1 CONFIGURATION
559
a269e0c2 560Like any other L<Catalyst::Component>, controllers have a config hash,
561accessible through $self->config from the controller actions. Some
562settings are in use by the Catalyst framework:
234763d4 563
564=head2 namespace
565
a269e0c2 566This specifies the internal namespace the controller should be bound
567to. By default the controller is bound to the URI version of the
568controller name. For instance controller 'MyApp::Controller::Foo::Bar'
569will be bound to 'foo/bar'. The default Root controller is an example
570of setting namespace to '' (the null string).
234763d4 571
27ae4114 572=head2 path
234763d4 573
574Sets 'path_prefix', as described below.
575
0a2577a8 576=head2 action
577
578Allows you to set the attributes that the dispatcher creates actions out of.
579This allows you to do 'rails style routes', or override some of the
f4dda4a8 580attribute definitions of actions composed from Roles.
0a2577a8 581You can set arguments globally (for all actions of the controller) and
582specifically (for a single action).
583
584 __PACKAGE__->config(
585 action => {
586 '*' => { Chained => 'base', Args => 0 },
587 base => { Chained => '/', PathPart => '', CaptureArgs => 0 },
588 },
589 );
590
591In the case above every sub in the package would be made into a Chain
592endpoint with a URI the same as the sub name for each sub, chained
593to the sub named C<base>. Ergo dispatch to C</example> would call the
594C<base> method, then the C<example> method.
595
c8136648 596=head2 action_args
597
4d4e5de8 598Allows you to set constructor arguments on your actions. You can set arguments
0a2577a8 599globally and specifically (as above).
600This is particularly useful when using C<ActionRole>s
b939ae6b 601(L<Catalyst::Controller::ActionRole>) and custom C<ActionClass>es.
c8136648 602
b939ae6b 603 __PACKAGE__->config(
c8136648 604 action_args => {
b939ae6b 605 '*' => { globalarg1 => 'hello', globalarg2 => 'goodbye' },
606 'specific_action' => { customarg => 'arg1' },
cea3f28a 607 },
b939ae6b 608 );
cea3f28a 609
b939ae6b 610In the case above the action class associated with C<specific_action> would get
611passed the following arguments, in addition to the normal action constructor
612arguments, when it is instantiated:
613
614 (globalarg1 => 'hello', globalarg2 => 'goodbye', customarg => 'arg1')
c8136648 615
234763d4 616=head1 METHODS
617
c4d02967 618=head2 BUILDARGS ($app, @args)
234763d4 619
c4d02967 620From L<Catalyst::Component::ApplicationAttribute>, stashes the application
621instance as $self->_application.
234763d4 622
623=head2 $self->action_for('name')
624
a269e0c2 625Returns the Catalyst::Action object (if any) for a given method name
626in this component.
234763d4 627
234763d4 628=head2 $self->action_namespace($c)
629
a269e0c2 630Returns the private namespace for actions in this component. Defaults
631to a value from the controller name (for
632e.g. MyApp::Controller::Foo::Bar becomes "foo/bar") or can be
633overridden from the "namespace" config key.
234763d4 634
635
636=head2 $self->path_prefix($c)
637
e5d2cfdb 638Returns the default path prefix for :PathPrefix, :Local, :LocalRegex and
639relative :Path actions in this component. Defaults to the action_namespace or
a269e0c2 640can be overridden from the "path" config key.
234763d4 641
c4d02967 642=head2 $self->register_actions($c)
643
644Finds all applicable actions for this component, creates
645Catalyst::Action objects (using $self->create_action) for them and
646registers them with $c->dispatcher.
647
648=head2 $self->get_action_methods()
649
650Returns a list of L<Moose::Meta::Method> objects, doing the
651L<MooseX::MethodAttributes::Role::Meta::Method> role, which are the set of
652action methods for this package.
653
654=head2 $self->register_action_methods($c, @methods)
655
656Creates action objects for a set of action methods using C< create_action >,
657and registers them with the dispatcher.
658
f0a9b791 659=head2 $self->action_class(%args)
660
661Used when a controller is creating an action to determine the correct base
24d2dfaf 662action class to use.
f0a9b791 663
234763d4 664=head2 $self->create_action(%args)
665
a269e0c2 666Called with a hash of data to be use for construction of a new
667Catalyst::Action (or appropriate sub/alternative class) object.
234763d4 668
eff60019 669=head2 $self->gather_action_roles(\%action_args)
670
671Gathers the list of roles to apply to an action with the given %action_args.
672
a269e0c2 673=head2 $self->_application
234763d4 674
675=head2 $self->_app
676
677Returns the application instance stored by C<new()>
5ee249f2 678
0bf7ab71 679=head1 AUTHORS
5ee249f2 680
0bf7ab71 681Catalyst Contributors, see Catalyst.pm
5ee249f2 682
683=head1 COPYRIGHT
684
536bee89 685This library is free software. You can redistribute it and/or modify
a269e0c2 686it under the same terms as Perl itself.
5ee249f2 687
688=cut