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