Fixes for generated_app.t so it doesn't rely on finding catalyst.pl in the current...
[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 my %cache;
21
22 =head1 NAME
23
24 Catalyst::Helper - Bootstrap a Catalyst application
25
26 =head1 SYNOPSIS
27
28   catalyst.pl <myappname>
29
30 =cut
31
32 sub get_sharedir_file {
33     my ($self, @filename) = @_;
34     my $dist_dir;
35     if (exists $ENV{CATALYST_DEVEL_SHAREDIR}) {
36         $dist_dir = $ENV{CATALYST_DEVEL_SHAREDIR};
37     }
38     elsif (-d "inc/.author" && -f "lib/Catalyst/Helper.pm"
39             ) { # Can't use sharedir if we're in a checkout
40                 # this feels horrible, better ideas?
41         $dist_dir = 'share';
42     }
43     else {
44         $dist_dir = dist_dir('Catalyst-Devel');
45     }
46     my $file = file( $dist_dir, @filename);
47     Carp::confess("Cannot find $file") unless -r $file;
48     my $contents = $file->slurp;
49     return $contents;
50 }
51
52 # Do not touch this method, *EVER*, it is needed for back compat.
53 sub get_file {
54     my ( $self, $class, $file ) = @_;
55     unless ( $cache{$class} ) {
56         local $/;
57         $cache{$class} = eval "package $class; <DATA>";
58     }
59     my $data = $cache{$class};
60     Carp::confess("Could not get data from __DATA__ segment for $class")
61         unless $data;
62     my @files = split /^__(.+)__\r?\n/m, $data;
63     shift @files;
64     while (@files) {
65         my ( $name, $content ) = splice @files, 0, 2;
66         return $content if $name eq $file;
67     }
68     return 0;
69 }
70
71
72 sub mk_app {
73     my ( $self, $name ) = @_;
74
75     # Needs to be here for PAR
76     require Catalyst;
77
78     if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
79         warn "Error: Invalid application name.\n";
80         return 0;
81     }
82     $self->{name            } = $name;
83     $self->{dir             } = $name;
84     $self->{dir             } =~ s/\:\:/-/g;
85     $self->{script          } = dir( $self->{dir}, 'script' );
86     $self->{appprefix       } = Catalyst::Utils::appprefix($name);
87     $self->{appenv          } = Catalyst::Utils::class2env($name);
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     if ( $self->{short} ) {
332         $self->{m} = dir( $self->{mod}, 'M' );
333         $self->mk_dir( $self->{m} );
334         $self->{v} = dir( $self->{mod}, 'V' );
335         $self->mk_dir( $self->{v} );
336         $self->{c} = dir( $self->{mod}, 'C' );
337         $self->mk_dir( $self->{c} );
338     }
339     else {
340         $self->{m} = dir( $self->{mod}, 'Model' );
341         $self->mk_dir( $self->{m} );
342         $self->{v} = dir( $self->{mod}, 'View' );
343         $self->mk_dir( $self->{v} );
344         $self->{c} = dir( $self->{mod}, 'Controller' );
345         $self->mk_dir( $self->{c} );
346     }
347     my $name = $self->{name};
348     $self->{rootname} =
349       $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
350     $self->{base} = dir( $self->{dir} )->absolute;
351 }
352
353 sub _mk_appclass {
354     my $self = shift;
355     my $mod  = $self->{mod};
356     $self->render_sharedir_file( file('lib', 'MyApp.pm.tt'), "$mod.pm" );
357 }
358
359 sub _mk_rootclass {
360     my $self = shift;
361     $self->render_sharedir_file( file('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
362         file( $self->{c}, "Root.pm" ) );
363 }
364
365 sub _mk_makefile {
366     my $self = shift;
367     $self->{path} = dir( 'lib', split( '::', $self->{name} ) );
368     $self->{path} .= '.pm';
369     my $dir = $self->{dir};
370     $self->render_sharedir_file( 'Makefile.PL.tt', file($dir, "Makefile.PL") );
371
372     if ( $self->{makefile} ) {
373
374         # deprecate the old Build.PL file when regenerating Makefile.PL
375         $self->_deprecate_file(
376             file( $self->{dir}, 'Build.PL' ) );
377     }
378 }
379
380 sub _mk_config {
381     my $self      = shift;
382     my $dir       = $self->{dir};
383     my $appprefix = $self->{appprefix};
384     $self->render_sharedir_file( 'myapp.conf.tt',
385         file( $dir, "$appprefix.conf" ) );
386 }
387
388 sub _mk_readme {
389     my $self = shift;
390     my $dir  = $self->{dir};
391     $self->render_sharedir_file( 'README.tt', file($dir, "README") );
392 }
393
394 sub _mk_changes {
395     my $self = shift;
396     my $dir  = $self->{dir};
397     my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
398     $self->render_sharedir_file( 'Changes.tt', file($dir, "Changes"), { time => $time } );
399 }
400
401 sub _mk_apptest {
402     my $self = shift;
403     my $t    = $self->{t};
404     $self->render_sharedir_file( file('t', '01app.t.tt'),         file($t, "01app.t") );
405     $self->render_sharedir_file( file('t', '02pod.t.tt'),         file($t, "02pod.t") );
406     $self->render_sharedir_file( file('t', '03podcoverage.t.tt'), file($t, "03podcoverage.t") );
407 }
408
409 sub _mk_cgi {
410     my $self      = shift;
411     my $script    = $self->{script};
412     my $appprefix = $self->{appprefix};
413     $self->render_sharedir_file( file('script', 'myapp_cgi.pl.tt'), file($script,"$appprefix\_cgi.pl") );
414     chmod 0700, file($script,"$appprefix\_cgi.pl");
415 }
416
417 sub _mk_fastcgi {
418     my $self      = shift;
419     my $script    = $self->{script};
420     my $appprefix = $self->{appprefix};
421     $self->render_sharedir_file( file('script', 'myapp_fastcgi.pl.tt'), file($script, "$appprefix\_fastcgi.pl") );
422     chmod 0700, file($script, "$appprefix\_fastcgi.pl");
423 }
424
425 sub _mk_server {
426     my $self      = shift;
427     my $script    = $self->{script};
428     my $appprefix = $self->{appprefix};
429     $self->render_sharedir_file( file('script', 'myapp_server.pl.tt'), file($script, "$appprefix\_server.pl") );
430     chmod 0700, file($script, "$appprefix\_server.pl");
431 }
432
433 sub _mk_test {
434     my $self      = shift;
435     my $script    = $self->{script};
436     my $appprefix = $self->{appprefix};
437     $self->render_sharedir_file( file('script', 'myapp_test.pl.tt'), file($script, "$appprefix\_test.pl") );
438     chmod 0700, file($script, "$appprefix\_test.pl");
439 }
440
441 sub _mk_create {
442     my $self      = shift;
443     my $script    = $self->{script};
444     my $appprefix = $self->{appprefix};
445     $self->render_sharedir_file( file('script', 'myapp_create.pl.tt'), file($script, "$appprefix\_create.pl") );
446     chmod 0700, file($script, "$appprefix\_create.pl");
447 }
448
449 sub _mk_compclass {
450     my $self = shift;
451     my $file = $self->{file};
452     return $self->render_sharedir_file( file('lib', 'Helper', 'compclass.pm.tt'), $file );
453 }
454
455 sub _mk_comptest {
456     my $self = shift;
457     my $test = $self->{test};
458     $self->render_sharedir_file( file('t', 'comptest.tt'), $test );  ## wtf do i rename this to?
459 }
460
461 sub _mk_images {
462     my $self   = shift;
463     my $images = $self->{images};
464     my @images =
465       qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
466       btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
467       btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
468     for my $name (@images) {
469         my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
470         $self->mk_file( file( $images, "$name.png" ), $image );
471     }
472 }
473
474 sub _mk_favicon {
475     my $self    = shift;
476     my $root    = $self->{root};
477     my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
478     my $dest = dir( $root, "favicon.ico" );
479     $self->mk_file( $dest, $favicon );
480
481 }
482
483 sub _deprecate_file {
484     my ( $self, $file ) = @_;
485     if ( -e $file ) {
486         my ($f, $oldcontent);
487         if ( $f = IO::File->new("< $file") ) {
488             $oldcontent = join( '', (<$f>) );
489         }
490         my $newfile = $file . '.deprecated';
491         if ( $f = IO::File->new("> $newfile") ) {
492             binmode $f;
493             print $f $oldcontent;
494             print qq/created "$newfile"\n/;
495             unlink $file;
496             print qq/removed "$file"\n/;
497             return 1;
498         }
499         Catalyst::Exception->throw(
500             message => qq/Couldn't create "$file", "$!"/ );
501     }
502 }
503
504 =head1 DESCRIPTION
505
506 This module is used by B<catalyst.pl> to create a set of scripts for a
507 new catalyst application. The scripts each contain documentation and
508 will output help on how to use them if called incorrectly or in some
509 cases, with no arguments.
510
511 It also provides some useful methods for a Helper module to call when
512 creating a component. See L</METHODS>.
513
514 =head1 SCRIPTS
515
516 =head2 _create.pl
517
518 Used to create new components for a catalyst application at the
519 development stage.
520
521 =head2 _server.pl
522
523 The catalyst test server, starts an HTTPD which outputs debugging to
524 the terminal.
525
526 =head2 _test.pl
527
528 A script for running tests from the command-line.
529
530 =head2 _cgi.pl
531
532 Run your application as a CGI.
533
534 =head2 _fastcgi.pl
535
536 Run the application as a fastcgi app. Either by hand, or call this
537 from FastCgiServer in your http server config.
538
539 =head1 HELPERS
540
541 The L</_create.pl> script creates application components using Helper
542 modules. The Catalyst team provides a good number of Helper modules
543 for you to use. You can also add your own.
544
545 Helpers are classes that provide two methods.
546
547     * mk_compclass - creates the Component class
548     * mk_comptest  - creates the Component test
549
550 So when you call C<scripts/myapp_create.pl view MyView TT>, create
551 will try to execute Catalyst::Helper::View::TT->mk_compclass and
552 Catalyst::Helper::View::TT->mk_comptest.
553
554 See L<Catalyst::Helper::View::TT> and
555 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
556
557 All helper classes should be under one of the following namespaces.
558
559     Catalyst::Helper::Model::
560     Catalyst::Helper::View::
561     Catalyst::Helper::Controller::
562
563 =head2 COMMON HELPERS
564
565 =over
566
567 =item *
568
569 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
570
571 =item *
572
573 L<Catalyst::Helper::View::TT> - Template Toolkit view
574
575 =item *
576
577 L<Catalyst::Helper::Model::LDAP>
578
579 =item *
580
581 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
582
583 =back
584
585 =head3 NOTE
586
587 The helpers will read author name from /etc/passwd by default.
588 To override, please export the AUTHOR variable.
589
590 =head1 METHODS
591
592 =head2 mk_compclass
593
594 This method in your Helper module is called with C<$helper>
595 which is a L<Catalyst::Helper> object, and whichever other arguments
596 the user added to the command-line. You can use the $helper to call methods
597 described below.
598
599 If the Helper module does not contain a C<mk_compclass> method, it
600 will fall back to calling L</render_file>, with an argument of
601 C<compclass>.
602
603 =head2 mk_comptest
604
605 This method in your Helper module is called with C<$helper>
606 which is a L<Catalyst::Helper> object, and whichever other arguments
607 the user added to the command-line. You can use the $helper to call methods
608 described below.
609
610 If the Helper module does not contain a C<mk_compclass> method, it
611 will fall back to calling L</render_file>, with an argument of
612 C<comptest>.
613
614 =head2 mk_stuff
615
616 This method is called if the user does not supply any of the usual
617 component types C<view>, C<controller>, C<model>. It is passed the
618 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
619 arguments the user typed.
620
621 There is no fallback for this method.
622
623 =head1 INTERNAL METHODS
624
625 These are the methods that the Helper classes can call on the
626 <$helper> object passed to them.
627
628 =head2 render_file ($file, $path, $vars)
629
630 Render and create a file from a template in DATA using Template
631 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
632 the path to the file and $vars is the hashref as expected by
633 L<Template Toolkit|Template>.
634
635 =head2 get_file ($class, $file)
636
637 Fetch file contents from the DATA section. This is used internally by
638 L</render_file>.  $class is the name of the class to get the DATA
639 section from.  __PACKAGE__ or ( caller(0) )[0] might be sensible
640 values for this.
641
642 =head2 mk_app
643
644 Create the main application skeleton. This is called by L<catalyst.pl>.
645
646 =head2 mk_component ($app)
647
648 This method is called by L<create.pl> to make new components
649 for your application.
650
651 =head2 mk_dir ($path)
652
653 Surprisingly, this function makes a directory.
654
655 =head2 mk_file ($file, $content)
656
657 Writes content to a file. Called by L</render_file>.
658
659 =head2 next_test ($test_name)
660
661 Calculates the name of the next numbered test file and returns it.
662 Don't give the number or the .t suffix for the test name.
663
664 =cut
665
666 =head2 get_sharedir_file
667
668 Method for getting a file out of share/
669
670 =cut
671
672 =head2 render_file_contents
673
674 Process a L<Template::Toolkit> template.
675
676 =cut
677
678 =head2 render_sharedir_file
679
680 Render a template/image file from our share directory
681
682 =cut
683
684 =head1 NOTE
685
686 The helpers will read author name from /etc/passwd by default.
687 To override, please export the AUTHOR variable.
688
689 =head1 SEE ALSO
690
691 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
692 L<Catalyst::Response>, L<Catalyst>
693
694 =head1 AUTHORS
695
696 Catalyst Contributors, see Catalyst.pm
697
698 =head1 LICENSE
699
700 This library is free software. You can redistribute it and/or modify
701 it under the same terms as Perl itself.
702
703 =cut
704
705 1;
706