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