cleaned up steps to create an app/component
[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::Path;
6 use FindBin;
7 use IO::File;
8 use POSIX 'strftime';
9 use Template;
10 use Catalyst::Devel;
11 use Catalyst::Utils;
12 use Catalyst::Exception;
13 use Path::Class qw/dir file/;
14 use File::ShareDir qw/dist_dir/;
15 #use namespace::autoclean;
16
17 my %cache;
18
19 =head1 NAME
20
21 Catalyst::Helper - Bootstrap a Catalyst application
22
23 =head1 SYNOPSIS
24
25   catalyst.pl <myappname>
26
27 =cut
28
29 sub get_sharedir_file {
30     my ($self, @filename) = @_;
31     my $dist_dir;
32     if (-d "inc/.author" && -f "lib/Catalyst/Helper.pm"
33             ) { # Can't use sharedir if we're in a checkout
34                 # this feels horrible, better ideas?
35         $dist_dir = 'share';
36     }
37     else {
38         $dist_dir = dist_dir('Catalyst-Devel');
39     }
40     my $file = file( $dist_dir, @filename);
41     Carp::confess("Cannot find $file") unless -r $file;
42     my $contents = $file->slurp;
43     return $contents;
44 }
45
46 # Do not touch this method, *EVER*, it is needed for back compat.
47 sub get_file {
48     my ( $self, $class, $file ) = @_;
49     unless ( $cache{$class} ) {
50         local $/;
51         $cache{$class} = eval "package $class; <DATA>";
52     }
53     my $data = $cache{$class};
54     Carp::confess("Could not get data from __DATA__ segment for $class")
55         unless $data;
56     my @files = split /^__(.+)__\r?\n/m, $data;
57     shift @files;
58     while (@files) {
59         my ( $name, $content ) = splice @files, 0, 2;
60         return $content if $name eq $file;
61     }
62     return 0;
63 }
64
65
66 sub mk_app {
67     my ( $self, $name ) = @_;
68
69     # Needs to be here for PAR
70     require Catalyst;
71
72     if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
73         warn "Error: Invalid application name.\n";
74         return 0;
75     }
76     $self->{name            } = $name;
77     $self->{dir             } = $name;
78     $self->{dir             } =~ s/\:\:/-/g;
79     $self->{script          } = dir( $self->{dir}, 'script' );
80     $self->{appprefix       } = Catalyst::Utils::appprefix($name);
81     $self->{appenv          } = Catalyst::Utils::class2env($name);
82     $self->{startperl       } = -r '/usr/bin/env'
83                                 ? '#!/usr/bin/env perl'
84                                 : "#!$Config{perlpath} -w";
85     $self->{scriptgen       } = $Catalyst::Devel::CATALYST_SCRIPT_GEN || 4;
86     $self->{catalyst_version} = $Catalyst::VERSION;
87     $self->{author          } = $self->{author} = $ENV{'AUTHOR'}
88       || eval { @{ [ getpwuid($<) ] }[6] }
89       || 'Catalyst developer';
90
91     my $gen_scripts  = ( $self->{makefile} ) ? 0 : 1;
92     my $gen_makefile = ( $self->{scripts} )  ? 0 : 1;
93     my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
94
95     if ($gen_app) {
96     
97         for ( qw/ _mk_dirs _mk_config _mk_appclass _mk_rootclass _mk_readme
98               _mk_changes _mk_apptest _mk_images _mk_favicon/ ) {
99             
100             $self->$_;
101        
102         }
103     }
104     if ($gen_makefile) {
105         $self->_mk_makefile;
106     }
107     if ($gen_scripts) {
108         for ( qw/ _mk_cgi _mk_fastcgi _mk_server 
109                   _mk_test _mk_create _mk_information / ) {
110         $self->$_;
111       #  probably want to only do this if a DBIC schema is specified, or move it
112       #  to C::H::Model::DBIC::Schema
113       #  $self->_mk_dbic_deploy; 
114         
115     }
116     return $self->{dir};
117 }
118
119
120 ## not much of this can really be changed, mk_compclass must be left for 
121 ## backcompat
122 sub mk_component {
123     my $self = shift;
124     my $app  = shift;
125     $self->{app} = $app;
126     $self->{author} = $self->{author} = $ENV{'AUTHOR'}
127       || eval { @{ [ getpwuid($<) ] }[6] }
128       || 'A clever guy';
129     $self->{base} ||= dir( $FindBin::Bin, '..' );
130     unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
131         my $helper = shift;
132         my @args   = @_;
133         my $class  = "Catalyst::Helper::$helper";
134         eval "require $class";
135
136         if ($@) {
137             Catalyst::Exception->throw(
138                 message => qq/Couldn't load helper "$class", "$@"/ );
139         }
140
141         if ( $class->can('mk_stuff') ) {
142             return 1 unless $class->mk_stuff( $self, @args );
143         }
144     }
145     else {
146         my $type   = shift;
147         my $name   = shift || "Missing name for model/view/controller";
148         my $helper = shift;
149         my @args   = @_;
150        return 0 if $name =~ /[^\w\:]/;
151         $type              = lc $type;
152         $self->{long_type} = ucfirst $type;
153         $type              = 'M' if $type =~ /model/i;
154         $type              = 'V' if $type =~ /view/i;
155         $type              = 'C' if $type =~ /controller/i;
156         my $appdir = dir( split /\:\:/, $app );
157         my $test_path =
158           dir( $self->{base}, 'lib', $appdir, 'C' );
159         $type = $self->{long_type} unless -d $test_path;
160         $self->{type}  = $type;
161         $self->{name}  = $name;
162         $self->{class} = "$app\::$type\::$name";
163
164         # Class
165         my $path =
166           dir( $self->{base}, 'lib', $appdir, $type );
167         my $file = $name;
168         if ( $name =~ /\:/ ) {
169             my @path = split /\:\:/, $name;
170             $file = pop @path;
171             $path = dir( $path, @path );
172         }
173         $self->mk_dir($path);
174         $file = file( $path, "$file.pm" );
175         $self->{file} = $file;
176
177         # Test
178         $self->{test_dir} = dir( $self->{base}, 't' );
179         $self->{test}     = $self->next_test;
180
181         # Helper
182         if ($helper) {
183             my $comp  = $self->{long_type};
184             my $class = "Catalyst::Helper::$comp\::$helper";
185             eval "require $class";
186
187             if ($@) {
188                 Catalyst::Exception->throw(
189                     message => qq/Couldn't load helper "$class", "$@"/ );
190             }
191
192             ## must be left for back compat! ###################################
193             if ( $class->can('mk_compclass') ) {
194                 return 1 unless $class->mk_compclass( $self, @args );
195             }
196             else { return 1 unless $self->_mk_compclass }
197
198             if ( $class->can('mk_comptest') ) {
199                 $class->mk_comptest( $self, @args );
200             }
201             else { $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 _mk_dbic_deploy {
481     my $self      = shift;
482     my $script    = $self->{script};
483     my $appprefix = $self->{appprefix};
484     $self->render_sharedir_file( file('script', 'myapp_deploy_schema.pl.tt'), file($script, "$appprefix\_deploy_schema.pl") );
485     chmod 0700, file($script, "$appprefix\_deploy_schema.pl");
486 }
487
488 sub _deprecate_file {
489     my ( $self, $file ) = @_;
490     if ( -e $file ) {
491         my $oldcontent;
492         if ( my $f = IO::File->new("< $file") ) {
493             $oldcontent = join( '', (<$f>) );
494         }
495         my $newfile = $file . '.deprecated';
496         if ( my $f = IO::File->new("> $newfile") ) {
497             binmode $f;
498             print $f $oldcontent;
499             print qq/created "$newfile"\n/;
500             unlink $file;
501             print qq/removed "$file"\n/;
502             return 1;
503         }
504         Catalyst::Exception->throw(
505             message => qq/Couldn't create "$file", "$!"/ );
506     }
507 }
508
509 =head1 DESCRIPTION
510
511 This module is used by B<catalyst.pl> to create a set of scripts for a
512 new catalyst application. The scripts each contain documentation and
513 will output help on how to use them if called incorrectly or in some
514 cases, with no arguments.
515
516 It also provides some useful methods for a Helper module to call when
517 creating a component. See L</METHODS>.
518
519 =head1 SCRIPTS
520
521 =head2 _create.pl
522
523 Used to create new components for a catalyst application at the
524 development stage.
525
526 =head2 _server.pl
527
528 The catalyst test server, starts an HTTPD which outputs debugging to
529 the terminal.
530
531 =head2 _deploy_dbic.pl
532
533 Deploy a L<DBIx::Class> schema to the database of your choice. 
534
535 =head2 _test.pl
536
537 A script for running tests from the command-line.
538
539 =head2 _cgi.pl
540
541 Run your application as a CGI.
542
543 =head2 _fastcgi.pl
544
545 Run the application as a fastcgi app. Either by hand, or call this
546 from FastCgiServer in your http server config.
547
548 =head1 HELPERS
549
550 The L</_create.pl> script creates application components using Helper
551 modules. The Catalyst team provides a good number of Helper modules
552 for you to use. You can also add your own.
553
554 Helpers are classes that provide two methods.
555
556     * mk_compclass - creates the Component class
557     * mk_comptest  - creates the Component test
558
559 So when you call C<scripts/myapp_create.pl view MyView TT>, create
560 will try to execute Catalyst::Helper::View::TT->mk_compclass and
561 Catalyst::Helper::View::TT->mk_comptest.
562
563 See L<Catalyst::Helper::View::TT> and
564 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
565
566 All helper classes should be under one of the following namespaces.
567
568     Catalyst::Helper::Model::
569     Catalyst::Helper::View::
570     Catalyst::Helper::Controller::
571
572 =head2 COMMON HELPERS
573
574 =over
575
576 =item *
577
578 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
579
580 =item *
581
582 L<Catalyst::Helper::View::TT> - Template Toolkit view
583
584 =item *
585
586 L<Catalyst::Helper::Model::LDAP>
587
588 =item *
589
590 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
591
592 =back
593
594 =head3 NOTE
595
596 The helpers will read author name from /etc/passwd by default. + To override, please export the AUTHOR variable.
597
598 =head1 METHODS
599
600 =head2 mk_compclass
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<compclass>.
610
611 =head2 mk_comptest
612
613 This method in your Helper module is called with C<$helper>
614 which is a L<Catalyst::Helper> object, and whichever other arguments
615 the user added to the command-line. You can use the $helper to call methods
616 described below.
617
618 If the Helper module does not contain a C<mk_compclass> method, it
619 will fall back to calling L</render_file>, with an argument of
620 C<comptest>.
621
622 =head2 mk_stuff
623
624 This method is called if the user does not supply any of the usual
625 component types C<view>, C<controller>, C<model>. It is passed the
626 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
627 arguments the user typed.
628
629 There is no fallback for this method.
630
631 =head1 INTERNAL METHODS
632
633 These are the methods that the Helper classes can call on the
634 <$helper> object passed to them.
635
636 =head2 render_file ($file, $path, $vars)
637
638 Render and create a file from a template in DATA using Template
639 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
640 the path to the file and $vars is the hashref as expected by
641 L<Template Toolkit|Template>.
642
643 =head2 get_file ($class, $file)
644
645 Fetch file contents from the DATA section. This is used internally by
646 L</render_file>.  $class is the name of the class to get the DATA
647 section from.  __PACKAGE__ or ( caller(0) )[0] might be sensible
648 values for this.
649
650 =head2 mk_app
651
652 Create the main application skeleton. This is called by L<catalyst.pl>.
653
654 =head2 mk_component ($app)
655
656 This method is called by L<create.pl> to make new components
657 for your application.
658
659 =head3 mk_dir ($path)
660
661 Surprisingly, this function makes a directory.
662
663 =head2 mk_file ($file, $content)
664
665 Writes content to a file. Called by L</render_file>.
666
667 =head2 next_test ($test_name)
668
669 Calculates the name of the next numbered test file and returns it.
670 Don't give the number or the .t suffix for the test name.
671
672 =cut
673
674 =head2 get_sharedir_file
675
676 Method for getting a file out of share/
677
678 =cut
679
680 =head2 render_file_contents
681
682 Process a L<Template::Toolkit> template.
683
684 =cut
685
686 =head2 render_sharedir_file
687
688 Render a template/image file from our share directory
689
690 =cut
691
692
693 =head1 NOTE
694
695 The helpers will read author name from /etc/passwd by default.
696 To override, please export the AUTHOR variable.
697
698 =head1 SEE ALSO
699
700 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
701 L<Catalyst::Response>, L<Catalyst>
702
703 =head1 AUTHORS
704
705 Catalyst Contributors, see Catalyst.pm
706
707 =head1 LICENSE
708
709 This library is free software. You can redistribute it and/or modify
710 it under the same terms as Perl itself.
711
712 =begin pod_to_ignore
713
714 =cut
715
716 1;
717