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