Clean up share path search:
[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 File::HomeDir;
17 use Path::Resolver::Resolver::Mux::Ordered;
18 use Path::Resolver::Resolver::FileSystem;
19 use namespace::autoclean;
20
21 with 'MooseX::Emulate::Class::Accessor::Fast';
22
23 # Change Catalyst/Devel.pm also
24 our $VERSION = '1.23';
25
26 my %cache;
27
28 =head1 NAME
29
30 Catalyst::Helper - Bootstrap a Catalyst application
31
32 =head1 SYNOPSIS
33
34   catalyst.pl <myappname>
35
36 =cut
37
38 # Return the (cached) path resolver
39 {
40     my $resolver;
41
42     sub get_resolver {
43         my $self = shift;
44
45         # Avoid typing this over and over
46         my $fs_path = sub {
47             Path::Resolver::Resolver::FileSystem->new({ root => shift })
48         };
49
50         unless ($resolver) {
51             my @resolvers;
52             # Search path: first try the environment variable
53             if (exists $ENV{CATALYST_DEVEL_SHAREDIR}) {
54                 push @resolvers, $fs_path->($ENV{CATALYST_DEVEL_SHAREDIR});
55             }
56             # Then the application's "helper" directory
57             if (exists $self->{base}) {
58                 push @resolvers, $fs_path->(dir($self->{base}, "helper"));
59             }
60             # Then ~/.catalyst/helper
61             push @resolvers, $fs_path->(
62                 dir(File::HomeDir->my_home, ".catalyst", "helper")
63             );
64             # Finally the Catalyst default
65             if (-d "inc/.author" && -f "lib/Catalyst/Helper.pm"
66                     ) { # Can't use sharedir if we're in a checkout
67                         # this feels horrible, better ideas?
68                 push @resolvers, $fs_path->('share');
69             }
70             else {
71                 push @resolvers, $fs_path->(dist_dir('Catalyst-Devel'));
72             }
73
74             $resolver = Path::Resolver::Resolver::Mux::Ordered->new({
75                 resolvers => \@resolvers
76             });
77         }
78
79         return $resolver;
80     }
81 }
82
83 sub get_sharedir_file {
84     my ($self, @filename) = @_;
85
86     my $filepath = file(@filename);
87     my $file = $self->get_resolver->entity_at("$filepath") # doesn't like object
88         or Carp::confess("Cannot find $filepath");
89     return $file->content;
90 }
91
92 # Do not touch this method, *EVER*, it is needed for back compat.
93 sub get_file {
94     my ( $self, $class, $file ) = @_;
95     unless ( $cache{$class} ) {
96         local $/;
97         $cache{$class} = eval "package $class; <DATA>";
98     }
99     my $data = $cache{$class};
100     Carp::confess("Could not get data from __DATA__ segment for $class")
101         unless $data;
102     my @files = split /^__(.+)__\r?\n/m, $data;
103     shift @files;
104     while (@files) {
105         my ( $name, $content ) = splice @files, 0, 2;
106         return $content if $name eq $file;
107     }
108     return 0;
109 }
110
111
112 sub mk_app {
113     my ( $self, $name ) = @_;
114
115     # Needs to be here for PAR
116     require Catalyst;
117
118     if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
119         warn "Error: Invalid application name.\n";
120         return 0;
121     }
122     $self->{name            } = $name;
123     $self->{dir             } = $name;
124     $self->{dir             } =~ s/\:\:/-/g;
125     $self->{script          } = dir( $self->{dir}, 'script' );
126     $self->{appprefix       } = Catalyst::Utils::appprefix($name);
127     $self->{appenv          } = Catalyst::Utils::class2env($name);
128     $self->{startperl       } = -r '/usr/bin/env'
129                                 ? '#!/usr/bin/env perl'
130                                 : "#!$Config{perlpath} -w";
131     $self->{scriptgen       } = $Catalyst::Devel::CATALYST_SCRIPT_GEN;
132     $self->{catalyst_version} = $Catalyst::VERSION;
133     $self->{author          } = $self->{author} = $ENV{'AUTHOR'}
134       || eval { @{ [ getpwuid($<) ] }[6] }
135       || 'Catalyst developer';
136
137     my $gen_scripts  = ( $self->{makefile} ) ? 0 : 1;
138     my $gen_makefile = ( $self->{scripts} )  ? 0 : 1;
139     my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
140
141     if ($gen_app) {
142         for ( qw/ _mk_dirs _mk_config _mk_appclass _mk_rootclass _mk_readme
143               _mk_changes _mk_apptest _mk_images _mk_favicon/ ) {
144             
145             $self->$_;
146         }
147     }
148     if ($gen_makefile) {
149         $self->_mk_makefile;
150     }
151     if ($gen_scripts) {
152         for ( qw/ _mk_cgi _mk_fastcgi _mk_server 
153                   _mk_test _mk_create _mk_information
154         / ) {
155               $self->$_;
156         }
157     }
158     return $self->{dir};
159 }
160
161 ## not much of this can really be changed, mk_compclass must be left for 
162 ## backcompat
163 sub mk_component {
164     my $self = shift;
165     my $app  = shift;
166     $self->{app} = $app;
167     $self->{author} = $self->{author} = $ENV{'AUTHOR'}
168       || eval { @{ [ getpwuid($<) ] }[6] }
169       || 'A clever guy';
170     $self->{base} ||= dir( $FindBin::Bin, '..' );
171     unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
172         my $helper = shift;
173         my @args   = @_;
174         my $class  = "Catalyst::Helper::$helper";
175         eval "require $class";
176
177         if ($@) {
178             Catalyst::Exception->throw(
179                 message => qq/Couldn't load helper "$class", "$@"/ );
180         }
181
182         if ( $class->can('mk_stuff') ) {
183             return 1 unless $class->mk_stuff( $self, @args );
184         }
185     }
186     else {
187         my $type   = shift;
188         my $name   = shift || "Missing name for model/view/controller";
189         my $helper = shift;
190         my @args   = @_;
191        return 0 if $name =~ /[^\w\:]/;
192         $type              = lc $type;
193         $self->{long_type} = ucfirst $type;
194         $type              = 'M' if $type =~ /model/i;
195         $type              = 'V' if $type =~ /view/i;
196         $type              = 'C' if $type =~ /controller/i;
197         my $appdir = dir( split /\:\:/, $app );
198         my $test_path =
199           dir( $self->{base}, 'lib', $appdir, 'C' );
200         $type = $self->{long_type} unless -d $test_path;
201         $self->{type}  = $type;
202         $self->{name}  = $name;
203         $self->{class} = "$app\::$type\::$name";
204
205         # Class
206         my $path =
207           dir( $self->{base}, 'lib', $appdir, $type );
208         my $file = $name;
209         if ( $name =~ /\:/ ) {
210             my @path = split /\:\:/, $name;
211             $file = pop @path;
212             $path = dir( $path, @path );
213         }
214         $self->mk_dir($path);
215         $file = file( $path, "$file.pm" );
216         $self->{file} = $file;
217
218         # Test
219         $self->{test_dir} = dir( $self->{base}, 't' );
220         $self->{test}     = $self->next_test;
221
222         # Helper
223         if ($helper) {
224             my $comp  = $self->{long_type};
225             my $class = "Catalyst::Helper::$comp\::$helper";
226             eval "require $class";
227
228             if ($@) {
229                 Catalyst::Exception->throw(
230                     message => qq/Couldn't load helper "$class", "$@"/ );
231             }
232
233             if ( $class->can('mk_compclass') ) {
234                 return 1 unless $class->mk_compclass( $self, @args );
235             }
236             else {
237                 return 1 unless $self->_mk_compclass
238             }
239
240             if ( $class->can('mk_comptest') ) {
241                 $class->mk_comptest( $self, @args );
242             }
243             else {
244                 $self->_mk_comptest
245             }
246         }
247
248         # Fallback
249         else {
250             return 1 unless $self->_mk_compclass;
251             $self->_mk_comptest;
252         }
253     }
254     return 1;
255 }
256
257 sub mk_dir {
258     my ( $self, $dir ) = @_;
259     if ( -d $dir ) {
260         print qq/ exists "$dir"\n/;
261         return 0;
262     }
263     if ( mkpath [$dir] ) {
264         print qq/created "$dir"\n/;
265         return 1;
266     }
267
268     Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
269 }
270
271 sub mk_file {
272     my ( $self, $file, $content ) = @_;
273     if ( -e $file && -s _ ) {
274         print qq/ exists "$file"\n/;
275         return 0
276           unless ( $self->{'.newfiles'}
277             || $self->{scripts}
278             || $self->{makefile} );
279         if ( $self->{'.newfiles'} ) {
280             if ( my $f = IO::File->new("< $file") ) {
281                 my $oldcontent = join( '', (<$f>) );
282                 return 0 if $content eq $oldcontent;
283             }
284             $file .= '.new';
285         }
286     }
287     
288     if ( my $f = IO::File->new("> $file") ) {
289         binmode $f;
290         print $f $content;
291         print qq/created "$file"\n/;
292         return 1;
293     }
294
295     Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
296 }
297
298 sub next_test {
299     my ( $self, $tname ) = @_;
300     if ($tname) { $tname = "$tname.t" }
301     else {
302         my $name   = $self->{name};
303         my $prefix = $name;
304         $prefix =~ s/::/-/g;
305         $prefix         = $prefix;
306         $tname          = $prefix . '.t';
307         $self->{prefix} = $prefix;
308         $prefix         = lc $prefix;
309         $prefix =~ s/-/\//g;
310         $self->{uri} = "/$prefix";
311     }
312     my $dir  = $self->{test_dir};
313     my $type = lc $self->{type};
314     $self->mk_dir($dir);
315     return file( $dir, "$type\_$tname" );
316 }
317
318 # Do not touch this method, *EVER*, it is needed for back compat.
319 ## addendum: we had to split this method so we could have backwards
320 ## compatability.  otherwise, we'd have no way to pass stuff from __DATA__
321
322 sub render_file {
323     my ( $self, $file, $path, $vars ) = @_;
324     my $template = $self->get_file( ( caller(0) )[0], $file );
325     $self->render_file_contents($template, $path, $vars);
326 }
327
328 sub render_sharedir_file {
329     my ( $self, $file, $path, $vars ) = @_;
330     my $template = $self->get_sharedir_file( $file );
331     die("Cannot get template from $file for $self\n") unless $template;
332     $self->render_file_contents($template, $path, $vars);
333 }
334
335 sub render_file_contents {
336     my ( $self, $template, $path, $vars ) = @_;
337     $vars ||= {};
338     my $t = Template->new;
339     return 0 unless $template;
340     my $output;
341     $t->process( \$template, { %{$self}, %$vars }, \$output )
342       || Catalyst::Exception->throw(
343         message => qq/Couldn't process "$template", / . $t->error() );
344     $self->mk_file( $path, $output );
345 }
346
347 sub _mk_information {
348     my $self = shift;
349     print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
350 }
351
352 sub _mk_dirs {
353     my $self = shift;
354     $self->mk_dir( $self->{dir} );
355     $self->mk_dir( $self->{script} );
356     $self->{lib} = dir( $self->{dir}, 'lib' );
357     $self->mk_dir( $self->{lib} );
358     $self->{root} = dir( $self->{dir}, 'root' );
359     $self->mk_dir( $self->{root} );
360     $self->{static} = dir( $self->{root}, 'static' );
361     $self->mk_dir( $self->{static} );
362     $self->{images} = dir( $self->{static}, 'images' );
363     $self->mk_dir( $self->{images} );
364     $self->{t} = dir( $self->{dir}, 't' );
365     $self->mk_dir( $self->{t} );
366
367     $self->{class} = dir( split( /\:\:/, $self->{name} ) );
368     $self->{mod} = dir( $self->{lib}, $self->{class} );
369     $self->mk_dir( $self->{mod} );
370
371     if ( $self->{short} ) {
372         $self->{m} = dir( $self->{mod}, 'M' );
373         $self->mk_dir( $self->{m} );
374         $self->{v} = dir( $self->{mod}, 'V' );
375         $self->mk_dir( $self->{v} );
376         $self->{c} = dir( $self->{mod}, 'C' );
377         $self->mk_dir( $self->{c} );
378     }
379     else {
380         $self->{m} = dir( $self->{mod}, 'Model' );
381         $self->mk_dir( $self->{m} );
382         $self->{v} = dir( $self->{mod}, 'View' );
383         $self->mk_dir( $self->{v} );
384         $self->{c} = dir( $self->{mod}, 'Controller' );
385         $self->mk_dir( $self->{c} );
386     }
387     my $name = $self->{name};
388     $self->{rootname} =
389       $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
390     $self->{base} = dir( $self->{dir} )->absolute;
391 }
392
393 sub _mk_appclass {
394     my $self = shift;
395     my $mod  = $self->{mod};
396     $self->render_sharedir_file( file('lib', 'MyApp.pm.tt'), "$mod.pm" );
397 }
398
399 sub _mk_rootclass {
400     my $self = shift;
401     $self->render_sharedir_file( file('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
402         file( $self->{c}, "Root.pm" ) );
403 }
404
405 sub _mk_makefile {
406     my $self = shift;
407     $self->{path} = dir( 'lib', split( '::', $self->{name} ) );
408     $self->{path} .= '.pm';
409     my $dir = $self->{dir};
410     $self->render_sharedir_file( 'Makefile.PL.tt', file($dir, "Makefile.PL") );
411
412     if ( $self->{makefile} ) {
413
414         # deprecate the old Build.PL file when regenerating Makefile.PL
415         $self->_deprecate_file(
416             file( $self->{dir}, 'Build.PL' ) );
417     }
418 }
419
420 sub _mk_config {
421     my $self      = shift;
422     my $dir       = $self->{dir};
423     my $appprefix = $self->{appprefix};
424     $self->render_sharedir_file( 'myapp.conf.tt',
425         file( $dir, "$appprefix.conf" ) );
426 }
427
428 sub _mk_readme {
429     my $self = shift;
430     my $dir  = $self->{dir};
431     $self->render_sharedir_file( 'README.tt', file($dir, "README") );
432 }
433
434 sub _mk_changes {
435     my $self = shift;
436     my $dir  = $self->{dir};
437     my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
438     $self->render_sharedir_file( 'Changes.tt', file($dir, "Changes"), { time => $time } );
439 }
440
441 sub _mk_apptest {
442     my $self = shift;
443     my $t    = $self->{t};
444     $self->render_sharedir_file( file('t', '01app.t.tt'),         file($t, "01app.t") );
445     $self->render_sharedir_file( file('t', '02pod.t.tt'),         file($t, "02pod.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;