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