Big change, new installer and home detection
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Helper.pm
1 package Catalyst::Helper;
2
3 use strict;
4 use base 'Class::Accessor::Fast';
5 use Config;
6 use File::Spec;
7 use File::Path;
8 use IO::File;
9 use FindBin;
10 use Template;
11 use Catalyst;
12
13 my %cache;
14
15 =head1 NAME
16
17 Catalyst::Helper - Bootstrap a Catalyst application
18
19 =head1 SYNOPSIS
20
21 See L<Catalyst::Manual::Intro>
22
23 =head1 DESCRIPTION
24
25 Bootstrap a Catalyst application. Autogenerates scripts
26
27 =head2 METHODS
28
29 =head3 get_file
30
31 Slurp file from DATA.
32
33 =cut
34
35 sub get_file {
36     my ( $self, $class, $file ) = @_;
37     unless ( $cache{$class} ) {
38         local $/;
39         $cache{$class} = eval "package $class; <DATA>";
40     }
41     my $data = $cache{$class};
42     my @files = split /^__(.+)__\n/m, $data;
43     shift @files;
44     while (@files) {
45         my ( $name, $content ) = splice @files, 0, 2;
46         return $content if $name eq $file;
47     }
48     return 0;
49 }
50
51 =head3 mk_app
52
53 Create the main application skeleton.
54
55 =cut
56
57 sub mk_app {
58     my ( $self, $name ) = @_;
59     return 0 if $name =~ /[^\w\:]/;
60     $self->{name} = $name;
61     $self->{dir}  = $name;
62     $self->{dir} =~ s/\:\:/-/g;
63     $self->{appprefix} = lc $self->{dir};
64     $self->{appprefix} =~ s/-/_/g;
65     $self->{startperl} = $Config{startperl};
66     $self->{scriptgen} = $Catalyst::CATALYST_SCRIPT_GEN;
67     $self->{author}    = $self->{author} = $ENV{'AUTHOR'}
68       || @{ [ getpwuid($<) ] }[6];
69     $self->_mk_dirs;
70     $self->_mk_appclass;
71     $self->_mk_build;
72     $self->_mk_makefile;
73     $self->_mk_readme;
74     $self->_mk_changes;
75     $self->_mk_apptest;
76     $self->_mk_cgi;
77     $self->_mk_fcgi;
78     $self->_mk_server;
79     $self->_mk_test;
80     $self->_mk_create;
81     return 1;
82 }
83
84 =head3 mk_component
85
86 This method is called by create.pl to make new components
87 for your application.
88
89 =cut
90
91 sub mk_component {
92     my $self = shift;
93     my $app  = shift;
94     $self->{app} = $app;
95     $self->{author} = $self->{author} = $ENV{'AUTHOR'}
96       || @{ [ getpwuid($<) ] }[6];
97     $self->{base} = File::Spec->catdir( $FindBin::Bin, '..' );
98     unless ( $_[0] =~ /^model|m|view|v|controller|c\$/i ) {
99         my $helper = shift;
100         my @args   = @_;
101         my $class  = "Catalyst::Helper::$helper";
102         eval "require $class";
103         die qq/Couldn't load helper "$class", "$@"/ if $@;
104         if ( $class->can('mk_stuff') ) {
105             return 1 unless $class->mk_stuff( $self, @args );
106         }
107     }
108     else {
109         my $type   = shift;
110         my $name   = shift;
111         my $helper = shift;
112         my @args   = @_;
113         return 0 if $name =~ /[^\w\:]/;
114         $type = 'M' if $type =~ /model|m/i;
115         $type = 'V' if $type =~ /view|v/i;
116         $type = 'C' if $type =~ /controller|c/i;
117         $self->{type}  = $type;
118         $self->{name}  = $name;
119         $self->{class} = "$app\::$type\::$name";
120
121         # Class
122         my $appdir = File::Spec->catdir( split /\:\:/, $app );
123         my $path =
124           File::Spec->catdir( $FindBin::Bin, '..', 'lib', $appdir, $type );
125         my $file = $name;
126         if ( $name =~ /\:/ ) {
127             my @path = split /\:\:/, $name;
128             $file = pop @path;
129             $path = File::Spec->catdir( $path, @path );
130             mkpath $path;
131         }
132         $file = File::Spec->catfile( $path, "$file.pm" );
133         $self->{file} = $file;
134
135         # Test
136         $self->{test_dir} = File::Spec->catdir( $FindBin::Bin, '..', 't' );
137         $self->{test}     = $self->next_test;
138
139         # Helper
140         if ($helper) {
141             my $comp = 'Model';
142             $comp = 'View'       if $type eq 'V';
143             $comp = 'Controller' if $type eq 'C';
144             my $class = "Catalyst::Helper::$comp\::$helper";
145             eval "require $class";
146             die qq/Couldn't load helper "$class", "$@"/ if $@;
147             if ( $class->can('mk_compclass') ) {
148                 return 1 unless $class->mk_compclass( $self, @args );
149             }
150             else { return 1 unless $self->_mk_compclass }
151
152             if ( $class->can('mk_comptest') ) {
153                 $class->mk_comptest( $self, @args );
154             }
155             else { $self->_mk_comptest }
156         }
157
158         # Fallback
159         else {
160             return 1 unless $self->_mk_compclass;
161             $self->_mk_comptest;
162         }
163     }
164     return 1;
165 }
166
167 =head3 mk_dir
168
169 Surprisingly, this function makes a directory.
170
171 =cut
172
173 sub mk_dir {
174     my ( $self, $dir ) = @_;
175     if ( -d $dir ) {
176         print qq/ exists "$dir"\n/;
177         return 0;
178     }
179     if ( mkpath $dir) {
180         print qq/created "$dir"\n/;
181         return 1;
182     }
183     die qq/Couldn't create "$dir", "$!"/;
184 }
185
186 =head3 mk_file
187
188 writes content to a file.
189
190 =cut
191
192 sub mk_file {
193     my ( $self, $file, $content ) = @_;
194     if ( -e $file ) {
195         print qq/ exists "$file"\n/;
196         return 0;
197     }
198     if ( my $f = IO::File->new("> $file") ) {
199         print $f $content;
200         print qq/created "$file"\n/;
201         return 1;
202     }
203     die qq/Couldn't create "$file", "$!"/;
204 }
205
206 =head3 next_test
207
208 =cut
209
210 sub next_test {
211     my ( $self, $tname ) = @_;
212     if ($tname) { $tname = "$tname.t" }
213     else {
214         my $name   = $self->{name};
215         my $prefix = $name;
216         $prefix =~ s/::/_/g;
217         $prefix         = lc $prefix;
218         $tname          = $prefix . '.t';
219         $self->{prefix} = $prefix;
220     }
221     my $dir  = $self->{test_dir};
222     my $type = lc $self->{type};
223     return File::Spec->catfile( $dir, $type, $tname );
224 }
225
226 =head3 render_file
227
228 Render and create a file from a template in DATA using 
229 Template Toolkit.
230
231 =cut
232
233 sub render_file {
234     my ( $self, $file, $path, $vars ) = @_;
235     $vars ||= {};
236     my $t = Template->new;
237     my $template = $self->get_file( ( caller(0) )[0], $file );
238     return 0 unless $template;
239     my $output;
240     $t->process( \$template, { %{$self}, %$vars }, \$output );
241     $self->mk_file( $path, $output );
242 }
243
244 sub _mk_dirs {
245     my $self = shift;
246     $self->mk_dir( $self->{dir} );
247     $self->{script} = File::Spec->catdir( $self->{dir}, 'script' );
248     $self->mk_dir( $self->{script} );
249     $self->{lib} = File::Spec->catdir( $self->{dir}, 'lib' );
250     $self->mk_dir( $self->{lib} );
251     $self->{root} = File::Spec->catdir( $self->{dir}, 'root' );
252     $self->mk_dir( $self->{root} );
253     $self->{t} = File::Spec->catdir( $self->{dir}, 't' );
254     $self->mk_dir( $self->{t} );
255     $self->mk_dir( File::Spec->catdir( $self->{t}, 'm' ) );
256     $self->mk_dir( File::Spec->catdir( $self->{t}, 'v' ) );
257     $self->mk_dir( File::Spec->catdir( $self->{t}, 'c' ) );
258     $self->{class} = File::Spec->catdir( split( /\:\:/, $self->{name} ) );
259     $self->{mod} = File::Spec->catdir( $self->{lib}, $self->{class} );
260     $self->mk_dir( $self->{mod} );
261     $self->{m} = File::Spec->catdir( $self->{mod}, 'M' );
262     $self->mk_dir( $self->{m} );
263     $self->{v} = File::Spec->catdir( $self->{mod}, 'V' );
264     $self->mk_dir( $self->{v} );
265     $self->{c} = File::Spec->catdir( $self->{mod}, 'C' );
266     $self->mk_dir( $self->{c} );
267     $self->{base} = File::Spec->rel2abs( $self->{dir} );
268 }
269
270 sub _mk_appclass {
271     my $self = shift;
272     my $mod  = $self->{mod};
273     $self->render_file( 'appclass', "$mod.pm" );
274 }
275
276 sub _mk_build {
277     my $self = shift;
278     my $dir  = $self->{dir};
279     $self->render_file( 'build', "$dir\/Build.PL" );
280 }
281
282 sub _mk_makefile {
283     my $self = shift;
284     my $dir  = $self->{dir};
285     $self->render_file( 'makefile', "$dir\/Makefile.PL" );
286 }
287
288 sub _mk_readme {
289     my $self = shift;
290     my $dir  = $self->{dir};
291     $self->render_file( 'readme', "$dir\/README" );
292 }
293
294 sub _mk_changes {
295     my $self = shift;
296     my $dir  = $self->{dir};
297     my $time = localtime time;
298     $self->render_file( 'changes', "$dir\/Changes", { time => $time } );
299 }
300
301 sub _mk_apptest {
302     my $self = shift;
303     my $t    = $self->{t};
304     $self->render_file( 'apptest',         "$t\/01app.t" );
305     $self->render_file( 'podtest',         "$t\/02pod.t" );
306     $self->render_file( 'podcoveragetest', "$t\/03podcoverage.t" );
307 }
308
309 sub _mk_cgi {
310     my $self   = shift;
311     my $script = $self->{script};
312     my $appprefix = $self->{appprefix};
313     $self->render_file( 'cgi', "$script\/$appprefix\_cgi.pl" );
314     chmod 0700, "$script/$appprefix\_cgi.pl";
315 }
316
317 sub _mk_fcgi {
318     my $self   = shift;
319     my $script = $self->{script};
320     my $appprefix = $self->{appprefix};
321     $self->render_file( 'fcgi', "$script\/$appprefix\_fcgi.pl" );
322     chmod 0700, "$script/$appprefix\_fcgi.pl";
323 }
324
325 sub _mk_server {
326     my $self   = shift;
327     my $script = $self->{script};
328     my $appprefix = $self->{appprefix};
329     $self->render_file( 'server', "$script\/$appprefix\_server.pl" );
330     chmod 0700, "$script/$appprefix\_server.pl";
331 }
332
333 sub _mk_test {
334     my $self   = shift;
335     my $script = $self->{script};
336     my $appprefix = $self->{appprefix};
337     $self->render_file( 'test', "$script/$appprefix\_test.pl" );
338     chmod 0700, "$script/$appprefix\_test.pl";
339 }
340
341 sub _mk_create {
342     my $self   = shift;
343     my $script = $self->{script};
344     my $appprefix = $self->{appprefix};
345     $self->render_file( 'create', "$script\/$appprefix\_create.pl" );
346     chmod 0700, "$script/$appprefix\_create.pl";
347 }
348
349 sub _mk_compclass {
350     my $self = shift;
351     my $file = $self->{file};
352     return $self->render_file( 'compclass', "$file" );
353 }
354
355 sub _mk_comptest {
356     my $self = shift;
357     my $test = $self->{test};
358     $self->render_file( 'comptest', "$test" );
359 }
360
361 =head1 HELPERS
362
363 Helpers are classes that provide two methods.
364
365     * mk_compclass - creates the Component class
366     * mk_comptest  - creates the Component test
367
368 So when you call C<bin/create view MyView TT>, create would try to execute
369 Catalyst::Helper::View::TT->mk_compclass and
370 Catalyst::Helper::View::TT->mk_comptest.
371
372 See L<Catalyst::Helper::View::TT> and L<Catalyst::Helper::Model::CDBI> for
373 examples.
374
375 All helper classes should be under one of the following namespaces.
376
377     Catalyst::Helper::Model::
378     Catalyst::Helper::View::
379     Catalyst::Helper::Controller::
380
381 =head1 NOTE
382
383 The helpers will read author name from /etc/passwd by default.
384 To override, please export the AUTHOR variable.
385
386 =head1 SEE ALSO
387
388 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
389 L<Catalyst::Response>, L<Catalyst>
390
391 =head1 AUTHOR
392
393 Sebastian Riedel, C<sri@oook.de>
394
395 =head1 LICENSE
396
397 This library is free software . You can redistribute it and/or modify 
398 it under the same terms as perl itself.
399
400 =cut
401
402 1;
403 __DATA__
404
405 __appclass__
406 package [% name %];
407
408 use strict;
409 use Catalyst qw/-Debug/;
410
411 our $VERSION = '0.01';
412
413 [% name %]->config(
414     name => '[% name %]',
415     root => '[% base %]/root',
416 );
417
418 [% name %]->setup;
419
420 sub default : Private {
421     my ( $self, $c ) = @_;
422     $c->res->output('Congratulations, [% name %] is on Catalyst!');
423 }
424
425 =head1 NAME
426
427 [% name %] - A very nice application
428
429 =head1 SYNOPSIS
430
431     Very simple to use
432
433 =head1 DESCRIPTION
434
435 Very nice application.
436
437 =head1 AUTHOR
438
439 [%author%]
440
441 =head1 LICENSE
442
443 This library is free software . You can redistribute it and/or modify 
444 it under the same terms as perl itself.
445
446 =cut
447
448 1;
449
450 __build__
451 use strict;
452 use Catalyst::Build;
453
454 my $build = Catalyst::Build->new(
455     create_makefile_pl => 'passthrough',
456     license            => 'perl',
457     module_name        => '[% name %]',
458     requires           => { Catalyst => '5.04' },
459     script_files       => [ glob('script/*') ],
460     test_files         => [ glob('t/*.t'), glob('t/*/*.t') ]
461 );
462 $build->create_build_script;
463
464 __readme__
465 Run script/[% apprefix %]_server.pl to test the application.
466
467 __changes__
468 This file documents the revision history for Perl extension [% name %].
469
470 0.01  [% time %]
471         - initial revision, generated by Catalyst
472
473 __apptest__
474 use Test::More tests => 2;
475 use_ok( Catalyst::Test, '[% name %]' );
476
477 ok( request('/')->is_success );
478
479 __podtest__
480 use Test::More;
481
482 eval "use Test::Pod 1.14";
483 plan skip_all => 'Test::Pod 1.14 required' if $@;
484 plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
485
486 all_pod_files_ok();
487
488 __podcoveragetest__
489 use Test::More;
490
491 eval "use Test::Pod::Coverage 1.04";
492 plan skip_all => 'Test::Pod::Coverage 1.04 required' if $@;
493 plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
494
495 all_pod_coverage_ok();
496
497 __cgi__
498 [% startperl %] -w
499 BEGIN { $ENV{CATALYST_ENGINE} = 'CGI' }
500
501 use strict;
502 use FindBin;
503 use lib "$FindBin::Bin/../lib";
504 use [% name %];
505
506 [% name %]->run;
507
508 1;
509
510 =head1 NAME
511
512 cgi - Catalyst CGI
513
514 =head1 SYNOPSIS
515
516 See L<Catalyst::Manual>
517
518 =head1 DESCRIPTION
519
520 Run a Catalyst application as cgi.
521
522 =head1 AUTHOR
523
524 Sebastian Riedel, C<sri@oook.de>
525
526 =head1 COPYRIGHT
527
528 Copyright 2004 Sebastian Riedel. All rights reserved.
529
530 This library is free software. You can redistribute it and/or modify 
531 it under the same terms as perl itself.
532
533 =cut
534
535 __fcgi__
536 [% startperl %] -w
537
538 BEGIN { $ENV{CATALYST_ENGINE} = 'FCGI' }
539
540 use strict;
541 use FindBin;
542 use lib "$FindBin::Bin/../lib";
543 use [% name %];
544
545 [% name %]->run;
546
547 1;
548
549 =head1 NAME
550
551 fcgi - Catalyst FCGI
552
553 =head1 SYNOPSIS
554
555 See L<Catalyst::Manual>
556
557 =head1 DESCRIPTION
558
559 Run a Catalyst application as fcgi.
560
561 =head1 AUTHOR
562
563 Sebastian Riedel, C<sri@oook.de>
564
565 =head1 COPYRIGHT
566
567 Copyright 2004 Sebastian Riedel. All rights reserved.
568
569 This library is free software. You can redistribute it and/or modify 
570 it under the same terms as perl itself.
571
572 =cut
573
574 __server__
575 [% startperl %] -w
576
577 BEGIN { 
578     $ENV{CATALYST_ENGINE} = 'HTTP';
579     $ENV{CATALYST_SCRIPT_GEN} = [% scriptgen %];
580 }  
581
582 use strict;
583 use Getopt::Long;
584 use Pod::Usage;
585 use FindBin;
586 use lib "$FindBin::Bin/../lib";
587 use [% name %];
588
589 my $help = 0;
590 my $port = 3000;
591
592 GetOptions( 'help|?' => \$help, 'port=s' => \$port );
593
594 pod2usage(1) if $help;
595
596 [% name %]->run($port);
597
598 1;
599
600 =head1 NAME
601
602 server - Catalyst Testserver
603
604 =head1 SYNOPSIS
605
606 server.pl [options]
607
608  Options:
609    -? -help    display this help and exits
610    -p -port    port (defaults to 3000)
611
612  See also:
613    perldoc Catalyst::Manual
614    perldoc Catalyst::Manual::Intro
615
616 =head1 DESCRIPTION
617
618 Run a Catalyst Testserver for this application.
619
620 =head1 AUTHOR
621
622 Sebastian Riedel, C<sri@oook.de>
623
624 =head1 COPYRIGHT
625
626 Copyright 2004 Sebastian Riedel. All rights reserved.
627
628 This library is free software. You can redistribute it and/or modify 
629 it under the same terms as perl itself.
630
631 =cut
632
633 __test__
634 [% startperl %] -w
635
636 BEGIN { $ENV{CATALYST_ENGINE} = 'Test' }
637
638 use strict;
639 use Getopt::Long;
640 use Pod::Usage;
641 use FindBin;
642 use lib "$FindBin::Bin/../lib";
643 use [% name %];
644
645 my $help = 0;
646
647 GetOptions( 'help|?' => \$help );
648
649 pod2usage(1) if ( $help || !$ARGV[0] );
650
651 print [% name %]->run($ARGV[0])->content . "\n";
652
653 1;
654
655 =head1 NAME
656
657 test - Catalyst Test
658
659 =head1 SYNOPSIS
660
661 test.pl [options] uri
662
663  Options:
664    -help    display this help and exits
665
666  Examples:
667    test.pl http://localhost/some_action
668    test.pl /some_action
669
670  See also:
671    perldoc Catalyst::Manual
672    perldoc Catalyst::Manual::Intro
673
674 =head1 DESCRIPTION
675
676 Run a Catalyst action from the comand line.
677
678 =head1 AUTHOR
679
680 Sebastian Riedel, C<sri@oook.de>
681
682 =head1 COPYRIGHT
683
684 Copyright 2004 Sebastian Riedel. All rights reserved.
685
686 This library is free software. You can redistribute it and/or modify 
687 it under the same terms as perl itself.
688
689 =cut
690
691 __create__
692 [% startperl %] -w
693
694 use strict;
695 use Getopt::Long;
696 use Pod::Usage;
697 use Catalyst::Helper;
698
699 my $help = 0;
700
701 GetOptions( 'help|?' => \$help );
702
703 pod2usage(1) if ( $help || !$ARGV[0] );
704
705 my $helper = Catalyst::Helper->new;
706 pod2usage(1) unless $helper->mk_component( '[% name %]', @ARGV );
707
708 1;
709
710 =head1 NAME
711
712 create - Create a new Catalyst Component
713
714 =head1 SYNOPSIS
715
716 create.pl [options] model|view|controller name [helper] [options]
717
718  Options:
719    -help    display this help and exits
720
721  Examples:
722    create.pl controller My::Controller
723    create.pl view My::View
724    create.pl view MyView TT
725    create.pl view TT TT
726    create.pl model My::Model
727    create.pl model SomeDB CDBI dbi:SQLite:/tmp/my.db
728    create.pl model AnotherDB CDBI dbi:Pg:dbname=foo root 4321
729    create.pl Ajax
730
731  See also:
732    perldoc Catalyst::Manual
733    perldoc Catalyst::Manual::Intro
734
735 =head1 DESCRIPTION
736
737 Create a new Catalyst Component.
738
739 =head1 AUTHOR
740
741 Sebastian Riedel, C<sri\@oook.de>
742
743 =head1 COPYRIGHT
744
745 Copyright 2004 Sebastian Riedel. All rights reserved.
746
747 This library is free software. You can redistribute it and/or modify 
748 it under the same terms as perl itself.
749
750 =cut
751
752 __compclass__
753 package [% class %];
754
755 use strict;
756 use base 'Catalyst::Base';
757
758 [% IF type == 'C' %]
759 sub default : Private {
760     my ( $self, $c ) = @_;
761     $c->res->output('Congratulations, [% class %] is on Catalyst!');
762 }
763
764 [% END %]
765 =head1 NAME
766
767 [% class %] - A Component
768
769 =head1 SYNOPSIS
770
771     Very simple to use
772
773 =head1 DESCRIPTION
774
775 Very nice component.
776
777 =head1 AUTHOR
778
779 [%author%]
780
781 =head1 LICENSE
782
783 This library is free software . You can redistribute it and/or modify 
784 it under the same terms as perl itself.
785
786 =cut
787
788 1;
789
790 __comptest__
791 [% IF type == 'C' %]
792 use Test::More tests => 3;
793 use_ok( Catalyst::Test, '[% app %]' );
794 use_ok('[% class %]');
795
796 ok( request('[% prefix %]')->is_success );
797 [% ELSE %]
798 use Test::More tests => 1;
799 use_ok('[% class %]');
800 [% END %]