9e84b2344bc68b49376da7f0b79c6bc084745a35
[catagits/Catalyst-Devel.git] / lib / Catalyst / Helper.pm
1 package Catalyst::Helper;
2 use Config;
3 use File::Spec;
4 use File::Path;
5 use FindBin;
6 use IO::File;
7 use POSIX 'strftime';
8 use Template;
9 use Catalyst::Devel;
10 use Catalyst::Utils;
11 use Catalyst::Exception;
12 use Path::Class qw/dir file/;
13 use File::ShareDir qw/dist_dir/;
14
15 my %cache;
16
17 =head1 NAME
18
19 Catalyst::Helper - Bootstrap a Catalyst application
20
21 =head1 SYNOPSIS
22
23   catalyst.pl <myappname>
24
25 =cut
26
27 sub get_sharedir_file {
28     my ($self, @filename) = @_;
29     my $dist_dir;
30     if (-d "inc/.author" && -f "lib/Catalyst/Helper.pm"
31             ) { # Can't use sharedir if we're in a checkout
32                 # this feels horrible, better ideas?
33         $dist_dir = 'share';
34     }
35     else {
36         $dist_dir = dist_dir('Catalyst-Devel');
37     }
38     my $file = file( $dist_dir, @filename);
39     Carp::confess("Cannot find $file") unless -r $file;
40     my $contents = $file->slurp;
41     return $contents;
42 }
43
44 # Do not touch this method, *EVER*, it is needed for back compat.
45 sub get_file {
46     my ( $self, $class, $file ) = @_;
47     unless ( $cache{$class} ) {
48         local $/;
49         $cache{$class} = eval "package $class; <DATA>";
50     }
51     my $data = $cache{$class};
52     Carp::confess("Could not get data from __DATA__ segment for $class")
53         unless $data;
54     my @files = split /^__(.+)__\r?\n/m, $data;
55     shift @files;
56     while (@files) {
57         my ( $name, $content ) = splice @files, 0, 2;
58         return $content if $name eq $file;
59     }
60     return 0;
61 }
62
63
64 sub mk_app {
65     my ( $self, $name ) = @_;
66
67     # Needs to be here for PAR
68     require Catalyst;
69
70     if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
71         warn "Error: Invalid application name.\n";
72         return 0;
73     }
74     $self->{name            } = $name;
75     $self->{dir             } = $name;
76     $self->{dir             } =~ s/\:\:/-/g;
77     $self->{script          } = dir( $self->{dir}, 'script' );
78     $self->{appprefix       } = Catalyst::Utils::appprefix($name);
79     $self->{appenv          } = Catalyst::Utils::class2env($name);
80     $self->{startperl       } = -r '/usr/bin/env'
81                                 ? '#!/usr/bin/env perl'
82                                 : "#!$Config{perlpath} -w";
83     $self->{scriptgen       } = $Catalyst::Devel::CATALYST_SCRIPT_GEN || 34;
84     $self->{catalyst_version} = $Catalyst::VERSION;
85     $self->{author          } = $self->{author} = $ENV{'AUTHOR'}
86       || eval { @{ [ getpwuid($<) ] }[6] }
87       || 'Catalyst developer';
88
89     my $gen_scripts  = ( $self->{makefile} ) ? 0 : 1;
90     my $gen_makefile = ( $self->{scripts} )  ? 0 : 1;
91     my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
92
93     if ($gen_app) {
94         for ( qw/ _mk_dirs _mk_config _mk_appclass _mk_rootclass _mk_readme
95               _mk_changes _mk_apptest _mk_images _mk_favicon/ ) {
96             
97             $self->$_;
98         }
99     }
100     if ($gen_makefile) {
101         $self->_mk_makefile;
102     }
103     if ($gen_scripts) {
104         for ( qw/ _mk_cgi _mk_fastcgi _mk_server 
105                   _mk_test _mk_create _mk_information / ) {
106         $self->$_;
107     }
108     return $self->{dir};
109 }
110
111
112 ## not much of this can really be changed, mk_compclass must be left for 
113 ## backcompat
114 sub mk_component {
115     my $self = shift;
116     my $app  = shift;
117     $self->{app} = $app;
118     $self->{author} = $self->{author} = $ENV{'AUTHOR'}
119       || eval { @{ [ getpwuid($<) ] }[6] }
120       || 'A clever guy';
121     $self->{base} ||= dir( $FindBin::Bin, '..' );
122     unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
123         my $helper = shift;
124         my @args   = @_;
125         my $class  = "Catalyst::Helper::$helper";
126         eval "require $class";
127
128         if ($@) {
129             Catalyst::Exception->throw(
130                 message => qq/Couldn't load helper "$class", "$@"/ );
131         }
132
133         if ( $class->can('mk_stuff') ) {
134             return 1 unless $class->mk_stuff( $self, @args );
135         }
136     }
137     else {
138         my $type   = shift;
139         my $name   = shift || "Missing name for model/view/controller";
140         my $helper = shift;
141         my @args   = @_;
142        return 0 if $name =~ /[^\w\:]/;
143         $type              = lc $type;
144         $self->{long_type} = ucfirst $type;
145         $type              = 'M' if $type =~ /model/i;
146         $type              = 'V' if $type =~ /view/i;
147         $type              = 'C' if $type =~ /controller/i;
148         my $appdir = dir( split /\:\:/, $app );
149         my $test_path =
150           dir( $self->{base}, 'lib', $appdir, 'C' );
151         $type = $self->{long_type} unless -d $test_path;
152         $self->{type}  = $type;
153         $self->{name}  = $name;
154         $self->{class} = "$app\::$type\::$name";
155
156         # Class
157         my $path =
158           dir( $self->{base}, 'lib', $appdir, $type );
159         my $file = $name;
160         if ( $name =~ /\:/ ) {
161             my @path = split /\:\:/, $name;
162             $file = pop @path;
163             $path = dir( $path, @path );
164         }
165         $self->mk_dir($path);
166         $file = file( $path, "$file.pm" );
167         $self->{file} = $file;
168
169         # Test
170         $self->{test_dir} = dir( $self->{base}, 't' );
171         $self->{test}     = $self->next_test;
172
173         # Helper
174         if ($helper) {
175             my $comp  = $self->{long_type};
176             my $class = "Catalyst::Helper::$comp\::$helper";
177             eval "require $class";
178
179             if ($@) {
180                 Catalyst::Exception->throw(
181                     message => qq/Couldn't load helper "$class", "$@"/ );
182             }
183
184             ## must be left for back compat! ###################################
185             if ( $class->can('mk_compclass') ) {
186                 return 1 unless $class->mk_compclass( $self, @args );
187             }
188             else { return 1 unless $self->_mk_compclass }
189
190             if ( $class->can('mk_comptest') ) {
191                 $class->mk_comptest( $self, @args );
192             }
193             else { $self->_mk_comptest }
194             ####################################################################
195         }
196
197         # Fallback
198         else {
199             return 1 unless $self->_mk_compclass;
200             $self->_mk_comptest;
201         }
202     }
203     return 1;
204 }
205
206 sub mk_dir {
207     my ( $self, $dir ) = @_;
208     if ( -d $dir ) {
209         print qq/ exists "$dir"\n/;
210         return 0;
211     }
212     if ( mkpath [$dir] ) {
213         print qq/created "$dir"\n/;
214         return 1;
215     }
216
217     Catalyst::Exception->throw( message => qq/Couldn't create "$dir", "$!"/ );
218 }
219
220 sub mk_file {
221     my ( $self, $file, $content ) = @_;
222     if ( -e $file && -s _ ) {
223         print qq/ exists "$file"\n/;
224         return 0
225           unless ( $self->{'.newfiles'}
226             || $self->{scripts}
227             || $self->{makefile} );
228         if ( $self->{'.newfiles'} ) {
229             if ( my $f = IO::File->new("< $file") ) {
230                 my $oldcontent = join( '', (<$f>) );
231                 return 0 if $content eq $oldcontent;
232             }
233             $file .= '.new';
234         }
235     }
236     
237     if ( my $f = IO::File->new("> $file") ) {
238         binmode $f;
239         print $f $content;
240         print qq/created "$file"\n/;
241         return 1;
242     }
243
244     Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
245 }
246
247 sub next_test {
248     my ( $self, $tname ) = @_;
249     if ($tname) { $tname = "$tname.t" }
250     else {
251         my $name   = $self->{name};
252         my $prefix = $name;
253         $prefix =~ s/::/-/g;
254         $prefix         = $prefix;
255         $tname          = $prefix . '.t';
256         $self->{prefix} = $prefix;
257         $prefix         = lc $prefix;
258         $prefix =~ s/-/\//g;
259         $self->{uri} = "/$prefix";
260     }
261     my $dir  = $self->{test_dir};
262     my $type = lc $self->{type};
263     $self->mk_dir($dir);
264     return file( $dir, "$type\_$tname" );
265 }
266
267 # Do not touch this method, *EVER*, it is needed for back compat.
268 ## addendum: we had to split this method so we could have backwards
269 ## compatability.  otherwise, we'd have no way to pass stuff from __DATA__
270
271 sub render_file {
272     my ( $self, $file, $path, $vars ) = @_;
273     my $template = $self->get_file( ( caller(0) )[0], $file );
274     $self->render_file_contents($template, $path, $vars);
275 }
276
277 sub render_sharedir_file {
278     my ( $self, $file, $path, $vars ) = @_;
279     my $template = $self->get_sharedir_file( $file );
280     die("Cannot get template from $file for $self\n") unless $template;
281     $self->render_file_contents($template, $path, $vars);
282 }
283
284 sub render_file_contents {
285     my ( $self, $template, $path, $vars ) = @_;
286     $vars ||= {};
287     my $t = Template->new;
288     return 0 unless $template;
289     my $output;
290     $t->process( \$template, { %{$self}, %$vars }, \$output )
291       || Catalyst::Exception->throw(
292         message => qq/Couldn't process "$template", / . $t->error() );
293     $self->mk_file( $path, $output );
294 }
295
296 sub _mk_information {
297     my $self = shift;
298     print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
299 }
300
301 sub _mk_dirs {
302     my $self = shift;
303     $self->mk_dir( $self->{dir} );
304     $self->mk_dir( $self->{script} );
305     $self->{lib} = dir( $self->{dir}, 'lib' );
306     $self->mk_dir( $self->{lib} );
307     $self->{root} = dir( $self->{dir}, 'root' );
308     $self->mk_dir( $self->{root} );
309     $self->{static} = dir( $self->{root}, 'static' );
310     $self->mk_dir( $self->{static} );
311     $self->{images} = dir( $self->{static}, 'images' );
312     $self->mk_dir( $self->{images} );
313     $self->{t} = dir( $self->{dir}, 't' );
314     $self->mk_dir( $self->{t} );
315
316     $self->{class} = dir( split( /\:\:/, $self->{name} ) );
317     $self->{mod} = dir( $self->{lib}, $self->{class} );
318     $self->mk_dir( $self->{mod} );
319
320     if ( $self->{short} ) {
321         $self->{m} = dir( $self->{mod}, 'M' );
322         $self->mk_dir( $self->{m} );
323         $self->{v} = dir( $self->{mod}, 'V' );
324         $self->mk_dir( $self->{v} );
325         $self->{c} = dir( $self->{mod}, 'C' );
326         $self->mk_dir( $self->{c} );
327     }
328     else {
329         $self->{m} = dir( $self->{mod}, 'Model' );
330         $self->mk_dir( $self->{m} );
331         $self->{v} = dir( $self->{mod}, 'View' );
332         $self->mk_dir( $self->{v} );
333         $self->{c} = dir( $self->{mod}, 'Controller' );
334         $self->mk_dir( $self->{c} );
335     }
336     my $name = $self->{name};
337     $self->{rootname} =
338       $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
339     $self->{base} = dir( $self->{dir} )->absolute;
340 }
341
342 sub _mk_appclass {
343     my $self = shift;
344     my $mod  = $self->{mod};
345     $self->render_sharedir_file( file('lib', 'MyApp.pm.tt'), "$mod.pm" );
346 }
347
348 sub _mk_rootclass {
349     my $self = shift;
350     $self->render_sharedir_file( file('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
351         file( $self->{c}, "Root.pm" ) );
352 }
353
354 sub _mk_makefile {
355     my $self = shift;
356     $self->{path} = dir( 'lib', split( '::', $self->{name} ) );
357     $self->{path} .= '.pm';
358     my $dir = $self->{dir};
359     $self->render_sharedir_file( 'Makefile.PL.tt', file($dir, "Makefile.PL") );
360
361     if ( $self->{makefile} ) {
362
363         # deprecate the old Build.PL file when regenerating Makefile.PL
364         $self->_deprecate_file(
365             file( $self->{dir}, 'Build.PL' ) );
366     }
367 }
368
369 sub _mk_config {
370     my $self      = shift;
371     my $dir       = $self->{dir};
372     my $appprefix = $self->{appprefix};
373     $self->render_sharedir_file( 'myapp.conf.tt',
374         file( $dir, "$appprefix.conf" ) );
375 }
376
377 sub _mk_readme {
378     my $self = shift;
379     my $dir  = $self->{dir};
380     $self->render_sharedir_file( 'README.tt', file($dir, "README") );
381 }
382
383 sub _mk_changes {
384     my $self = shift;
385     my $dir  = $self->{dir};
386     my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
387     $self->render_sharedir_file( 'Changes.tt', file($dir, "Changes", { time => $time } );
388 }
389
390 sub _mk_apptest {
391     my $self = shift;
392     my $t    = $self->{t};
393     $self->render_sharedir_file( file('t', '01app.t.tt'),         file($t, "01app.t") );
394     $self->render_sharedir_file( file('t', '02pod.t.tt'),         file($t, "02pod.t") );
395     $self->render_sharedir_file( file('t', '03podcoverage.t.tt'), file($t, "03podcoverage.t") );
396 }
397
398 sub _mk_cgi {
399     my $self      = shift;
400     my $script    = $self->{script};
401     my $appprefix = $self->{appprefix};
402     $self->render_sharedir_file( file('script', 'myapp_cgi.pl.tt'), file($script,"$appprefix\_cgi.pl") );
403     chmod 0700, file($script,"$appprefix\_cgi.pl");
404 }
405
406 sub _mk_fastcgi {
407     my $self      = shift;
408     my $script    = $self->{script};
409     my $appprefix = $self->{appprefix};
410     $self->render_sharedir_file( file('script', 'myapp_fastcgi.pl.tt'), file($script, "$appprefix\_fastcgi.pl") );
411     chmod 0700, file($script, "$appprefix\_fastcgi.pl");
412 }
413
414 sub _mk_server {
415     my $self      = shift;
416     my $script    = $self->{script};
417     my $appprefix = $self->{appprefix};
418     $self->render_sharedir_file( file('script', 'myapp_server.pl.tt'), file($script, "$appprefix\_server.pl") );
419     chmod 0700, file($script, "$appprefix\_server.pl");
420 }
421
422 sub _mk_test {
423     my $self      = shift;
424     my $script    = $self->{script};
425     my $appprefix = $self->{appprefix};
426     $self->render_sharedir_file( file('script', 'myapp_test.pl.tt'), file($script, "$appprefix\_test.pl") );
427     chmod 0700, file($script, "$appprefix\_test.pl");
428 }
429
430 sub _mk_create {
431     my $self      = shift;
432     my $script    = $self->{script};
433     my $appprefix = $self->{appprefix};
434     $self->render_sharedir_file( file('script', 'myapp_create.pl.tt'), file($script, "$appprefix\_create.pl") );
435     chmod 0700, file($script, "$appprefix\_create.pl");
436 }
437
438 sub _mk_compclass {
439     my $self = shift;
440     my $file = $self->{file};
441     return $self->render_sharedir_file( file('lib', 'Helper', 'compclass.pm.tt'), $file );
442 }
443
444 sub _mk_comptest {
445     my $self = shift;
446     my $test = $self->{test};
447     $self->render_sharedir_file( file('t', 'comptest.tt'), $test );  ## wtf do i rename this to?
448 }
449
450 sub _mk_images {
451     my $self   = shift;
452     my $images = $self->{images};
453     my @images =
454       qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
455       btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
456       btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
457     for my $name (@images) {
458         my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
459         $self->mk_file( file( $images, "$name.png" ), $image );
460     }
461 }
462
463 sub _mk_favicon {
464     my $self    = shift;
465     my $root    = $self->{root};
466     my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
467     my $dest = dir( $root, "favicon.ico" );
468     $self->mk_file( $dest, $favicon );
469
470 }
471
472 sub _mk_dbic_deploy {
473     my $self      = shift;
474     my $script    = $self->{script};
475     my $appprefix = $self->{appprefix};
476     $self->render_sharedir_file( file('script', 'myapp_deploy_schema.pl.tt'), file($script, "$appprefix\_deploy_schema.pl") );
477     chmod 0700, file($script, "$appprefix\_deploy_schema.pl");
478 }
479
480 sub _deprecate_file {
481     my ( $self, $file ) = @_;
482     if ( -e $file ) {
483         my $oldcontent;
484         if ( my $f = IO::File->new("< $file") ) {
485             $oldcontent = join( '', (<$f>) );
486         }
487         my $newfile = $file . '.deprecated';
488         if ( my $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 =head3 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