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