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