Updated helper
[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( name => '[% name %]' );
414
415 [% name %]->setup;
416
417 sub default : Private {
418     my ( $self, $c ) = @_;
419     $c->res->output('Congratulations, [% name %] is on Catalyst!');
420 }
421
422 =head1 NAME
423
424 [% name %] - A very nice application
425
426 =head1 SYNOPSIS
427
428     Very simple to use
429
430 =head1 DESCRIPTION
431
432 Very nice application.
433
434 =head1 AUTHOR
435
436 [%author%]
437
438 =head1 LICENSE
439
440 This library is free software . You can redistribute it and/or modify 
441 it under the same terms as perl itself.
442
443 =cut
444
445 1;
446
447 __build__
448 use strict;
449 use Catalyst::Build;
450
451 my $build = Catalyst::Build->new(
452     create_makefile_pl => 'passthrough',
453     license            => 'perl',
454     module_name        => '[% name %]',
455     requires           => { Catalyst => '5.04' },
456     script_files       => [ glob('script/*') ],
457     test_files         => [ glob('t/*.t'), glob('t/*/*.t') ]
458 );
459 $build->create_build_script;
460
461 __readme__
462 Run script/[% apprefix %]_server.pl to test the application.
463
464 __changes__
465 This file documents the revision history for Perl extension [% name %].
466
467 0.01  [% time %]
468         - initial revision, generated by Catalyst
469
470 __apptest__
471 use Test::More tests => 2;
472 use_ok( Catalyst::Test, '[% name %]' );
473
474 ok( request('/')->is_success );
475
476 __podtest__
477 use Test::More;
478
479 eval "use Test::Pod 1.14";
480 plan skip_all => 'Test::Pod 1.14 required' if $@;
481 plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
482
483 all_pod_files_ok();
484
485 __podcoveragetest__
486 use Test::More;
487
488 eval "use Test::Pod::Coverage 1.04";
489 plan skip_all => 'Test::Pod::Coverage 1.04 required' if $@;
490 plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
491
492 all_pod_coverage_ok();
493
494 __cgi__
495 [% startperl %] -w
496 BEGIN { $ENV{CATALYST_ENGINE} = 'CGI' }
497
498 use strict;
499 use FindBin;
500 use lib "$FindBin::Bin/../lib";
501 use [% name %];
502
503 [% name %]->run;
504
505 1;
506
507 =head1 NAME
508
509 cgi - Catalyst CGI
510
511 =head1 SYNOPSIS
512
513 See L<Catalyst::Manual>
514
515 =head1 DESCRIPTION
516
517 Run a Catalyst application as cgi.
518
519 =head1 AUTHOR
520
521 Sebastian Riedel, C<sri@oook.de>
522
523 =head1 COPYRIGHT
524
525 Copyright 2004 Sebastian Riedel. All rights reserved.
526
527 This library is free software. You can redistribute it and/or modify 
528 it under the same terms as perl itself.
529
530 =cut
531
532 __fcgi__
533 [% startperl %] -w
534
535 BEGIN { $ENV{CATALYST_ENGINE} = 'FCGI' }
536
537 use strict;
538 use FindBin;
539 use lib "$FindBin::Bin/../lib";
540 use [% name %];
541
542 [% name %]->run;
543
544 1;
545
546 =head1 NAME
547
548 fcgi - Catalyst FCGI
549
550 =head1 SYNOPSIS
551
552 See L<Catalyst::Manual>
553
554 =head1 DESCRIPTION
555
556 Run a Catalyst application as fcgi.
557
558 =head1 AUTHOR
559
560 Sebastian Riedel, C<sri@oook.de>
561
562 =head1 COPYRIGHT
563
564 Copyright 2004 Sebastian Riedel. All rights reserved.
565
566 This library is free software. You can redistribute it and/or modify 
567 it under the same terms as perl itself.
568
569 =cut
570
571 __server__
572 [% startperl %] -w
573
574 BEGIN { 
575     $ENV{CATALYST_ENGINE} = 'HTTP';
576     $ENV{CATALYST_SCRIPT_GEN} = [% scriptgen %];
577 }  
578
579 use strict;
580 use Getopt::Long;
581 use Pod::Usage;
582 use FindBin;
583 use lib "$FindBin::Bin/../lib";
584 use [% name %];
585
586 my $help = 0;
587 my $port = 3000;
588
589 GetOptions( 'help|?' => \$help, 'port=s' => \$port );
590
591 pod2usage(1) if $help;
592
593 [% name %]->run($port);
594
595 1;
596
597 =head1 NAME
598
599 server - Catalyst Testserver
600
601 =head1 SYNOPSIS
602
603 server.pl [options]
604
605  Options:
606    -? -help    display this help and exits
607    -p -port    port (defaults to 3000)
608
609  See also:
610    perldoc Catalyst::Manual
611    perldoc Catalyst::Manual::Intro
612
613 =head1 DESCRIPTION
614
615 Run a Catalyst Testserver for this application.
616
617 =head1 AUTHOR
618
619 Sebastian Riedel, C<sri@oook.de>
620
621 =head1 COPYRIGHT
622
623 Copyright 2004 Sebastian Riedel. All rights reserved.
624
625 This library is free software. You can redistribute it and/or modify 
626 it under the same terms as perl itself.
627
628 =cut
629
630 __test__
631 [% startperl %] -w
632
633 BEGIN { $ENV{CATALYST_ENGINE} = 'Test' }
634
635 use strict;
636 use Getopt::Long;
637 use Pod::Usage;
638 use FindBin;
639 use lib "$FindBin::Bin/../lib";
640 use [% name %];
641
642 my $help = 0;
643
644 GetOptions( 'help|?' => \$help );
645
646 pod2usage(1) if ( $help || !$ARGV[0] );
647
648 print [% name %]->run($ARGV[0])->content . "\n";
649
650 1;
651
652 =head1 NAME
653
654 test - Catalyst Test
655
656 =head1 SYNOPSIS
657
658 test.pl [options] uri
659
660  Options:
661    -help    display this help and exits
662
663  Examples:
664    test.pl http://localhost/some_action
665    test.pl /some_action
666
667  See also:
668    perldoc Catalyst::Manual
669    perldoc Catalyst::Manual::Intro
670
671 =head1 DESCRIPTION
672
673 Run a Catalyst action from the comand line.
674
675 =head1 AUTHOR
676
677 Sebastian Riedel, C<sri@oook.de>
678
679 =head1 COPYRIGHT
680
681 Copyright 2004 Sebastian Riedel. All rights reserved.
682
683 This library is free software. You can redistribute it and/or modify 
684 it under the same terms as perl itself.
685
686 =cut
687
688 __create__
689 [% startperl %] -w
690
691 use strict;
692 use Getopt::Long;
693 use Pod::Usage;
694 use Catalyst::Helper;
695
696 my $help = 0;
697
698 GetOptions( 'help|?' => \$help );
699
700 pod2usage(1) if ( $help || !$ARGV[0] );
701
702 my $helper = Catalyst::Helper->new;
703 pod2usage(1) unless $helper->mk_component( '[% name %]', @ARGV );
704
705 1;
706
707 =head1 NAME
708
709 create - Create a new Catalyst Component
710
711 =head1 SYNOPSIS
712
713 create.pl [options] model|view|controller name [helper] [options]
714
715  Options:
716    -help    display this help and exits
717
718  Examples:
719    create.pl controller My::Controller
720    create.pl view My::View
721    create.pl view MyView TT
722    create.pl view TT TT
723    create.pl model My::Model
724    create.pl model SomeDB CDBI dbi:SQLite:/tmp/my.db
725    create.pl model AnotherDB CDBI dbi:Pg:dbname=foo root 4321
726    create.pl Ajax
727
728  See also:
729    perldoc Catalyst::Manual
730    perldoc Catalyst::Manual::Intro
731
732 =head1 DESCRIPTION
733
734 Create a new Catalyst Component.
735
736 =head1 AUTHOR
737
738 Sebastian Riedel, C<sri\@oook.de>
739
740 =head1 COPYRIGHT
741
742 Copyright 2004 Sebastian Riedel. All rights reserved.
743
744 This library is free software. You can redistribute it and/or modify 
745 it under the same terms as perl itself.
746
747 =cut
748
749 __compclass__
750 package [% class %];
751
752 use strict;
753 use base 'Catalyst::Base';
754
755 [% IF type == 'C' %]
756 sub default : Private {
757     my ( $self, $c ) = @_;
758     $c->res->output('Congratulations, [% class %] is on Catalyst!');
759 }
760
761 [% END %]
762 =head1 NAME
763
764 [% class %] - A Component
765
766 =head1 SYNOPSIS
767
768     Very simple to use
769
770 =head1 DESCRIPTION
771
772 Very nice component.
773
774 =head1 AUTHOR
775
776 [%author%]
777
778 =head1 LICENSE
779
780 This library is free software . You can redistribute it and/or modify 
781 it under the same terms as perl itself.
782
783 =cut
784
785 1;
786
787 __comptest__
788 [% IF type == 'C' %]
789 use Test::More tests => 3;
790 use_ok( Catalyst::Test, '[% app %]' );
791 use_ok('[% class %]');
792
793 ok( request('[% prefix %]')->is_success );
794 [% ELSE %]
795 use Test::More tests => 1;
796 use_ok('[% class %]');
797 [% END %]