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