Updated welcome screen
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
CommitLineData
fc7ec1d9 1package Catalyst;
2
3use strict;
fbcc39ad 4use base 'Catalyst::Base';
5use bytes;
fc7ec1d9 6use UNIVERSAL::require;
a2f2cde9 7use Catalyst::Exception;
fc7ec1d9 8use Catalyst::Log;
fbcc39ad 9use Catalyst::Request;
10use Catalyst::Request::Upload;
11use Catalyst::Response;
812a28c9 12use Catalyst::Utils;
5d9a6d47 13use NEXT;
fbcc39ad 14use Text::ASCIITable;
4f6748f1 15use Path::Class;
fbcc39ad 16use Time::HiRes qw/gettimeofday tv_interval/;
17use URI;
18use Scalar::Util qw/weaken/;
fc7ec1d9 19
fbcc39ad 20__PACKAGE__->mk_accessors(qw/counter depth request response state/);
10dd6896 21
fbcc39ad 22# Laziness++
23*comp = \&component;
24*req = \&request;
25*res = \&response;
26
27# For backwards compatibility
28*finalize_output = \&finalize_body;
29
30# For statistics
31our $COUNT = 1;
32our $START = time;
33our $RECURSION = 1000;
34our $DETACH = "catalyst_detach\n";
35
36require Module::Pluggable::Fast;
37
38# Helper script generation
6844bc1c 39our $CATALYST_SCRIPT_GEN = 8;
fbcc39ad 40
41__PACKAGE__->mk_classdata($_)
42 for qw/components arguments dispatcher engine log/;
43
44our $VERSION = '5.49_01';
45
46sub import {
47 my ( $class, @arguments ) = @_;
48
49 # We have to limit $class to Catalyst to avoid pushing Catalyst upon every
50 # callers @ISA.
51 return unless $class eq 'Catalyst';
52
53 my $caller = caller(0);
54
55 unless ( $caller->isa('Catalyst') ) {
56 no strict 'refs';
57 push @{"$caller\::ISA"}, $class;
58 }
59
60 $caller->arguments( [@arguments] );
61 $caller->setup_home;
62}
fc7ec1d9 63
64=head1 NAME
65
66Catalyst - The Elegant MVC Web Application Framework
67
68=head1 SYNOPSIS
69
70 # use the helper to start a new application
91864987 71 catalyst.pl MyApp
fc7ec1d9 72 cd MyApp
73
74 # add models, views, controllers
ae4e40a7 75 script/myapp_create.pl model Something
76 script/myapp_create.pl view Stuff
77 script/myapp_create.pl controller Yada
fc7ec1d9 78
79 # built in testserver
ae4e40a7 80 script/myapp_server.pl
fc7ec1d9 81
82 # command line interface
ae4e40a7 83 script/myapp_test.pl /yada
fc7ec1d9 84
85
fc7ec1d9 86 use Catalyst;
87
88 use Catalyst qw/My::Module My::OtherModule/;
89
90 use Catalyst '-Debug';
91
92 use Catalyst qw/-Debug -Engine=CGI/;
93
5a8ed4fe 94 sub default : Private { $_[1]->res->output('Hello') } );
95
e3dc9d78 96 sub index : Path('/index.html') {
5a8ed4fe 97 my ( $self, $c ) = @_;
98 $c->res->output('Hello');
064834ea 99 $c->forward('foo');
5a8ed4fe 100 }
101
064834ea 102 sub product : Regex('^product[_]*(\d*).html$') {
5a8ed4fe 103 my ( $self, $c ) = @_;
104 $c->stash->{template} = 'product.tt';
105 $c->stash->{product} = $c->req->snippets->[0];
106 }
fc7ec1d9 107
3803e98f 108See also L<Catalyst::Manual::Intro>
109
fc7ec1d9 110=head1 DESCRIPTION
111
fc7ec1d9 112The key concept of Catalyst is DRY (Don't Repeat Yourself).
113
114See L<Catalyst::Manual> for more documentation.
115
23f9d934 116Catalyst plugins can be loaded by naming them as arguments to the "use Catalyst" statement.
1985c30b 117Omit the C<Catalyst::Plugin::> prefix from the plugin name,
23f9d934 118so C<Catalyst::Plugin::My::Module> becomes C<My::Module>.
fc7ec1d9 119
120 use Catalyst 'My::Module';
121
26e73131 122Special flags like -Debug and -Engine can also be specified as arguments when
23f9d934 123Catalyst is loaded:
fc7ec1d9 124
125 use Catalyst qw/-Debug My::Module/;
126
23f9d934 127The position of plugins and flags in the chain is important, because they are
128loaded in exactly the order that they appear.
fc7ec1d9 129
23f9d934 130The following flags are supported:
131
132=over 4
133
134=item -Debug
135
136enables debug output, i.e.:
fc7ec1d9 137
138 use Catalyst '-Debug';
139
23f9d934 140this is equivalent to:
fc7ec1d9 141
142 use Catalyst;
143 sub debug { 1 }
144
fbcc39ad 145=item -Dispatcher
146
147Force Catalyst to use a specific dispatcher.
148
23f9d934 149=item -Engine
fc7ec1d9 150
151Force Catalyst to use a specific engine.
23f9d934 152Omit the C<Catalyst::Engine::> prefix of the engine name, i.e.:
fc7ec1d9 153
154 use Catalyst '-Engine=CGI';
155
fbcc39ad 156=item -Home
157
158Force Catalyst to use a specific home directory.
159
160=item -Log
161
162Specify log level.
163
23f9d934 164=back
fc7ec1d9 165
23f9d934 166=head1 METHODS
167
168=over 4
169
fbcc39ad 170=item $c->comp($name)
171
172=item $c->component($name)
173
174Get a component object by name.
175
176 $c->comp('MyApp::Model::MyModel')->do_stuff;
177
178=cut
179
180sub component {
181 my $c = shift;
182
183 if (@_) {
184
185 my $name = shift;
186
187 my $appclass = ref $c || $c;
188
189 my @names = (
190 $name, "${appclass}::${name}",
191 map { "${appclass}::${_}::${name}" } qw/M V C/
192 );
193
194 foreach my $try (@names) {
195
196 if ( exists $c->components->{$try} ) {
197
198 return $c->components->{$try};
199 }
200 }
201
202 foreach my $component ( keys %{ $c->components } ) {
203
204 return $c->components->{$component} if $component =~ /$name/i;
205 }
206
207 }
208
209 return sort keys %{ $c->components };
210}
211
212=item config
213
214Returns a hashref containing your applications settings.
215
23f9d934 216=item debug
fc7ec1d9 217
218Overload to enable debug messages.
219
220=cut
221
222sub debug { 0 }
223
fbcc39ad 224=item $c->detach( $command [, \@arguments ] )
fc7ec1d9 225
fbcc39ad 226Like C<forward> but doesn't return.
fc7ec1d9 227
228=cut
229
fbcc39ad 230sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) }
231
232=item $c->dispatcher
233
234Contains the dispatcher instance.
235Stringifies to class.
236
237=item $c->forward( $command [, \@arguments ] )
238
239Forward processing to a private action or a method from a class.
240If you define a class without method it will default to process().
241also takes an optional arrayref containing arguments to be passed
242to the new function. $c->req->args will be reset upon returning
243from the function.
244
245 $c->forward('/foo');
246 $c->forward('index');
247 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
248 $c->forward('MyApp::View::TT');
249
250=cut
251
252sub forward { my $c = shift; $c->dispatcher->forward( $c, @_ ) }
253
254=item $c->setup
255
256Setup.
257
258 $c->setup;
259
260=cut
261
262sub setup {
0319a12c 263 my ( $class, @arguments ) = @_;
599b5295 264
fbcc39ad 265 unless ( $class->isa('Catalyst') ) {
953b0e15 266
fbcc39ad 267 Catalyst::Exception->throw(
268 message => qq/'$class' does not inherit from Catalyst/ );
1c99e125 269 }
0319a12c 270
fbcc39ad 271 if ( $class->arguments ) {
272 @arguments = ( @arguments, @{ $class->arguments } );
273 }
274
275 # Process options
276 my $flags = {};
277
278 foreach (@arguments) {
279
280 if (/^-Debug$/) {
281 $flags->{log} =
282 ( $flags->{log} ) ? 'debug,' . $flags->{log} : 'debug';
283 }
284 elsif (/^-(\w+)=?(.*)$/) {
285 $flags->{ lc $1 } = $2;
286 }
287 else {
288 push @{ $flags->{plugins} }, $_;
289 }
290 }
291
292 $class->setup_log( delete $flags->{log} );
293 $class->setup_plugins( delete $flags->{plugins} );
294 $class->setup_dispatcher( delete $flags->{dispatcher} );
295 $class->setup_engine( delete $flags->{engine} );
296 $class->setup_home( delete $flags->{home} );
297
298 for my $flag ( sort keys %{$flags} ) {
299
300 if ( my $code = $class->can( 'setup_' . $flag ) ) {
301 &$code( $class, delete $flags->{$flag} );
302 }
303 else {
304 $class->log->warn(qq/Unknown flag "$flag"/);
305 }
306 }
307
308 $class->log->warn( "You are running an old helper script! "
309 . "Please update your scripts by regenerating the "
310 . "application and copying over the new scripts." )
311 if ( $ENV{CATALYST_SCRIPT_GEN}
312 && ( $ENV{CATALYST_SCRIPT_GEN} < $Catalyst::CATALYST_SCRIPT_GEN ) );
313
314 if ( $class->debug ) {
315
316 my @plugins = ();
317
318 {
319 no strict 'refs';
320 @plugins = grep { /^Catalyst::Plugin/ } @{"$class\::ISA"};
321 }
322
323 if (@plugins) {
324 my $t = Text::ASCIITable->new;
325 $t->setOptions( 'hide_HeadRow', 1 );
326 $t->setOptions( 'hide_HeadLine', 1 );
327 $t->setCols('Class');
328 $t->setColWidth( 'Class', 75, 1 );
329 $t->addRow($_) for @plugins;
330 $class->log->debug( "Loaded plugins:\n" . $t->draw );
331 }
332
333 my $dispatcher = $class->dispatcher;
334 my $engine = $class->engine;
335 my $home = $class->config->{home};
336
337 $class->log->debug(qq/Loaded dispatcher "$dispatcher"/);
338 $class->log->debug(qq/Loaded engine "$engine"/);
339
340 $home
341 ? ( -d $home )
342 ? $class->log->debug(qq/Found home "$home"/)
343 : $class->log->debug(qq/Home "$home" doesn't exist/)
344 : $class->log->debug(q/Couldn't find home/);
345 }
346
347 # Call plugins setup
348 {
349 no warnings qw/redefine/;
350 local *setup = sub { };
351 $class->setup;
352 }
353
354 # Initialize our data structure
355 $class->components( {} );
356
357 $class->setup_components;
358
359 if ( $class->debug ) {
360 my $t = Text::ASCIITable->new;
361 $t->setOptions( 'hide_HeadRow', 1 );
362 $t->setOptions( 'hide_HeadLine', 1 );
363 $t->setCols('Class');
364 $t->setColWidth( 'Class', 75, 1 );
365 $t->addRow($_) for sort keys %{ $class->components };
366 $class->log->debug( "Loaded components:\n" . $t->draw )
367 if ( @{ $t->{tbl_rows} } );
368 }
369
370 # Add our self to components, since we are also a component
371 $class->components->{$class} = $class;
372
373 $class->setup_actions;
374
375 if ( $class->debug ) {
376 my $name = $class->config->{name} || 'Application';
377 $class->log->info("$name powered by Catalyst $Catalyst::VERSION");
378 }
379 $class->log->_flush() if $class->log->can('_flush');
380}
381
382=item $c->uri_for($path)
383
384Merges path with $c->request->base for absolute uri's and with
385$c->request->match for relative uri's, then returns a normalized
386L<URI> object.
387
388=cut
389
390sub uri_for {
391 my ( $c, $path ) = @_;
392 my $base = $c->request->base->clone;
393 my $basepath = $base->path;
394 $basepath =~ s/\/$//;
fdba7a9d 395 $basepath .= '/';
fbcc39ad 396 my $match = $c->request->match;
397 $match =~ s/^\///;
398 $match .= '/' if $match;
399 $match = '' if $path =~ /^\//;
400 $path =~ s/^\///;
401 return URI->new_abs( URI->new_abs( $path, "$basepath$match" ), $base )
402 ->canonical;
403}
404
405=item $c->error
406
407=item $c->error($error, ...)
408
409=item $c->error($arrayref)
410
411Returns an arrayref containing error messages.
412
413 my @error = @{ $c->error };
414
415Add a new error.
416
417 $c->error('Something bad happened');
418
419=cut
420
421sub error {
422 my $c = shift;
423 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
424 push @{ $c->{error} }, @$error;
425 return $c->{error};
0319a12c 426}
427
428=item $c->engine
429
fbcc39ad 430Contains the engine instance.
431Stringifies to the class.
fc7ec1d9 432
0319a12c 433=item $c->log
434
435Contains the logging object. Unless it is already set Catalyst sets this up with a
436C<Catalyst::Log> object. To use your own log class:
437
438 $c->log( MyLogger->new );
439 $c->log->info("now logging with my own logger!");
440
441Your log class should implement the methods described in the C<Catalyst::Log>
442man page.
443
444=item $c->plugin( $name, $class, @args )
445
446Instant plugins for Catalyst.
447Classdata accessor/mutator will be created, class loaded and instantiated.
448
449 MyApp->plugin( 'prototype', 'HTML::Prototype' );
450
451 $c->prototype->define_javascript_functions;
452
453=cut
454
455sub plugin {
456 my ( $class, $name, $plugin, @args ) = @_;
457 $plugin->require;
458
459 if ( my $error = $UNIVERSAL::require::ERROR ) {
460 Catalyst::Exception->throw(
fbcc39ad 461 message => qq/Couldn't load instant plugin "$plugin", "$error"/ );
0319a12c 462 }
463
464 eval { $plugin->import };
465 $class->mk_classdata($name);
466 my $obj;
467 eval { $obj = $plugin->new(@args) };
468
fbcc39ad 469 if ($@) {
470 Catalyst::Exception->throw( message =>
471 qq/Couldn't instantiate instant plugin "$plugin", "$@"/ );
0319a12c 472 }
473
474 $class->$name($obj);
475 $class->log->debug(qq/Initialized instant plugin "$plugin" as "$name"/)
476 if $class->debug;
477}
478
fbcc39ad 479=item $c->request
480
481=item $c->req
482
483Returns a C<Catalyst::Request> object.
484
485 my $req = $c->req;
486
487=item $c->response
488
489=item $c->res
490
491Returns a C<Catalyst::Response> object.
492
493 my $res = $c->res;
494
495=item $c->state
496
497Contains the return value of the last executed action.
498
499=item $c->stash
500
501Returns a hashref containing all your data.
502
fbcc39ad 503 print $c->stash->{foo};
504
23eb3f51 505Keys may be set in the stash by assigning to the hash reference, or by passing
506either a single hash reference or a list of key/value pairs as arguments.
507
508For example:
509
510 $c->stash->{foo} ||= 'yada';
511 $c->stash( { moose => 'majestic', qux => 0 } );
512 $c->stash( bar => 1, gorch => 2 );
513
fbcc39ad 514=cut
515
516sub stash {
517 my $c = shift;
518 if (@_) {
519 my $stash = @_ > 1 ? {@_} : $_[0];
520 while ( my ( $key, $val ) = each %$stash ) {
521 $c->{stash}->{$key} = $val;
522 }
523 }
524 return $c->{stash};
525}
526
ab2374d3 527=head1 $c->welcome_message
528
529Returns the Catalyst welcome HTML page.
530
531=cut
532
533sub welcome_message {
534 my $c = shift;
535 my $name = $c->config->{name};
f68d720e 536 my $logo = $c->uri_for('/static/images/catalyst_logo.png');
ab2374d3 537 return <<"EOF";
538<html>
539 <head>
540 <title>$name on Catalyst $VERSION</title>
541 <style type="text/css">
542 body {
543 text-align: center;
544 padding-left: 50%;
545 color: #000;
546 background-color: #eee;
547 }
548 div#content {
549 width: 640px;
550 margin-left: -320px;
551 margin-top: 10px;
552 margin-bottom: 10px;
553 text-align: left;
554 background-color: #ccc;
555 border: 1px solid #aaa;
556 -moz-border-radius: 10px;
557 }
d84c4dab 558 p, h1, h2 {
ab2374d3 559 margin-left: 20px;
560 margin-right: 20px;
16215972 561 font-family: verdana, tahoma, sans-serif;
ab2374d3 562 }
d84c4dab 563 a {
564 font-family: verdana, tahoma, sans-serif;
565 }
d114e033 566 :link, :visited {
567 text-decoration: none;
568 color: #b00;
569 border-bottom: 1px dotted #bbb;
570 }
571 :link:hover, :visited:hover {
d114e033 572 color: #555;
573 }
ab2374d3 574 div#topbar {
575 margin: 0px;
576 }
3e82a295 577 pre {
3e82a295 578 margin: 10px;
579 padding: 8px;
580 }
ab2374d3 581 div#answers {
582 padding: 8px;
583 margin: 10px;
d114e033 584 background-color: #fff;
ab2374d3 585 border: 1px solid #aaa;
586 -moz-border-radius: 10px;
587 }
588 h1 {
33108eaf 589 font-size: 0.9em;
590 font-weight: normal;
ab2374d3 591 text-align: center;
592 }
593 h2 {
594 font-size: 1.0em;
595 }
596 p {
597 font-size: 0.9em;
598 }
ae7c5252 599 p img {
600 float: right;
601 margin-left: 10px;
602 }
33108eaf 603 b#appname {
604 font-size: 1.6em;
ab2374d3 605 }
606 </style>
607 </head>
608 <body>
609 <div id="content">
610 <div id="topbar">
33108eaf 611 <h1><b id="appname">$name</b> on <a href="http://catalyst.perl.org">Catalyst</a>
d84c4dab 612 $VERSION</h1>
ab2374d3 613 </div>
614 <div id="answers">
ae7c5252 615 <p>
f68d720e 616 <img src="$logo"/>
ae7c5252 617 </p>
4b8cb778 618 <p>Welcome to the wonderful world of Catalyst.
619 This MVC framework will make web development
ab2374d3 620 something you had never expected it to be:
621 Fun, rewarding and quick.</p>
622 <h2>What to do now?</h2>
4b8cb778 623 <p>That really depends on what <b>you</b> want to do.
ab2374d3 624 We do, however, provide you with a few starting points.</p>
625 <p>If you want to jump right into web development with Catalyst
5db7f9a1 626 you might want to check out the documentation.</p>
d84c4dab 627 <pre><code>perldoc <a href="http://cpansearch.perl.org/dist/Catalyst/lib/Catalyst/Manual.pod">Catalyst::Manual</a>
628perldoc <a href="http://cpansearch.perl.org/dist/Catalyst/lib/Catalyst/Manual/Intro.pod">Catalyst::Manual::Intro</a></code></pre>
ab2374d3 629 <p>If you would like some background information on the
4b8cb778 630 MVC-pattern, these links might be of help to you.</p>
ab2374d3 631 <ul>
632 <li>
3e82a295 633 <a href="http://dev.catalyst.perl.org/wiki/Models">
ab2374d3 634 Introduction to Models
635 </a>
636 </li>
637 <li>
3e82a295 638 <a href="http://dev.catalyst.perl.org/wiki/Views">
ab2374d3 639 Introduction to Views
640 </a>
641 </li>
642 <li>
3e82a295 643 <a href="http://dev.catalyst.perl.org/wiki/Controllers">
ab2374d3 644 Introduction to Controllers
645 </a>
646 </li>
647 </ul>
648 <h2>What to do next?</h2>
16215972 649 <p>Next you need to create an actual application. Use the
ab2374d3 650 helper scripts for what they are worth, they can save you
4b8cb778 651 a lot of work getting everything set up. Also, be sure to
652 check out the vast array of plugins for Catalyst on CPAN.
16215972 653 They can handle everything from A to Z
654 , and a whole lot in between.</p>
82245cc4 655 <h2>Need help?</h2>
16215972 656 <p>Catalyst has a very active community. The main places to get
657 in touch are these.</p>
658 <ul>
659 <li>
2b9a7d76 660 <a href="http://dev.catalyst.perl.org">Wiki</a>
16215972 661 </li>
662 <li>
663 <a href="http://lists.rawmode.org/mailman/listinfo/catalyst">Mailing-List</a>
664 </li>
665 <li>
ea7cd80d 666 <a href="irc://irc.perl.org/catalyst">IRC channel #catalyst on irc.perl.org</a>
16215972 667 </li>
668 </ul>
ab2374d3 669 <h2>In conclusion</h2>
ea7cd80d 670 <p>The Catalyst team hope you will enjoy using Catalyst as much
4b8cb778 671 as we enjoyed making it, and rest assured that any and all
ea7cd80d 672 feedback is welcomed.</p>
ab2374d3 673 </div>
674 </div>
675 </body>
676</html>
677EOF
678}
679
fbcc39ad 680=back
681
682=head1 INTERNAL METHODS
683
684=over 4
685
686=item $c->benchmark($coderef)
687
688Takes a coderef with arguments and returns elapsed time as float.
689
690 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
691 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
692
693=cut
694
695sub benchmark {
696 my $c = shift;
697 my $code = shift;
698 my $time = [gettimeofday];
699 my @return = &$code(@_);
700 my $elapsed = tv_interval $time;
701 return wantarray ? ( $elapsed, @return ) : $elapsed;
702}
703
704=item $c->components
705
706Contains the components.
707
708=item $c->counter
709
710Returns a hashref containing coderefs and execution counts.
711(Needed for deep recursion detection)
712
713=item $c->depth
714
715Returns the actual forward depth.
716
717=item $c->dispatch
718
719Dispatch request to actions.
720
721=cut
722
723sub dispatch { my $c = shift; $c->dispatcher->dispatch( $c, @_ ) }
724
725=item $c->execute($class, $coderef)
726
727Execute a coderef in given class and catch exceptions.
728Errors are available via $c->error.
729
730=cut
731
732sub execute {
733 my ( $c, $class, $code ) = @_;
734 $class = $c->components->{$class} || $class;
735 $c->state(0);
736 my $callsub = ( caller(1) )[3];
737
738 my $action = '';
739 if ( $c->debug ) {
740 $action = "$code";
741 $action = "/$action" unless $action =~ /\-\>/;
742 $c->counter->{"$code"}++;
743
744 if ( $c->counter->{"$code"} > $RECURSION ) {
745 my $error = qq/Deep recursion detected in "$action"/;
746 $c->log->error($error);
747 $c->error($error);
748 $c->state(0);
749 return $c->state;
750 }
751
752 $action = "-> $action" if $callsub =~ /forward$/;
753 }
754 $c->{depth}++;
755 eval {
756 if ( $c->debug )
757 {
758 my ( $elapsed, @state ) =
759 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
760 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
761 $c->state(@state);
762 }
763 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }
764 };
765 $c->{depth}--;
766
767 if ( my $error = $@ ) {
768
769 if ( $error eq $DETACH ) { die $DETACH if $c->{depth} > 1 }
770 else {
771 unless ( ref $error ) {
772 chomp $error;
773 $error = qq/Caught exception "$error"/;
774 }
775
776 $c->log->error($error);
777 $c->error($error);
778 $c->state(0);
779 }
780 }
781 return $c->state;
782}
783
784=item $c->finalize
785
786Finalize request.
787
788=cut
789
790sub finalize {
791 my $c = shift;
792
793 $c->finalize_uploads;
794
795 # Error
796 if ( $#{ $c->error } >= 0 ) {
797 $c->finalize_error;
798 }
799
800 $c->finalize_headers;
801
802 # HEAD request
803 if ( $c->request->method eq 'HEAD' ) {
804 $c->response->body('');
805 }
806
807 $c->finalize_body;
808
809 return $c->response->status;
810}
811
812=item $c->finalize_body
813
814Finalize body.
815
816=cut
817
818sub finalize_body { my $c = shift; $c->engine->finalize_body( $c, @_ ) }
819
820=item $c->finalize_cookies
821
822Finalize cookies.
823
824=cut
825
826sub finalize_cookies { my $c = shift; $c->engine->finalize_cookies( $c, @_ ) }
827
828=item $c->finalize_error
829
830Finalize error.
831
832=cut
833
834sub finalize_error { my $c = shift; $c->engine->finalize_error( $c, @_ ) }
835
836=item $c->finalize_headers
837
838Finalize headers.
839
840=cut
841
842sub finalize_headers {
843 my $c = shift;
844
845 # Check if we already finalized headers
846 return if $c->response->{_finalized_headers};
847
848 # Handle redirects
849 if ( my $location = $c->response->redirect ) {
850 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
851 $c->response->header( Location => $location );
852 }
853
854 # Content-Length
855 if ( $c->response->body && !$c->response->content_length ) {
856 $c->response->content_length( bytes::length( $c->response->body ) );
857 }
858
859 # Errors
860 if ( $c->response->status =~ /^(1\d\d|[23]04)$/ ) {
861 $c->response->headers->remove_header("Content-Length");
862 $c->response->body('');
863 }
864
865 $c->finalize_cookies;
866
867 $c->engine->finalize_headers( $c, @_ );
868
869 # Done
870 $c->response->{_finalized_headers} = 1;
871}
872
873=item $c->finalize_output
874
875An alias for finalize_body.
876
877=item $c->finalize_read
878
879Finalize the input after reading is complete.
880
881=cut
882
883sub finalize_read { my $c = shift; $c->engine->finalize_read( $c, @_ ) }
884
885=item $c->finalize_uploads
886
887Finalize uploads. Cleans up any temporary files.
888
889=cut
890
891sub finalize_uploads { my $c = shift; $c->engine->finalize_uploads( $c, @_ ) }
892
893=item $c->get_action( $action, $namespace, $inherit )
894
895Get an action in a given namespace.
896
897=cut
898
899sub get_action { my $c = shift; $c->dispatcher->get_action( $c, @_ ) }
900
901=item handle_request( $class, @arguments )
902
903Handles the request.
904
905=cut
906
907sub handle_request {
908 my ( $class, @arguments ) = @_;
909
910 # Always expect worst case!
911 my $status = -1;
912 eval {
913 my @stats = ();
914
915 my $handler = sub {
916 my $c = $class->prepare(@arguments);
917 $c->{stats} = \@stats;
918 $c->dispatch;
919 return $c->finalize;
920 };
921
922 if ( $class->debug ) {
923 my $elapsed;
924 ( $elapsed, $status ) = $class->benchmark($handler);
925 $elapsed = sprintf '%f', $elapsed;
926 my $av = sprintf '%.3f',
927 ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) );
928 my $t = Text::ASCIITable->new;
929 $t->setCols( 'Action', 'Time' );
930 $t->setColWidth( 'Action', 64, 1 );
931 $t->setColWidth( 'Time', 9, 1 );
932
933 for my $stat (@stats) { $t->addRow( $stat->[0], $stat->[1] ) }
934 $class->log->info(
935 "Request took ${elapsed}s ($av/s)\n" . $t->draw );
936 }
937 else { $status = &$handler }
938
939 };
940
941 if ( my $error = $@ ) {
942 chomp $error;
943 $class->log->error(qq/Caught exception in engine "$error"/);
944 }
945
946 $COUNT++;
947 $class->log->_flush() if $class->log->can('_flush');
948 return $status;
949}
950
951=item $c->prepare(@arguments)
952
953Turns the engine-specific request( Apache, CGI ... )
954into a Catalyst context .
955
956=cut
957
958sub prepare {
959 my ( $class, @arguments ) = @_;
960
961 my $c = bless {
962 counter => {},
963 depth => 0,
964 request => Catalyst::Request->new(
965 {
966 arguments => [],
967 body_parameters => {},
968 cookies => {},
fbcc39ad 969 headers => HTTP::Headers->new,
970 parameters => {},
971 query_parameters => {},
972 secure => 0,
973 snippets => [],
974 uploads => {}
975 }
976 ),
977 response => Catalyst::Response->new(
978 {
979 body => '',
980 cookies => {},
fbcc39ad 981 headers => HTTP::Headers->new(),
982 status => 200
983 }
984 ),
985 stash => {},
986 state => 0
987 }, $class;
988
989 # For on-demand data
990 $c->request->{_context} = $c;
991 $c->response->{_context} = $c;
992 weaken( $c->request->{_context} );
993 weaken( $c->response->{_context} );
994
995 if ( $c->debug ) {
996 my $secs = time - $START || 1;
997 my $av = sprintf '%.3f', $COUNT / $secs;
998 $c->log->debug('**********************************');
999 $c->log->debug("* Request $COUNT ($av/s) [$$]");
1000 $c->log->debug('**********************************');
1001 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
1002 }
1003
1004 $c->prepare_request(@arguments);
1005 $c->prepare_connection;
1006 $c->prepare_query_parameters;
1007 $c->prepare_headers;
1008 $c->prepare_cookies;
1009 $c->prepare_path;
1010
1011 # On-demand parsing
1012 $c->prepare_body unless $c->config->{parse_on_demand};
1013
1014 $c->prepare_action;
1015 my $method = $c->req->method || '';
1016 my $path = $c->req->path || '';
1017 my $address = $c->req->address || '';
1018
1019 $c->log->debug(qq/"$method" request for "$path" from $address/)
1020 if $c->debug;
1021
1022 return $c;
1023}
1024
1025=item $c->prepare_action
1026
1027Prepare action.
1028
1029=cut
1030
1031sub prepare_action { my $c = shift; $c->dispatcher->prepare_action( $c, @_ ) }
1032
1033=item $c->prepare_body
1034
1035Prepare message body.
1036
1037=cut
1038
1039sub prepare_body {
1040 my $c = shift;
1041
1042 # Do we run for the first time?
1043 return if defined $c->request->{_body};
1044
1045 # Initialize on-demand data
1046 $c->engine->prepare_body( $c, @_ );
1047 $c->prepare_parameters;
1048 $c->prepare_uploads;
1049
1050 if ( $c->debug && keys %{ $c->req->body_parameters } ) {
1051 my $t = Text::ASCIITable->new;
1052 $t->setCols( 'Key', 'Value' );
1053 $t->setColWidth( 'Key', 37, 1 );
1054 $t->setColWidth( 'Value', 36, 1 );
1055 $t->alignCol( 'Value', 'right' );
1056 for my $key ( sort keys %{ $c->req->body_parameters } ) {
1057 my $param = $c->req->body_parameters->{$key};
1058 my $value = defined($param) ? $param : '';
1059 $t->addRow( $key,
1060 ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
1061 }
1062 $c->log->debug( "Body Parameters are:\n" . $t->draw );
1063 }
1064}
1065
4bd82c41 1066=item $c->prepare_body_chunk( $chunk )
1067
1068Prepare a chunk of data before sending it to HTTP::Body.
1069
1070=cut
1071
4f5ebacd 1072sub prepare_body_chunk {
1073 my $c = shift;
4bd82c41 1074 $c->engine->prepare_body_chunk( $c, @_ );
1075}
1076
fbcc39ad 1077=item $c->prepare_body_parameters
1078
1079Prepare body parameters.
1080
1081=cut
1082
1083sub prepare_body_parameters {
1084 my $c = shift;
1085 $c->engine->prepare_body_parameters( $c, @_ );
1086}
1087
1088=item $c->prepare_connection
1089
1090Prepare connection.
1091
1092=cut
1093
1094sub prepare_connection {
1095 my $c = shift;
1096 $c->engine->prepare_connection( $c, @_ );
1097}
1098
1099=item $c->prepare_cookies
1100
1101Prepare cookies.
1102
1103=cut
1104
1105sub prepare_cookies { my $c = shift; $c->engine->prepare_cookies( $c, @_ ) }
1106
1107=item $c->prepare_headers
1108
1109Prepare headers.
1110
1111=cut
1112
1113sub prepare_headers { my $c = shift; $c->engine->prepare_headers( $c, @_ ) }
1114
1115=item $c->prepare_parameters
1116
1117Prepare parameters.
1118
1119=cut
1120
1121sub prepare_parameters {
1122 my $c = shift;
1123 $c->prepare_body_parameters;
1124 $c->engine->prepare_parameters( $c, @_ );
1125}
1126
1127=item $c->prepare_path
1128
1129Prepare path and base.
1130
1131=cut
1132
1133sub prepare_path { my $c = shift; $c->engine->prepare_path( $c, @_ ) }
1134
1135=item $c->prepare_query_parameters
1136
1137Prepare query parameters.
1138
1139=cut
1140
1141sub prepare_query_parameters {
1142 my $c = shift;
1143
1144 $c->engine->prepare_query_parameters( $c, @_ );
1145
1146 if ( $c->debug && keys %{ $c->request->query_parameters } ) {
1147 my $t = Text::ASCIITable->new;
1148 $t->setCols( 'Key', 'Value' );
1149 $t->setColWidth( 'Key', 37, 1 );
1150 $t->setColWidth( 'Value', 36, 1 );
1151 $t->alignCol( 'Value', 'right' );
1152 for my $key ( sort keys %{ $c->req->query_parameters } ) {
1153 my $param = $c->req->query_parameters->{$key};
1154 my $value = defined($param) ? $param : '';
1155 $t->addRow( $key,
1156 ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
1157 }
1158 $c->log->debug( "Query Parameters are:\n" . $t->draw );
1159 }
1160}
1161
1162=item $c->prepare_read
1163
1164Prepare the input for reading.
1165
1166=cut
1167
1168sub prepare_read { my $c = shift; $c->engine->prepare_read( $c, @_ ) }
1169
1170=item $c->prepare_request
1171
1172Prepare the engine request.
1173
1174=cut
1175
1176sub prepare_request { my $c = shift; $c->engine->prepare_request( $c, @_ ) }
1177
1178=item $c->prepare_uploads
1179
1180Prepare uploads.
1181
1182=cut
1183
1184sub prepare_uploads {
1185 my $c = shift;
1186
1187 $c->engine->prepare_uploads( $c, @_ );
1188
1189 if ( $c->debug && keys %{ $c->request->uploads } ) {
1190 my $t = Text::ASCIITable->new;
bc2beef5 1191 $t->setCols( 'Key', 'Filename', 'Type', 'Size' );
1192 $t->setColWidth( 'Key', 12, 1 );
1193 $t->setColWidth( 'Filename', 28, 1 );
1194 $t->setColWidth( 'Type', 18, 1 );
fbcc39ad 1195 $t->setColWidth( 'Size', 9, 1 );
1196 $t->alignCol( 'Size', 'left' );
1197 for my $key ( sort keys %{ $c->request->uploads } ) {
1198 my $upload = $c->request->uploads->{$key};
1199 for my $u ( ref $upload eq 'ARRAY' ? @{$upload} : ($upload) ) {
bc2beef5 1200 $t->addRow( $key, $u->filename, $u->type, $u->size );
fbcc39ad 1201 }
1202 }
1203 $c->log->debug( "File Uploads are:\n" . $t->draw );
1204 }
1205}
1206
1207=item $c->prepare_write
1208
1209Prepare the output for writing.
1210
1211=cut
1212
1213sub prepare_write { my $c = shift; $c->engine->prepare_write( $c, @_ ) }
1214
1215=item $c->read( [$maxlength] )
1216
1217Read a chunk of data from the request body. This method is designed to be
1218used in a while loop, reading $maxlength bytes on every call. $maxlength
1219defaults to the size of the request if not specified.
1220
1221You have to set MyApp->config->{parse_on_demand} to use this directly.
1222
1223=cut
1224
1225sub read { my $c = shift; return $c->engine->read( $c, @_ ) }
1226
1227=item $c->run
1228
1229Starts the engine.
1230
1231=cut
1232
1233sub run { my $c = shift; return $c->engine->run( $c, @_ ) }
1234
1235=item $c->set_action( $action, $code, $namespace, $attrs )
1236
1237Set an action in a given namespace.
1238
1239=cut
1240
1241sub set_action { my $c = shift; $c->dispatcher->set_action( $c, @_ ) }
1242
1243=item $c->setup_actions($component)
1244
1245Setup actions for a component.
1246
1247=cut
1248
1249sub setup_actions { my $c = shift; $c->dispatcher->setup_actions( $c, @_ ) }
1250
1251=item $c->setup_components
1252
1253Setup components.
1254
1255=cut
1256
1257sub setup_components {
1258 my $class = shift;
1259
1260 my $callback = sub {
1261 my ( $component, $context ) = @_;
1262
1263 unless ( $component->isa('Catalyst::Base') ) {
1264 return $component;
1265 }
1266
1267 my $suffix = Catalyst::Utils::class2classsuffix($component);
1268 my $config = $class->config->{$suffix} || {};
1269
1270 my $instance;
1271
1272 eval { $instance = $component->new( $context, $config ); };
1273
1274 if ( my $error = $@ ) {
1275
1276 chomp $error;
1277
1278 Catalyst::Exception->throw( message =>
1279 qq/Couldn't instantiate component "$component", "$error"/ );
1280 }
1281
1282 Catalyst::Exception->throw( message =>
1283qq/Couldn't instantiate component "$component", "new() didn't return a object"/
1284 )
1285 unless ref $instance;
1286 return $instance;
1287 };
1288
1289 eval {
1290 Module::Pluggable::Fast->import(
1291 name => '_catalyst_components',
1292 search => [
1293 "$class\::Controller", "$class\::C",
1294 "$class\::Model", "$class\::M",
1295 "$class\::View", "$class\::V"
1296 ],
1297 callback => $callback
1298 );
1299 };
1300
1301 if ( my $error = $@ ) {
1302
1303 chomp $error;
1304
1305 Catalyst::Exception->throw(
1306 message => qq/Couldn't load components "$error"/ );
1307 }
1308
1309 for my $component ( $class->_catalyst_components($class) ) {
1310 $class->components->{ ref $component || $component } = $component;
1311 }
1312}
1313
1314=item $c->setup_dispatcher
1315
1316=cut
1317
1318sub setup_dispatcher {
1319 my ( $class, $dispatcher ) = @_;
1320
1321 if ($dispatcher) {
1322 $dispatcher = 'Catalyst::Dispatcher::' . $dispatcher;
1323 }
1324
1325 if ( $ENV{CATALYST_DISPATCHER} ) {
1326 $dispatcher = 'Catalyst::Dispatcher::' . $ENV{CATALYST_DISPATCHER};
1327 }
1328
1329 if ( $ENV{ uc($class) . '_DISPATCHER' } ) {
1330 $dispatcher =
1331 'Catalyst::Dispatcher::' . $ENV{ uc($class) . '_DISPATCHER' };
1332 }
1333
1334 unless ($dispatcher) {
1335 $dispatcher = 'Catalyst::Dispatcher';
1336 }
1337
1338 $dispatcher->require;
1339
1340 if ($@) {
1341 Catalyst::Exception->throw(
1342 message => qq/Couldn't load dispatcher "$dispatcher", "$@"/ );
1343 }
1344
1345 # dispatcher instance
1346 $class->dispatcher( $dispatcher->new );
1347}
1348
1349=item $c->setup_engine
1350
1351=cut
1352
1353sub setup_engine {
1354 my ( $class, $engine ) = @_;
1355
1356 if ($engine) {
1357 $engine = 'Catalyst::Engine::' . $engine;
1358 }
1359
1360 if ( $ENV{CATALYST_ENGINE} ) {
1361 $engine = 'Catalyst::Engine::' . $ENV{CATALYST_ENGINE};
1362 }
1363
1364 if ( $ENV{ uc($class) . '_ENGINE' } ) {
1365 $engine = 'Catalyst::Engine::' . $ENV{ uc($class) . '_ENGINE' };
1366 }
1367
1368 if ( !$engine && $ENV{MOD_PERL} ) {
1369
1370 # create the apache method
1371 {
1372 no strict 'refs';
1373 *{"$class\::apache"} = sub { shift->engine->apache };
1374 }
1375
1376 my ( $software, $version ) =
1377 $ENV{MOD_PERL} =~ /^(\S+)\/(\d+(?:[\.\_]\d+)+)/;
1378
1379 $version =~ s/_//g;
1380 $version =~ s/(\.[^.]+)\./$1/g;
1381
1382 if ( $software eq 'mod_perl' ) {
1383
1384 if ( $version >= 1.99922 ) {
1385 $engine = 'Catalyst::Engine::Apache2::MP20';
1386 }
1387
1388 elsif ( $version >= 1.9901 ) {
1389 $engine = 'Catalyst::Engine::Apache2::MP19';
1390 }
1391
1392 elsif ( $version >= 1.24 ) {
1393 $engine = 'Catalyst::Engine::Apache::MP13';
1394 }
1395
1396 else {
1397 Catalyst::Exception->throw( message =>
1398 qq/Unsupported mod_perl version: $ENV{MOD_PERL}/ );
1399 }
1400
1401 # install the correct mod_perl handler
1402 if ( $version >= 1.9901 ) {
1403 *handler = sub : method {
1404 shift->handle_request(@_);
1405 };
1406 }
1407 else {
1408 *handler = sub ($$) { shift->handle_request(@_) };
1409 }
1410
1411 }
1412
1413 elsif ( $software eq 'Zeus-Perl' ) {
1414 $engine = 'Catalyst::Engine::Zeus';
1415 }
1416
1417 else {
1418 Catalyst::Exception->throw(
1419 message => qq/Unsupported mod_perl: $ENV{MOD_PERL}/ );
1420 }
1421 }
1422
1423 unless ($engine) {
1424 $engine = 'Catalyst::Engine::CGI';
1425 }
1426
1427 $engine->require;
1428
1429 if ($@) {
1430 Catalyst::Exception->throw( message =>
1431qq/Couldn't load engine "$engine" (maybe you forgot to install it?), "$@"/
1432 );
1433 }
1434
1435 # engine instance
1436 $class->engine( $engine->new );
1437}
1438
1439=item $c->setup_home
1440
1441=cut
1442
1443sub setup_home {
1444 my ( $class, $home ) = @_;
1445
1446 if ( $ENV{CATALYST_HOME} ) {
1447 $home = $ENV{CATALYST_HOME};
1448 }
1449
1450 if ( $ENV{ uc($class) . '_HOME' } ) {
1451 $home = $ENV{ uc($class) . '_HOME' };
1452 }
1453
1454 unless ($home) {
1455 $home = Catalyst::Utils::home($class);
1456 }
1457
1458 if ($home) {
1459 $class->config->{home} ||= $home;
1460 $class->config->{root} ||= dir($home)->subdir('root');
1461 }
1462}
1463
1464=item $c->setup_log
1465
1466=cut
1467
1468sub setup_log {
1469 my ( $class, $debug ) = @_;
1470
1471 unless ( $class->log ) {
1472 $class->log( Catalyst::Log->new );
1473 }
1474
1475 if ( $ENV{CATALYST_DEBUG} || $ENV{ uc($class) . '_DEBUG' } || $debug ) {
1476 no strict 'refs';
1477 *{"$class\::debug"} = sub { 1 };
1478 $class->log->debug('Debug messages enabled');
1479 }
1480}
1481
1482=item $c->setup_plugins
1483
1484=cut
1485
1486sub setup_plugins {
1487 my ( $class, $plugins ) = @_;
1488
1489 $plugins ||= [];
1490 for my $plugin ( reverse @$plugins ) {
1491
1492 $plugin = "Catalyst::Plugin::$plugin";
1493
1494 $plugin->require;
1495
1496 if ($@) {
1497 Catalyst::Exception->throw(
1498 message => qq/Couldn't load plugin "$plugin", "$@"/ );
1499 }
1500
1501 {
1502 no strict 'refs';
1503 unshift @{"$class\::ISA"}, $plugin;
1504 }
1505 }
1506}
1507
1508=item $c->write( $data )
1509
1510Writes $data to the output stream. When using this method directly, you will
1511need to manually set the Content-Length header to the length of your output
1512data, if known.
1513
1514=cut
1515
4f5ebacd 1516sub write {
1517 my $c = shift;
1518
1519 # Finalize headers if someone manually writes output
1520 $c->finalize_headers;
1521
1522 return $c->engine->write( $c, @_ );
1523}
fbcc39ad 1524
23f9d934 1525=back
1526
d2ee9760 1527=head1 CASE SENSITIVITY
1528
1529By default Catalyst is not case sensitive, so C<MyApp::C::FOO::Bar> becomes
1530C</foo/bar>.
1531
1532But you can activate case sensitivity with a config parameter.
1533
1534 MyApp->config->{case_sensitive} = 1;
1535
fbcc39ad 1536So C<MyApp::C::Foo::Bar> becomes C</Foo/Bar>.
1537
1538=head1 ON-DEMAND PARSER
1539
1540The request body is usually parsed at the beginning of a request,
1541but if you want to handle input yourself or speed things up a bit
1542you can enable on-demand parsing with a config parameter.
1543
1544 MyApp->config->{parse_on_demand} = 1;
1545
1546=head1 PROXY SUPPORT
1547
1548Many production servers operate using the common double-server approach, with
1549a lightweight frontend web server passing requests to a larger backend
1550server. An application running on the backend server must deal with two
1551problems: the remote user always appears to be '127.0.0.1' and the server's
1552hostname will appear to be 'localhost' regardless of the virtual host the
1553user connected through.
1554
1555Catalyst will automatically detect this situation when you are running both
1556the frontend and backend servers on the same machine. The following changes
1557are made to the request.
1558
1559 $c->req->address is set to the user's real IP address, as read from the
1560 HTTP_X_FORWARDED_FOR header.
1561
1562 The host value for $c->req->base and $c->req->uri is set to the real host,
1563 as read from the HTTP_X_FORWARDED_HOST header.
1564
1565Obviously, your web server must support these 2 headers for this to work.
1566
1567In a more complex server farm environment where you may have your frontend
1568proxy server(s) on different machines, you will need to set a configuration
1569option to tell Catalyst to read the proxied data from the headers.
1570
1571 MyApp->config->{using_frontend_proxy} = 1;
1572
1573If you do not wish to use the proxy support at all, you may set:
d1a31ac6 1574
fbcc39ad 1575 MyApp->config->{ignore_frontend_proxy} = 1;
1576
1577=head1 THREAD SAFETY
1578
1579Catalyst has been tested under Apache 2's threading mpm_worker, mpm_winnt,
1580and the standalone forking HTTP server on Windows. We believe the Catalyst
1581core to be thread-safe.
1582
1583If you plan to operate in a threaded environment, remember that all other
1584modules you are using must also be thread-safe. Some modules, most notably
1585DBD::SQLite, are not thread-safe.
d1a31ac6 1586
3cb1db8c 1587=head1 SUPPORT
1588
1589IRC:
1590
1591 Join #catalyst on irc.perl.org.
1592
1593Mailing-Lists:
1594
1595 http://lists.rawmode.org/mailman/listinfo/catalyst
1596 http://lists.rawmode.org/mailman/listinfo/catalyst-dev
1985c30b 1597
432d507d 1598Web:
1599
1600 http://catalyst.perl.org
1601
fc7ec1d9 1602=head1 SEE ALSO
1603
61b1e958 1604=over 4
1605
1606=item L<Catalyst::Manual> - The Catalyst Manual
1607
1608=item L<Catalyst::Engine> - Core Engine
1609
1610=item L<Catalyst::Log> - The Log Class.
1611
1612=item L<Catalyst::Request> - The Request Object
1613
1614=item L<Catalyst::Response> - The Response Object
1615
1616=item L<Catalyst::Test> - The test suite.
1617
1618=back
fc7ec1d9 1619
15f0b5b7 1620=head1 CREDITS
fc7ec1d9 1621
15f0b5b7 1622Andy Grundman
1623
fbcc39ad 1624Andy Wardley
1625
33108eaf 1626Andreas Marienborg
1627
15f0b5b7 1628Andrew Ford
1629
1630Andrew Ruthven
1631
fbcc39ad 1632Arthur Bergman
1633
15f0b5b7 1634Autrijus Tang
1635
1636Christian Hansen
1637
1638Christopher Hicks
1639
1640Dan Sully
1641
1642Danijel Milicevic
1643
1644David Naughton
1645
1646Gary Ashton Jones
1647
1648Geoff Richards
1649
1650Jesse Sheidlower
1651
fbcc39ad 1652Jesse Vincent
1653
15f0b5b7 1654Jody Belka
1655
1656Johan Lindstrom
1657
1658Juan Camacho
1659
1660Leon Brocard
1661
1662Marcus Ramberg
1663
1664Matt S Trout
1665
71c3bcc3 1666Robert Sedlacek
1667
15f0b5b7 1668Tatsuhiko Miyagawa
fc7ec1d9 1669
51f0308d 1670Ulf Edvinsson
1671
bdcb95ef 1672Yuval Kogman
1673
51f0308d 1674=head1 AUTHOR
1675
1676Sebastian Riedel, C<sri@oook.de>
1677
fc7ec1d9 1678=head1 LICENSE
1679
1680This library is free software . You can redistribute it and/or modify it under
1681the same terms as perl itself.
1682
1683=cut
1684
16851;