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