ed9ea20bf3bfe8ec95177381d49f9c0329bf5131
[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     if ( my $f = IO::File->new("> $file") ) {
243         binmode $f;
244         print $f $content;
245         print qq/created "$file"\n/;
246         return 1;
247     }
248
249     Catalyst::Exception->throw( message => qq/Couldn't create "$file", "$!"/ );
250 }
251
252 sub next_test {
253     my ( $self, $tname ) = @_;
254     if ($tname) { $tname = "$tname.t" }
255     else {
256         my $name   = $self->{name};
257         my $prefix = $name;
258         $prefix =~ s/::/-/g;
259         $prefix         = $prefix;
260         $tname          = $prefix . '.t';
261         $self->{prefix} = $prefix;
262         $prefix         = lc $prefix;
263         $prefix =~ s/-/\//g;
264         $self->{uri} = "/$prefix";
265     }
266     my $dir  = $self->{test_dir};
267     my $type = lc $self->{type};
268     $self->mk_dir($dir);
269     return File::Spec->catfile( $dir, "$type\_$tname" );
270 }
271
272 # Do not touch this method, *EVER*, it is needed for back compat.
273 ## addendum: we had to split this method so we could have backwards
274 ## compatability.  otherwise, we'd have no way to pass stuff from __DATA__
275
276 sub render_file {
277     my ( $self, $file, $path, $vars ) = @_;
278     my $template = $self->get_file( ( caller(0) )[0], $file );
279     $self->render_file_contents($template, $path, $vars);
280 }
281
282 sub render_sharedir_file {
283     my ( $self, $file, $path, $vars ) = @_;
284     my $template = $self->get_sharedir_file( $file );
285     $self->render_file_contents($template, $path, $vars);
286 }
287
288 sub render_file_contents {
289     my ( $self, $template, $path, $vars ) = @_;
290     $vars ||= {};
291     my $t = Template->new;
292     return 0 unless $template;
293     my $output;
294     $t->process( \$template, { %{$self}, %$vars }, \$output )
295       || Catalyst::Exception->throw(
296         message => qq/Couldn't process "$template", / . $t->error() );
297     $self->mk_file( $path, $output );
298 }
299
300 sub _mk_information {
301     my $self = shift;
302     print qq/Change to application directory and Run "perl Makefile.PL" to make sure your install is complete\n/;
303 }
304
305 sub _mk_dirs {
306     my $self = shift;
307     $self->mk_dir( $self->{dir} );
308     $self->mk_dir( $self->{script} );
309     $self->{lib} = File::Spec->catdir( $self->{dir}, 'lib' );
310     $self->mk_dir( $self->{lib} );
311     $self->{root} = File::Spec->catdir( $self->{dir}, 'root' );
312     $self->mk_dir( $self->{root} );
313     $self->{static} = File::Spec->catdir( $self->{root}, 'static' );
314     $self->mk_dir( $self->{static} );
315     $self->{images} = File::Spec->catdir( $self->{static}, 'images' );
316     $self->mk_dir( $self->{images} );
317     $self->{t} = File::Spec->catdir( $self->{dir}, 't' );
318     $self->mk_dir( $self->{t} );
319
320     $self->{class} = File::Spec->catdir( split( /\:\:/, $self->{name} ) );
321     $self->{mod} = File::Spec->catdir( $self->{lib}, $self->{class} );
322     $self->mk_dir( $self->{mod} );
323
324     if ( $self->{short} ) {
325         $self->{m} = File::Spec->catdir( $self->{mod}, 'M' );
326         $self->mk_dir( $self->{m} );
327         $self->{v} = File::Spec->catdir( $self->{mod}, 'V' );
328         $self->mk_dir( $self->{v} );
329         $self->{c} = File::Spec->catdir( $self->{mod}, 'C' );
330         $self->mk_dir( $self->{c} );
331     }
332     else {
333         $self->{m} = File::Spec->catdir( $self->{mod}, 'Model' );
334         $self->mk_dir( $self->{m} );
335         $self->{v} = File::Spec->catdir( $self->{mod}, 'View' );
336         $self->mk_dir( $self->{v} );
337         $self->{c} = File::Spec->catdir( $self->{mod}, 'Controller' );
338         $self->mk_dir( $self->{c} );
339     }
340     my $name = $self->{name};
341     $self->{rootname} =
342       $self->{short} ? "$name\::C::Root" : "$name\::Controller::Root";
343     $self->{base} = File::Spec->rel2abs( $self->{dir} );
344 }
345
346 sub _mk_appclass {
347     my $self = shift;
348     my $mod  = $self->{mod};
349     $self->render_sharedir_file( File::Spec->catfile('lib', 'MyApp.pm.tt'), "$mod.pm" );
350 }
351
352 sub _mk_rootclass {
353     my $self = shift;
354     $self->render_sharedir_file( File::Spec->catfile('lib', 'MyApp', 'Controller', 'Root.pm.tt'),
355         File::Spec->catfile( $self->{c}, "Root.pm" ) );
356 }
357
358 sub _mk_makefile {
359     my $self = shift;
360     $self->{path} = File::Spec->catfile( 'lib', split( '::', $self->{name} ) );
361     $self->{path} .= '.pm';
362     my $dir = $self->{dir};
363     $self->render_sharedir_file( 'Makefile.PL.tt', "$dir\/Makefile.PL" );
364
365     if ( $self->{makefile} ) {
366
367         # deprecate the old Build.PL file when regenerating Makefile.PL
368         $self->_deprecate_file(
369             File::Spec->catdir( $self->{dir}, 'Build.PL' ) );
370     }
371 }
372
373 sub _mk_config {
374     my $self      = shift;
375     my $dir       = $self->{dir};
376     my $appprefix = $self->{appprefix};
377     $self->render_sharedir_file( 'myapp.conf.tt',
378         File::Spec->catfile( $dir, "$appprefix.conf" ) );
379 }
380
381 sub _mk_readme {
382     my $self = shift;
383     my $dir  = $self->{dir};
384     $self->render_sharedir_file( 'README.tt', "$dir\/README" );
385 }
386
387 sub _mk_changes {
388     my $self = shift;
389     my $dir  = $self->{dir};
390     my $time = strftime('%Y-%m-%d %H:%M:%S', localtime time);
391     $self->render_sharedir_file( 'Changes.tt', "$dir\/Changes", { time => $time } );
392 }
393
394 sub _mk_apptest {
395     my $self = shift;
396     my $t    = $self->{t};
397     $self->render_sharedir_file( File::Spec->catfile('t', '01app.t.tt'),         "$t\/01app.t" );
398     $self->render_sharedir_file( File::Spec->catfile('t', '02pod.t.tt'),         "$t\/02pod.t" );
399     $self->render_sharedir_file( File::Spec->catfile('t', '03podcoverage.t.tt'), "$t\/03podcoverage.t" );
400 }
401
402 sub _mk_cgi {
403     my $self      = shift;
404     my $script    = $self->{script};
405     my $appprefix = $self->{appprefix};
406     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_cgi.pl.tt'), "$script\/$appprefix\_cgi.pl" );
407     chmod 0700, "$script/$appprefix\_cgi.pl";
408 }
409
410 sub _mk_fastcgi {
411     my $self      = shift;
412     my $script    = $self->{script};
413     my $appprefix = $self->{appprefix};
414     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_fastcgi.pl.tt'), "$script\/$appprefix\_fastcgi.pl" );
415     chmod 0700, "$script/$appprefix\_fastcgi.pl";
416 }
417
418 sub _mk_server {
419     my $self      = shift;
420     my $script    = $self->{script};
421     my $appprefix = $self->{appprefix};
422     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_server.pl.tt'), "$script\/$appprefix\_server.pl" );
423     chmod 0700, "$script/$appprefix\_server.pl";
424 }
425
426 sub _mk_test {
427     my $self      = shift;
428     my $script    = $self->{script};
429     my $appprefix = $self->{appprefix};
430     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_test.pl.tt'), "$script/$appprefix\_test.pl" );
431     chmod 0700, "$script/$appprefix\_test.pl";
432 }
433
434 sub _mk_create {
435     my $self      = shift;
436     my $script    = $self->{script};
437     my $appprefix = $self->{appprefix};
438     $self->render_sharedir_file( File::Spec->catfile('script', 'myapp_create.pl.tt'), "$script\/$appprefix\_create.pl" );
439     chmod 0700, "$script/$appprefix\_create.pl";
440 }
441
442 sub _mk_compclass {
443     my $self = shift;
444     my $file = $self->{file};
445     return $self->render_sharedir_file( 'lib', 'Helper', 'compclass.pl.tt', "$file" );
446 }
447
448 sub _mk_comptest {
449     my $self = shift;
450     my $test = $self->{test};
451     $self->render_sharedir_file( 't', 'comptest.tt', "$test" );  ## wtf do i rename this to?
452 }
453
454 sub _mk_images {
455     my $self   = shift;
456     my $images = $self->{images};
457     my @images =
458       qw/catalyst_logo btn_120x50_built btn_120x50_built_shadow
459       btn_120x50_powered btn_120x50_powered_shadow btn_88x31_built
460       btn_88x31_built_shadow btn_88x31_powered btn_88x31_powered_shadow/;
461     for my $name (@images) {
462         my $image = $self->get_sharedir_file("root", "static", "images", "$name.png.bin");
463         $self->mk_file( File::Spec->catfile( $images, "$name.png" ), $image );
464     }
465 }
466
467 sub _mk_favicon {
468     my $self    = shift;
469     my $root    = $self->{root};
470     my $favicon = $self->get_sharedir_file( 'root', 'favicon.ico.bin' );
471     my $dest = File::Spec->catfile( $root, "favicon.ico" );
472     $self->mk_file( $dest, $favicon );
473
474 }
475
476 sub _deprecate_file {
477     my ( $self, $file ) = @_;
478     if ( -e $file ) {
479         my $oldcontent;
480         if ( my $f = IO::File->new("< $file") ) {
481             $oldcontent = join( '', (<$f>) );
482         }
483         my $newfile = $file . '.deprecated';
484         if ( my $f = IO::File->new("> $newfile") ) {
485             binmode $f;
486             print $f $oldcontent;
487             print qq/created "$newfile"\n/;
488             unlink $file;
489             print qq/removed "$file"\n/;
490             return 1;
491         }
492         Catalyst::Exception->throw(
493             message => qq/Couldn't create "$file", "$!"/ );
494     }
495 }
496
497
498 ## this is so you don't have to do make install after every change to test
499 sub _find_share_dir {
500   my ($self, $args) = @_;
501   my $share_name = $self->name;
502   if ($share_name =~ s!^/(.*?)/!!) {
503     my $dist = $1;
504     $args->{share_base_dir} = eval {
505         Dir->new(File::ShareDir::dist_dir($dist))
506            ->subdir('share');
507     };
508     if ($@) {
509         # not installed
510         my $file = __FILE__;
511         my $dir = Dir->new(dirname($file));
512         my $share_base;
513         while ($dir->parent) {
514             if (-d $dir->subdir('share') && -d $dir->subdir('share')->subdir('root')) {
515                 $share_base = $dir->subdir('share')->subdir('root');
516                 last;
517             }
518             $dir = $dir->parent;
519         }
520         confess "could not find sharebase by recursion. ended up at $dir, from $file"
521           unless $share_base;
522         $args->{share_base_dir} = $share_base;
523     }
524   }
525   my $base = $args->{share_base_dir}->subdir($share_name);
526   confess "No such share base directory ${base}"
527     unless -d $base;
528   $self->share_dir($base);
529 };
530
531
532
533 =head1 DESCRIPTION
534
535 This module is used by B<catalyst.pl> to create a set of scripts for a
536 new catalyst application. The scripts each contain documentation and
537 will output help on how to use them if called incorrectly or in some
538 cases, with no arguments.
539
540 It also provides some useful methods for a Helper module to call when
541 creating a component. See L</METHODS>.
542
543 =head1 SCRIPTS
544
545 =head2 _create.pl
546
547 Used to create new components for a catalyst application at the
548 development stage.
549
550 =head2 _server.pl
551
552 The catalyst test server, starts an HTTPD which outputs debugging to
553 the terminal.
554
555 =head2 _test.pl
556
557 A script for running tests from the command-line.
558
559 =head2 _cgi.pl
560
561 Run your application as a CGI.
562
563 =head2 _fastcgi.pl
564
565 Run the application as a fastcgi app. Either by hand, or call this
566 from FastCgiServer in your http server config.
567
568 =head1 HELPERS
569
570 The L</_create.pl> script creates application components using Helper
571 modules. The Catalyst team provides a good number of Helper modules
572 for you to use. You can also add your own.
573
574 Helpers are classes that provide two methods.
575
576     * mk_compclass - creates the Component class
577     * mk_comptest  - creates the Component test
578
579 So when you call C<scripts/myapp_create.pl view MyView TT>, create
580 will try to execute Catalyst::Helper::View::TT->mk_compclass and
581 Catalyst::Helper::View::TT->mk_comptest.
582
583 See L<Catalyst::Helper::View::TT> and
584 L<Catalyst::Helper::Model::DBIC::Schema> for examples.
585
586 All helper classes should be under one of the following namespaces.
587
588     Catalyst::Helper::Model::
589     Catalyst::Helper::View::
590     Catalyst::Helper::Controller::
591
592 =head2 COMMON HELPERS
593
594 =over
595
596 =item *
597
598 L<Catalyst::Helper::Model::DBIC::Schema> - DBIx::Class models
599
600 =item *
601
602 L<Catalyst::Helper::View::TT> - Template Toolkit view
603
604 =item *
605
606 L<Catalyst::Helper::Model::LDAP>
607
608 =item *
609
610 L<Catalyst::Helper::Model::Adaptor> - wrap any class into a Catalyst model
611
612 =back
613
614 =head3 NOTE
615
616 The helpers will read author name from /etc/passwd by default. + To override, please export the AUTHOR variable.
617
618 =head1 METHODS
619
620 =head2 mk_compclass
621
622 This method in your Helper module is called with C<$helper>
623 which is a L<Catalyst::Helper> object, and whichever other arguments
624 the user added to the command-line. You can use the $helper to call methods
625 described below.
626
627 If the Helper module does not contain a C<mk_compclass> method, it
628 will fall back to calling L</render_file>, with an argument of
629 C<compclass>.
630
631 =head2 mk_comptest
632
633 This method in your Helper module is called with C<$helper>
634 which is a L<Catalyst::Helper> object, and whichever other arguments
635 the user added to the command-line. You can use the $helper to call methods
636 described below.
637
638 If the Helper module does not contain a C<mk_compclass> method, it
639 will fall back to calling L</render_file>, with an argument of
640 C<comptest>.
641
642 =head2 mk_stuff
643
644 This method is called if the user does not supply any of the usual
645 component types C<view>, C<controller>, C<model>. It is passed the
646 C<$helper> object (an instance of L<Catalyst::Helper>), and any other
647 arguments the user typed.
648
649 There is no fallback for this method.
650
651 =head1 INTERNAL METHODS
652
653 These are the methods that the Helper classes can call on the
654 <$helper> object passed to them.
655
656 =head2 render_file ($file, $path, $vars)
657
658 Render and create a file from a template in DATA using Template
659 Toolkit. $file is the relevent chunk of the __DATA__ section, $path is
660 the path to the file and $vars is the hashref as expected by
661 L<Template Toolkit|Template>.
662
663 =head2 get_file ($class, $file)
664
665 Fetch file contents from the DATA section. This is used internally by
666 L</render_file>.  $class is the name of the class to get the DATA
667 section from.  __PACKAGE__ or ( caller(0) )[0] might be sensible
668 values for this.
669
670 =head2 mk_app
671
672 Create the main application skeleton. This is called by L<catalyst.pl>.
673
674 =head2 mk_component ($app)
675
676 This method is called by L<create.pl> to make new components
677 for your application.
678
679 =head3 mk_dir ($path)
680
681 Surprisingly, this function makes a directory.
682
683 =head2 mk_file ($file, $content)
684
685 Writes content to a file. Called by L</render_file>.
686
687 =head2 next_test ($test_name)
688
689 Calculates the name of the next numbered test file and returns it.
690 Don't give the number or the .t suffix for the test name.
691
692 =head2 Dir
693
694 Alias for L<Path::Class::Dir>
695
696 =cut
697
698 =head2 get_sharedir_file
699
700 Method for getting a file out of share/
701
702 =cut
703
704 =head2 render_file_contents
705
706 Process a L<Template::Toolkit> template.
707
708 =cut
709
710 =head2 render_sharedir_file
711
712 Render a template/image file from our share directory
713
714 =cut
715
716
717 =head1 NOTE
718
719 The helpers will read author name from /etc/passwd by default.
720 To override, please export the AUTHOR variable.
721
722 =head1 SEE ALSO
723
724 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
725 L<Catalyst::Response>, L<Catalyst>
726
727 =head1 AUTHORS
728
729 Catalyst Contributors, see Catalyst.pm
730
731 =head1 LICENSE
732
733 This library is free software. You can redistribute it and/or modify
734 it under the same terms as Perl itself.
735
736 =begin pod_to_ignore
737
738 =cut
739
740 1;
741