9785aecaee3c590a790cf223ca672d9324b11998
[catagits/Catalyst-Devel.git] / lib / Catalyst / Helper.pm
1 package Catalyst::Helper;
2 use Moose;
3 use Config;
4 use File::Spec;
5 use File::Spec::Unix;
6 use File::Path;
7 use FindBin;
8 use IO::File;
9 use POSIX 'strftime';
10 use Template;
11 use Catalyst::Devel;
12 use Catalyst::Utils;
13 use Catalyst::Exception;
14 use Path::Class qw/dir file/;
15 use File::ShareDir qw/dist_dir/;
16 use YAML::Tiny;
17 use namespace::autoclean;
18
19 with 'MooseX::Emulate::Class::Accessor::Fast';
20
21 our $VERSION = '1.41';
22 $VERSION =~ tr/_//d;
23
24 my %cache;
25
26 sub get_sharedir_file {
27     my ($self, @filename) = @_;
28     my $dist_dir;
29     if (exists $ENV{CATALYST_DEVEL_SHAREDIR}) {
30         $dist_dir = $ENV{CATALYST_DEVEL_SHAREDIR};
31     }
32     elsif (-d "inc/.author" && -f "lib/Catalyst/Helper.pm"
33             ) { # Can't use sharedir if we're in a checkout
34                 # this feels horrible, better ideas?
35         $dist_dir = 'share';
36     }
37     else {
38         $dist_dir = dist_dir('Catalyst-Devel');
39     }
40     my $file = file( $dist_dir, @filename);
41     Carp::confess("Cannot find $file") unless -r $file;
42     my $contents = $file->slurp(iomode =>  "<:raw");
43     return $contents;
44 }
45
46 # Do not touch this method, *EVER*, it is needed for back compat.
47 sub get_file {
48     my ( $self, $class, $file ) = @_;
49     unless ( $cache{$class} ) {
50         local $/;
51         $cache{$class} = eval "package $class; <DATA>";
52     }
53     my $data = $cache{$class};
54     Carp::confess("Could not get data from __DATA__ segment for $class")
55         unless $data;
56     my @files = split /^__(.+)__\r?\n/m, $data;
57     shift @files;
58     while (@files) {
59         my ( $name, $content ) = splice @files, 0, 2;
60         return $content if $name eq $file;
61     }
62     return 0;
63 }
64
65
66 sub mk_app {
67     my ( $self, $name ) = @_;
68
69     # Needs to be here for PAR
70     require Catalyst;
71
72     if($name eq '.') {
73         if(!-e 'META.yml') {
74             system perl => 'Makefile.PL'
75                 and Catalyst::Exception->throw(message => q(
76                     Failed to run "perl Makefile.PL".
77                 ));
78         }
79
80         $name = YAML::Tiny->read('META.yml')->[0]->{'name'};
81         $name =~ s/-/::/g;
82         $self->{dir} = '.';
83     }
84
85     if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
86         warn "Error: Invalid application name.\n";
87         return 0;
88     }
89
90
91     if(!defined $self->{'dir'}) {
92         $self->{dir} = $name;
93         $self->{dir} =~ s/\:\:/-/g;
94     }
95
96     $self->{name            } = $name;
97     $self->{script          } = dir( $self->{dir}, 'script' );
98     $self->{appprefix       } = Catalyst::Utils::appprefix($name);
99     $self->{appenv          } = Catalyst::Utils::class2env($name);
100     $self->{startperl       } = -r '/usr/bin/env'
101                                 ? '#!/usr/bin/env perl'
102                                 : "#!$Config{perlpath}";
103     $self->{scriptgen       } = $Catalyst::Devel::CATALYST_SCRIPT_GEN;
104     $self->{catalyst_version} = $Catalyst::VERSION;
105     $self->{author          } ||= $ENV{'AUTHOR'}
106       || eval { @{ [ getpwuid($<) ] }[6] }
107       || 'Catalyst developer';
108
109     my $gen_scripts  = ( $self->{makefile} ) ? 0 : 1;
110     my $gen_makefile = ( $self->{scripts} )  ? 0 : 1;
111     my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
112
113     if ($gen_app) {
114         for ( qw/ _mk_dirs _mk_config _mk_psgi _mk_appclass _mk_rootclass
115               _mk_readme _mk_changes _mk_apptest _mk_podtest _mk_podcoveragetest
116               _mk_images _mk_favicon/ ) {
117             $self->$_;
118         }
119     }
120     if ($gen_makefile) {
121         $self->_mk_makefile;
122     }
123     if ($gen_scripts) {
124         for ( qw/ _mk_cgi _mk_fastcgi _mk_server
125                   _mk_test _mk_create _mk_information
126         / ) {
127               $self->$_;
128         }
129     }
130     return $self->{dir};
131 }
132
133 ## not much of this can really be changed, mk_compclass must be left for
134 ## backcompat
135 sub mk_component {
136     my $self = shift;
137     my $app  = shift;
138     $self->{app} = $app;
139     $self->{author} = $self->{author} = $ENV{'AUTHOR'}
140       || eval { @{ [ getpwuid($<) ] }[6] }
141       || 'A clever guy';
142     $self->{base} ||= dir( $FindBin::Bin, '..' );
143     unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
144         my $helper = shift;
145         my @args   = @_;
146         my $class  = "Catalyst::Helper::$helper";
147         eval "require $class";
148
149         if ($@) {
150             Catalyst::Exception->throw(
151                 message => qq/Couldn't load helper "$class", "$@"/ );
152         }
153
154         if ( $class->can('mk_stuff') ) {
155             return 1 unless $class->mk_stuff( $self, @args );
156         }
157     }
158     else {
159         my $type   = shift;
160         my $name   = shift || "Missing name for model/view/controller";
161         my $helper = shift;
162         my @args   = @_;
163        return 0 if $name =~ /[^\w\:]/;
164         $type              = lc $type;
165         $self->{long_type} = ucfirst $type;
166         $type              = 'M' if $type =~ /model/i;
167         $type              = 'V' if $type =~ /view/i;
168         $type              = 'C' if $type =~ /controller/i;
169         my $appdir = dir( split /\:\:/, $app );
170         my $test_path =
171           dir( $self->{base}, 'lib', $appdir, 'C' );
172         $type = $self->{long_type} unless -d $test_path;
173         $self->{type}  = $type;
174         $self->{name}  = $name;
175         $self->{class} = "$app\::$type\::$name";
176
177         # Class
178         my $path =
179           dir( $self->{base}, 'lib', $appdir, $type );
180         my $file = $name;
181         if ( $name =~ /\:/ ) {
182             my @path = split /\:\:/, $name;
183             $file = pop @path;
184             $path = dir( $path, @path );
185         }
186         $self->mk_dir($path);
187         $file = file( $path, "$file.pm" );
188         $self->{file} = $file;
189
190         # Test
191         $self->{test_dir} = dir( $self->{base}, 't' );
192         $self->{test}     = $self->next_test;
193
194         # Helper
195         if ($helper) {
196             my $comp  = $self->{long_type};
197             my $class = "Catalyst::Helper::$comp\::$helper";
198             eval "require $class";
199
200             if ($@) {
201                 Catalyst::Exception->throw(
202                     message => qq/Couldn't load helper "$class", "$@"/ );
203             }
204
205             if ( $class->can('mk_compclass') ) {
206                 return 1 unless $class->mk_compclass( $self, @args );
207             }
208             else {
209                 return 1 unless $self->_mk_compclass
210             }
211
212             if ( $class->can('mk_comptest') ) {
213                 $class->mk_comptest( $self, @args );
214             }
215             else {
216                 $self->_mk_comptest
217             }
218         }
219
220         # Fallback
221         else {
222             return 1 unless $self->_mk_compclass;
223             $self->_mk_comptest;
224         }
225     }
226     return 1;
227 }
228
229 sub mk_dir {
230     my ( $self, $dir ) = @_;
231     if ( -d $dir ) {
232         print qq/ exists "$dir"\n/;
233         return 0;
234     }
235     if ( mkpath [$dir] ) {
236         print qq/created "$dir"\n/;
237         return 1;
238     }
239
240     Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
241 }
242
243 sub mk_file {
244     my ( $self, $file, $content ) = @_;
245     if ( -e $file && -s _ ) {
246         print qq/ exists "$file"\n/;
247         return 0
248           unless ( $self->{'.newfiles'}
249             || $self->{scripts}
250             || $self->{makefile} );
251         if ( $self->{'.newfiles'} ) {
252             if ( my $f = IO::File->new("< $file") ) {
253                 my $oldcontent = join( '', (<$f>) );
254                 return 0 if $content eq $oldcontent;
255             }
256             $file .= '.new';
257         }
258     }
259
260     if ( my $f = IO::File->new("> $file") ) {
261         binmode $f;
262         print $f $content;
263         print qq/created "$file"\n/;
264         return $file;
265     }
266
267     Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
268 }
269
270 sub next_test {
271     my ( $self, $tname ) = @_;
272     if ($tname) { $tname = "$tname.t" }
273     else {
274         my $name   = $self->{name};
275         my $prefix = $name;
276         $prefix =~ s/::/-/g;
277         $prefix         = $prefix;
278         $tname          = $prefix . '.t';
279         $self->{prefix} = $prefix;
280         $prefix         = lc $prefix;
281         $prefix =~ s/-/\//g;
282         $self->{uri} = "/$prefix";
283     }
284     my $dir  = $self->{test_dir};
285     my $type = lc $self->{type};
286     $self->mk_dir($dir);
287     return file( $dir, "$type\_$tname" );
288 }
289
290 # Do not touch this method, *EVER*, it is needed for back compat.
291 ## addendum: we had to split this method so we could have backwards
292 ## compatibility.  otherwise, we'd have no way to pass stuff from __DATA__
293
294 sub render_file {
295     my ( $self, $file, $path, $vars, $perms ) = @_;
296     my $template = $self->get_file( ( caller(0) )[0], $file );
297     $self->render_file_contents($template, $path, $vars, $perms);
298 }
299
300 sub render_sharedir_file {
301     my ( $self, $file, $path, $vars, $perms ) = @_;
302     my $template = $self->get_sharedir_file( $file );
303     die("Cannot get template from $file for $self\n") unless $template;
304     $self->render_file_contents($template, $path, $vars, $perms);
305 }
306
307 sub render_file_contents {
308     my ( $self, $template, $path, $vars, $perms ) = @_;
309     $vars ||= {};
310     my $t = Template->new;
311     return 0 unless $template;
312     my $output;
313     $t->process( \$template, { %{$self}, %$vars }, \$output )
314       || Catalyst::Exception->throw(
315         message => qq/Couldn't process "$template", / . $t->error() );
316     my $file = $self->mk_file( $path, $output );
317     chmod $perms, file($file) if defined $perms;
318     return $file;
319 }
320
321 sub _mk_information {
322     my $self = shift;
323     print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
324 }
325
326 sub _mk_dirs {
327     my $self = shift;
328     $self->mk_dir( $self->{dir} );
329     $self->mk_dir( $self->{script} );
330     $self->{lib} = dir( $self->{dir}, 'lib' );
331     $self->mk_dir( $self->{lib} );
332     $self->{root} = dir( $self->{dir}, 'root' );
333     $self->mk_dir( $self->{root} );
334     $self->{static} = dir( $self->{root}, 'static' );
335     $self->mk_dir( $self->{static} );
336     $self->{images} = dir( $self->{static}, 'images' );
337     $self->mk_dir( $self->{images} );
338     $self->{t} = dir( $self->{dir}, 't' );
339     $self->mk_dir( $self->{t} );
340
341     $self->{class} = dir( split( /\:\:/, $self->{name} ) );
342     $self->{mod} = dir( $self->{lib}, $self->{class} );
343     $self->mk_dir( $self->{mod} );
344
345     if ( $self->{short} ) {
346         $self->{m} = dir( $self->{mod}, 'M' );
347         $self->mk_dir( $self->{m} );
348         $self->{v} = dir( $self->{mod}, 'V' );
349         $self->mk_dir( $self->{v} );
350         $self->{c} = dir( $self->{mod}, 'C' );
351         $self->mk_dir( $self->{c} );
352     }
353     else {
354         $self->{m} = dir( $self->{mod}, 'Model' );
355         $self->mk_dir( $self->{m} );
356         $self->{v} = dir( $self->{mod}, 'View' );
357         $self->mk_dir( $self->{v} );
358         $self->{c} = dir( $self->{mod}, 'Controller' );
359         $self->mk_dir( $self->{c} );
360     }
361     my $name = $self->{name};
362     $self->{rootname} =
363       $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
364     $self->{base} = dir( $self->{dir} )->absolute;
365 }
366
367 sub _mk_appclass {
368     my $self = shift;
369     my $mod  = $self->{mod};
370     $self->render_sharedir_file( file('lib', 'MyApp.pm.tt'), "$mod.pm" );
371 }
372
373 sub _mk_rootclass {
374     my $self = shift;
375     $self->render_sharedir_file( file('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
376         file( $self->{c}, "Root.pm" ) );
377 }
378
379 sub _mk_makefile {
380     my $self = shift;
381     $self->{path} = join('/', 'lib', split( '::', $self->{name} ) );
382     $self->{path} .= '.pm';
383     my $dir = $self->{dir};
384     $self->render_sharedir_file( 'Makefile.PL.tt', file($dir, "Makefile.PL") );
385
386     if ( $self->{makefile} ) {
387
388         # deprecate the old Build.PL file when regenerating Makefile.PL
389         $self->_deprecate_file(
390             file( $self->{dir}, 'Build.PL' ) );
391     }
392 }
393
394 sub _mk_psgi {
395     my $self      = shift;
396     my $dir       = $self->{dir};
397     my $appprefix = $self->{appprefix};
398     $self->render_sharedir_file( 'myapp.psgi.tt',
399         file( $dir, "$appprefix.psgi" ) );
400 }
401
402 sub _mk_config {
403     my $self      = shift;
404     my $dir       = $self->{dir};
405     my $appprefix = $self->{appprefix};
406     $self->render_sharedir_file( 'myapp.conf.tt',
407         file( $dir, "$appprefix.conf" ) );
408 }
409
410 sub _mk_readme {
411     my $self = shift;
412     my $dir  = $self->{dir};
413     $self->render_sharedir_file( 'README.tt', file($dir, "README") );
414 }
415
416 sub _mk_changes {
417     my $self = shift;
418     my $dir  = $self->{dir};
419     my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
420     $self->render_sharedir_file( 'Changes.tt', file($dir, "Changes"), { time => $time } );
421 }
422
423 sub _mk_apptest {
424     my $self = shift;
425     my $t    = $self->{t};
426     $self->render_sharedir_file( file('t', '01app.t.tt'),         file($t, "01app.t") );
427 }
428
429 sub _mk_podtest {
430     my $self = shift;
431     my $t    = $self->{t};
432     $self->render_sharedir_file( file('t', '02pod.t.tt'),         file($t, "02pod.t") );
433 }
434
435 sub _mk_podcoveragetest {
436     my $self = shift;
437     my $t    = $self->{t};
438     $self->render_sharedir_file( file('t', '03podcoverage.t.tt'), file($t, "03podcoverage.t") );
439 }
440
441 sub _mk_cgi {
442     my $self      = shift;
443     my $script    = $self->{script};
444     my $appprefix = $self->{appprefix};
445     $self->render_sharedir_file( file('script', 'myapp_cgi.pl.tt'),
446         file($script,"$appprefix\_cgi.pl"), undef, 0755 );
447 }
448
449 sub _mk_fastcgi {
450     my $self      = shift;
451     my $script    = $self->{script};
452     my $appprefix = $self->{appprefix};
453     $self->render_sharedir_file( file('script', 'myapp_fastcgi.pl.tt'),
454         file($script, "$appprefix\_fastcgi.pl"), undef, 0755 );
455 }
456
457 sub _mk_server {
458     my $self      = shift;
459     my $script    = $self->{script};
460     my $appprefix = $self->{appprefix};
461     $self->render_sharedir_file( file('script', 'myapp_server.pl.tt'),
462         file($script, "$appprefix\_server.pl"), undef, 0755 );
463 }
464
465 sub _mk_test {
466     my $self      = shift;
467     my $script    = $self->{script};
468     my $appprefix = $self->{appprefix};
469     $self->render_sharedir_file( file('script', 'myapp_test.pl.tt'),
470         file($script, "$appprefix\_test.pl"), undef, 0755 );
471 }
472
473 sub _mk_create {
474     my $self      = shift;
475     my $script    = $self->{script};
476     my $appprefix = $self->{appprefix};
477     $self->render_sharedir_file( file('script', 'myapp_create.pl.tt'),
478         file($script, "$appprefix\_create.pl"), undef, 0755 );
479 }
480
481 sub _mk_compclass {
482     my $self = shift;
483     my $file = $self->{file};
484     return $self->render_sharedir_file( file('lib', 'Helper', 'compclass.pm.tt'), $file );
485 }
486
487 sub _mk_comptest {
488     my $self = shift;
489     my $test = $self->{test};
490     $self->render_sharedir_file( file('t', 'comptest.tt'), $test );  ## wtf do i rename this to?
491 }
492
493 sub _mk_images {
494     my $self   = shift;
495     my $images = $self->{images};
496     my @images =
497       qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
498       btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
499       btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
500     for my $name (@images) {
501         my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
502         $self->mk_file( file( $images, "$name.png" ), $image );
503     }
504 }
505
506 sub _mk_favicon {
507     my $self    = shift;
508     my $root    = $self->{root};
509     my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
510     my $dest = dir( $root, "favicon.ico" );
511     $self->mk_file( $dest, $favicon );
512
513 }
514
515 sub _deprecate_file {
516     my ( $self, $file ) = @_;
517     if ( -e $file ) {
518         my ($f, $oldcontent);
519         if ( $f = IO::File->new("< $file") ) {
520             $oldcontent = join( '', (<$f>) );
521         }
522         my $newfile = $file . '.deprecated';
523         if ( $f = IO::File->new("> $newfile") ) {
524             binmode $f;
525             print $f $oldcontent;
526             print qq/created "$newfile"\n/;
527             unlink $file;
528             print qq/removed "$file"\n/;
529             return 1;
530         }
531         Catalyst::Exception->throw(
532             message => qq/Couldn't create "$file", "$!"/ );
533     }
534 }
535
536 1;
537 __END__
538
539 =head1 NAME
540
541 Catalyst::Helper - Bootstrap a Catalyst application
542
543 =head1 SYNOPSIS
544
545   catalyst.pl <myappname>
546
547 =head1 DESCRIPTION
548
549 This module is used by B<catalyst.pl> to create a set of scripts for a
550 new catalyst application. The scripts each contain documentation and
551 will output help on how to use them if called incorrectly or in some
552 cases, with no arguments.
553
554 It also provides some useful methods for a Helper module to call when
555 creating a component. See L</METHODS>.
556
557 =head1 SCRIPTS
558
559 =head2 _create.pl
560
561 Used to create new components for a catalyst application at the
562 development stage.
563
564 =head2 _server.pl
565
566 The catalyst test server, starts an HTTPD which outputs debugging to
567 the terminal.
568
569 =head2 _test.pl
570
571 A script for running tests from the command-line.
572
573 =head2 _cgi.pl
574
575 Run your application as a CGI.
576
577 =head2 _fastcgi.pl
578
579 Run the application as a fastcgi app. Either by hand, or call this
580 from FastCgiServer in your http server config.
581
582 =head1 HELPERS
583
584 The L</_create.pl> script creates application components using Helper
585 modules. The Catalyst team provides a good number of Helper modules
586 for you to use. You can also add your own.
587
588 Helpers are classes that provide two methods.
589
590     * mk_compclass - creates the Component class
591     * mk_comptest  - creates the Component test
592
593 So when you call C<scripts/myapp_create.pl view MyView TT>, create
594 will try to execute Catalyst::Helper::View::TT->mk_compclass and
595 Catalyst::Helper::View::TT->mk_comptest.
596
597 See L<Catalyst::Helper::View::TT> and
598 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
599
600 All helper classes should be under one of the following namespaces.
601
602     Catalyst::Helper::Model::
603     Catalyst::Helper::View::
604     Catalyst::Helper::Controller::
605
606 =head2 COMMON HELPERS
607
608 =over
609
610 =item *
611
612 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
613
614 =item *
615
616 L<Catalyst::Helper::View::TT> - Template Toolkit view
617
618 =item *
619
620 L<Catalyst::Helper::Model::LDAP>
621
622 =item *
623
624 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
625
626 =back
627
628 =head3 NOTE
629
630 The helpers will read author name from /etc/passwd by default.
631 To override, please export the AUTHOR variable.
632
633 =head1 METHODS
634
635 =head2 mk_compclass
636
637 This method in your Helper module is called with C<$helper>
638 which is a L<Catalyst::Helper> object, and whichever other arguments
639 the user added to the command-line. You can use the $helper to call methods
640 described below.
641
642 If the Helper module does not contain a C<mk_compclass> method, it
643 will fall back to calling L</render_file>, with an argument of
644 C<compclass>.
645
646 =head2 mk_comptest
647
648 This method in your Helper module is called with C<$helper>
649 which is a L<Catalyst::Helper> object, and whichever other arguments
650 the user added to the command-line. You can use the $helper to call methods
651 described below.
652
653 If the Helper module does not contain a C<mk_compclass> method, it
654 will fall back to calling L</render_file>, with an argument of
655 C<comptest>.
656
657 =head2 mk_stuff
658
659 This method is called if the user does not supply any of the usual
660 component types C<view>, C<controller>, C<model>. It is passed the
661 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
662 arguments the user typed.
663
664 There is no fallback for this method.
665
666 =head1 INTERNAL METHODS
667
668 These are the methods that the Helper classes can call on the
669 <$helper> object passed to them.
670
671 =head2 render_file ($file, $path, $vars, $perms)
672
673 Render and create a file from a template in DATA using Template
674 Toolkit. $file is the relevant chunk of the __DATA__ section, $path is
675 the path to the file, $vars is the hashref as expected by
676 L<Template Toolkit|Template> and $perms are desired permissions (or system
677 defaults if not set).
678
679 =head2 get_file ($class, $file)
680
681 Fetch file contents from the DATA section. This is used internally by
682 L</render_file>.  $class is the name of the class to get the DATA
683 section from.  __PACKAGE__ or ( caller(0) )[0] might be sensible
684 values for this.
685
686 =head2 mk_app
687
688 Create the main application skeleton. This is called by L<catalyst.pl>.
689
690 =head2 mk_component ($app)
691
692 This method is called by L<create.pl> to make new components
693 for your application.
694
695 =head2 mk_dir ($path)
696
697 Surprisingly, this function makes a directory.
698
699 =head2 mk_file ($file, $content)
700
701 Writes content to a file. Called by L</render_file>.
702
703 =head2 next_test ($test_name)
704
705 Calculates the name of the next numbered test file and returns it.
706 Don't give the number or the .t suffix for the test name.
707
708 =cut
709
710 =head2 get_sharedir_file
711
712 Method for getting a file out of share/
713
714 =head2 render_file_contents
715
716 Process a L<Template::Toolkit> template.
717
718 =head2 render_sharedir_file
719
720 Render a template/image file from our share directory
721
722 =head1 NOTE
723
724 The helpers will read author name from /etc/passwd by default.
725 To override, please export the AUTHOR variable.
726
727 =head1 SEE ALSO
728
729 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
730 L<Catalyst::Response>, L<Catalyst>
731
732 =head1 AUTHORS
733
734 Catalyst Contributors, see Catalyst.pm
735
736 =head1 LICENSE
737
738 This library is free software. You can redistribute it and/or modify
739 it under the same terms as Perl itself.
740
741 =cut