1 package Catalyst::Helper;
5 use base 'Class::Accessor::Fast';
15 use Catalyst::Exception;
16 use Path::Class qw/dir file/;
17 use File::ShareDir qw/dist_dir/;
19 use aliased 'Path::Class::Dir';
26 Catalyst::Helper - Bootstrap a Catalyst application
30 catalyst.pl <myappname>
36 sub get_sharedir_file {
37 my ($self, @filename) = @_;
39 if (-d "inc/.author") { # Can't use sharedir if we're in a checkout
40 # this feels horrible, better ideas?
44 $dist_dir = dist_dir('Catalyst-Devel');
46 my $file = file( $dist_dir, @filename);
47 my $contents = $file->slurp;
51 # Do not touch this method, *EVER*, it is needed for back compat.
53 my ( $self, $class, $file ) = @_;
54 unless ( $cache{$class} ) {
56 $cache{$class} = eval "package $class; <DATA>";
58 my $data = $cache{$class};
59 my @files = split /^__(.+)__\r?\n/m, $data;
62 my ( $name, $content ) = splice @files, 0, 2;
63 return $content if $name eq $file;
70 my ( $self, $name ) = @_;
72 # Needs to be here for PAR
75 if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
76 warn "Error: Invalid application name.\n";
79 $self->{name } = $name;
80 $self->{dir } = $name;
81 $self->{dir } =~ s/\:\:/-/g;
82 $self->{script } = File::Spec->catdir( $self->{dir}, 'script' );
83 $self->{appprefix } = Catalyst::Utils::appprefix($name);
84 $self->{appenv } = Catalyst::Utils::class2env($name);
85 $self->{startperl } = -r '/usr/bin/env'
86 ? '#!/usr/bin/env perl'
87 : "#!$Config{perlpath} -w";
88 $self->{scriptgen } = $Catalyst::Devel::CATALYST_SCRIPT_GEN || 4;
89 $self->{catalyst_version} = $Catalyst::VERSION;
90 $self->{author } = $self->{author} = $ENV{'AUTHOR'}
91 || eval { @{ [ getpwuid($<) ] }[6] }
92 || 'Catalyst developer';
94 my $gen_scripts = ( $self->{makefile} ) ? 0 : 1;
95 my $gen_makefile = ( $self->{scripts} ) ? 0 : 1;
96 my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
102 $self->_mk_rootclass;
118 $self->_mk_information;
127 $self->{author} = $self->{author} = $ENV{'AUTHOR'}
128 || eval { @{ [ getpwuid($<) ] }[6] }
130 $self->{base} ||= File::Spec->catdir( $FindBin::Bin, '..' );
131 unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
134 my $class = "Catalyst::Helper::$helper";
135 eval "require $class";
138 Catalyst::Exception->throw(
139 message => qq/Couldn't load helper "$class", "$@"/ );
142 if ( $class->can('mk_stuff') ) {
143 return 1 unless $class->mk_stuff( $self, @args );
148 my $name = shift || "Missing name for model/view/controller";
151 return 0 if $name =~ /[^\w\:]/;
153 $self->{long_type} = ucfirst $type;
154 $type = 'M' if $type =~ /model/i;
155 $type = 'V' if $type =~ /view/i;
156 $type = 'C' if $type =~ /controller/i;
157 my $appdir = File::Spec->catdir( split /\:\:/, $app );
159 File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, 'C' );
160 $type = $self->{long_type} unless -d $test_path;
161 $self->{type} = $type;
162 $self->{name} = $name;
163 $self->{class} = "$app\::$type\::$name";
167 File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, $type );
169 if ( $name =~ /\:/ ) {
170 my @path = split /\:\:/, $name;
172 $path = File::Spec->catdir( $path, @path );
174 $self->mk_dir($path);
175 $file = File::Spec->catfile( $path, "$file.pm" );
176 $self->{file} = $file;
179 $self->{test_dir} = File::Spec->catdir( $FindBin::Bin, '..', 't' );
180 $self->{test} = $self->next_test;
184 my $comp = $self->{long_type};
185 my $class = "Catalyst::Helper::$comp\::$helper";
186 eval "require $class";
189 Catalyst::Exception->throw(
190 message => qq/Couldn't load helper "$class", "$@"/ );
193 if ( $class->can('mk_compclass') ) {
194 return 1 unless $class->mk_compclass( $self, @args );
196 else { return 1 unless $self->_mk_compclass }
198 if ( $class->can('mk_comptest') ) {
199 $class->mk_comptest( $self, @args );
201 else { $self->_mk_comptest }
206 return 1 unless $self->_mk_compclass;
214 my ( $self, $dir ) = @_;
216 print qq/ exists "$dir"\n/;
219 if ( mkpath [$dir] ) {
220 print qq/created "$dir"\n/;
224 Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
228 my ( $self, $file, $content ) = @_;
229 if ( -e $file && -s _ ) {
230 print qq/ exists "$file"\n/;
232 unless ( $self->{'.newfiles'}
234 || $self->{makefile} );
235 if ( $self->{'.newfiles'} ) {
236 if ( my $f = IO::File->new("< $file") ) {
237 my $oldcontent = join( '', (<$f>) );
238 return 0 if $content eq $oldcontent;
243 if ( my $f = IO::File->new("> $file") ) {
246 print qq/created "$file"\n/;
250 Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
254 my ( $self, $tname ) = @_;
255 if ($tname) { $tname = "$tname.t" }
257 my $name = $self->{name};
261 $tname = $prefix . '.t';
262 $self->{prefix} = $prefix;
263 $prefix = lc $prefix;
265 $self->{uri} = "/$prefix";
267 my $dir = $self->{test_dir};
268 my $type = lc $self->{type};
270 return File::Spec->catfile( $dir, "$type\_$tname" );
273 # Do not touch this method, *EVER*, it is needed for back compat.
274 ## addendum: we had to split this method so we could have backwards
275 ## compatability. otherwise, we'd have no way to pass stuff from __DATA__
278 my ( $self, $file, $path, $vars ) = @_;
279 my $template = $self->get_file( ( caller(0) )[0], $file );
280 $self->render_file_contents($template, $path, $vars);
283 sub render_sharedir_file {
284 my ( $self, $file, $path, $vars ) = @_;
285 my $template = $self->get_sharedir_file( $file );
286 $self->render_file_contents($template, $path, $vars);
289 sub render_file_contents {
290 my ( $self, $template, $path, $vars ) = @_;
292 my $t = Template->new;
293 return 0 unless $template;
295 $t->process( \$template, { %{$self}, %$vars }, \$output )
296 || Catalyst::Exception->throw(
297 message => qq/Couldn't process "$template", / . $t->error() );
298 $self->mk_file( $path, $output );
301 sub _mk_information {
303 print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
308 $self->mk_dir( $self->{dir} );
309 $self->mk_dir( $self->{script} );
310 $self->{lib} = File::Spec->catdir( $self->{dir}, 'lib' );
311 $self->mk_dir( $self->{lib} );
312 $self->{root} = File::Spec->catdir( $self->{dir}, 'root' );
313 $self->mk_dir( $self->{root} );
314 $self->{static} = File::Spec->catdir( $self->{root}, 'static' );
315 $self->mk_dir( $self->{static} );
316 $self->{images} = File::Spec->catdir( $self->{static}, 'images' );
317 $self->mk_dir( $self->{images} );
318 $self->{t} = File::Spec->catdir( $self->{dir}, 't' );
319 $self->mk_dir( $self->{t} );
321 $self->{class} = File::Spec->catdir( split( /\:\:/, $self->{name} ) );
322 $self->{mod} = File::Spec->catdir( $self->{lib}, $self->{class} );
323 $self->mk_dir( $self->{mod} );
325 if ( $self->{short} ) {
326 $self->{m} = File::Spec->catdir( $self->{mod}, 'M' );
327 $self->mk_dir( $self->{m} );
328 $self->{v} = File::Spec->catdir( $self->{mod}, 'V' );
329 $self->mk_dir( $self->{v} );
330 $self->{c} = File::Spec->catdir( $self->{mod}, 'C' );
331 $self->mk_dir( $self->{c} );
334 $self->{m} = File::Spec->catdir( $self->{mod}, 'Model' );
335 $self->mk_dir( $self->{m} );
336 $self->{v} = File::Spec->catdir( $self->{mod}, 'View' );
337 $self->mk_dir( $self->{v} );
338 $self->{c} = File::Spec->catdir( $self->{mod}, 'Controller' );
339 $self->mk_dir( $self->{c} );
341 my $name = $self->{name};
343 $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
344 $self->{base} = File::Spec->rel2abs( $self->{dir} );
349 my $mod = $self->{mod};
350 $self->render_file( 'appclass.tt', "$mod.pm" );
355 $self->render_file( 'rootclass.tt',
356 File::Spec->catfile( $self->{c}, "Root.pm" ) );
361 $self->{path} = File::Spec->catfile( 'lib', split( '::', $self->{name} ) );
362 $self->{path} .= '.pm';
363 my $dir = $self->{dir};
364 $self->render_file( 'makefile.tt', "$dir\/Makefile.PL" );
366 if ( $self->{makefile} ) {
368 # deprecate the old Build.PL file when regenerating Makefile.PL
369 $self->_deprecate_file(
370 File::Spec->catdir( $self->{dir}, 'Build.PL' ) );
376 my $dir = $self->{dir};
377 my $appprefix = $self->{appprefix};
378 $self->render_file( 'config.tt',
379 File::Spec->catfile( $dir, "$appprefix.conf" ) );
384 my $dir = $self->{dir};
385 $self->render_file( 'readme.tt', "$dir\/README" );
390 my $dir = $self->{dir};
391 my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
392 $self->render_file( 'changes.tt', "$dir\/Changes", { time => $time } );
398 $self->render_file( 'apptest.tt', "$t\/01app.t" );
399 $self->render_file( 'podtest.tt', "$t\/02pod.t" );
400 $self->render_file( 'podcoveragetest.tt', "$t\/03podcoverage.t" );
405 my $script = $self->{script};
406 my $appprefix = $self->{appprefix};
407 $self->render_file( 'cgi.tt', "$script\/$appprefix\_cgi.pl" );
408 chmod 0700, "$script/$appprefix\_cgi.pl";
413 my $script = $self->{script};
414 my $appprefix = $self->{appprefix};
415 $self->render_file( 'fastcgi.tt', "$script\/$appprefix\_fastcgi.pl" );
416 chmod 0700, "$script/$appprefix\_fastcgi.pl";
421 my $script = $self->{script};
422 my $appprefix = $self->{appprefix};
423 $self->render_file( 'server.tt', "$script\/$appprefix\_server.pl" );
424 chmod 0700, "$script/$appprefix\_server.pl";
429 my $script = $self->{script};
430 my $appprefix = $self->{appprefix};
431 $self->render_file( 'test.tt', "$script/$appprefix\_test.pl" );
432 chmod 0700, "$script/$appprefix\_test.pl";
437 my $script = $self->{script};
438 my $appprefix = $self->{appprefix};
439 $self->render_file( 'create.tt', "$script\/$appprefix\_create.pl" );
440 chmod 0700, "$script/$appprefix\_create.pl";
445 my $file = $self->{file};
446 return $self->render_file( 'compclass.tt', "$file" );
451 my $test = $self->{test};
452 $self->render_file( 'comptest.tt', "$test" );
457 my $images = $self->{images};
459 qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
460 btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
461 btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
462 for my $name (@images) {
463 my $image = $self->get_sharedir_file("root", "$name.png");
464 $self->mk_file( File::Spec->catfile( $images, "$name.png" ), $image );
470 my $root = $self->{root};
471 my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico' );
472 my $dest = File::Spec->catfile( $root, "favicon.ico" );
473 $self->mk_file( $dest, $favicon );
477 sub _deprecate_file {
478 my ( $self, $file ) = @_;
481 if ( my $f = IO::File->new("< $file") ) {
482 $oldcontent = join( '', (<$f>) );
484 my $newfile = $file . '.deprecated';
485 if ( my $f = IO::File->new("> $newfile") ) {
487 print $f $oldcontent;
488 print qq/created "$newfile"\n/;
490 print qq/removed "$file"\n/;
493 Catalyst::Exception->throw(
494 message => qq/Couldn't create "$file", "$!"/ );
499 ## this is so you don't have to do make install after every change to test
500 sub _find_share_dir {
501 my ($self, $args) = @_;
502 my $share_name = $self->name;
503 if ($share_name =~ s!^/(.*?)/!!) {
505 $args->{share_base_dir} = eval {
506 Dir->new(File::ShareDir::dist_dir($dist))
512 my $dir = Dir->new(dirname($file));
514 while ($dir->parent) {
515 if (-d $dir->subdir('share') && -d $dir->subdir('share')->subdir('root')) {
516 $share_base = $dir->subdir('share')->subdir('root');
521 confess "could not find sharebase by recursion. ended up at $dir, from $file"
523 $args->{share_base_dir} = $share_base;
526 my $base = $args->{share_base_dir}->subdir($share_name);
527 confess "No such share base directory ${base}"
529 $self->share_dir($base);
536 This module is used by B<catalyst.pl> to create a set of scripts for a
537 new catalyst application. The scripts each contain documentation and
538 will output help on how to use them if called incorrectly or in some
539 cases, with no arguments.
541 It also provides some useful methods for a Helper module to call when
542 creating a component. See L</METHODS>.
548 Used to create new components for a catalyst application at the
553 The catalyst test server, starts an HTTPD which outputs debugging to
558 A script for running tests from the command-line.
562 Run your application as a CGI.
566 Run the application as a fastcgi app. Either by hand, or call this
567 from FastCgiServer in your http server config.
571 The L</_create.pl> script creates application components using Helper
572 modules. The Catalyst team provides a good number of Helper modules
573 for you to use. You can also add your own.
575 Helpers are classes that provide two methods.
577 * mk_compclass - creates the Component class
578 * mk_comptest - creates the Component test
580 So when you call C<scripts/myapp_create.pl view MyView TT>, create
581 will try to execute Catalyst::Helper::View::TT->mk_compclass and
582 Catalyst::Helper::View::TT->mk_comptest.
584 See L<Catalyst::Helper::View::TT> and
585 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
587 All helper classes should be under one of the following namespaces.
589 Catalyst::Helper::Model::
590 Catalyst::Helper::View::
591 Catalyst::Helper::Controller::
593 =head2 COMMON HELPERS
599 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
603 L<Catalyst::Helper::View::TT> - Template Toolkit view
607 L<Catalyst::Helper::Model::LDAP>
611 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
617 The helpers will read author name from /etc/passwd by default. + To override, please export the AUTHOR variable.
623 This method in your Helper module is called with C<$helper>
624 which is a L<Catalyst::Helper> object, and whichever other arguments
625 the user added to the command-line. You can use the $helper to call methods
628 If the Helper module does not contain a C<mk_compclass> method, it
629 will fall back to calling L</render_file>, with an argument of
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
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
645 This method is called if the user does not supply any of the usual
646 component types C<view>, C<controller>, C<model>. It is passed the
647 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
648 arguments the user typed.
650 There is no fallback for this method.
652 =head1 INTERNAL METHODS
654 These are the methods that the Helper classes can call on the
655 <$helper> object passed to them.
657 =head2 render_file ($file, $path, $vars)
659 Render and create a file from a template in DATA using Template
660 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
661 the path to the file and $vars is the hashref as expected by
662 L<Template Toolkit|Template>.
664 =head2 get_file ($class, $file)
666 Fetch file contents from the DATA section. This is used internally by
667 L</render_file>. $class is the name of the class to get the DATA
668 section from. __PACKAGE__ or ( caller(0) )[0] might be sensible
673 Create the main application skeleton. This is called by L<catalyst.pl>.
675 =head2 mk_component ($app)
677 This method is called by L<create.pl> to make new components
678 for your application.
680 =head3 mk_dir ($path)
682 Surprisingly, this function makes a directory.
684 =head2 mk_file ($file, $content)
686 Writes content to a file. Called by L</render_file>.
688 =head2 next_test ($test_name)
690 Calculates the name of the next numbered test file and returns it.
691 Don't give the number or the .t suffix for the test name.
695 The helpers will read author name from /etc/passwd by default.
696 To override, please export the AUTHOR variable.
700 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
701 L<Catalyst::Response>, L<Catalyst>
705 Catalyst Contributors, see Catalyst.pm
709 This library is free software. You can redistribute it and/or modify
710 it under the same terms as Perl itself.