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