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