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 Carp::confess("Could not get data from __DATA__ segment for $class")
61 my @files = split /^__(.+)__\r?\n/m, $data;
64 my ( $name, $content ) = splice @files, 0, 2;
65 return $content if $name eq $file;
72 my ( $self, $name ) = @_;
74 # Needs to be here for PAR
77 if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
78 warn "Error: Invalid application name.\n";
81 $self->{name } = $name;
82 $self->{dir } = $name;
83 $self->{dir } =~ s/\:\:/-/g;
84 $self->{script } = File::Spec->catdir( $self->{dir}, 'script' );
85 $self->{appprefix } = Catalyst::Utils::appprefix($name);
86 $self->{appenv } = Catalyst::Utils::class2env($name);
87 $self->{startperl } = -r '/usr/bin/env'
88 ? '#!/usr/bin/env perl'
89 : "#!$Config{perlpath} -w";
90 $self->{scriptgen } = $Catalyst::Devel::CATALYST_SCRIPT_GEN || 4;
91 $self->{catalyst_version} = $Catalyst::VERSION;
92 $self->{author } = $self->{author} = $ENV{'AUTHOR'}
93 || eval { @{ [ getpwuid($<) ] }[6] }
94 || 'Catalyst developer';
96 my $gen_scripts = ( $self->{makefile} ) ? 0 : 1;
97 my $gen_makefile = ( $self->{scripts} ) ? 0 : 1;
98 my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
104 $self->_mk_rootclass;
120 $self->_mk_information;
129 $self->{author} = $self->{author} = $ENV{'AUTHOR'}
130 || eval { @{ [ getpwuid($<) ] }[6] }
132 $self->{base} ||= File::Spec->catdir( $FindBin::Bin, '..' );
133 unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
136 my $class = "Catalyst::Helper::$helper";
137 eval "require $class";
140 Catalyst::Exception->throw(
141 message => qq/Couldn't load helper "$class", "$@"/ );
144 if ( $class->can('mk_stuff') ) {
145 return 1 unless $class->mk_stuff( $self, @args );
150 my $name = shift || "Missing name for model/view/controller";
153 return 0 if $name =~ /[^\w\:]/;
155 $self->{long_type} = ucfirst $type;
156 $type = 'M' if $type =~ /model/i;
157 $type = 'V' if $type =~ /view/i;
158 $type = 'C' if $type =~ /controller/i;
159 my $appdir = File::Spec->catdir( split /\:\:/, $app );
161 File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, 'C' );
162 $type = $self->{long_type} unless -d $test_path;
163 $self->{type} = $type;
164 $self->{name} = $name;
165 $self->{class} = "$app\::$type\::$name";
169 File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, $type );
171 if ( $name =~ /\:/ ) {
172 my @path = split /\:\:/, $name;
174 $path = File::Spec->catdir( $path, @path );
176 $self->mk_dir($path);
177 $file = File::Spec->catfile( $path, "$file.pm" );
178 $self->{file} = $file;
181 $self->{test_dir} = File::Spec->catdir( $FindBin::Bin, '..', 't' );
182 $self->{test} = $self->next_test;
186 my $comp = $self->{long_type};
187 my $class = "Catalyst::Helper::$comp\::$helper";
188 eval "require $class";
191 Catalyst::Exception->throw(
192 message => qq/Couldn't load helper "$class", "$@"/ );
195 if ( $class->can('mk_compclass') ) {
196 return 1 unless $class->mk_compclass( $self, @args );
198 else { return 1 unless $self->_mk_compclass }
200 if ( $class->can('mk_comptest') ) {
201 $class->mk_comptest( $self, @args );
203 else { $self->_mk_comptest }
208 return 1 unless $self->_mk_compclass;
216 my ( $self, $dir ) = @_;
218 print qq/ exists "$dir"\n/;
221 if ( mkpath [$dir] ) {
222 print qq/created "$dir"\n/;
226 Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
230 my ( $self, $file, $content ) = @_;
231 if ( -e $file && -s _ ) {
232 print qq/ exists "$file"\n/;
234 unless ( $self->{'.newfiles'}
236 || $self->{makefile} );
237 if ( $self->{'.newfiles'} ) {
238 if ( my $f = IO::File->new("< $file") ) {
239 my $oldcontent = join( '', (<$f>) );
240 return 0 if $content eq $oldcontent;
245 if ( my $f = IO::File->new("> $file") ) {
248 print qq/created "$file"\n/;
252 Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
256 my ( $self, $tname ) = @_;
257 if ($tname) { $tname = "$tname.t" }
259 my $name = $self->{name};
263 $tname = $prefix . '.t';
264 $self->{prefix} = $prefix;
265 $prefix = lc $prefix;
267 $self->{uri} = "/$prefix";
269 my $dir = $self->{test_dir};
270 my $type = lc $self->{type};
272 return File::Spec->catfile( $dir, "$type\_$tname" );
275 # Do not touch this method, *EVER*, it is needed for back compat.
276 ## addendum: we had to split this method so we could have backwards
277 ## compatability. otherwise, we'd have no way to pass stuff from __DATA__
280 my ( $self, $file, $path, $vars ) = @_;
281 my $template = $self->get_file( ( caller(0) )[0], $file );
282 $self->render_file_contents($template, $path, $vars);
285 sub render_sharedir_file {
286 my ( $self, $file, $path, $vars ) = @_;
287 my $template = $self->get_sharedir_file( $file );
288 $self->render_file_contents($template, $path, $vars);
291 sub render_file_contents {
292 my ( $self, $template, $path, $vars ) = @_;
294 my $t = Template->new;
295 return 0 unless $template;
297 $t->process( \$template, { %{$self}, %$vars }, \$output )
298 || Catalyst::Exception->throw(
299 message => qq/Couldn't process "$template", / . $t->error() );
300 $self->mk_file( $path, $output );
303 sub _mk_information {
305 print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
310 $self->mk_dir( $self->{dir} );
311 $self->mk_dir( $self->{script} );
312 $self->{lib} = File::Spec->catdir( $self->{dir}, 'lib' );
313 $self->mk_dir( $self->{lib} );
314 $self->{root} = File::Spec->catdir( $self->{dir}, 'root' );
315 $self->mk_dir( $self->{root} );
316 $self->{static} = File::Spec->catdir( $self->{root}, 'static' );
317 $self->mk_dir( $self->{static} );
318 $self->{images} = File::Spec->catdir( $self->{static}, 'images' );
319 $self->mk_dir( $self->{images} );
320 $self->{t} = File::Spec->catdir( $self->{dir}, 't' );
321 $self->mk_dir( $self->{t} );
323 $self->{class} = File::Spec->catdir( split( /\:\:/, $self->{name} ) );
324 $self->{mod} = File::Spec->catdir( $self->{lib}, $self->{class} );
325 $self->mk_dir( $self->{mod} );
327 if ( $self->{short} ) {
328 $self->{m} = File::Spec->catdir( $self->{mod}, 'M' );
329 $self->mk_dir( $self->{m} );
330 $self->{v} = File::Spec->catdir( $self->{mod}, 'V' );
331 $self->mk_dir( $self->{v} );
332 $self->{c} = File::Spec->catdir( $self->{mod}, 'C' );
333 $self->mk_dir( $self->{c} );
336 $self->{m} = File::Spec->catdir( $self->{mod}, 'Model' );
337 $self->mk_dir( $self->{m} );
338 $self->{v} = File::Spec->catdir( $self->{mod}, 'View' );
339 $self->mk_dir( $self->{v} );
340 $self->{c} = File::Spec->catdir( $self->{mod}, 'Controller' );
341 $self->mk_dir( $self->{c} );
343 my $name = $self->{name};
345 $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
346 $self->{base} = File::Spec->rel2abs( $self->{dir} );
351 my $mod = $self->{mod};
352 $self->render_sharedir_file( 'lib/appclass.tt', "$mod.pm" );
357 $self->render_sharedir_file( 'lib/MyApp/Controller/rootclass.tt',
358 File::Spec->catfile( $self->{c}, "Root.pm" ) );
363 $self->{path} = File::Spec->catfile( 'lib', split( '::', $self->{name} ) );
364 $self->{path} .= '.pm';
365 my $dir = $self->{dir};
366 $self->render_sharedir_file( 'Makefile.PL.tt', "$dir\/Makefile.PL" );
368 if ( $self->{makefile} ) {
370 # deprecate the old Build.PL file when regenerating Makefile.PL
371 $self->_deprecate_file(
372 File::Spec->catdir( $self->{dir}, 'Build.PL' ) );
378 my $dir = $self->{dir};
379 my $appprefix = $self->{appprefix};
380 $self->render_sharedir_file( 'myapp.conf.tt',
381 File::Spec->catfile( $dir, "$appprefix.conf" ) );
386 my $dir = $self->{dir};
387 $self->render_sharedir_file( 'README.tt', "$dir\/README" );
392 my $dir = $self->{dir};
393 my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
394 $self->render_sharedir_file( 'Changes.tt', "$dir\/Changes", { time => $time } );
400 $self->render_sharedir_file( 't/apptest.tt', "$t\/01app.t" );
401 $self->render_sharedir_file( 't/podtest.tt', "$t\/02pod.t" );
402 $self->render_sharedir_file( 't/podcoveragetest.tt', "$t\/03podcoverage.t" );
407 my $script = $self->{script};
408 my $appprefix = $self->{appprefix};
409 $self->render_sharedir_file( 'script/cgi.tt', "$script\/$appprefix\_cgi.pl" );
410 chmod 0700, "$script/$appprefix\_cgi.pl";
415 my $script = $self->{script};
416 my $appprefix = $self->{appprefix};
417 $self->render_sharedir_file( 'script/fastcgi.tt', "$script\/$appprefix\_fastcgi.pl" );
418 chmod 0700, "$script/$appprefix\_fastcgi.pl";
423 my $script = $self->{script};
424 my $appprefix = $self->{appprefix};
425 $self->render_sharedir_file( 'script/server.tt', "$script\/$appprefix\_server.pl" );
426 chmod 0700, "$script/$appprefix\_server.pl";
431 my $script = $self->{script};
432 my $appprefix = $self->{appprefix};
433 $self->render_sharedir_file( 'script/test.tt', "$script/$appprefix\_test.pl" );
434 chmod 0700, "$script/$appprefix\_test.pl";
439 my $script = $self->{script};
440 my $appprefix = $self->{appprefix};
441 $self->render_sharedir_file( 'script/create.tt', "$script\/$appprefix\_create.pl" );
442 chmod 0700, "$script/$appprefix\_create.pl";
447 my $file = $self->{file};
448 return $self->render_sharedir_file( 'compclass.tt', "$file" );
453 my $test = $self->{test};
454 $self->render_sharedir_file( 'comptest.tt', "$test" );
459 my $images = $self->{images};
461 qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
462 btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
463 btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
464 for my $name (@images) {
465 my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
466 $self->mk_file( File::Spec->catfile( $images, "$name.png" ), $image );
472 my $root = $self->{root};
473 my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
474 my $dest = File::Spec->catfile( $root, "favicon.ico" );
475 $self->mk_file( $dest, $favicon );
479 sub _deprecate_file {
480 my ( $self, $file ) = @_;
483 if ( my $f = IO::File->new("< $file") ) {
484 $oldcontent = join( '', (<$f>) );
486 my $newfile = $file . '.deprecated';
487 if ( my $f = IO::File->new("> $newfile") ) {
489 print $f $oldcontent;
490 print qq/created "$newfile"\n/;
492 print qq/removed "$file"\n/;
495 Catalyst::Exception->throw(
496 message => qq/Couldn't create "$file", "$!"/ );
501 ## this is so you don't have to do make install after every change to test
502 sub _find_share_dir {
503 my ($self, $args) = @_;
504 my $share_name = $self->name;
505 if ($share_name =~ s!^/(.*?)/!!) {
507 $args->{share_base_dir} = eval {
508 Dir->new(File::ShareDir::dist_dir($dist))
514 my $dir = Dir->new(dirname($file));
516 while ($dir->parent) {
517 if (-d $dir->subdir('share') && -d $dir->subdir('share')->subdir('root')) {
518 $share_base = $dir->subdir('share')->subdir('root');
523 confess "could not find sharebase by recursion. ended up at $dir, from $file"
525 $args->{share_base_dir} = $share_base;
528 my $base = $args->{share_base_dir}->subdir($share_name);
529 confess "No such share base directory ${base}"
531 $self->share_dir($base);
538 This module is used by B<catalyst.pl> to create a set of scripts for a
539 new catalyst application. The scripts each contain documentation and
540 will output help on how to use them if called incorrectly or in some
541 cases, with no arguments.
543 It also provides some useful methods for a Helper module to call when
544 creating a component. See L</METHODS>.
550 Used to create new components for a catalyst application at the
555 The catalyst test server, starts an HTTPD which outputs debugging to
560 A script for running tests from the command-line.
564 Run your application as a CGI.
568 Run the application as a fastcgi app. Either by hand, or call this
569 from FastCgiServer in your http server config.
573 The L</_create.pl> script creates application components using Helper
574 modules. The Catalyst team provides a good number of Helper modules
575 for you to use. You can also add your own.
577 Helpers are classes that provide two methods.
579 * mk_compclass - creates the Component class
580 * mk_comptest - creates the Component test
582 So when you call C<scripts/myapp_create.pl view MyView TT>, create
583 will try to execute Catalyst::Helper::View::TT->mk_compclass and
584 Catalyst::Helper::View::TT->mk_comptest.
586 See L<Catalyst::Helper::View::TT> and
587 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
589 All helper classes should be under one of the following namespaces.
591 Catalyst::Helper::Model::
592 Catalyst::Helper::View::
593 Catalyst::Helper::Controller::
595 =head2 COMMON HELPERS
601 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
605 L<Catalyst::Helper::View::TT> - Template Toolkit view
609 L<Catalyst::Helper::Model::LDAP>
613 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
619 The helpers will read author name from /etc/passwd by default. + To override, please export the AUTHOR variable.
625 This method in your Helper module is called with C<$helper>
626 which is a L<Catalyst::Helper> object, and whichever other arguments
627 the user added to the command-line. You can use the $helper to call methods
630 If the Helper module does not contain a C<mk_compclass> method, it
631 will fall back to calling L</render_file>, with an argument of
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 is called if the user does not supply any of the usual
648 component types C<view>, C<controller>, C<model>. It is passed the
649 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
650 arguments the user typed.
652 There is no fallback for this method.
654 =head1 INTERNAL METHODS
656 These are the methods that the Helper classes can call on the
657 <$helper> object passed to them.
659 =head2 render_file ($file, $path, $vars)
661 Render and create a file from a template in DATA using Template
662 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
663 the path to the file and $vars is the hashref as expected by
664 L<Template Toolkit|Template>.
666 =head2 get_file ($class, $file)
668 Fetch file contents from the DATA section. This is used internally by
669 L</render_file>. $class is the name of the class to get the DATA
670 section from. __PACKAGE__ or ( caller(0) )[0] might be sensible
675 Create the main application skeleton. This is called by L<catalyst.pl>.
677 =head2 mk_component ($app)
679 This method is called by L<create.pl> to make new components
680 for your application.
682 =head3 mk_dir ($path)
684 Surprisingly, this function makes a directory.
686 =head2 mk_file ($file, $content)
688 Writes content to a file. Called by L</render_file>.
690 =head2 next_test ($test_name)
692 Calculates the name of the next numbered test file and returns it.
693 Don't give the number or the .t suffix for the test name.
697 The helpers will read author name from /etc/passwd by default.
698 To override, please export the AUTHOR variable.
702 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
703 L<Catalyst::Response>, L<Catalyst>
707 Catalyst Contributors, see Catalyst.pm
711 This library is free software. You can redistribute it and/or modify
712 it under the same terms as Perl itself.