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" && -f "lib/Catalyst/Helper.pm"
40 ) { # Can't use sharedir if we're in a checkout
41 # this feels horrible, better ideas?
45 $dist_dir = dist_dir('Catalyst-Devel');
47 my $file = file( $dist_dir, @filename);
48 my $contents = $file->slurp;
52 # Do not touch this method, *EVER*, it is needed for back compat.
54 my ( $self, $class, $file ) = @_;
55 unless ( $cache{$class} ) {
57 $cache{$class} = eval "package $class; <DATA>";
59 my $data = $cache{$class};
60 Carp::confess("Could not get data from __DATA__ segment for $class")
62 my @files = split /^__(.+)__\r?\n/m, $data;
65 my ( $name, $content ) = splice @files, 0, 2;
66 return $content if $name eq $file;
73 my ( $self, $name ) = @_;
75 # Needs to be here for PAR
78 if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
79 warn "Error: Invalid application name.\n";
82 $self->{name } = $name;
83 $self->{dir } = $name;
84 $self->{dir } =~ s/\:\:/-/g;
85 $self->{script } = File::Spec->catdir( $self->{dir}, 'script' );
86 $self->{appprefix } = Catalyst::Utils::appprefix($name);
87 $self->{appenv } = Catalyst::Utils::class2env($name);
88 $self->{startperl } = -r '/usr/bin/env'
89 ? '#!/usr/bin/env perl'
90 : "#!$Config{perlpath} -w";
91 $self->{scriptgen } = $Catalyst::Devel::CATALYST_SCRIPT_GEN || 4;
92 $self->{catalyst_version} = $Catalyst::VERSION;
93 $self->{author } = $self->{author} = $ENV{'AUTHOR'}
94 || eval { @{ [ getpwuid($<) ] }[6] }
95 || 'Catalyst developer';
97 my $gen_scripts = ( $self->{makefile} ) ? 0 : 1;
98 my $gen_makefile = ( $self->{scripts} ) ? 0 : 1;
99 my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
105 $self->_mk_rootclass;
121 $self->_mk_information;
130 $self->{author} = $self->{author} = $ENV{'AUTHOR'}
131 || eval { @{ [ getpwuid($<) ] }[6] }
133 $self->{base} ||= File::Spec->catdir( $FindBin::Bin, '..' );
134 unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
137 my $class = "Catalyst::Helper::$helper";
138 eval "require $class";
141 Catalyst::Exception->throw(
142 message => qq/Couldn't load helper "$class", "$@"/ );
145 if ( $class->can('mk_stuff') ) {
146 return 1 unless $class->mk_stuff( $self, @args );
151 my $name = shift || "Missing name for model/view/controller";
154 return 0 if $name =~ /[^\w\:]/;
156 $self->{long_type} = ucfirst $type;
157 $type = 'M' if $type =~ /model/i;
158 $type = 'V' if $type =~ /view/i;
159 $type = 'C' if $type =~ /controller/i;
160 my $appdir = File::Spec->catdir( split /\:\:/, $app );
162 File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, 'C' );
163 $type = $self->{long_type} unless -d $test_path;
164 $self->{type} = $type;
165 $self->{name} = $name;
166 $self->{class} = "$app\::$type\::$name";
170 File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, $type );
172 if ( $name =~ /\:/ ) {
173 my @path = split /\:\:/, $name;
175 $path = File::Spec->catdir( $path, @path );
177 $self->mk_dir($path);
178 $file = File::Spec->catfile( $path, "$file.pm" );
179 $self->{file} = $file;
182 $self->{test_dir} = File::Spec->catdir( $FindBin::Bin, '..', 't' );
183 $self->{test} = $self->next_test;
187 my $comp = $self->{long_type};
188 my $class = "Catalyst::Helper::$comp\::$helper";
189 eval "require $class";
192 Catalyst::Exception->throw(
193 message => qq/Couldn't load helper "$class", "$@"/ );
196 if ( $class->can('mk_compclass') ) {
197 return 1 unless $class->mk_compclass( $self, @args );
199 else { return 1 unless $self->_mk_compclass }
201 if ( $class->can('mk_comptest') ) {
202 $class->mk_comptest( $self, @args );
204 else { $self->_mk_comptest }
209 return 1 unless $self->_mk_compclass;
217 my ( $self, $dir ) = @_;
219 print qq/ exists "$dir"\n/;
222 if ( mkpath [$dir] ) {
223 print qq/created "$dir"\n/;
227 Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
231 my ( $self, $file, $content ) = @_;
232 if ( -e $file && -s _ ) {
233 print qq/ exists "$file"\n/;
235 unless ( $self->{'.newfiles'}
237 || $self->{makefile} );
238 if ( $self->{'.newfiles'} ) {
239 if ( my $f = IO::File->new("< $file") ) {
240 my $oldcontent = join( '', (<$f>) );
241 return 0 if $content eq $oldcontent;
246 if ( my $f = IO::File->new("> $file") ) {
249 print qq/created "$file"\n/;
253 Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
257 my ( $self, $tname ) = @_;
258 if ($tname) { $tname = "$tname.t" }
260 my $name = $self->{name};
264 $tname = $prefix . '.t';
265 $self->{prefix} = $prefix;
266 $prefix = lc $prefix;
268 $self->{uri} = "/$prefix";
270 my $dir = $self->{test_dir};
271 my $type = lc $self->{type};
273 return File::Spec->catfile( $dir, "$type\_$tname" );
276 # Do not touch this method, *EVER*, it is needed for back compat.
277 ## addendum: we had to split this method so we could have backwards
278 ## compatability. otherwise, we'd have no way to pass stuff from __DATA__
281 my ( $self, $file, $path, $vars ) = @_;
282 my $template = $self->get_file( ( caller(0) )[0], $file );
283 $self->render_file_contents($template, $path, $vars);
286 sub render_sharedir_file {
287 my ( $self, $file, $path, $vars ) = @_;
288 my $template = $self->get_sharedir_file( $file );
289 $self->render_file_contents($template, $path, $vars);
292 sub render_file_contents {
293 my ( $self, $template, $path, $vars ) = @_;
295 my $t = Template->new;
296 return 0 unless $template;
298 $t->process( \$template, { %{$self}, %$vars }, \$output )
299 || Catalyst::Exception->throw(
300 message => qq/Couldn't process "$template", / . $t->error() );
301 $self->mk_file( $path, $output );
304 sub _mk_information {
306 print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
311 $self->mk_dir( $self->{dir} );
312 $self->mk_dir( $self->{script} );
313 $self->{lib} = File::Spec->catdir( $self->{dir}, 'lib' );
314 $self->mk_dir( $self->{lib} );
315 $self->{root} = File::Spec->catdir( $self->{dir}, 'root' );
316 $self->mk_dir( $self->{root} );
317 $self->{static} = File::Spec->catdir( $self->{root}, 'static' );
318 $self->mk_dir( $self->{static} );
319 $self->{images} = File::Spec->catdir( $self->{static}, 'images' );
320 $self->mk_dir( $self->{images} );
321 $self->{t} = File::Spec->catdir( $self->{dir}, 't' );
322 $self->mk_dir( $self->{t} );
324 $self->{class} = File::Spec->catdir( split( /\:\:/, $self->{name} ) );
325 $self->{mod} = File::Spec->catdir( $self->{lib}, $self->{class} );
326 $self->mk_dir( $self->{mod} );
328 if ( $self->{short} ) {
329 $self->{m} = File::Spec->catdir( $self->{mod}, 'M' );
330 $self->mk_dir( $self->{m} );
331 $self->{v} = File::Spec->catdir( $self->{mod}, 'V' );
332 $self->mk_dir( $self->{v} );
333 $self->{c} = File::Spec->catdir( $self->{mod}, 'C' );
334 $self->mk_dir( $self->{c} );
337 $self->{m} = File::Spec->catdir( $self->{mod}, 'Model' );
338 $self->mk_dir( $self->{m} );
339 $self->{v} = File::Spec->catdir( $self->{mod}, 'View' );
340 $self->mk_dir( $self->{v} );
341 $self->{c} = File::Spec->catdir( $self->{mod}, 'Controller' );
342 $self->mk_dir( $self->{c} );
344 my $name = $self->{name};
346 $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
347 $self->{base} = File::Spec->rel2abs( $self->{dir} );
352 my $mod = $self->{mod};
353 $self->render_sharedir_file( File::Spec->catfile('lib', 'MyApp.pm.tt'), "$mod.pm" );
358 $self->render_sharedir_file( File::Spec->catfile('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
359 File::Spec->catfile( $self->{c}, "Root.pm" ) );
364 $self->{path} = File::Spec->catfile( 'lib', split( '::', $self->{name} ) );
365 $self->{path} .= '.pm';
366 my $dir = $self->{dir};
367 $self->render_sharedir_file( 'Makefile.PL.tt', "$dir\/Makefile.PL" );
369 if ( $self->{makefile} ) {
371 # deprecate the old Build.PL file when regenerating Makefile.PL
372 $self->_deprecate_file(
373 File::Spec->catdir( $self->{dir}, 'Build.PL' ) );
379 my $dir = $self->{dir};
380 my $appprefix = $self->{appprefix};
381 $self->render_sharedir_file( 'myapp.conf.tt',
382 File::Spec->catfile( $dir, "$appprefix.conf" ) );
387 my $dir = $self->{dir};
388 $self->render_sharedir_file( 'README.tt', "$dir\/README" );
393 my $dir = $self->{dir};
394 my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
395 $self->render_sharedir_file( 'Changes.tt', "$dir\/Changes", { time => $time } );
401 $self->render_sharedir_file( File::Spec->catfile('t', '01app.t.tt'), "$t\/01app.t" );
402 $self->render_sharedir_file( File::Spec->catfile('t', '02pod.t.tt'), "$t\/02pod.t" );
403 $self->render_sharedir_file( File::Spec->catfile('t', '03podcoverage.t.tt'), "$t\/03podcoverage.t" );
408 my $script = $self->{script};
409 my $appprefix = $self->{appprefix};
410 $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_cgi.pl.tt'), "$script\/$appprefix\_cgi.pl" );
411 chmod 0700, "$script/$appprefix\_cgi.pl";
416 my $script = $self->{script};
417 my $appprefix = $self->{appprefix};
418 $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_fastcgi.pl.tt'), "$script\/$appprefix\_fastcgi.pl" );
419 chmod 0700, "$script/$appprefix\_fastcgi.pl";
424 my $script = $self->{script};
425 my $appprefix = $self->{appprefix};
426 $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_server.pl.tt'), "$script\/$appprefix\_server.pl" );
427 chmod 0700, "$script/$appprefix\_server.pl";
432 my $script = $self->{script};
433 my $appprefix = $self->{appprefix};
434 $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_test.pl.tt'), "$script/$appprefix\_test.pl" );
435 chmod 0700, "$script/$appprefix\_test.pl";
440 my $script = $self->{script};
441 my $appprefix = $self->{appprefix};
442 $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_create.pl.tt'), "$script\/$appprefix\_create.pl" );
443 chmod 0700, "$script/$appprefix\_create.pl";
448 my $file = $self->{file};
449 return $self->render_sharedir_file( 'myapp_compclass.pl.tt', "$file" );
454 my $test = $self->{test};
455 $self->render_sharedir_file( 'comptest.tt', "$test" ); ## wtf do i rename this to?
460 my $images = $self->{images};
462 qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
463 btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
464 btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
465 for my $name (@images) {
466 my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
467 $self->mk_file( File::Spec->catfile( $images, "$name.png" ), $image );
473 my $root = $self->{root};
474 my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
475 my $dest = File::Spec->catfile( $root, "favicon.ico" );
476 $self->mk_file( $dest, $favicon );
480 sub _deprecate_file {
481 my ( $self, $file ) = @_;
484 if ( my $f = IO::File->new("< $file") ) {
485 $oldcontent = join( '', (<$f>) );
487 my $newfile = $file . '.deprecated';
488 if ( my $f = IO::File->new("> $newfile") ) {
490 print $f $oldcontent;
491 print qq/created "$newfile"\n/;
493 print qq/removed "$file"\n/;
496 Catalyst::Exception->throw(
497 message => qq/Couldn't create "$file", "$!"/ );
502 ## this is so you don't have to do make install after every change to test
503 sub _find_share_dir {
504 my ($self, $args) = @_;
505 my $share_name = $self->name;
506 if ($share_name =~ s!^/(.*?)/!!) {
508 $args->{share_base_dir} = eval {
509 Dir->new(File::ShareDir::dist_dir($dist))
515 my $dir = Dir->new(dirname($file));
517 while ($dir->parent) {
518 if (-d $dir->subdir('share') && -d $dir->subdir('share')->subdir('root')) {
519 $share_base = $dir->subdir('share')->subdir('root');
524 confess "could not find sharebase by recursion. ended up at $dir, from $file"
526 $args->{share_base_dir} = $share_base;
529 my $base = $args->{share_base_dir}->subdir($share_name);
530 confess "No such share base directory ${base}"
532 $self->share_dir($base);
539 This module is used by B<catalyst.pl> to create a set of scripts for a
540 new catalyst application. The scripts each contain documentation and
541 will output help on how to use them if called incorrectly or in some
542 cases, with no arguments.
544 It also provides some useful methods for a Helper module to call when
545 creating a component. See L</METHODS>.
551 Used to create new components for a catalyst application at the
556 The catalyst test server, starts an HTTPD which outputs debugging to
561 A script for running tests from the command-line.
565 Run your application as a CGI.
569 Run the application as a fastcgi app. Either by hand, or call this
570 from FastCgiServer in your http server config.
574 The L</_create.pl> script creates application components using Helper
575 modules. The Catalyst team provides a good number of Helper modules
576 for you to use. You can also add your own.
578 Helpers are classes that provide two methods.
580 * mk_compclass - creates the Component class
581 * mk_comptest - creates the Component test
583 So when you call C<scripts/myapp_create.pl view MyView TT>, create
584 will try to execute Catalyst::Helper::View::TT->mk_compclass and
585 Catalyst::Helper::View::TT->mk_comptest.
587 See L<Catalyst::Helper::View::TT> and
588 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
590 All helper classes should be under one of the following namespaces.
592 Catalyst::Helper::Model::
593 Catalyst::Helper::View::
594 Catalyst::Helper::Controller::
596 =head2 COMMON HELPERS
602 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
606 L<Catalyst::Helper::View::TT> - Template Toolkit view
610 L<Catalyst::Helper::Model::LDAP>
614 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
620 The helpers will read author name from /etc/passwd by default. + To override, please export the AUTHOR variable.
626 This method in your Helper module is called with C<$helper>
627 which is a L<Catalyst::Helper> object, and whichever other arguments
628 the user added to the command-line. You can use the $helper to call methods
631 If the Helper module does not contain a C<mk_compclass> method, it
632 will fall back to calling L</render_file>, with an argument of
637 This method in your Helper module is called with C<$helper>
638 which is a L<Catalyst::Helper> object, and whichever other arguments
639 the user added to the command-line. You can use the $helper to call methods
642 If the Helper module does not contain a C<mk_compclass> method, it
643 will fall back to calling L</render_file>, with an argument of
648 This method is called if the user does not supply any of the usual
649 component types C<view>, C<controller>, C<model>. It is passed the
650 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
651 arguments the user typed.
653 There is no fallback for this method.
655 =head1 INTERNAL METHODS
657 These are the methods that the Helper classes can call on the
658 <$helper> object passed to them.
660 =head2 render_file ($file, $path, $vars)
662 Render and create a file from a template in DATA using Template
663 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
664 the path to the file and $vars is the hashref as expected by
665 L<Template Toolkit|Template>.
667 =head2 get_file ($class, $file)
669 Fetch file contents from the DATA section. This is used internally by
670 L</render_file>. $class is the name of the class to get the DATA
671 section from. __PACKAGE__ or ( caller(0) )[0] might be sensible
676 Create the main application skeleton. This is called by L<catalyst.pl>.
678 =head2 mk_component ($app)
680 This method is called by L<create.pl> to make new components
681 for your application.
683 =head3 mk_dir ($path)
685 Surprisingly, this function makes a directory.
687 =head2 mk_file ($file, $content)
689 Writes content to a file. Called by L</render_file>.
691 =head2 next_test ($test_name)
693 Calculates the name of the next numbered test file and returns it.
694 Don't give the number or the .t suffix for the test name.
698 Alias for L<Path::Class::Dir>
702 =head2 get_sharedir_file
704 Method for getting a file out of share/
708 =head2 render_file_contents
710 Process a L<Template::Toolkit> template.
714 =head2 render_sharedir_file
716 Render a template/image file from our share directory
723 The helpers will read author name from /etc/passwd by default.
724 To override, please export the AUTHOR variable.
728 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
729 L<Catalyst::Response>, L<Catalyst>
733 Catalyst Contributors, see Catalyst.pm
737 This library is free software. You can redistribute it and/or modify
738 it under the same terms as Perl itself.