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