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