1 package Catalyst::Helper;
13 use Catalyst::Exception;
14 use Path::Class qw/dir file/;
15 use File::ShareDir qw/dist_dir/;
17 use Path::Resolver::Resolver::Mux::Ordered;
18 use Path::Resolver::Resolver::FileSystem;
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, $fs_path->(dist_dir('Catalyst-Devel'));
74 $resolver = Path::Resolver::Resolver::Mux::Ordered->new({
75 resolvers => \@resolvers
83 sub get_sharedir_file {
84 my ($self, @filename) = @_;
86 my $filepath = file(@filename);
87 my $file = $self->get_resolver->entity_at("$filepath") # doesn't like object
88 or Carp::confess("Cannot find $filepath");
89 return $file->content;
92 # Do not touch this method, *EVER*, it is needed for back compat.
94 my ( $self, $class, $file ) = @_;
95 unless ( $cache{$class} ) {
97 $cache{$class} = eval "package $class; <DATA>";
99 my $data = $cache{$class};
100 Carp::confess("Could not get data from __DATA__ segment for $class")
102 my @files = split /^__(.+)__\r?\n/m, $data;
105 my ( $name, $content ) = splice @files, 0, 2;
106 return $content if $name eq $file;
113 my ( $self, $name ) = @_;
115 # Needs to be here for PAR
118 if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
119 warn "Error: Invalid application name.\n";
122 $self->{name } = $name;
123 $self->{dir } = $name;
124 $self->{dir } =~ s/\:\:/-/g;
125 $self->{script } = dir( $self->{dir}, 'script' );
126 $self->{appprefix } = Catalyst::Utils::appprefix($name);
127 $self->{appenv } = Catalyst::Utils::class2env($name);
128 $self->{startperl } = -r '/usr/bin/env'
129 ? '#!/usr/bin/env perl'
130 : "#!$Config{perlpath} -w";
131 $self->{scriptgen } = $Catalyst::Devel::CATALYST_SCRIPT_GEN;
132 $self->{catalyst_version} = $Catalyst::VERSION;
133 $self->{author } = $self->{author} = $ENV{'AUTHOR'}
134 || eval { @{ [ getpwuid($<) ] }[6] }
135 || 'Catalyst developer';
137 my $gen_scripts = ( $self->{makefile} ) ? 0 : 1;
138 my $gen_makefile = ( $self->{scripts} ) ? 0 : 1;
139 my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
142 for ( qw/ _mk_dirs _mk_config _mk_appclass _mk_rootclass _mk_readme
143 _mk_changes _mk_apptest _mk_images _mk_favicon/ ) {
152 for ( qw/ _mk_cgi _mk_fastcgi _mk_server
153 _mk_test _mk_create _mk_information
161 ## not much of this can really be changed, mk_compclass must be left for
167 $self->{author} = $self->{author} = $ENV{'AUTHOR'}
168 || eval { @{ [ getpwuid($<) ] }[6] }
170 $self->{base} ||= dir( $FindBin::Bin, '..' );
171 unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
174 my $class = "Catalyst::Helper::$helper";
175 eval "require $class";
178 Catalyst::Exception->throw(
179 message => qq/Couldn't load helper "$class", "$@"/ );
182 if ( $class->can('mk_stuff') ) {
183 return 1 unless $class->mk_stuff( $self, @args );
188 my $name = shift || "Missing name for model/view/controller";
191 return 0 if $name =~ /[^\w\:]/;
193 $self->{long_type} = ucfirst $type;
194 $type = 'M' if $type =~ /model/i;
195 $type = 'V' if $type =~ /view/i;
196 $type = 'C' if $type =~ /controller/i;
197 my $appdir = dir( split /\:\:/, $app );
199 dir( $self->{base}, 'lib', $appdir, 'C' );
200 $type = $self->{long_type} unless -d $test_path;
201 $self->{type} = $type;
202 $self->{name} = $name;
203 $self->{class} = "$app\::$type\::$name";
207 dir( $self->{base}, 'lib', $appdir, $type );
209 if ( $name =~ /\:/ ) {
210 my @path = split /\:\:/, $name;
212 $path = dir( $path, @path );
214 $self->mk_dir($path);
215 $file = file( $path, "$file.pm" );
216 $self->{file} = $file;
219 $self->{test_dir} = dir( $self->{base}, 't' );
220 $self->{test} = $self->next_test;
224 my $comp = $self->{long_type};
225 my $class = "Catalyst::Helper::$comp\::$helper";
226 eval "require $class";
229 Catalyst::Exception->throw(
230 message => qq/Couldn't load helper "$class", "$@"/ );
233 if ( $class->can('mk_compclass') ) {
234 return 1 unless $class->mk_compclass( $self, @args );
237 return 1 unless $self->_mk_compclass
240 if ( $class->can('mk_comptest') ) {
241 $class->mk_comptest( $self, @args );
250 return 1 unless $self->_mk_compclass;
258 my ( $self, $dir ) = @_;
260 print qq/ exists "$dir"\n/;
263 if ( mkpath [$dir] ) {
264 print qq/created "$dir"\n/;
268 Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
272 my ( $self, $file, $content ) = @_;
273 if ( -e $file && -s _ ) {
274 print qq/ exists "$file"\n/;
276 unless ( $self->{'.newfiles'}
278 || $self->{makefile} );
279 if ( $self->{'.newfiles'} ) {
280 if ( my $f = IO::File->new("< $file") ) {
281 my $oldcontent = join( '', (<$f>) );
282 return 0 if $content eq $oldcontent;
288 if ( my $f = IO::File->new("> $file") ) {
291 print qq/created "$file"\n/;
295 Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
299 my ( $self, $tname ) = @_;
300 if ($tname) { $tname = "$tname.t" }
302 my $name = $self->{name};
306 $tname = $prefix . '.t';
307 $self->{prefix} = $prefix;
308 $prefix = lc $prefix;
310 $self->{uri} = "/$prefix";
312 my $dir = $self->{test_dir};
313 my $type = lc $self->{type};
315 return file( $dir, "$type\_$tname" );
318 # Do not touch this method, *EVER*, it is needed for back compat.
319 ## addendum: we had to split this method so we could have backwards
320 ## compatability. otherwise, we'd have no way to pass stuff from __DATA__
323 my ( $self, $file, $path, $vars ) = @_;
324 my $template = $self->get_file( ( caller(0) )[0], $file );
325 $self->render_file_contents($template, $path, $vars);
328 sub render_sharedir_file {
329 my ( $self, $file, $path, $vars ) = @_;
330 my $template = $self->get_sharedir_file( $file );
331 die("Cannot get template from $file for $self\n") unless $template;
332 $self->render_file_contents($template, $path, $vars);
335 sub render_file_contents {
336 my ( $self, $template, $path, $vars ) = @_;
338 my $t = Template->new;
339 return 0 unless $template;
341 $t->process( \$template, { %{$self}, %$vars }, \$output )
342 || Catalyst::Exception->throw(
343 message => qq/Couldn't process "$template", / . $t->error() );
344 $self->mk_file( $path, $output );
347 sub _mk_information {
349 print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
354 $self->mk_dir( $self->{dir} );
355 $self->mk_dir( $self->{script} );
356 $self->{lib} = dir( $self->{dir}, 'lib' );
357 $self->mk_dir( $self->{lib} );
358 $self->{root} = dir( $self->{dir}, 'root' );
359 $self->mk_dir( $self->{root} );
360 $self->{static} = dir( $self->{root}, 'static' );
361 $self->mk_dir( $self->{static} );
362 $self->{images} = dir( $self->{static}, 'images' );
363 $self->mk_dir( $self->{images} );
364 $self->{t} = dir( $self->{dir}, 't' );
365 $self->mk_dir( $self->{t} );
367 $self->{class} = dir( split( /\:\:/, $self->{name} ) );
368 $self->{mod} = dir( $self->{lib}, $self->{class} );
369 $self->mk_dir( $self->{mod} );
371 if ( $self->{short} ) {
372 $self->{m} = dir( $self->{mod}, 'M' );
373 $self->mk_dir( $self->{m} );
374 $self->{v} = dir( $self->{mod}, 'V' );
375 $self->mk_dir( $self->{v} );
376 $self->{c} = dir( $self->{mod}, 'C' );
377 $self->mk_dir( $self->{c} );
380 $self->{m} = dir( $self->{mod}, 'Model' );
381 $self->mk_dir( $self->{m} );
382 $self->{v} = dir( $self->{mod}, 'View' );
383 $self->mk_dir( $self->{v} );
384 $self->{c} = dir( $self->{mod}, 'Controller' );
385 $self->mk_dir( $self->{c} );
387 my $name = $self->{name};
389 $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
390 $self->{base} = dir( $self->{dir} )->absolute;
395 my $mod = $self->{mod};
396 $self->render_sharedir_file( file('lib', 'MyApp.pm.tt'), "$mod.pm" );
401 $self->render_sharedir_file( file('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
402 file( $self->{c}, "Root.pm" ) );
407 $self->{path} = dir( 'lib', split( '::', $self->{name} ) );
408 $self->{path} .= '.pm';
409 my $dir = $self->{dir};
410 $self->render_sharedir_file( 'Makefile.PL.tt', file($dir, "Makefile.PL") );
412 if ( $self->{makefile} ) {
414 # deprecate the old Build.PL file when regenerating Makefile.PL
415 $self->_deprecate_file(
416 file( $self->{dir}, 'Build.PL' ) );
422 my $dir = $self->{dir};
423 my $appprefix = $self->{appprefix};
424 $self->render_sharedir_file( 'myapp.conf.tt',
425 file( $dir, "$appprefix.conf" ) );
430 my $dir = $self->{dir};
431 $self->render_sharedir_file( 'README.tt', file($dir, "README") );
436 my $dir = $self->{dir};
437 my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
438 $self->render_sharedir_file( 'Changes.tt', file($dir, "Changes"), { time => $time } );
444 $self->render_sharedir_file( file('t', '01app.t.tt'), file($t, "01app.t") );
445 $self->render_sharedir_file( file('t', '02pod.t.tt'), file($t, "02pod.t") );
446 $self->render_sharedir_file( file('t', '03podcoverage.t.tt'), file($t, "03podcoverage.t") );
451 my $script = $self->{script};
452 my $appprefix = $self->{appprefix};
453 $self->render_sharedir_file( file('script', 'myapp_cgi.pl.tt'), file($script,"$appprefix\_cgi.pl") );
454 chmod 0700, file($script,"$appprefix\_cgi.pl");
459 my $script = $self->{script};
460 my $appprefix = $self->{appprefix};
461 $self->render_sharedir_file( file('script', 'myapp_fastcgi.pl.tt'), file($script, "$appprefix\_fastcgi.pl") );
462 chmod 0700, file($script, "$appprefix\_fastcgi.pl");
467 my $script = $self->{script};
468 my $appprefix = $self->{appprefix};
469 $self->render_sharedir_file( file('script', 'myapp_server.pl.tt'), file($script, "$appprefix\_server.pl") );
470 chmod 0700, file($script, "$appprefix\_server.pl");
475 my $script = $self->{script};
476 my $appprefix = $self->{appprefix};
477 $self->render_sharedir_file( file('script', 'myapp_test.pl.tt'), file($script, "$appprefix\_test.pl") );
478 chmod 0700, file($script, "$appprefix\_test.pl");
483 my $script = $self->{script};
484 my $appprefix = $self->{appprefix};
485 $self->render_sharedir_file( file('script', 'myapp_create.pl.tt'), file($script, "$appprefix\_create.pl") );
486 chmod 0700, file($script, "$appprefix\_create.pl");
491 my $file = $self->{file};
492 return $self->render_sharedir_file( file('lib', 'Helper', 'compclass.pm.tt'), $file );
497 my $test = $self->{test};
498 $self->render_sharedir_file( file('t', 'comptest.tt'), $test ); ## wtf do i rename this to?
503 my $images = $self->{images};
505 qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
506 btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
507 btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
508 for my $name (@images) {
509 my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
510 $self->mk_file( file( $images, "$name.png" ), $image );
516 my $root = $self->{root};
517 my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
518 my $dest = dir( $root, "favicon.ico" );
519 $self->mk_file( $dest, $favicon );
523 sub _deprecate_file {
524 my ( $self, $file ) = @_;
526 my ($f, $oldcontent);
527 if ( $f = IO::File->new("< $file") ) {
528 $oldcontent = join( '', (<$f>) );
530 my $newfile = $file . '.deprecated';
531 if ( $f = IO::File->new("> $newfile") ) {
533 print $f $oldcontent;
534 print qq/created "$newfile"\n/;
536 print qq/removed "$file"\n/;
539 Catalyst::Exception->throw(
540 message => qq/Couldn't create "$file", "$!"/ );
546 This module is used by B<catalyst.pl> to create a set of scripts for a
547 new catalyst application. The scripts each contain documentation and
548 will output help on how to use them if called incorrectly or in some
549 cases, with no arguments.
551 It also provides some useful methods for a Helper module to call when
552 creating a component. See L</METHODS>.
558 Used to create new components for a catalyst application at the
563 The catalyst test server, starts an HTTPD which outputs debugging to
568 A script for running tests from the command-line.
572 Run your application as a CGI.
576 Run the application as a fastcgi app. Either by hand, or call this
577 from FastCgiServer in your http server config.
581 The L</_create.pl> script creates application components using Helper
582 modules. The Catalyst team provides a good number of Helper modules
583 for you to use. You can also add your own.
585 Helpers are classes that provide two methods.
587 * mk_compclass - creates the Component class
588 * mk_comptest - creates the Component test
590 So when you call C<scripts/myapp_create.pl view MyView TT>, create
591 will try to execute Catalyst::Helper::View::TT->mk_compclass and
592 Catalyst::Helper::View::TT->mk_comptest.
594 See L<Catalyst::Helper::View::TT> and
595 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
597 All helper classes should be under one of the following namespaces.
599 Catalyst::Helper::Model::
600 Catalyst::Helper::View::
601 Catalyst::Helper::Controller::
603 =head2 COMMON HELPERS
609 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
613 L<Catalyst::Helper::View::TT> - Template Toolkit view
617 L<Catalyst::Helper::Model::LDAP>
621 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
627 The helpers will read author name from /etc/passwd by default.
628 To override, please export the AUTHOR variable.
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 in your Helper module is called with C<$helper>
646 which is a L<Catalyst::Helper> object, and whichever other arguments
647 the user added to the command-line. You can use the $helper to call methods
650 If the Helper module does not contain a C<mk_compclass> method, it
651 will fall back to calling L</render_file>, with an argument of
656 This method is called if the user does not supply any of the usual
657 component types C<view>, C<controller>, C<model>. It is passed the
658 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
659 arguments the user typed.
661 There is no fallback for this method.
663 =head1 INTERNAL METHODS
665 These are the methods that the Helper classes can call on the
666 <$helper> object passed to them.
668 =head2 render_file ($file, $path, $vars)
670 Render and create a file from a template in DATA using Template
671 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
672 the path to the file and $vars is the hashref as expected by
673 L<Template Toolkit|Template>.
675 =head2 get_file ($class, $file)
677 Fetch file contents from the DATA section. This is used internally by
678 L</render_file>. $class is the name of the class to get the DATA
679 section from. __PACKAGE__ or ( caller(0) )[0] might be sensible
684 Create the main application skeleton. This is called by L<catalyst.pl>.
686 =head2 mk_component ($app)
688 This method is called by L<create.pl> to make new components
689 for your application.
691 =head2 mk_dir ($path)
693 Surprisingly, this function makes a directory.
695 =head2 mk_file ($file, $content)
697 Writes content to a file. Called by L</render_file>.
699 =head2 next_test ($test_name)
701 Calculates the name of the next numbered test file and returns it.
702 Don't give the number or the .t suffix for the test name.
706 =head2 get_sharedir_file
708 Method for getting a file out of share/
712 =head2 render_file_contents
714 Process a L<Template::Toolkit> template.
718 =head2 render_sharedir_file
720 Render a template/image file from our share directory
726 The helpers will read author name from /etc/passwd by default.
727 To override, please export the AUTHOR variable.
731 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
732 L<Catalyst::Response>, L<Catalyst>
736 Catalyst Contributors, see Catalyst.pm
740 This library is free software. You can redistribute it and/or modify
741 it under the same terms as Perl itself.