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