Fixed Catalyst pod
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
424b2705 4use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
d70195d8 5use attributes ();
fc7ec1d9 6use UNIVERSAL::require;
6dc87a0f 7use CGI::Cookie;
fc7ec1d9 8use Data::Dumper;
9use HTML::Entities;
10use HTTP::Headers;
11use Time::HiRes qw/gettimeofday tv_interval/;
0f7ecc53 12use Text::ASCIITable;
fc7ec1d9 13use Catalyst::Request;
146554c5 14use Catalyst::Request::Upload;
fc7ec1d9 15use Catalyst::Response;
a268a011 16use Catalyst::Utils;
fc7ec1d9 17
18require Module::Pluggable::Fast;
19
99fe1710 20# For pretty dumps
fc7ec1d9 21$Data::Dumper::Terse = 1;
22
1abd6db7 23__PACKAGE__->mk_classdata('components');
6ef62eb2 24__PACKAGE__->mk_accessors(qw/counter depth request response state/);
fc7ec1d9 25
fc7ec1d9 26*comp = \&component;
27*req = \&request;
28*res = \&response;
29
06e1b616 30# For backwards compatibility
31*finalize_output = \&finalize_body;
32
99fe1710 33# For statistics
e88fa058 34our $COUNT = 1;
35our $START = time;
36our $RECURSION = 1000;
6ef62eb2 37our $DETACH = "catalyst_detach\n";
fc7ec1d9 38
39=head1 NAME
40
41Catalyst::Engine - The Catalyst Engine
42
43=head1 SYNOPSIS
44
45See L<Catalyst>.
46
47=head1 DESCRIPTION
48
23f9d934 49=head1 METHODS
fc7ec1d9 50
23f9d934 51=over 4
52
23f9d934 53=item $c->benchmark($coderef)
fc7ec1d9 54
55Takes a coderef with arguments and returns elapsed time as float.
56
57 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
58 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
59
60=cut
61
62sub benchmark {
63 my $c = shift;
64 my $code = shift;
65 my $time = [gettimeofday];
66 my @return = &$code(@_);
67 my $elapsed = tv_interval $time;
68 return wantarray ? ( $elapsed, @return ) : $elapsed;
69}
70
23f9d934 71=item $c->comp($name)
72
73=item $c->component($name)
fc7ec1d9 74
75Get a component object by name.
76
77 $c->comp('MyApp::Model::MyModel')->do_stuff;
78
79Regex search for a component.
80
81 $c->comp('mymodel')->do_stuff;
82
83=cut
84
85sub component {
3245f607 86 my $c = shift;
99fe1710 87
e88fa058 88 if (@_) {
99fe1710 89
3245f607 90 my $name = shift;
91
92 if ( my $component = $c->components->{$name} ) {
93 return $component;
94 }
95
96 else {
97 for my $component ( keys %{ $c->components } ) {
98 return $c->components->{$component} if $component =~ /$name/i;
99 }
fc7ec1d9 100 }
101 }
99fe1710 102
3245f607 103 return sort keys %{ $c->components };
fc7ec1d9 104}
105
e88fa058 106=item $c->counter
107
108Returns a hashref containing coderefs and execution counts.
109(Needed for deep recursion detection)
110
6ef62eb2 111=item $c->depth
112
113Returns the actual forward depth.
114
a554cc3b 115=item $c->error
23f9d934 116
a554cc3b 117=item $c->error($error, ...)
23f9d934 118
a554cc3b 119=item $c->error($arrayref)
fc7ec1d9 120
a554cc3b 121Returns an arrayref containing error messages.
fc7ec1d9 122
a554cc3b 123 my @error = @{ $c->error };
fc7ec1d9 124
125Add a new error.
126
a554cc3b 127 $c->error('Something bad happened');
fc7ec1d9 128
129=cut
130
a554cc3b 131sub error {
fc7ec1d9 132 my $c = shift;
a554cc3b 133 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
134 push @{ $c->{error} }, @$error;
135 return $c->{error};
fc7ec1d9 136}
137
6dc87a0f 138=item $c->execute($class, $coderef)
139
140Execute a coderef in given class and catch exceptions.
141Errors are available via $c->error.
142
143=cut
144
145sub execute {
146 my ( $c, $class, $code ) = @_;
91571b7b 147 $class = $c->components->{$class} || $class;
6dc87a0f 148 $c->state(0);
39de91b0 149 my $callsub = ( caller(1) )[3];
99fe1710 150
e88fa058 151 my $action = '';
152 if ( $c->debug ) {
153 $action = $c->actions->{reverse}->{"$code"};
154 $action = "/$action" unless $action =~ /\-\>/;
155 $c->counter->{"$code"}++;
156
157 if ( $c->counter->{"$code"} > $RECURSION ) {
158 my $error = qq/Deep recursion detected in "$action"/;
159 $c->log->error($error);
160 $c->error($error);
161 $c->state(0);
162 return $c->state;
163 }
164
165 $action = "-> $action" if $callsub =~ /forward$/;
166 }
167
6ef62eb2 168 $c->{depth}++;
6dc87a0f 169 eval {
170 if ( $c->debug )
171 {
6ef62eb2 172 my ( $elapsed, @state ) =
173 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
0f7ecc53 174 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
6dc87a0f 175 $c->state(@state);
176 }
3ceed047 177 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }
6dc87a0f 178 };
6ef62eb2 179 $c->{depth}--;
99fe1710 180
6dc87a0f 181 if ( my $error = $@ ) {
b9ffe28b 182
6ef62eb2 183 if ( $error eq $DETACH ) { die $DETACH if $c->{depth} > 1 }
184 else {
185 unless ( ref $error ) {
186 chomp $error;
187 $error = qq/Caught exception "$error"/;
188 }
b9ffe28b 189
6ef62eb2 190 $c->log->error($error);
191 $c->error($error);
192 $c->state(0);
193 }
6dc87a0f 194 }
195 return $c->state;
196}
197
23f9d934 198=item $c->finalize
fc7ec1d9 199
ca39d576 200Finalize request.
fc7ec1d9 201
202=cut
203
204sub finalize {
205 my $c = shift;
23f9d934 206
6dc87a0f 207 $c->finalize_cookies;
208
49490aab 209 if ( my $location = $c->response->redirect ) {
23f9d934 210 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
6dc87a0f 211 $c->response->header( Location => $location );
e7c0c583 212 $c->response->status(302) if $c->response->status !~ /^3\d\d$/;
6dc87a0f 213 }
214
969647fd 215 if ( $#{ $c->error } >= 0 ) {
216 $c->finalize_error;
23f9d934 217 }
6ef62eb2 218
d290eee8 219 if ( !$c->response->body && $c->response->status == 200 ) {
969647fd 220 $c->finalize_error;
221 }
fc7ec1d9 222
d7945f32 223 if ( $c->response->body && !$c->response->content_length ) {
d290eee8 224 $c->response->content_length( bytes::length( $c->response->body ) );
225 }
6ef62eb2 226
d290eee8 227 if ( $c->response->status =~ /^(1\d\d|[23]04)$/ ) {
228 $c->response->headers->remove_header("Content-Length");
229 $c->response->body('');
230 }
6ef62eb2 231
d290eee8 232 if ( $c->request->method eq 'HEAD' ) {
233 $c->response->body('');
fc7ec1d9 234 }
969647fd 235
fc7ec1d9 236 my $status = $c->finalize_headers;
06e1b616 237 $c->finalize_body;
fc7ec1d9 238 return $status;
239}
240
cd3bb248 241=item $c->finalize_output
242
243<obsolete>, see finalize_body
244
06e1b616 245=item $c->finalize_body
246
247Finalize body.
248
249=cut
250
251sub finalize_body { }
252
6dc87a0f 253=item $c->finalize_cookies
254
255Finalize cookies.
256
257=cut
258
259sub finalize_cookies {
260 my $c = shift;
261
262 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
263 my $cookie = CGI::Cookie->new(
264 -name => $name,
265 -value => $cookie->{value},
266 -expires => $cookie->{expires},
267 -domain => $cookie->{domain},
268 -path => $cookie->{path},
269 -secure => $cookie->{secure} || 0
270 );
271
272 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
273 }
274}
275
969647fd 276=item $c->finalize_error
277
ca39d576 278Finalize error.
969647fd 279
280=cut
281
282sub finalize_error {
283 my $c = shift;
284
285 $c->res->headers->content_type('text/html');
286 my $name = $c->config->{name} || 'Catalyst Application';
287
288 my ( $title, $error, $infos );
289 if ( $c->debug ) {
290 $error = join '<br/>', @{ $c->error };
291 $error ||= 'No output';
292 $title = $name = "$name on Catalyst $Catalyst::VERSION";
293 my $req = encode_entities Dumper $c->req;
294 my $res = encode_entities Dumper $c->res;
295 my $stash = encode_entities Dumper $c->stash;
296 $infos = <<"";
297<br/>
298<b><u>Request</u></b><br/>
299<pre>$req</pre>
300<b><u>Response</u></b><br/>
301<pre>$res</pre>
302<b><u>Stash</u></b><br/>
303<pre>$stash</pre>
304
305 }
306 else {
307 $title = $name;
308 $error = '';
309 $infos = <<"";
310<pre>
311(en) Please come back later
312(de) Bitte versuchen sie es spaeter nocheinmal
313(nl) Gelieve te komen later terug
314(no) Vennligst prov igjen senere
315(fr) Veuillez revenir plus tard
316(es) Vuelto por favor mas adelante
317(pt) Voltado por favor mais tarde
318(it) Ritornato prego più successivamente
319</pre>
320
321 $name = '';
322 }
e060fe05 323 $c->res->body( <<"" );
969647fd 324<html>
325<head>
326 <title>$title</title>
327 <style type="text/css">
328 body {
329 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
330 Tahoma, Arial, helvetica, sans-serif;
331 color: #ddd;
332 background-color: #eee;
333 margin: 0px;
334 padding: 0px;
335 }
336 div.box {
337 background-color: #ccc;
338 border: 1px solid #aaa;
339 padding: 4px;
340 margin: 10px;
341 -moz-border-radius: 10px;
342 }
343 div.error {
344 background-color: #977;
345 border: 1px solid #755;
346 padding: 8px;
347 margin: 4px;
348 margin-bottom: 10px;
349 -moz-border-radius: 10px;
350 }
351 div.infos {
352 background-color: #797;
353 border: 1px solid #575;
354 padding: 8px;
355 margin: 4px;
356 margin-bottom: 10px;
357 -moz-border-radius: 10px;
358 }
359 div.name {
360 background-color: #779;
361 border: 1px solid #557;
362 padding: 8px;
363 margin: 4px;
364 -moz-border-radius: 10px;
365 }
366 </style>
367</head>
368<body>
369 <div class="box">
370 <div class="error">$error</div>
371 <div class="infos">$infos</div>
372 <div class="name">$name</div>
373 </div>
374</body>
375</html>
376
377}
378
23f9d934 379=item $c->finalize_headers
fc7ec1d9 380
ca39d576 381Finalize headers.
fc7ec1d9 382
383=cut
384
385sub finalize_headers { }
386
e2fd5b5f 387=item $c->handler( $class, @arguments )
fc7ec1d9 388
ca39d576 389Handles the request.
fc7ec1d9 390
391=cut
392
6dc87a0f 393sub handler {
e2fd5b5f 394 my ( $class, @arguments ) = @_;
fc7ec1d9 395
396 # Always expect worst case!
397 my $status = -1;
398 eval {
d41516b2 399 my @stats = ();
99fe1710 400
fc7ec1d9 401 my $handler = sub {
e2fd5b5f 402 my $c = $class->prepare(@arguments);
d41516b2 403 $c->{stats} = \@stats;
63b763c5 404 $c->dispatch;
fc7ec1d9 405 return $c->finalize;
406 };
99fe1710 407
fc7ec1d9 408 if ( $class->debug ) {
409 my $elapsed;
410 ( $elapsed, $status ) = $class->benchmark($handler);
411 $elapsed = sprintf '%f', $elapsed;
e88fa058 412 my $av = sprintf '%.3f',
413 ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) );
0f7ecc53 414 my $t = Text::ASCIITable->new;
415 $t->setCols( 'Action', 'Time' );
3f36a3a3 416 $t->setColWidth( 'Action', 64, 1 );
417 $t->setColWidth( 'Time', 9, 1 );
0822f9a4 418
cd677e12 419 for my $stat (@stats) { $t->addRow( $stat->[0], $stat->[1] ) }
0f7ecc53 420 $class->log->info( "Request took $elapsed" . "s ($av/s)",
421 $t->draw );
fc7ec1d9 422 }
423 else { $status = &$handler }
99fe1710 424
fc7ec1d9 425 };
99fe1710 426
fc7ec1d9 427 if ( my $error = $@ ) {
428 chomp $error;
429 $class->log->error(qq/Caught exception in engine "$error"/);
430 }
99fe1710 431
fc7ec1d9 432 $COUNT++;
433 return $status;
434}
435
e2fd5b5f 436=item $c->prepare(@arguments)
fc7ec1d9 437
a554cc3b 438Turns the engine-specific request( Apache, CGI ... )
439into a Catalyst context .
fc7ec1d9 440
441=cut
442
443sub prepare {
e2fd5b5f 444 my ( $class, @arguments ) = @_;
99fe1710 445
fc7ec1d9 446 my $c = bless {
e88fa058 447 counter => {},
6ef62eb2 448 depth => 0,
fc7ec1d9 449 request => Catalyst::Request->new(
450 {
451 arguments => [],
452 cookies => {},
453 headers => HTTP::Headers->new,
454 parameters => {},
bfde09a2 455 secure => 0,
fc7ec1d9 456 snippets => [],
457 uploads => {}
458 }
459 ),
460 response => Catalyst::Response->new(
bfde09a2 461 {
03222156 462 body => '',
bfde09a2 463 cookies => {},
d290eee8 464 headers => HTTP::Headers->new( 'Content-Length' => 0 ),
03222156 465 status => 200
bfde09a2 466 }
fc7ec1d9 467 ),
b768faa3 468 stash => {},
469 state => 0
fc7ec1d9 470 }, $class;
99fe1710 471
fc7ec1d9 472 if ( $c->debug ) {
473 my $secs = time - $START || 1;
474 my $av = sprintf '%.3f', $COUNT / $secs;
1a0250cb 475 $c->log->debug('**********************************');
fc7ec1d9 476 $c->log->debug("* Request $COUNT ($av/s) [$$]");
1a0250cb 477 $c->log->debug('**********************************');
fc7ec1d9 478 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
479 }
99fe1710 480
e2fd5b5f 481 $c->prepare_request(@arguments);
bfde09a2 482 $c->prepare_connection;
ac733264 483 $c->prepare_headers;
1a80619d 484 $c->prepare_cookies;
bfde09a2 485 $c->prepare_path;
06e1b616 486 $c->prepare_action;
99fe1710 487
6ef62eb2 488 my $method = $c->req->method || '';
489 my $path = $c->req->path || '';
490 my $address = $c->req->address || '';
06e1b616 491
b4ca0ee8 492 $c->log->debug(qq/"$method" request for "$path" from $address/)
0556eb49 493 if $c->debug;
99fe1710 494
06e1b616 495 if ( $c->request->method eq 'POST' and $c->request->content_length ) {
496
497 if ( $c->req->content_type eq 'application/x-www-form-urlencoded' ) {
498 $c->prepare_parameters;
499 }
500 elsif ( $c->req->content_type eq 'multipart/form-data' ) {
501 $c->prepare_parameters;
502 $c->prepare_uploads;
503 }
504 else {
505 $c->prepare_body;
506 }
507 }
508
509 if ( $c->request->method eq 'GET' ) {
510 $c->prepare_parameters;
511 }
c85ff642 512
513 if ( $c->debug && keys %{ $c->req->params } ) {
0f7ecc53 514 my $t = Text::ASCIITable->new;
515 $t->setCols( 'Key', 'Value' );
0822f9a4 516 $t->setColWidth( 'Key', 37, 1 );
517 $t->setColWidth( 'Value', 36, 1 );
f78172f1 518 for my $key ( sort keys %{ $c->req->params } ) {
6d1ab915 519 my $param = $c->req->params->{$key};
520 my $value = defined($param) ? $param : '';
cd677e12 521 $t->addRow( $key, $value );
c85ff642 522 }
0f7ecc53 523 $c->log->debug( 'Parameters are', $t->draw );
c85ff642 524 }
99fe1710 525
fc7ec1d9 526 return $c;
527}
528
23f9d934 529=item $c->prepare_action
fc7ec1d9 530
ca39d576 531Prepare action.
fc7ec1d9 532
533=cut
534
535sub prepare_action {
536 my $c = shift;
537 my $path = $c->req->path;
538 my @path = split /\//, $c->req->path;
539 $c->req->args( \my @args );
99fe1710 540
fc7ec1d9 541 while (@path) {
7833fdfc 542 $path = join '/', @path;
0169d3a8 543 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 544
545 # It's a regex
546 if ($#$result) {
7e5adedd 547 my $match = $result->[1];
548 my @snippets = @{ $result->[2] };
81f6fc50 549 $c->log->debug(
550 qq/Requested action is "$path" and matched "$match"/)
fc7ec1d9 551 if $c->debug;
552 $c->log->debug(
553 'Snippets are "' . join( ' ', @snippets ) . '"' )
554 if ( $c->debug && @snippets );
555 $c->req->action($match);
556 $c->req->snippets( \@snippets );
557 }
99fe1710 558
fc7ec1d9 559 else {
560 $c->req->action($path);
81f6fc50 561 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
fc7ec1d9 562 }
99fe1710 563
fc7ec1d9 564 $c->req->match($path);
fc7ec1d9 565 last;
566 }
567 unshift @args, pop @path;
568 }
99fe1710 569
fc7ec1d9 570 unless ( $c->req->action ) {
ac733264 571 $c->req->action('default');
87e67021 572 $c->req->match('');
fc7ec1d9 573 }
99fe1710 574
5783a9a5 575 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
576 if ( $c->debug && @args );
fc7ec1d9 577}
578
06e1b616 579=item $c->prepare_body
580
581Prepare message body.
582
583=cut
584
585sub prepare_body { }
586
c9afa5fc 587=item $c->prepare_connection
0556eb49 588
ca39d576 589Prepare connection.
0556eb49 590
591=cut
592
593sub prepare_connection { }
594
c9afa5fc 595=item $c->prepare_cookies
fc7ec1d9 596
ca39d576 597Prepare cookies.
fc7ec1d9 598
599=cut
600
6dc87a0f 601sub prepare_cookies {
602 my $c = shift;
603
604 if ( my $header = $c->request->header('Cookie') ) {
605 $c->req->cookies( { CGI::Cookie->parse($header) } );
606 }
607}
fc7ec1d9 608
23f9d934 609=item $c->prepare_headers
fc7ec1d9 610
ca39d576 611Prepare headers.
fc7ec1d9 612
613=cut
614
615sub prepare_headers { }
616
23f9d934 617=item $c->prepare_parameters
fc7ec1d9 618
ca39d576 619Prepare parameters.
fc7ec1d9 620
621=cut
622
623sub prepare_parameters { }
624
23f9d934 625=item $c->prepare_path
fc7ec1d9 626
ca39d576 627Prepare path and base.
fc7ec1d9 628
629=cut
630
631sub prepare_path { }
632
23f9d934 633=item $c->prepare_request
fc7ec1d9 634
ca39d576 635Prepare the engine request.
fc7ec1d9 636
637=cut
638
639sub prepare_request { }
640
23f9d934 641=item $c->prepare_uploads
fc7ec1d9 642
ca39d576 643Prepare uploads.
fc7ec1d9 644
645=cut
646
647sub prepare_uploads { }
648
c9afa5fc 649=item $c->run
650
ca39d576 651Starts the engine.
c9afa5fc 652
653=cut
654
655sub run { }
656
61b1e958 657=item $c->request
fc7ec1d9 658
ca39d576 659=item $c->req
23f9d934 660
ca39d576 661Returns a C<Catalyst::Request> object.
fc7ec1d9 662
ca39d576 663 my $req = $c->req;
61b1e958 664
665=item $c->response
666
ca39d576 667=item $c->res
668
fc7ec1d9 669Returns a C<Catalyst::Response> object.
670
671 my $res = $c->res;
672
23f9d934 673=item $class->setup
fc7ec1d9 674
ca39d576 675Setup.
fc7ec1d9 676
677 MyApp->setup;
678
679=cut
680
681sub setup {
682 my $self = shift;
a268a011 683
684 # Initialize our data structure
6ef62eb2 685 $self->components( {} );
a268a011 686
fc7ec1d9 687 $self->setup_components;
a268a011 688
689 if ( $self->debug ) {
690 my $t = Text::ASCIITable->new;
6ef62eb2 691 $t->setOptions( 'hide_HeadRow', 1 );
a268a011 692 $t->setOptions( 'hide_HeadLine', 1 );
693 $t->setCols('Class');
694 $t->setColWidth( 'Class', 75, 1 );
695 $t->addRow($_) for sort keys %{ $self->components };
696 $self->log->debug( 'Loaded components', $t->draw )
697 if ( @{ $t->{tbl_rows} } );
698 }
6ef62eb2 699
a268a011 700 # Add our self to components, since we are also a component
6ef62eb2 701 $self->components->{$self} = $self;
a268a011 702
703 $self->setup_actions;
704
fc7ec1d9 705 if ( $self->debug ) {
706 my $name = $self->config->{name} || 'Application';
707 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
708 }
709}
710
23f9d934 711=item $class->setup_components
fc7ec1d9 712
ca39d576 713Setup components.
fc7ec1d9 714
715=cut
716
717sub setup_components {
718 my $self = shift;
6ef62eb2 719
a268a011 720 my $callback = sub {
721 my ( $component, $context ) = @_;
6ef62eb2 722
a268a011 723 unless ( $component->isa('Catalyst::Base') ) {
724 return $component;
725 }
726
727 my $suffix = Catalyst::Utils::class2classsuffix($component);
728 my $config = $self->config->{$suffix} || {};
729
730 my $instance;
731
6ef62eb2 732 eval { $instance = $component->new( $context, $config ); };
a268a011 733
846772b7 734 if ( my $error = $@ ) {
735 chomp $error;
736 die qq/Couldn't instantiate component "$component", "$error"/;
a268a011 737 }
738
739 return $instance;
740 };
741
742 eval {
743 Module::Pluggable::Fast->import(
6ef62eb2 744 name => '_components',
745 search => [
a268a011 746 "$self\::Controller", "$self\::C",
747 "$self\::Model", "$self\::M",
748 "$self\::View", "$self\::V"
749 ],
750 callback => $callback
751 );
752 };
3245f607 753
b18987fe 754 if ( my $error = $@ ) {
755 chomp $error;
756 die qq/Couldn't load components "$error"/;
757 }
99fe1710 758
a268a011 759 for my $component ( $self->_components($self) ) {
760 $self->components->{ ref $component || $component } = $component;
3245f607 761 }
fc7ec1d9 762}
763
63b763c5 764=item $c->state
765
766Contains the return value of the last executed action.
767
23f9d934 768=item $c->stash
fc7ec1d9 769
ca39d576 770Returns a hashref containing all your data.
fc7ec1d9 771
772 $c->stash->{foo} ||= 'yada';
773 print $c->stash->{foo};
774
775=cut
776
777sub stash {
778 my $self = shift;
e88fa058 779 if (@_) {
c19e2f4a 780 my $stash = @_ > 1 ? {@_} : $_[0];
fc7ec1d9 781 while ( my ( $key, $val ) = each %$stash ) {
782 $self->{stash}->{$key} = $val;
783 }
784 }
785 return $self->{stash};
786}
787
23f9d934 788=back
789
fc7ec1d9 790=head1 AUTHOR
791
792Sebastian Riedel, C<sri@cpan.org>
793
794=head1 COPYRIGHT
795
796This program is free software, you can redistribute it and/or modify it under
797the same terms as Perl itself.
798
799=cut
800
8011;