unfucked
[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::Path;
6 use FindBin;
7 use IO::File;
8 use POSIX 'strftime';
9 use Template;
10 use Catalyst::Devel;
11 use Catalyst::Utils;
12 use Catalyst::Exception;
13 use Path::Class qw/dir file/;
14 use File::ShareDir qw/dist_dir/;
15 use aliased 'Path::Class::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
31
32 sub get_sharedir_file {
33     my ($self, @filename) = @_;
34     my $dist_dir;
35     if (-d "inc/.author" && -f "lib/Catalyst/Helper.pm"
36             ) { # Can't use sharedir if we're in a checkout
37                 # this feels horrible, better ideas?
38         $dist_dir = 'share';
39     }
40     else {
41         $dist_dir = dist_dir('Catalyst-Devel');
42     }
43     my $file = file( $dist_dir, @filename);
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
68 sub mk_app {
69     my ( $self, $name ) = @_;
70
71     # Needs to be here for PAR
72     require Catalyst;
73
74     if ( $name =~ /[^\w:]/ || $name =~ /^\d/ || $name =~ /\b:\b|:{3,}/) {
75         warn "Error: Invalid application name.\n";
76         return 0;
77     }
78     $self->{name            } = $name;
79     $self->{dir             } = $name;
80     $self->{dir             } =~ s/\:\:/-/g;
81     $self->{script          } = File::Spec->catdir( $self->{dir}, 'script' );
82     $self->{appprefix       } = Catalyst::Utils::appprefix($name);
83     $self->{appenv          } = Catalyst::Utils::class2env($name);
84     $self->{startperl       } = -r '/usr/bin/env'
85                                 ? '#!/usr/bin/env perl'
86                                 : "#!$Config{perlpath} -w";
87     $self->{scriptgen       } = $Catalyst::Devel::CATALYST_SCRIPT_GEN || 4;
88     $self->{catalyst_version} = $Catalyst::VERSION;
89     $self->{author          } = $self->{author} = $ENV{'AUTHOR'}
90       || eval { @{ [ getpwuid($<) ] }[6] }
91       || 'Catalyst developer';
92
93     my $gen_scripts  = ( $self->{makefile} ) ? 0 : 1;
94     my $gen_makefile = ( $self->{scripts} )  ? 0 : 1;
95     my $gen_app = ( $self->{scripts} || $self->{makefile} ) ? 0 : 1;
96
97     if ($gen_app) {
98         $self->_mk_dirs;
99         $self->_mk_config;
100         $self->_mk_appclass;
101         $self->_mk_rootclass;
102         $self->_mk_readme;
103         $self->_mk_changes;
104         $self->_mk_apptest;
105         $self->_mk_images;
106         $self->_mk_favicon;
107     }
108     if ($gen_makefile) {
109         $self->_mk_makefile;
110     }
111     if ($gen_scripts) {
112         $self->_mk_cgi;
113         $self->_mk_fastcgi;
114         $self->_mk_server;
115         $self->_mk_test;
116         $self->_mk_create;
117         $self->_mk_information;
118     }
119     return $self->{dir};
120 }
121
122 sub mk_component {
123     my $self = shift;
124     my $app  = shift;
125     $self->{app} = $app;
126     $self->{author} = $self->{author} = $ENV{'AUTHOR'}
127       || eval { @{ [ getpwuid($<) ] }[6] }
128       || 'A clever guy';
129     $self->{base} ||= File::Spec->catdir( $FindBin::Bin, '..' );
130     unless ( $_[0] =~ /^(?:model|view|controller)$/i ) {
131         my $helper = shift;
132         my @args   = @_;
133         my $class  = "Catalyst::Helper::$helper";
134         eval "require $class";
135
136         if ($@) {
137             Catalyst::Exception->throw(
138                 message => qq/Couldn't load helper "$class", "$@"/ );
139         }
140
141         if ( $class->can('mk_stuff') ) {
142             return 1 unless $class->mk_stuff( $self, @args );
143         }
144     }
145     else {
146         my $type   = shift;
147         my $name   = shift || "Missing name for model/view/controller";
148         my $helper = shift;
149         my @args   = @_;
150        return 0 if $name =~ /[^\w\:]/;
151         $type              = lc $type;
152         $self->{long_type} = ucfirst $type;
153         $type              = 'M' if $type =~ /model/i;
154         $type              = 'V' if $type =~ /view/i;
155         $type              = 'C' if $type =~ /controller/i;
156         my $appdir = File::Spec->catdir( split /\:\:/, $app );
157         my $test_path =
158           File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, 'C' );
159         $type = $self->{long_type} unless -d $test_path;
160         $self->{type}  = $type;
161         $self->{name}  = $name;
162         $self->{class} = "$app\::$type\::$name";
163
164         # Class
165         my $path =
166           File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, $type );
167         my $file = $name;
168         if ( $name =~ /\:/ ) {
169             my @path = split /\:\:/, $name;
170             $file = pop @path;
171             $path = File::Spec->catdir( $path, @path );
172         }
173         $self->mk_dir($path);
174         $file = File::Spec->catfile( $path, "$file.pm" );
175         $self->{file} = $file;
176
177         # Test
178         $self->{test_dir} = File::Spec->catdir( $FindBin::Bin, '..', 't' );
179         $self->{test}     = $self->next_test;
180
181         # Helper
182         if ($helper) {
183             my $comp  = $self->{long_type};
184             my $class = "Catalyst::Helper::$comp\::$helper";
185             eval "require $class";
186
187             if ($@) {
188                 Catalyst::Exception->throw(
189                     message => qq/Couldn't load helper "$class", "$@"/ );
190             }
191
192             if ( $class->can('mk_compclass') ) {
193                 return 1 unless $class->mk_compclass( $self, @args );
194             }
195             else { return 1 unless $self->_mk_compclass }
196
197             if ( $class->can('mk_comptest') ) {
198                 $class->mk_comptest( $self, @args );
199             }
200             else { $self->_mk_comptest }
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::Spec->catfile( $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     $self->render_file_contents($template, $path, $vars);
287 }
288
289 sub render_file_contents {
290     my ( $self, $template, $path, $vars ) = @_;
291     $vars ||= {};
292     my $t = Template->new;
293     return 0 unless $template;
294     my $output;
295     $t->process( \$template, { %{$self}, %$vars }, \$output )
296       || Catalyst::Exception->throw(
297         message => qq/Couldn't process "$template", / . $t->error() );
298     $self->mk_file( $path, $output );
299 }
300
301 sub _mk_information {
302     my $self = shift;
303     print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
304 }
305
306 sub _mk_dirs {
307     my $self = shift;
308     $self->mk_dir( $self->{dir} );
309     $self->mk_dir( $self->{script} );
310     $self->{lib} = File::Spec->catdir( $self->{dir}, 'lib' );
311     $self->mk_dir( $self->{lib} );
312     $self->{root} = File::Spec->catdir( $self->{dir}, 'root' );
313     $self->mk_dir( $self->{root} );
314     $self->{static} = File::Spec->catdir( $self->{root}, 'static' );
315     $self->mk_dir( $self->{static} );
316     $self->{images} = File::Spec->catdir( $self->{static}, 'images' );
317     $self->mk_dir( $self->{images} );
318     $self->{t} = File::Spec->catdir( $self->{dir}, 't' );
319     $self->mk_dir( $self->{t} );
320
321     $self->{class} = File::Spec->catdir( split( /\:\:/, $self->{name} ) );
322     $self->{mod} = File::Spec->catdir( $self->{lib}, $self->{class} );
323     $self->mk_dir( $self->{mod} );
324
325     if ( $self->{short} ) {
326         $self->{m} = File::Spec->catdir( $self->{mod}, 'M' );
327         $self->mk_dir( $self->{m} );
328         $self->{v} = File::Spec->catdir( $self->{mod}, 'V' );
329         $self->mk_dir( $self->{v} );
330         $self->{c} = File::Spec->catdir( $self->{mod}, 'C' );
331         $self->mk_dir( $self->{c} );
332     }
333     else {
334         $self->{m} = File::Spec->catdir( $self->{mod}, 'Model' );
335         $self->mk_dir( $self->{m} );
336         $self->{v} = File::Spec->catdir( $self->{mod}, 'View' );
337         $self->mk_dir( $self->{v} );
338         $self->{c} = File::Spec->catdir( $self->{mod}, 'Controller' );
339         $self->mk_dir( $self->{c} );
340     }
341     my $name = $self->{name};
342     $self->{rootname} =
343       $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
344     $self->{base} = File::Spec->rel2abs( $self->{dir} );
345 }
346
347 sub _mk_appclass {
348     my $self = shift;
349     my $mod  = $self->{mod};
350     $self->render_sharedir_file( File::Spec->catfile('lib', 'MyApp.pm.tt'), "$mod.pm" );
351 }
352
353 sub _mk_rootclass {
354     my $self = shift;
355     $self->render_sharedir_file( File::Spec->catfile('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
356         File::Spec->catfile( $self->{c}, "Root.pm" ) );
357 }
358
359 sub _mk_makefile {
360     my $self = shift;
361     $self->{path} = File::Spec->catfile( 'lib', split( '::', $self->{name} ) );
362     $self->{path} .= '.pm';
363     my $dir = $self->{dir};
364     $self->render_sharedir_file( 'Makefile.PL.tt', "$dir\/Makefile.PL" );
365
366     if ( $self->{makefile} ) {
367
368         # deprecate the old Build.PL file when regenerating Makefile.PL
369         $self->_deprecate_file(
370             File::Spec->catdir( $self->{dir}, 'Build.PL' ) );
371     }
372 }
373
374 sub _mk_config {
375     my $self      = shift;
376     my $dir       = $self->{dir};
377     my $appprefix = $self->{appprefix};
378     $self->render_sharedir_file( 'myapp.conf.tt',
379         File::Spec->catfile( $dir, "$appprefix.conf" ) );
380 }
381
382 sub _mk_readme {
383     my $self = shift;
384     my $dir  = $self->{dir};
385     $self->render_sharedir_file( 'README.tt', "$dir\/README" );
386 }
387
388 sub _mk_changes {
389     my $self = shift;
390     my $dir  = $self->{dir};
391     my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
392     $self->render_sharedir_file( 'Changes.tt', "$dir\/Changes", { time => $time } );
393 }
394
395 sub _mk_apptest {
396     my $self = shift;
397     my $t    = $self->{t};
398     $self->render_sharedir_file( File::Spec->catfile('t', '01app.t.tt'),         "$t\/01app.t" );
399     $self->render_sharedir_file( File::Spec->catfile('t', '02pod.t.tt'),         "$t\/02pod.t" );
400     $self->render_sharedir_file( File::Spec->catfile('t', '03podcoverage.t.tt'), "$t\/03podcoverage.t" );
401 }
402
403 sub _mk_cgi {
404     my $self      = shift;
405     my $script    = $self->{script};
406     my $appprefix = $self->{appprefix};
407     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_cgi.pl.tt'), "$script\/$appprefix\_cgi.pl" );
408     chmod 0700, "$script/$appprefix\_cgi.pl";
409 }
410
411 sub _mk_fastcgi {
412     my $self      = shift;
413     my $script    = $self->{script};
414     my $appprefix = $self->{appprefix};
415     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_fastcgi.pl.tt'), "$script\/$appprefix\_fastcgi.pl" );
416     chmod 0700, "$script/$appprefix\_fastcgi.pl";
417 }
418
419 sub _mk_server {
420     my $self      = shift;
421     my $script    = $self->{script};
422     my $appprefix = $self->{appprefix};
423     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_server.pl.tt'), "$script\/$appprefix\_server.pl" );
424     chmod 0700, "$script/$appprefix\_server.pl";
425 }
426
427 sub _mk_test {
428     my $self      = shift;
429     my $script    = $self->{script};
430     my $appprefix = $self->{appprefix};
431     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_test.pl.tt'), "$script/$appprefix\_test.pl" );
432     chmod 0700, "$script/$appprefix\_test.pl";
433 }
434
435 sub _mk_create {
436     my $self      = shift;
437     my $script    = $self->{script};
438     my $appprefix = $self->{appprefix};
439     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_create.pl.tt'), "$script\/$appprefix\_create.pl" );
440     chmod 0700, "$script/$appprefix\_create.pl";
441 }
442
443 sub _mk_compclass {
444     my $self = shift;
445     my $file = $self->{file};
446     return $self->render_sharedir_file( 'lib', 'Helper', 'compclass.tt', "$file" );
447 }
448
449 sub _mk_comptest {
450     my $self = shift;
451     my $test = $self->{test};
452     $self->render_sharedir_file( 't', 'comptest.tt', "$test" );  ## wtf do i rename this to?
453 }
454
455 sub _mk_images {
456     my $self   = shift;
457     my $images = $self->{images};
458     my @images =
459       qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
460       btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
461       btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
462     for my $name (@images) {
463         my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
464         $self->mk_file( File::Spec->catfile( $images, "$name.png" ), $image );
465     }
466 }
467
468 sub _mk_favicon {
469     my $self    = shift;
470     my $root    = $self->{root};
471     my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
472     my $dest = File::Spec->catfile( $root, "favicon.ico" );
473     $self->mk_file( $dest, $favicon );
474
475 }
476
477 sub _deprecate_file {
478     my ( $self, $file ) = @_;
479     if ( -e $file ) {
480         my $oldcontent;
481         if ( my $f = IO::File->new("< $file") ) {
482             $oldcontent = join( '', (<$f>) );
483         }
484         my $newfile = $file . '.deprecated';
485         if ( my $f = IO::File->new("> $newfile") ) {
486             binmode $f;
487             print $f $oldcontent;
488             print qq/created "$newfile"\n/;
489             unlink $file;
490             print qq/removed "$file"\n/;
491             return 1;
492         }
493         Catalyst::Exception->throw(
494             message => qq/Couldn't create "$file", "$!"/ );
495     }
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. + To override, please export the AUTHOR variable.
583
584 =head1 METHODS
585
586 =head2 mk_compclass
587
588 This method in your Helper module is called with C<$helper>
589 which is a L<Catalyst::Helper> object, and whichever other arguments
590 the user added to the command-line. You can use the $helper to call methods
591 described below.
592
593 If the Helper module does not contain a C<mk_compclass> method, it
594 will fall back to calling L</render_file>, with an argument of
595 C<compclass>.
596
597 =head2 mk_comptest
598
599 This method in your Helper module is called with C<$helper>
600 which is a L<Catalyst::Helper> object, and whichever other arguments
601 the user added to the command-line. You can use the $helper to call methods
602 described below.
603
604 If the Helper module does not contain a C<mk_compclass> method, it
605 will fall back to calling L</render_file>, with an argument of
606 C<comptest>.
607
608 =head2 mk_stuff
609
610 This method is called if the user does not supply any of the usual
611 component types C<view>, C<controller>, C<model>. It is passed the
612 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
613 arguments the user typed.
614
615 There is no fallback for this method.
616
617 =head1 INTERNAL METHODS
618
619 These are the methods that the Helper classes can call on the
620 <$helper> object passed to them.
621
622 =head2 render_file ($file, $path, $vars)
623
624 Render and create a file from a template in DATA using Template
625 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
626 the path to the file and $vars is the hashref as expected by
627 L<Template Toolkit|Template>.
628
629 =head2 get_file ($class, $file)
630
631 Fetch file contents from the DATA section. This is used internally by
632 L</render_file>.  $class is the name of the class to get the DATA
633 section from.  __PACKAGE__ or ( caller(0) )[0] might be sensible
634 values for this.
635
636 =head2 mk_app
637
638 Create the main application skeleton. This is called by L<catalyst.pl>.
639
640 =head2 mk_component ($app)
641
642 This method is called by L<create.pl> to make new components
643 for your application.
644
645 =head3 mk_dir ($path)
646
647 Surprisingly, this function makes a directory.
648
649 =head2 mk_file ($file, $content)
650
651 Writes content to a file. Called by L</render_file>.
652
653 =head2 next_test ($test_name)
654
655 Calculates the name of the next numbered test file and returns it.
656 Don't give the number or the .t suffix for the test name.
657
658 =head2 Dir
659
660 Alias for L<Path::Class::Dir>
661
662 =cut
663
664 =head2 get_sharedir_file
665
666 Method for getting a file out of share/
667
668 =cut
669
670 =head2 render_file_contents
671
672 Process a L<Template::Toolkit> template.
673
674 =cut
675
676 =head2 render_sharedir_file
677
678 Render a template/image file from our share directory
679
680 =cut
681
682
683 =head1 NOTE
684
685 The helpers will read author name from /etc/passwd by default.
686 To override, please export the AUTHOR variable.
687
688 =head1 SEE ALSO
689
690 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
691 L<Catalyst::Response>, L<Catalyst>
692
693 =head1 AUTHORS
694
695 Catalyst Contributors, see Catalyst.pm
696
697 =head1 LICENSE
698
699 This library is free software. You can redistribute it and/or modify
700 it under the same terms as Perl itself.
701
702 =begin pod_to_ignore
703
704 =cut
705
706 1;
707