Search a list of potential template directories, including the project base.
[catagits/Catalyst-Devel.git] / lib / Catalyst / Helper.pm
1 package Catalyst::Helper;
2 use Moose;
3 use Config;
4 use File::Spec;
5 use File::Spec::Unix;
6 use File::Path;
7 use FindBin;
8 use IO::File;
9 use POSIX 'strftime';
10 use Template;
11 use Catalyst::Devel;
12 use Catalyst::Utils;
13 use Catalyst::Exception;
14 use Path::Class qw/dir file/;
15 use File::ShareDir qw/dist_dir/;
16 use namespace::autoclean;
17
18 with 'MooseX::Emulate::Class::Accessor::Fast';
19
20 # Change Catalyst/Devel.pm also
21 our $VERSION = '1.23';
22
23 my %cache;
24
25 =head1 NAME
26
27 Catalyst::Helper - Bootstrap a Catalyst application
28
29 =head1 SYNOPSIS
30
31   catalyst.pl <myappname>
32
33 =cut
34
35 sub get_sharedir_file {
36     my ($self, @filename) = @_;
37
38     my @try_dirs;
39     if (exists $self->{base}) {
40         push @try_dirs, $self->{base};
41     }
42     if (exists $ENV{CATALYST_DEVEL_SHAREDIR}) {
43         push @try_dirs, $ENV{CATALYST_DEVEL_SHAREDIR}
44     }
45     if (-d "inc/.author" && -f "lib/Catalyst/Helper.pm"
46             ) { # Can't use sharedir if we're in a checkout
47                 # this feels horrible, better ideas?
48         push @try_dirs, 'share';
49     }
50     else {
51         push @try_dirs, dist_dir('Catalyst-Devel');
52     }
53
54     my $file;
55     foreach my $dist_dir (@try_dirs) {
56       $file = file( $dist_dir, @filename);
57       last if -r $file;
58     }
59     Carp::confess("Cannot find $file") unless -r $file;
60     my $contents = $file->slurp;
61     return $contents;
62 }
63
64 # Do not touch this method, *EVER*, it is needed for back compat.
65 sub get_file {
66     my ( $self, $class, $file ) = @_;
67     unless ( $cache{$class} ) {
68         local $/;
69         $cache{$class} = eval "package $class; <DATA>";
70     }
71     my $data = $cache{$class};
72     Carp::confess("Could not get data from __DATA__ segment for $class")
73         unless $data;
74     my @files = split /^__(.+)__\r?\n/m, $data;
75     shift @files;
76     while (@files) {
77         my ( $name, $content ) = splice @files, 0, 2;
78         return $content if $name eq $file;
79     }
80     return 0;
81 }
82
83
84 sub mk_app {
85     my ( $self, $name ) = @_;
86
87     # Needs to be here for PAR
88     require Catalyst;
89
90     if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
91         warn "Error: Invalid application name.\n";
92         return 0;
93     }
94     $self->{name            } = $name;
95     $self->{dir             } = $name;
96     $self->{dir             } =~ s/\:\:/-/g;
97     $self->{script          } = dir( $self->{dir}, 'script' );
98     $self->{appprefix       } = Catalyst::Utils::appprefix($name);
99     $self->{appenv          } = Catalyst::Utils::class2env($name);
100     $self->{startperl       } = -r '/usr/bin/env'
101                                 ? '#!/usr/bin/env perl'
102                                 : "#!$Config{perlpath} -w";
103     $self->{scriptgen       } = $Catalyst::Devel::CATALYST_SCRIPT_GEN;
104     $self->{catalyst_version} = $Catalyst::VERSION;
105     $self->{author          } = $self->{author} = $ENV{'AUTHOR'}
106       || eval { @{ [ getpwuid($<) ] }[6] }
107       || 'Catalyst developer';
108
109     my $gen_scripts  = ( $self->{makefile} ) ? 0 : 1;
110     my $gen_makefile = ( $self->{scripts} )  ? 0 : 1;
111     my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
112
113     if ($gen_app) {
114         for ( qw/ _mk_dirs _mk_config _mk_appclass _mk_rootclass _mk_readme
115               _mk_changes _mk_apptest _mk_images _mk_favicon/ ) {
116             
117             $self->$_;
118         }
119     }
120     if ($gen_makefile) {
121         $self->_mk_makefile;
122     }
123     if ($gen_scripts) {
124         for ( qw/ _mk_cgi _mk_fastcgi _mk_server 
125                   _mk_test _mk_create _mk_information
126         / ) {
127               $self->$_;
128         }
129     }
130     return $self->{dir};
131 }
132
133 ## not much of this can really be changed, mk_compclass must be left for 
134 ## backcompat
135 sub mk_component {
136     my $self = shift;
137     my $app  = shift;
138     $self->{app} = $app;
139     $self->{author} = $self->{author} = $ENV{'AUTHOR'}
140       || eval { @{ [ getpwuid($<) ] }[6] }
141       || 'A clever guy';
142     $self->{base} ||= dir( $FindBin::Bin, '..' );
143     unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
144         my $helper = shift;
145         my @args   = @_;
146         my $class  = "Catalyst::Helper::$helper";
147         eval "require $class";
148
149         if ($@) {
150             Catalyst::Exception->throw(
151                 message => qq/Couldn't load helper "$class", "$@"/ );
152         }
153
154         if ( $class->can('mk_stuff') ) {
155             return 1 unless $class->mk_stuff( $self, @args );
156         }
157     }
158     else {
159         my $type   = shift;
160         my $name   = shift || "Missing name for model/view/controller";
161         my $helper = shift;
162         my @args   = @_;
163        return 0 if $name =~ /[^\w\:]/;
164         $type              = lc $type;
165         $self->{long_type} = ucfirst $type;
166         $type              = 'M' if $type =~ /model/i;
167         $type              = 'V' if $type =~ /view/i;
168         $type              = 'C' if $type =~ /controller/i;
169         my $appdir = dir( split /\:\:/, $app );
170         my $test_path =
171           dir( $self->{base}, 'lib', $appdir, 'C' );
172         $type = $self->{long_type} unless -d $test_path;
173         $self->{type}  = $type;
174         $self->{name}  = $name;
175         $self->{class} = "$app\::$type\::$name";
176
177         # Class
178         my $path =
179           dir( $self->{base}, 'lib', $appdir, $type );
180         my $file = $name;
181         if ( $name =~ /\:/ ) {
182             my @path = split /\:\:/, $name;
183             $file = pop @path;
184             $path = dir( $path, @path );
185         }
186         $self->mk_dir($path);
187         $file = file( $path, "$file.pm" );
188         $self->{file} = $file;
189
190         # Test
191         $self->{test_dir} = dir( $self->{base}, 't' );
192         $self->{test}     = $self->next_test;
193
194         # Helper
195         if ($helper) {
196             my $comp  = $self->{long_type};
197             my $class = "Catalyst::Helper::$comp\::$helper";
198             eval "require $class";
199
200             if ($@) {
201                 Catalyst::Exception->throw(
202                     message => qq/Couldn't load helper "$class", "$@"/ );
203             }
204
205             if ( $class->can('mk_compclass') ) {
206                 return 1 unless $class->mk_compclass( $self, @args );
207             }
208             else {
209                 return 1 unless $self->_mk_compclass
210             }
211
212             if ( $class->can('mk_comptest') ) {
213                 $class->mk_comptest( $self, @args );
214             }
215             else {
216                 $self->_mk_comptest
217             }
218         }
219
220         # Fallback
221         else {
222             return 1 unless $self->_mk_compclass;
223             $self->_mk_comptest;
224         }
225     }
226     return 1;
227 }
228
229 sub mk_dir {
230     my ( $self, $dir ) = @_;
231     if ( -d $dir ) {
232         print qq/ exists "$dir"\n/;
233         return 0;
234     }
235     if ( mkpath [$dir] ) {
236         print qq/created "$dir"\n/;
237         return 1;
238     }
239
240     Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
241 }
242
243 sub mk_file {
244     my ( $self, $file, $content ) = @_;
245     if ( -e $file && -s _ ) {
246         print qq/ exists "$file"\n/;
247         return 0
248           unless ( $self->{'.newfiles'}
249             || $self->{scripts}
250             || $self->{makefile} );
251         if ( $self->{'.newfiles'} ) {
252             if ( my $f = IO::File->new("< $file") ) {
253                 my $oldcontent = join( '', (<$f>) );
254                 return 0 if $content eq $oldcontent;
255             }
256             $file .= '.new';
257         }
258     }
259     
260     if ( my $f = IO::File->new("> $file") ) {
261         binmode $f;
262         print $f $content;
263         print qq/created "$file"\n/;
264         return 1;
265     }
266
267     Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
268 }
269
270 sub next_test {
271     my ( $self, $tname ) = @_;
272     if ($tname) { $tname = "$tname.t" }
273     else {
274         my $name   = $self->{name};
275         my $prefix = $name;
276         $prefix =~ s/::/-/g;
277         $prefix         = $prefix;
278         $tname          = $prefix . '.t';
279         $self->{prefix} = $prefix;
280         $prefix         = lc $prefix;
281         $prefix =~ s/-/\//g;
282         $self->{uri} = "/$prefix";
283     }
284     my $dir  = $self->{test_dir};
285     my $type = lc $self->{type};
286     $self->mk_dir($dir);
287     return file( $dir, "$type\_$tname" );
288 }
289
290 # Do not touch this method, *EVER*, it is needed for back compat.
291 ## addendum: we had to split this method so we could have backwards
292 ## compatability.  otherwise, we'd have no way to pass stuff from __DATA__
293
294 sub render_file {
295     my ( $self, $file, $path, $vars ) = @_;
296     my $template = $self->get_file( ( caller(0) )[0], $file );
297     $self->render_file_contents($template, $path, $vars);
298 }
299
300 sub render_sharedir_file {
301     my ( $self, $file, $path, $vars ) = @_;
302     my $template = $self->get_sharedir_file( $file );
303     die("Cannot get template from $file for $self\n") unless $template;
304     $self->render_file_contents($template, $path, $vars);
305 }
306
307 sub render_file_contents {
308     my ( $self, $template, $path, $vars ) = @_;
309     $vars ||= {};
310     my $t = Template->new;
311     return 0 unless $template;
312     my $output;
313     $t->process( \$template, { %{$self}, %$vars }, \$output )
314       || Catalyst::Exception->throw(
315         message => qq/Couldn't process "$template", / . $t->error() );
316     $self->mk_file( $path, $output );
317 }
318
319 sub _mk_information {
320     my $self = shift;
321     print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
322 }
323
324 sub _mk_dirs {
325     my $self = shift;
326     $self->mk_dir( $self->{dir} );
327     $self->mk_dir( $self->{script} );
328     $self->{lib} = dir( $self->{dir}, 'lib' );
329     $self->mk_dir( $self->{lib} );
330     $self->{root} = dir( $self->{dir}, 'root' );
331     $self->mk_dir( $self->{root} );
332     $self->{static} = dir( $self->{root}, 'static' );
333     $self->mk_dir( $self->{static} );
334     $self->{images} = dir( $self->{static}, 'images' );
335     $self->mk_dir( $self->{images} );
336     $self->{t} = dir( $self->{dir}, 't' );
337     $self->mk_dir( $self->{t} );
338
339     $self->{class} = dir( split( /\:\:/, $self->{name} ) );
340     $self->{mod} = dir( $self->{lib}, $self->{class} );
341     $self->mk_dir( $self->{mod} );
342
343     if ( $self->{short} ) {
344         $self->{m} = dir( $self->{mod}, 'M' );
345         $self->mk_dir( $self->{m} );
346         $self->{v} = dir( $self->{mod}, 'V' );
347         $self->mk_dir( $self->{v} );
348         $self->{c} = dir( $self->{mod}, 'C' );
349         $self->mk_dir( $self->{c} );
350     }
351     else {
352         $self->{m} = dir( $self->{mod}, 'Model' );
353         $self->mk_dir( $self->{m} );
354         $self->{v} = dir( $self->{mod}, 'View' );
355         $self->mk_dir( $self->{v} );
356         $self->{c} = dir( $self->{mod}, 'Controller' );
357         $self->mk_dir( $self->{c} );
358     }
359     my $name = $self->{name};
360     $self->{rootname} =
361       $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
362     $self->{base} = dir( $self->{dir} )->absolute;
363 }
364
365 sub _mk_appclass {
366     my $self = shift;
367     my $mod  = $self->{mod};
368     $self->render_sharedir_file( file('lib', 'MyApp.pm.tt'), "$mod.pm" );
369 }
370
371 sub _mk_rootclass {
372     my $self = shift;
373     $self->render_sharedir_file( file('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
374         file( $self->{c}, "Root.pm" ) );
375 }
376
377 sub _mk_makefile {
378     my $self = shift;
379     $self->{path} = dir( 'lib', split( '::', $self->{name} ) );
380     $self->{path} .= '.pm';
381     my $dir = $self->{dir};
382     $self->render_sharedir_file( 'Makefile.PL.tt', file($dir, "Makefile.PL") );
383
384     if ( $self->{makefile} ) {
385
386         # deprecate the old Build.PL file when regenerating Makefile.PL
387         $self->_deprecate_file(
388             file( $self->{dir}, 'Build.PL' ) );
389     }
390 }
391
392 sub _mk_config {
393     my $self      = shift;
394     my $dir       = $self->{dir};
395     my $appprefix = $self->{appprefix};
396     $self->render_sharedir_file( 'myapp.conf.tt',
397         file( $dir, "$appprefix.conf" ) );
398 }
399
400 sub _mk_readme {
401     my $self = shift;
402     my $dir  = $self->{dir};
403     $self->render_sharedir_file( 'README.tt', file($dir, "README") );
404 }
405
406 sub _mk_changes {
407     my $self = shift;
408     my $dir  = $self->{dir};
409     my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
410     $self->render_sharedir_file( 'Changes.tt', file($dir, "Changes"), { time => $time } );
411 }
412
413 sub _mk_apptest {
414     my $self = shift;
415     my $t    = $self->{t};
416     $self->render_sharedir_file( file('t', '01app.t.tt'),         file($t, "01app.t") );
417     $self->render_sharedir_file( file('t', '02pod.t.tt'),         file($t, "02pod.t") );
418     $self->render_sharedir_file( file('t', '03podcoverage.t.tt'), file($t, "03podcoverage.t") );
419 }
420
421 sub _mk_cgi {
422     my $self      = shift;
423     my $script    = $self->{script};
424     my $appprefix = $self->{appprefix};
425     $self->render_sharedir_file( file('script', 'myapp_cgi.pl.tt'), file($script,"$appprefix\_cgi.pl") );
426     chmod 0700, file($script,"$appprefix\_cgi.pl");
427 }
428
429 sub _mk_fastcgi {
430     my $self      = shift;
431     my $script    = $self->{script};
432     my $appprefix = $self->{appprefix};
433     $self->render_sharedir_file( file('script', 'myapp_fastcgi.pl.tt'), file($script, "$appprefix\_fastcgi.pl") );
434     chmod 0700, file($script, "$appprefix\_fastcgi.pl");
435 }
436
437 sub _mk_server {
438     my $self      = shift;
439     my $script    = $self->{script};
440     my $appprefix = $self->{appprefix};
441     $self->render_sharedir_file( file('script', 'myapp_server.pl.tt'), file($script, "$appprefix\_server.pl") );
442     chmod 0700, file($script, "$appprefix\_server.pl");
443 }
444
445 sub _mk_test {
446     my $self      = shift;
447     my $script    = $self->{script};
448     my $appprefix = $self->{appprefix};
449     $self->render_sharedir_file( file('script', 'myapp_test.pl.tt'), file($script, "$appprefix\_test.pl") );
450     chmod 0700, file($script, "$appprefix\_test.pl");
451 }
452
453 sub _mk_create {
454     my $self      = shift;
455     my $script    = $self->{script};
456     my $appprefix = $self->{appprefix};
457     $self->render_sharedir_file( file('script', 'myapp_create.pl.tt'), file($script, "$appprefix\_create.pl") );
458     chmod 0700, file($script, "$appprefix\_create.pl");
459 }
460
461 sub _mk_compclass {
462     my $self = shift;
463     my $file = $self->{file};
464     return $self->render_sharedir_file( file('lib', 'Helper', 'compclass.pm.tt'), $file );
465 }
466
467 sub _mk_comptest {
468     my $self = shift;
469     my $test = $self->{test};
470     $self->render_sharedir_file( file('t', 'comptest.tt'), $test );  ## wtf do i rename this to?
471 }
472
473 sub _mk_images {
474     my $self   = shift;
475     my $images = $self->{images};
476     my @images =
477       qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
478       btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
479       btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
480     for my $name (@images) {
481         my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
482         $self->mk_file( file( $images, "$name.png" ), $image );
483     }
484 }
485
486 sub _mk_favicon {
487     my $self    = shift;
488     my $root    = $self->{root};
489     my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
490     my $dest = dir( $root, "favicon.ico" );
491     $self->mk_file( $dest, $favicon );
492
493 }
494
495 sub _deprecate_file {
496     my ( $self, $file ) = @_;
497     if ( -e $file ) {
498         my ($f, $oldcontent);
499         if ( $f = IO::File->new("< $file") ) {
500             $oldcontent = join( '', (<$f>) );
501         }
502         my $newfile = $file . '.deprecated';
503         if ( $f = IO::File->new("> $newfile") ) {
504             binmode $f;
505             print $f $oldcontent;
506             print qq/created "$newfile"\n/;
507             unlink $file;
508             print qq/removed "$file"\n/;
509             return 1;
510         }
511         Catalyst::Exception->throw(
512             message => qq/Couldn't create "$file", "$!"/ );
513     }
514 }
515
516 =head1 DESCRIPTION
517
518 This module is used by B<catalyst.pl> to create a set of scripts for a
519 new catalyst application. The scripts each contain documentation and
520 will output help on how to use them if called incorrectly or in some
521 cases, with no arguments.
522
523 It also provides some useful methods for a Helper module to call when
524 creating a component. See L</METHODS>.
525
526 =head1 SCRIPTS
527
528 =head2 _create.pl
529
530 Used to create new components for a catalyst application at the
531 development stage.
532
533 =head2 _server.pl
534
535 The catalyst test server, starts an HTTPD which outputs debugging to
536 the terminal.
537
538 =head2 _test.pl
539
540 A script for running tests from the command-line.
541
542 =head2 _cgi.pl
543
544 Run your application as a CGI.
545
546 =head2 _fastcgi.pl
547
548 Run the application as a fastcgi app. Either by hand, or call this
549 from FastCgiServer in your http server config.
550
551 =head1 HELPERS
552
553 The L</_create.pl> script creates application components using Helper
554 modules. The Catalyst team provides a good number of Helper modules
555 for you to use. You can also add your own.
556
557 Helpers are classes that provide two methods.
558
559     * mk_compclass - creates the Component class
560     * mk_comptest  - creates the Component test
561
562 So when you call C<scripts/myapp_create.pl view MyView TT>, create
563 will try to execute Catalyst::Helper::View::TT->mk_compclass and
564 Catalyst::Helper::View::TT->mk_comptest.
565
566 See L<Catalyst::Helper::View::TT> and
567 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
568
569 All helper classes should be under one of the following namespaces.
570
571     Catalyst::Helper::Model::
572     Catalyst::Helper::View::
573     Catalyst::Helper::Controller::
574
575 =head2 COMMON HELPERS
576
577 =over
578
579 =item *
580
581 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
582
583 =item *
584
585 L<Catalyst::Helper::View::TT> - Template Toolkit view
586
587 =item *
588
589 L<Catalyst::Helper::Model::LDAP>
590
591 =item *
592
593 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
594
595 =back
596
597 =head3 NOTE
598
599 The helpers will read author name from /etc/passwd by default.
600 To override, please export the AUTHOR variable.
601
602 =head1 METHODS
603
604 =head2 mk_compclass
605
606 This method in your Helper module is called with C<$helper>
607 which is a L<Catalyst::Helper> object, and whichever other arguments
608 the user added to the command-line. You can use the $helper to call methods
609 described below.
610
611 If the Helper module does not contain a C<mk_compclass> method, it
612 will fall back to calling L</render_file>, with an argument of
613 C<compclass>.
614
615 =head2 mk_comptest
616
617 This method in your Helper module is called with C<$helper>
618 which is a L<Catalyst::Helper> object, and whichever other arguments
619 the user added to the command-line. You can use the $helper to call methods
620 described below.
621
622 If the Helper module does not contain a C<mk_compclass> method, it
623 will fall back to calling L</render_file>, with an argument of
624 C<comptest>.
625
626 =head2 mk_stuff
627
628 This method is called if the user does not supply any of the usual
629 component types C<view>, C<controller>, C<model>. It is passed the
630 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
631 arguments the user typed.
632
633 There is no fallback for this method.
634
635 =head1 INTERNAL METHODS
636
637 These are the methods that the Helper classes can call on the
638 <$helper> object passed to them.
639
640 =head2 render_file ($file, $path, $vars)
641
642 Render and create a file from a template in DATA using Template
643 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
644 the path to the file and $vars is the hashref as expected by
645 L<Template Toolkit|Template>.
646
647 =head2 get_file ($class, $file)
648
649 Fetch file contents from the DATA section. This is used internally by
650 L</render_file>.  $class is the name of the class to get the DATA
651 section from.  __PACKAGE__ or ( caller(0) )[0] might be sensible
652 values for this.
653
654 =head2 mk_app
655
656 Create the main application skeleton. This is called by L<catalyst.pl>.
657
658 =head2 mk_component ($app)
659
660 This method is called by L<create.pl> to make new components
661 for your application.
662
663 =head2 mk_dir ($path)
664
665 Surprisingly, this function makes a directory.
666
667 =head2 mk_file ($file, $content)
668
669 Writes content to a file. Called by L</render_file>.
670
671 =head2 next_test ($test_name)
672
673 Calculates the name of the next numbered test file and returns it.
674 Don't give the number or the .t suffix for the test name.
675
676 =cut
677
678 =head2 get_sharedir_file
679
680 Method for getting a file out of share/
681
682 =cut
683
684 =head2 render_file_contents
685
686 Process a L<Template::Toolkit> template.
687
688 =cut
689
690 =head2 render_sharedir_file
691
692 Render a template/image file from our share directory
693
694 =cut
695
696 =head1 NOTE
697
698 The helpers will read author name from /etc/passwd by default.
699 To override, please export the AUTHOR variable.
700
701 =head1 SEE ALSO
702
703 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
704 L<Catalyst::Response>, L<Catalyst>
705
706 =head1 AUTHORS
707
708 Catalyst Contributors, see Catalyst.pm
709
710 =head1 LICENSE
711
712 This library is free software. You can redistribute it and/or modify
713 it under the same terms as Perl itself.
714
715 =cut
716
717 1;
718