version 0.32
[catagits/Catalyst-Plugin-Static-Simple.git] / lib / Catalyst / Plugin / Static / Simple.pm
CommitLineData
d6d29b9b 1package Catalyst::Plugin::Static::Simple;
2
d925e93e 3use Moose::Role;
d6d29b9b 4use File::stat;
bdf5afa1 5use File::Spec ();
6use IO::File ();
7use MIME::Types ();
151b8e0f 8use MooseX::Types::Moose qw/ArrayRef Str/;
ee1e7faf 9use Catalyst::Utils;
aa5935f1 10use namespace::autoclean;
d6d29b9b 11
b6f6ec3f 12our $VERSION = '0.32';
d6d29b9b 13
d925e93e 14has _static_file => ( is => 'rw' );
151b8e0f 15has _static_debug_message => ( is => 'rw', isa => ArrayRef[Str] );
d6d29b9b 16
659bc603 17after setup_finalize => sub {
18 my $c = shift;
19
20 # New: Turn off new 'autoflush' flag in logger (see Catalyst::Log).
21 # This is needed to surpress output of debug log messages for
22 # static requests:
23 $c->log->autoflush(0) if $c->log->can('autoflush');
24};
25
d925e93e 26before prepare_action => sub {
d6d29b9b 27 my $c = shift;
d6d29b9b 28 my $path = $c->req->path;
ee1e7faf 29 my $config = $c->config->{'Plugin::Static::Simple'};
b108737b 30
792411e6 31 $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
b1d96e3e 32
d6d29b9b 33 # is the URI in a static-defined path?
bdf5afa1 34 foreach my $dir ( @{ $config->{dirs} } ) {
9d557523 35 my $dir_re = quotemeta $dir;
b108737b 36
0495a293 37 # strip trailing slashes, they'll be added in our regex
38 $dir_re =~ s{/$}{};
b108737b 39
a15abf71 40 my $re;
41
42 if ( $dir =~ m{^qr/}xms ) {
43 $re = eval $dir;
44
45 if ($@) {
46 $c->error( "Error compiling static dir regex '$dir': $@" );
47 }
b1d96e3e 48 }
a15abf71 49 else {
50 $re = qr{^${dir_re}/};
51 }
52
d6d29b9b 53 if ( $path =~ $re ) {
c8ee8fd2 54 if ( $c->_locate_static_file( $path, 1 ) ) {
b06be085 55 $c->_debug_msg( 'from static directory' )
bdf5afa1 56 if $config->{debug};
d6d29b9b 57 } else {
58 $c->_debug_msg( "404: file not found: $path" )
bdf5afa1 59 if $config->{debug};
d6d29b9b 60 $c->res->status( 404 );
0495a293 61 $c->res->content_type( 'text/html' );
d6d29b9b 62 }
63 }
64 }
b108737b 65
d6d29b9b 66 # Does the path have an extension?
b1d96e3e 67 if ( $path =~ /.*\.(\S{1,})$/xms ) {
d6d29b9b 68 # and does it exist?
792411e6 69 $c->_locate_static_file( $path );
d6d29b9b 70 }
d925e93e 71};
b108737b 72
7c97dd21 73around dispatch => sub {
74 my $orig = shift;
b1d96e3e 75 my $c = shift;
b108737b 76
2268e329 77 return if ( $c->res->status != 200 );
b108737b 78
b1d96e3e 79 if ( $c->_static_file ) {
ee1e7faf 80 if ( $c->config->{'Plugin::Static::Simple'}->{no_logs} && $c->log->can('abort') ) {
a28d35e9 81 $c->log->abort( 1 );
82 }
b1d96e3e 83 return $c->_serve_static;
84 }
85 else {
7c97dd21 86 return $c->$orig(@_);
b1d96e3e 87 }
d925e93e 88};
b1d96e3e 89
d925e93e 90before finalize => sub {
d6d29b9b 91 my $c = shift;
b108737b 92
d6d29b9b 93 # display all log messages
ee1e7faf 94 if ( $c->config->{'Plugin::Static::Simple'}->{debug} && scalar @{$c->_debug_msg} ) {
be327929 95 $c->log->debug( 'Static::Simple: ' . join q{ }, @{$c->_debug_msg} );
d6d29b9b 96 }
d925e93e 97};
d6d29b9b 98
aa5935f1 99before setup_finalize => sub {
d6d29b9b 100 my $c = shift;
b108737b 101
ee1e7faf 102 $c->log->warn("Deprecated 'static' config key used, please use the key 'Plugin::Static::Simple' instead")
103 if exists $c->config->{static};
104 my $config
105 = $c->config->{'Plugin::Static::Simple'}
106 = $c->config->{'static'}
107 = Catalyst::Utils::merge_hashes(
108 $c->config->{'Plugin::Static::Simple'} || {},
109 $c->config->{static} || {}
110 );
b108737b 111
bdf5afa1 112 $config->{dirs} ||= [];
113 $config->{include_path} ||= [ $c->config->{root} ];
114 $config->{mime_types} ||= {};
115 $config->{ignore_extensions} ||= [ qw/tmpl tt tt2 html xhtml/ ];
116 $config->{ignore_dirs} ||= [];
117 $config->{debug} ||= $c->debug;
118 $config->{no_logs} = 1 unless defined $config->{no_logs};
6a009cf0 119 $config->{no_logs} = 0 if $config->{logging};
b108737b 120
d6d29b9b 121 # load up a MIME::Types object, only loading types with
122 # at least 1 file extension
bdf5afa1 123 $config->{mime_types_obj} = MIME::Types->new( only_complete => 1 );
aa5935f1 124};
d6d29b9b 125
126# Search through all included directories for the static file
127# Based on Template Toolkit INCLUDE_PATH code
128sub _locate_static_file {
c8ee8fd2 129 my ( $c, $path, $in_static_dir ) = @_;
b108737b 130
bdf5afa1 131 $path = File::Spec->catdir(
b108737b 132 File::Spec->no_upwards( File::Spec->splitdir( $path ) )
bdf5afa1 133 );
b108737b 134
ee1e7faf 135 my $config = $c->config->{'Plugin::Static::Simple'};
bdf5afa1 136 my @ipaths = @{ $config->{include_path} };
d6d29b9b 137 my $dpaths;
138 my $count = 64; # maximum number of directories to search
b108737b 139
8cc672a2 140 DIR_CHECK:
d6d29b9b 141 while ( @ipaths && --$count) {
8cc672a2 142 my $dir = shift @ipaths || next DIR_CHECK;
b108737b 143
d6d29b9b 144 if ( ref $dir eq 'CODE' ) {
145 eval { $dpaths = &$dir( $c ) };
146 if ($@) {
b06be085 147 $c->log->error( 'Static::Simple: include_path error: ' . $@ );
d6d29b9b 148 } else {
b06be085 149 unshift @ipaths, @$dpaths;
8cc672a2 150 next DIR_CHECK;
d6d29b9b 151 }
152 } else {
48791b66 153 $dir =~ s/(\/|\\)$//xms;
d6d29b9b 154 if ( -d $dir && -f $dir . '/' . $path ) {
b108737b 155
c8ee8fd2 156 # Don't ignore any files in static dirs defined with 'dirs'
157 unless ( $in_static_dir ) {
158 # do we need to ignore the file?
159 for my $ignore ( @{ $config->{ignore_dirs} } ) {
160 $ignore =~ s{(/|\\)$}{};
161 if ( $path =~ /^$ignore(\/|\\)/ ) {
162 $c->_debug_msg( "Ignoring directory `$ignore`" )
163 if $config->{debug};
164 next DIR_CHECK;
165 }
8cc672a2 166 }
b108737b 167
c8ee8fd2 168 # do we need to ignore based on extension?
169 for my $ignore_ext ( @{ $config->{ignore_extensions} } ) {
170 if ( $path =~ /.*\.${ignore_ext}$/ixms ) {
171 $c->_debug_msg( "Ignoring extension `$ignore_ext`" )
172 if $config->{debug};
173 next DIR_CHECK;
174 }
bdf5afa1 175 }
8cc672a2 176 }
b108737b 177
8cc672a2 178 $c->_debug_msg( 'Serving ' . $dir . '/' . $path )
bdf5afa1 179 if $config->{debug};
d6d29b9b 180 return $c->_static_file( $dir . '/' . $path );
181 }
182 }
183 }
b108737b 184
2268e329 185 return;
d6d29b9b 186}
187
d6d29b9b 188sub _serve_static {
189 my $c = shift;
ee1e7faf 190 my $config = $c->config->{'Plugin::Static::Simple'};
b108737b 191
ab02ca0d 192 my $full_path = shift || $c->_static_file;
792411e6 193 my $type = $c->_ext_to_type( $full_path );
194 my $stat = stat $full_path;
d6d29b9b 195
d6d29b9b 196 $c->res->headers->content_type( $type );
197 $c->res->headers->content_length( $stat->size );
198 $c->res->headers->last_modified( $stat->mtime );
41cac5ef 199 # Tell Firefox & friends its OK to cache, even over SSL:
200 $c->res->headers->header('Cache-control' => 'public');
201 # Optionally, set a fixed expiry time:
202 if ($config->{expires}) {
203 $c->res->headers->expires(time() + $config->{expires});
204 }
2cb3d585 205
b108737b 206 my $fh = IO::File->new( $full_path, 'r' );
207 if ( defined $fh ) {
208 binmode $fh;
209 $c->res->body( $fh );
2cb3d585 210 }
211 else {
b108737b 212 Catalyst::Exception->throw(
213 message => "Unable to open $full_path for reading" );
2cb3d585 214 }
b108737b 215
b1d96e3e 216 return 1;
217}
218
ab02ca0d 219sub serve_static_file {
220 my ( $c, $full_path ) = @_;
221
ee1e7faf 222 my $config = $c->config->{'Plugin::Static::Simple'};
b108737b 223
ab02ca0d 224 if ( -e $full_path ) {
225 $c->_debug_msg( "Serving static file: $full_path" )
226 if $config->{debug};
227 }
228 else {
229 $c->_debug_msg( "404: file not found: $full_path" )
230 if $config->{debug};
231 $c->res->status( 404 );
0495a293 232 $c->res->content_type( 'text/html' );
ab02ca0d 233 return;
234 }
235
236 $c->_serve_static( $full_path );
237}
238
b1d96e3e 239# looks up the correct MIME type for the current file extension
240sub _ext_to_type {
792411e6 241 my ( $c, $full_path ) = @_;
b108737b 242
ee1e7faf 243 my $config = $c->config->{'Plugin::Static::Simple'};
b108737b 244
792411e6 245 if ( $full_path =~ /.*\.(\S{1,})$/xms ) {
b1d96e3e 246 my $ext = $1;
b108737b 247 my $type = $config->{mime_types}{$ext}
bdf5afa1 248 || $config->{mime_types_obj}->mimeTypeOf( $ext );
2268e329 249 if ( $type ) {
bdf5afa1 250 $c->_debug_msg( "as $type" ) if $config->{debug};
5224ce15 251 return ( ref $type ) ? $type->type : $type;
b1d96e3e 252 }
253 else {
254 $c->_debug_msg( "as text/plain (unknown extension $ext)" )
bdf5afa1 255 if $config->{debug};
b1d96e3e 256 return 'text/plain';
257 }
258 }
259 else {
260 $c->_debug_msg( 'as text/plain (no extension)' )
bdf5afa1 261 if $config->{debug};
b1d96e3e 262 return 'text/plain';
263 }
d6d29b9b 264}
265
266sub _debug_msg {
267 my ( $c, $msg ) = @_;
b108737b 268
2268e329 269 if ( !defined $c->_static_debug_message ) {
270 $c->_static_debug_message( [] );
b1d96e3e 271 }
b108737b 272
b1d96e3e 273 if ( $msg ) {
2268e329 274 push @{ $c->_static_debug_message }, $msg;
b1d96e3e 275 }
b108737b 276
2268e329 277 return $c->_static_debug_message;
d6d29b9b 278}
b1d96e3e 279
2801;
281__END__
282
283=head1 NAME
284
285Catalyst::Plugin::Static::Simple - Make serving static pages painless.
286
287=head1 SYNOPSIS
288
b6fdf01d 289 package MyApp;
290 use Catalyst qw/ Static::Simple /;
291 MyApp->setup;
b648683e 292 # that's it; static content is automatically served by Catalyst
293 # from the application's root directory, though you can configure
294 # things or bypass Catalyst entirely in a production environment
295 #
296 # one caveat: the files must be served from an absolute path
6e89d83c 297 # (i.e. /images/foo.png)
b1d96e3e 298
299=head1 DESCRIPTION
300
bc5b1283 301The Static::Simple plugin is designed to make serving static content in
302your application during development quick and easy, without requiring a
303single line of code from you.
b1d96e3e 304
bc5b1283 305This plugin detects static files by looking at the file extension in the
306URL (such as B<.css> or B<.png> or B<.js>). The plugin uses the
307lightweight L<MIME::Types> module to map file extensions to
308IANA-registered MIME types, and will serve your static files with the
309correct MIME type directly to the browser, without being processed
310through Catalyst.
b1d96e3e 311
312Note that actions mapped to paths using periods (.) will still operate
313properly.
314
200e206c 315If the plugin can not find the file, the request is dispatched to your
b108737b 316application instead. This means you are responsible for generating a
200e206c 317C<404> error if your applicaton can not process the request:
318
319 # handled by static::simple, not dispatched to your application
320 /images/exists.png
aa5935f1 321
200e206c 322 # static::simple will not find the file and let your application
323 # handle the request. You are responsible for generating a file
324 # or returning a 404 error
325 /images/does_not_exist.png
326
bc5b1283 327Though Static::Simple is designed to work out-of-the-box, you can tweak
328the operation by adding various configuration options. In a production
329environment, you will probably want to use your webserver to deliver
330static content; for an example see L<USING WITH APACHE>, below.
331
41cac5ef 332=head1 DEFAULT BEHAVIOUR
bc5b1283 333
334By default, Static::Simple will deliver all files having extensions
335(that is, bits of text following a period (C<.>)), I<except> files
336having the extensions C<tmpl>, C<tt>, C<tt2>, C<html>, and
337C<xhtml>. These files, and all files without extensions, will be
338processed through Catalyst. If L<MIME::Types> doesn't recognize an
339extension, it will be served as C<text/plain>.
340
341To restate: files having the extensions C<tmpl>, C<tt>, C<tt2>, C<html>,
342and C<xhtml> I<will not> be served statically by default, they will be
343processed by Catalyst. Thus if you want to use C<.html> files from
344within a Catalyst app as static files, you need to change the
345configuration of Static::Simple. Note also that files having any other
346extension I<will> be served statically, so if you're using any other
347extension for template files, you should also change the configuration.
348
349Logging of static files is turned off by default.
b1d96e3e 350
351=head1 ADVANCED CONFIGURATION
352
bc5b1283 353Configuration is completely optional and is specified within
fa25e422 354C<MyApp-E<gt>config-E<gt>{Plugin::Static::Simple}>. If you use any of these options,
86880b0d 355this module will probably feel less "simple" to you!
b1d96e3e 356
bc5b1283 357=head2 Enabling request logging
2de14076 358
bc5b1283 359Since Catalyst 5.50, logging of static requests is turned off by
360default; static requests tend to clutter the log output and rarely
361reveal anything useful. However, if you want to enable logging of static
362requests, you can do so by setting
fa25e422 363C<MyApp-E<gt>config-E<gt>{Plugin::Static::Simple}-E<gt>{logging}> to 1.
2de14076 364
2268e329 365=head2 Forcing directories into static mode
b1d96e3e 366
bc5b1283 367Define a list of top-level directories beneath your 'root' directory
368that should always be served in static mode. Regular expressions may be
369specified using C<qr//>.
b1d96e3e 370
a5d909f1 371 MyApp->config(
fa25e422 372 'Plugin::Static::Simple' => {
a5d909f1 373 dirs => [
374 'static',
375 qr/^(images|css)/,
376 ],
377 }
378 );
b1d96e3e 379
fa43d6b5 380=head2 Including additional directories
b1d96e3e 381
382You may specify a list of directories in which to search for your static
bc5b1283 383files. The directories will be searched in order and will return the
384first file found. Note that your root directory is B<not> automatically
385added to the search path when you specify an C<include_path>. You should
386use C<MyApp-E<gt>config-E<gt>{root}> to add it.
b1d96e3e 387
a5d909f1 388 MyApp->config(
fa25e422 389 'Plugin::Static::Simple' => {
a5d909f1 390 include_path => [
391 '/path/to/overlay',
392 \&incpath_generator,
393 MyApp->config->{root},
394 ],
395 },
396 );
b108737b 397
bc5b1283 398With the above setting, a request for the file C</images/logo.jpg> will search
b1d96e3e 399for the following files, returning the first one found:
400
401 /path/to/overlay/images/logo.jpg
402 /dynamic/path/images/logo.jpg
403 /your/app/home/root/images/logo.jpg
b108737b 404
b1d96e3e 405The include path can contain a subroutine reference to dynamically return a
bc5b1283 406list of available directories. This method will receive the C<$c> object as a
b1d96e3e 407parameter and should return a reference to a list of directories. Errors can
bc5b1283 408be reported using C<die()>. This method will be called every time a file is
b1d96e3e 409requested that appears to be a static file (i.e. it has an extension).
410
411For example:
412
413 sub incpath_generator {
414 my $c = shift;
a5d909f1 415
b1d96e3e 416 if ( $c->session->{customer_dir} ) {
417 return [ $c->session->{customer_dir} ];
418 } else {
419 die "No customer dir defined.";
420 }
421 }
b108737b 422
8cc672a2 423=head2 Ignoring certain types of files
424
bc5b1283 425There are some file types you may not wish to serve as static files.
426Most important in this category are your raw template files. By
427default, files with the extensions C<tmpl>, C<tt>, C<tt2>, C<html>, and
428C<xhtml> will be ignored by Static::Simple in the interest of security.
429If you wish to define your own extensions to ignore, use the
430C<ignore_extensions> option:
8cc672a2 431
a5d909f1 432 MyApp->config(
fa25e422 433 'Plugin::Static::Simple' => {
a5d909f1 434 ignore_extensions => [ qw/html asp php/ ],
435 },
436 );
b108737b 437
8cc672a2 438=head2 Ignoring entire directories
439
bc5b1283 440To prevent an entire directory from being served statically, you can use
441the C<ignore_dirs> option. This option contains a list of relative
442directory paths to ignore. If using C<include_path>, the path will be
443checked against every included path.
8cc672a2 444
a5d909f1 445 MyApp->config(
fa25e422 446 'Plugin::Static::Simple' => {
a5d909f1 447 ignore_dirs => [ qw/tmpl css/ ],
448 },
449 );
b108737b 450
bc5b1283 451For example, if combined with the above C<include_path> setting, this
452C<ignore_dirs> value will ignore the following directories if they exist:
8cc672a2 453
454 /path/to/overlay/tmpl
455 /path/to/overlay/css
456 /dynamic/path/tmpl
457 /dynamic/path/css
458 /your/app/home/root/tmpl
b108737b 459 /your/app/home/root/css
b1d96e3e 460
2268e329 461=head2 Custom MIME types
b1d96e3e 462
bc5b1283 463To override or add to the default MIME types set by the L<MIME::Types>
464module, you may enter your own extension to MIME type mapping.
b1d96e3e 465
a5d909f1 466 MyApp->config(
fa25e422 467 'Plugin::Static::Simple' => {
a5d909f1 468 mime_types => {
469 jpg => 'image/jpg',
470 png => 'image/png',
471 },
472 },
473 );
2268e329 474
41cac5ef 475=head2 Controlling caching with Expires header
476
477The files served by Static::Simple will have a Last-Modified header set,
478which allows some browsers to cache them for a while. However if you want
479to explicitly set an Expires header, such as to allow proxies to cache your
480static content, then you can do so by setting the "expires" config option.
481
482The value indicates the number of seconds after access time to allow caching.
483So a value of zero really means "don't cache at all", and any higher values
484will keep the file around for that long.
485
486 MyApp->config(
fa25e422 487 'Plugin::Static::Simple' => {
41cac5ef 488 expires => 3600, # Caching allowed for one hour.
489 },
490 );
491
d38d0ed6 492=head2 Compatibility with other plugins
b1d96e3e 493
d38d0ed6 494Since version 0.12, Static::Simple plays nice with other plugins. It no
bc5b1283 495longer short-circuits the C<prepare_action> stage as it was causing too
496many compatibility issues with other plugins.
b1d96e3e 497
2268e329 498=head2 Debugging information
b1d96e3e 499
500Enable additional debugging information printed in the Catalyst log. This
501is automatically enabled when running Catalyst in -Debug mode.
502
a5d909f1 503 MyApp->config(
fa25e422 504 'Plugin::Static::Simple' => {
a5d909f1 505 debug => 1,
506 },
507 );
b108737b 508
2cb3d585 509=head1 USING WITH APACHE
510
6e89d83c 511While Static::Simple will work just fine serving files through Catalyst
512in mod_perl, for increased performance you may wish to have Apache
513handle the serving of your static files directly. To do this, simply use
514a dedicated directory for your static files and configure an Apache
515Location block for that directory This approach is recommended for
516production installations.
2cb3d585 517
6e89d83c 518 <Location /myapp/static>
2cb3d585 519 SetHandler default-handler
520 </Location>
b1d96e3e 521
bc5b1283 522Using this approach Apache will bypass any handling of these directories
523through Catalyst. You can leave Static::Simple as part of your
524application, and it will continue to function on a development server,
525or using Catalyst's built-in server.
526
6e89d83c 527In practice, your Catalyst application is probably (i.e. should be)
528structured in the recommended way (i.e., that generated by bootstrapping
529the application with the C<catalyst.pl> script, with a main directory
530under which is a C<lib/> directory for module files and a C<root/>
531directory for templates and static files). Thus, unless you break up
532this structure when deploying your app by moving the static files to a
533different location in your filesystem, you will need to use an Alias
534directive in Apache to point to the right place. You will then need to
535add a Directory block to give permission for Apache to serve these
536files. The final configuration will look something like this:
537
538 Alias /myapp/static /filesystem/path/to/MyApp/root/static
539 <Directory /filesystem/path/to/MyApp/root/static>
540 allow from all
541 </Directory>
542 <Location /myapp/static>
543 SetHandler default-handler
544 </Location>
545
071c0042 546If you are running in a VirtualHost, you can just set the DocumentRoot
b108737b 547location to the location of your root directory; see
071c0042 548L<Catalyst::Engine::Apache2::MP20>.
549
ab02ca0d 550=head1 PUBLIC METHODS
551
552=head2 serve_static_file $file_path
553
554Will serve the file located in $file_path statically. This is useful when
555you need to autogenerate them if they don't exist, or they are stored in a model.
556
557 package MyApp::Controller::User;
558
559 sub curr_user_thumb : PathPart("my_thumbnail.png") {
560 my ( $self, $c ) = @_;
561 my $file_path = $c->user->picture_thumbnail_path;
562 $c->serve_static_file($file_path);
563 }
564
033a7581 565=head1 INTERNAL EXTENDED METHODS
566
567Static::Simple extends the following steps in the Catalyst process.
568
b108737b 569=head2 prepare_action
033a7581 570
bc5b1283 571C<prepare_action> is used to first check if the request path is a static
572file. If so, we skip all other C<prepare_action> steps to improve
573performance.
033a7581 574
575=head2 dispatch
576
bc5b1283 577C<dispatch> takes the file found during C<prepare_action> and writes it
578to the output.
033a7581 579
580=head2 finalize
581
bc5b1283 582C<finalize> serves up final header information and displays any log
583messages.
033a7581 584
585=head2 setup
586
bc5b1283 587C<setup> initializes all default values.
033a7581 588
d6d29b9b 589=head1 SEE ALSO
590
b108737b 591L<Catalyst>, L<Catalyst::Plugin::Static>,
b1d96e3e 592L<http://www.iana.org/assignments/media-types/>
d6d29b9b 593
594=head1 AUTHOR
595
b1d96e3e 596Andy Grundman, <andy@hybridized.org>
d6d29b9b 597
fa43d6b5 598=head1 CONTRIBUTORS
599
600Marcus Ramberg, <mramberg@cpan.org>
ab02ca0d 601
bc5b1283 602Jesse Sheidlower, <jester@panix.com>
fa43d6b5 603
ab02ca0d 604Guillermo Roditi, <groditi@cpan.org>
605
9936ddfa 606Florian Ragwitz, <rafl@debian.org>
607
608Tomas Doran, <bobtfish@bobtfish.net>
609
610Justin Wheeler (dnm)
b108737b 611
7c97dd21 612Matt S Trout, <mst@shadowcat.co.uk>
613
41cac5ef 614Toby Corkindale, <tjc@wintrmute.net>
615
d6d29b9b 616=head1 THANKS
617
618The authors of Catalyst::Plugin::Static:
619
620 Sebastian Riedel
621 Christian Hansen
622 Marcus Ramberg
623
624For the include_path code from Template Toolkit:
625
626 Andy Wardley
627
628=head1 COPYRIGHT
629
41cac5ef 630Copyright (c) 2005 - 2011
1cc75f96 631the Catalyst::Plugin::Static::Simple L</AUTHOR> and L</CONTRIBUTORS>
632as listed above.
633
634=head1 LICENSE
635
d6d29b9b 636This program is free software, you can redistribute it and/or modify it under
637the same terms as Perl itself.
638
639=cut