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