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