1 package Catalyst::Helper;
13 use Catalyst::Exception;
14 use Path::Class qw/dir file/;
16 use Path::Resolver::Resolver::Mux::Ordered;
17 use Path::Resolver::Resolver::FileSystem;
18 use Path::Resolver::Resolver::DistDir;
19 use namespace::autoclean;
21 with 'MooseX::Emulate::Class::Accessor::Fast';
23 # Change Catalyst/Devel.pm also
24 our $VERSION = '1.23';
30 Catalyst::Helper - Bootstrap a Catalyst application
34 catalyst.pl <myappname>
38 # Return the (cached) path resolver
45 # Avoid typing this over and over
47 Path::Resolver::Resolver::FileSystem->new({ root => shift })
52 # Search path: first try the environment variable
53 if (exists $ENV{CATALYST_DEVEL_SHAREDIR}) {
54 push @resolvers, $fs_path->($ENV{CATALYST_DEVEL_SHAREDIR});
56 # Then the application's "helper" directory
57 if (exists $self->{base}) {
58 push @resolvers, $fs_path->(dir($self->{base}, "helper"));
60 # Then ~/.catalyst/helper
61 push @resolvers, $fs_path->(
62 dir(File::HomeDir->my_home, ".catalyst", "helper")
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');
71 push @resolvers, Path::Resolver::Resolver::DistDir->new({
72 dist_name => "Catalyst-Devel"
76 $resolver = Path::Resolver::Resolver::Mux::Ordered->new({
77 resolvers => \@resolvers
85 sub get_sharedir_file {
86 my ($self, @filename) = @_;
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;
94 # Do not touch this method, *EVER*, it is needed for back compat.
96 my ( $self, $class, $file ) = @_;
97 unless ( $cache{$class} ) {
99 $cache{$class} = eval "package $class; <DATA>";
101 my $data = $cache{$class};
102 Carp::confess("Could not get data from __DATA__ segment for $class")
104 my @files = split /^__(.+)__\r?\n/m, $data;
107 my ( $name, $content ) = splice @files, 0, 2;
108 return $content if $name eq $file;
115 my ( $self, $name ) = @_;
117 # Needs to be here for PAR
120 if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
121 warn "Error: Invalid application name.\n";
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';
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;
144 for ( qw/ _mk_dirs _mk_config _mk_appclass _mk_rootclass _mk_readme
145 _mk_changes _mk_apptest _mk_images _mk_favicon/ ) {
154 for ( qw/ _mk_cgi _mk_fastcgi _mk_server
155 _mk_test _mk_create _mk_information
163 ## not much of this can really be changed, mk_compclass must be left for
169 $self->{author} = $self->{author} = $ENV{'AUTHOR'}
170 || eval { @{ [ getpwuid($<) ] }[6] }
172 $self->{base} ||= dir( $FindBin::Bin, '..' );
173 unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
176 my $class = "Catalyst::Helper::$helper";
177 eval "require $class";
180 Catalyst::Exception->throw(
181 message => qq/Couldn't load helper "$class", "$@"/ );
184 if ( $class->can('mk_stuff') ) {
185 return 1 unless $class->mk_stuff( $self, @args );
190 my $name = shift || "Missing name for model/view/controller";
193 return 0 if $name =~ /[^\w\:]/;
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 );
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";
209 dir( $self->{base}, 'lib', $appdir, $type );
211 if ( $name =~ /\:/ ) {
212 my @path = split /\:\:/, $name;
214 $path = dir( $path, @path );
216 $self->mk_dir($path);
217 $file = file( $path, "$file.pm" );
218 $self->{file} = $file;
221 $self->{test_dir} = dir( $self->{base}, 't' );
222 $self->{test} = $self->next_test;
226 my $comp = $self->{long_type};
227 my $class = "Catalyst::Helper::$comp\::$helper";
228 eval "require $class";
231 Catalyst::Exception->throw(
232 message => qq/Couldn't load helper "$class", "$@"/ );
235 if ( $class->can('mk_compclass') ) {
236 return 1 unless $class->mk_compclass( $self, @args );
239 return 1 unless $self->_mk_compclass
242 if ( $class->can('mk_comptest') ) {
243 $class->mk_comptest( $self, @args );
252 return 1 unless $self->_mk_compclass;
260 my ( $self, $dir ) = @_;
262 print qq/ exists "$dir"\n/;
265 if ( mkpath [$dir] ) {
266 print qq/created "$dir"\n/;
270 Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
274 my ( $self, $file, $content ) = @_;
275 if ( -e $file && -s _ ) {
276 print qq/ exists "$file"\n/;
278 unless ( $self->{'.newfiles'}
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;
290 if ( my $f = IO::File->new("> $file") ) {
293 print qq/created "$file"\n/;
297 Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
301 my ( $self, $tname ) = @_;
302 if ($tname) { $tname = "$tname.t" }
304 my $name = $self->{name};
308 $tname = $prefix . '.t';
309 $self->{prefix} = $prefix;
310 $prefix = lc $prefix;
312 $self->{uri} = "/$prefix";
314 my $dir = $self->{test_dir};
315 my $type = lc $self->{type};
317 return file( $dir, "$type\_$tname" );
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__
325 my ( $self, $file, $path, $vars ) = @_;
326 my $template = $self->get_file( ( caller(0) )[0], $file );
327 $self->render_file_contents($template, $path, $vars);
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);
337 sub render_file_contents {
338 my ( $self, $template, $path, $vars ) = @_;
340 my $t = Template->new;
341 return 0 unless $template;
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 );
349 sub _mk_information {
351 print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
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} );
369 $self->{class} = dir( split( /\:\:/, $self->{name} ) );
370 $self->{mod} = dir( $self->{lib}, $self->{class} );
371 $self->mk_dir( $self->{mod} );
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} );
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} );
389 my $name = $self->{name};
391 $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
392 $self->{base} = dir( $self->{dir} )->absolute;
397 my $mod = $self->{mod};
398 $self->render_sharedir_file( file('lib', 'MyApp.pm.tt'), "$mod.pm" );
403 $self->render_sharedir_file( file('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
404 file( $self->{c}, "Root.pm" ) );
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") );
414 if ( $self->{makefile} ) {
416 # deprecate the old Build.PL file when regenerating Makefile.PL
417 $self->_deprecate_file(
418 file( $self->{dir}, 'Build.PL' ) );
424 my $dir = $self->{dir};
425 my $appprefix = $self->{appprefix};
426 $self->render_sharedir_file( 'myapp.conf.tt',
427 file( $dir, "$appprefix.conf" ) );
432 my $dir = $self->{dir};
433 $self->render_sharedir_file( 'README.tt', file($dir, "README") );
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 } );
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") );
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");
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");
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");
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");
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");
493 my $file = $self->{file};
494 return $self->render_sharedir_file( file('lib', 'Helper', 'compclass.pm.tt'), $file );
499 my $test = $self->{test};
500 $self->render_sharedir_file( file('t', 'comptest.tt'), $test ); ## wtf do i rename this to?
505 my $images = $self->{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 );
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 );
525 sub _deprecate_file {
526 my ( $self, $file ) = @_;
528 my ($f, $oldcontent);
529 if ( $f = IO::File->new("< $file") ) {
530 $oldcontent = join( '', (<$f>) );
532 my $newfile = $file . '.deprecated';
533 if ( $f = IO::File->new("> $newfile") ) {
535 print $f $oldcontent;
536 print qq/created "$newfile"\n/;
538 print qq/removed "$file"\n/;
541 Catalyst::Exception->throw(
542 message => qq/Couldn't create "$file", "$!"/ );
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.
553 It also provides some useful methods for a Helper module to call when
554 creating a component. See L</METHODS>.
560 Used to create new components for a catalyst application at the
565 The catalyst test server, starts an HTTPD which outputs debugging to
570 A script for running tests from the command-line.
574 Run your application as a CGI.
578 Run the application as a fastcgi app. Either by hand, or call this
579 from FastCgiServer in your http server config.
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.
587 Helpers are classes that provide two methods.
589 * mk_compclass - creates the Component class
590 * mk_comptest - creates the Component test
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.
596 See L<Catalyst::Helper::View::TT> and
597 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
599 All helper classes should be under one of the following namespaces.
601 Catalyst::Helper::Model::
602 Catalyst::Helper::View::
603 Catalyst::Helper::Controller::
605 =head2 COMMON HELPERS
611 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
615 L<Catalyst::Helper::View::TT> - Template Toolkit view
619 L<Catalyst::Helper::Model::LDAP>
623 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
629 The helpers will read author name from /etc/passwd by default.
630 To override, please export the AUTHOR variable.
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
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
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
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
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.
663 There is no fallback for this method.
665 =head1 INTERNAL METHODS
667 These are the methods that the Helper classes can call on the
668 <$helper> object passed to them.
670 =head2 render_file ($file, $path, $vars)
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>.
677 =head2 get_file ($class, $file)
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
686 Create the main application skeleton. This is called by L<catalyst.pl>.
688 =head2 mk_component ($app)
690 This method is called by L<create.pl> to make new components
691 for your application.
693 =head2 mk_dir ($path)
695 Surprisingly, this function makes a directory.
697 =head2 mk_file ($file, $content)
699 Writes content to a file. Called by L</render_file>.
701 =head2 next_test ($test_name)
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.
708 =head2 get_sharedir_file
710 Method for getting a file out of share/
714 =head2 render_file_contents
716 Process a L<Template::Toolkit> template.
720 =head2 render_sharedir_file
722 Render a template/image file from our share directory
728 The helpers will read author name from /etc/passwd by default.
729 To override, please export the AUTHOR variable.
733 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
734 L<Catalyst::Response>, L<Catalyst>
738 Catalyst Contributors, see Catalyst.pm
742 This library is free software. You can redistribute it and/or modify
743 it under the same terms as Perl itself.