add $c->finalize_error and improve $c->finalize
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
4use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
5use UNIVERSAL::require;
6dc87a0f 6use CGI::Cookie;
fc7ec1d9 7use Data::Dumper;
8use HTML::Entities;
9use HTTP::Headers;
87e67021 10use Memoize;
fc7ec1d9 11use Time::HiRes qw/gettimeofday tv_interval/;
87e67021 12use Tree::Simple;
13use Tree::Simple::Visitor::FindByPath;
fc7ec1d9 14use Catalyst::Request;
15use Catalyst::Response;
16
17require Module::Pluggable::Fast;
18
19$Data::Dumper::Terse = 1;
20
87e67021 21__PACKAGE__->mk_classdata($_) for qw/actions components tree/;
b768faa3 22__PACKAGE__->mk_accessors(qw/request response state/);
fc7ec1d9 23
24__PACKAGE__->actions(
ac733264 25 { plain => {}, private => {}, regex => {}, compiled => [], reverse => {} }
87e67021 26);
27__PACKAGE__->tree( Tree::Simple->new( 0, Tree::Simple->ROOT ) );
fc7ec1d9 28
29*comp = \&component;
30*req = \&request;
31*res = \&response;
32
33our $COUNT = 1;
34our $START = time;
35
87e67021 36memoize('_class2prefix');
37
fc7ec1d9 38=head1 NAME
39
40Catalyst::Engine - The Catalyst Engine
41
42=head1 SYNOPSIS
43
44See L<Catalyst>.
45
46=head1 DESCRIPTION
47
23f9d934 48=head1 METHODS
fc7ec1d9 49
23f9d934 50=over 4
51
23f9d934 52=item $c->benchmark($coderef)
fc7ec1d9 53
54Takes a coderef with arguments and returns elapsed time as float.
55
56 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
57 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
58
59=cut
60
61sub benchmark {
62 my $c = shift;
63 my $code = shift;
64 my $time = [gettimeofday];
65 my @return = &$code(@_);
66 my $elapsed = tv_interval $time;
67 return wantarray ? ( $elapsed, @return ) : $elapsed;
68}
69
23f9d934 70=item $c->comp($name)
71
72=item $c->component($name)
fc7ec1d9 73
74Get a component object by name.
75
76 $c->comp('MyApp::Model::MyModel')->do_stuff;
77
78Regex search for a component.
79
80 $c->comp('mymodel')->do_stuff;
81
82=cut
83
84sub component {
85 my ( $c, $name ) = @_;
86 if ( my $component = $c->components->{$name} ) {
87 return $component;
88 }
89 else {
90 for my $component ( keys %{ $c->components } ) {
91 return $c->components->{$component} if $component =~ /$name/i;
92 }
93 }
94}
95
a554cc3b 96=item $c->error
23f9d934 97
a554cc3b 98=item $c->error($error, ...)
23f9d934 99
a554cc3b 100=item $c->error($arrayref)
fc7ec1d9 101
a554cc3b 102Returns an arrayref containing error messages.
fc7ec1d9 103
a554cc3b 104 my @error = @{ $c->error };
fc7ec1d9 105
106Add a new error.
107
a554cc3b 108 $c->error('Something bad happened');
fc7ec1d9 109
110=cut
111
a554cc3b 112sub error {
fc7ec1d9 113 my $c = shift;
a554cc3b 114 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
115 push @{ $c->{error} }, @$error;
116 return $c->{error};
fc7ec1d9 117}
118
6dc87a0f 119=item $c->execute($class, $coderef)
120
121Execute a coderef in given class and catch exceptions.
122Errors are available via $c->error.
123
124=cut
125
126sub execute {
127 my ( $c, $class, $code ) = @_;
128 $class = $c->comp($class) || $class;
129 $c->state(0);
130 eval {
131 if ( $c->debug )
132 {
133 my $action = $c->actions->{reverse}->{"$code"};
134 $action = "/$action" unless $action =~ /\-\>/;
135 my ( $elapsed, @state ) =
136 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
137 push @{ $c->{stats} },
138 _prettify( $action, sprintf( '%fs', $elapsed ), '' );
139 $c->state(@state);
140 }
141 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
142 };
143 if ( my $error = $@ ) {
144 chomp $error;
145 $error = qq/Caught exception "$error"/;
146 $c->log->error($error);
147 $c->error($error) if $c->debug;
148 $c->state(0);
149 }
150 return $c->state;
151}
152
23f9d934 153=item $c->finalize
fc7ec1d9 154
155Finalize request.
156
157=cut
158
159sub finalize {
160 my $c = shift;
23f9d934 161
6dc87a0f 162 $c->finalize_cookies;
163
23f9d934 164 if ( my $location = $c->res->redirect ) {
165 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
6dc87a0f 166 $c->response->header( Location => $location );
167 $c->response->status(302);
168 }
169
969647fd 170 if ( $#{ $c->error } >= 0 ) {
171 $c->finalize_error;
23f9d934 172 }
173
969647fd 174 if ( !$c->res->output && $c->res->status !~ /^(1\d\d|[23]04)$/ ) {
175 $c->finalize_error;
176 }
fc7ec1d9 177
969647fd 178 if ( $c->res->output ) {
179 use bytes; # play safe with a utf8 aware perl
180 $c->response->content_length( length $c->res->output );
fc7ec1d9 181 }
969647fd 182
fc7ec1d9 183 my $status = $c->finalize_headers;
184 $c->finalize_output;
185 return $status;
186}
187
6dc87a0f 188=item $c->finalize_cookies
189
190Finalize cookies.
191
192=cut
193
194sub finalize_cookies {
195 my $c = shift;
196
197 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
198 my $cookie = CGI::Cookie->new(
199 -name => $name,
200 -value => $cookie->{value},
201 -expires => $cookie->{expires},
202 -domain => $cookie->{domain},
203 -path => $cookie->{path},
204 -secure => $cookie->{secure} || 0
205 );
206
207 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
208 }
209}
210
969647fd 211=item $c->finalize_error
212
213Finalize error.
214
215=cut
216
217sub finalize_error {
218 my $c = shift;
219
220 $c->res->headers->content_type('text/html');
221 my $name = $c->config->{name} || 'Catalyst Application';
222
223 my ( $title, $error, $infos );
224 if ( $c->debug ) {
225 $error = join '<br/>', @{ $c->error };
226 $error ||= 'No output';
227 $title = $name = "$name on Catalyst $Catalyst::VERSION";
228 my $req = encode_entities Dumper $c->req;
229 my $res = encode_entities Dumper $c->res;
230 my $stash = encode_entities Dumper $c->stash;
231 $infos = <<"";
232<br/>
233<b><u>Request</u></b><br/>
234<pre>$req</pre>
235<b><u>Response</u></b><br/>
236<pre>$res</pre>
237<b><u>Stash</u></b><br/>
238<pre>$stash</pre>
239
240 }
241 else {
242 $title = $name;
243 $error = '';
244 $infos = <<"";
245<pre>
246(en) Please come back later
247(de) Bitte versuchen sie es spaeter nocheinmal
248(nl) Gelieve te komen later terug
249(no) Vennligst prov igjen senere
250(fr) Veuillez revenir plus tard
251(es) Vuelto por favor mas adelante
252(pt) Voltado por favor mais tarde
253(it) Ritornato prego più successivamente
254</pre>
255
256 $name = '';
257 }
258 $c->res->output( <<"" );
259<html>
260<head>
261 <title>$title</title>
262 <style type="text/css">
263 body {
264 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
265 Tahoma, Arial, helvetica, sans-serif;
266 color: #ddd;
267 background-color: #eee;
268 margin: 0px;
269 padding: 0px;
270 }
271 div.box {
272 background-color: #ccc;
273 border: 1px solid #aaa;
274 padding: 4px;
275 margin: 10px;
276 -moz-border-radius: 10px;
277 }
278 div.error {
279 background-color: #977;
280 border: 1px solid #755;
281 padding: 8px;
282 margin: 4px;
283 margin-bottom: 10px;
284 -moz-border-radius: 10px;
285 }
286 div.infos {
287 background-color: #797;
288 border: 1px solid #575;
289 padding: 8px;
290 margin: 4px;
291 margin-bottom: 10px;
292 -moz-border-radius: 10px;
293 }
294 div.name {
295 background-color: #779;
296 border: 1px solid #557;
297 padding: 8px;
298 margin: 4px;
299 -moz-border-radius: 10px;
300 }
301 </style>
302</head>
303<body>
304 <div class="box">
305 <div class="error">$error</div>
306 <div class="infos">$infos</div>
307 <div class="name">$name</div>
308 </div>
309</body>
310</html>
311
312}
313
23f9d934 314=item $c->finalize_headers
fc7ec1d9 315
316Finalize headers.
317
318=cut
319
320sub finalize_headers { }
321
23f9d934 322=item $c->finalize_output
fc7ec1d9 323
324Finalize output.
325
326=cut
327
328sub finalize_output { }
329
23f9d934 330=item $c->forward($command)
fc7ec1d9 331
ac733264 332Forward processing to a private action or a method from a class.
fc7ec1d9 333If you define a class without method it will default to process().
334
6196207f 335 $c->forward('/foo');
ac733264 336 $c->forward('index');
fc7ec1d9 337 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
338 $c->forward('MyApp::View::TT');
339
340=cut
341
342sub forward {
343 my $c = shift;
344 my $command = shift;
345 unless ($command) {
346 $c->log->debug('Nothing to forward to') if $c->debug;
347 return 0;
348 }
ac733264 349 my $caller = caller(0);
350 my $namespace = '/';
6196207f 351 if ( $command =~ /^\// ) {
89c5fe2d 352 $command =~ /^(.*)\/(\w+)$/;
353 $namespace = $1 || '/';
354 $command = $2;
355 }
ac733264 356 else { $namespace = _class2prefix($caller) || '/' }
66d9e175 357 my $results = $c->get_action( $command, $namespace );
ac733264 358 unless ( @{$results} ) {
b768faa3 359 my $class = $command;
fc7ec1d9 360 if ( $class =~ /[^\w\:]/ ) {
361 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
362 return 0;
363 }
364 my $method = shift || 'process';
b768faa3 365 if ( my $code = $class->can($method) ) {
fc7ec1d9 366 $c->actions->{reverse}->{"$code"} = "$class->$method";
2deb0d7c 367 $results = [ [ [ $class, $code ] ] ];
fc7ec1d9 368 }
369 else {
370 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
371 if $c->debug;
372 return 0;
373 }
374 }
2deb0d7c 375 for my $result ( @{$results} ) {
970cc51d 376 $c->state( $c->execute( @{ $result->[0] } ) );
2deb0d7c 377 }
b768faa3 378 return $c->state;
fc7ec1d9 379}
380
66d9e175 381=item $c->get_action( $action, $namespace )
382
383Get an action in a given namespace.
384
385=cut
386
387sub get_action {
388 my ( $c, $action, $namespace ) = @_;
389 $namespace ||= '';
ac733264 390 if ($namespace) {
391 $namespace = '' if $namespace eq '/';
66d9e175 392 my $parent = $c->tree;
393 my @results;
394 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
395 push @results, [$result] if $result;
396 my $visitor = Tree::Simple::Visitor::FindByPath->new;
397 for my $part ( split '/', $namespace ) {
398 $visitor->setSearchPath($part);
399 $parent->accept($visitor);
400 my $child = $visitor->getResult;
401 my $uid = $child->getUID if $child;
402 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
6d9a6748 403 push @results, [$match] if $match;
66d9e175 404 $parent = $child if $child;
405 }
406 return \@results;
407 }
408 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
409 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
410 else {
ac733264 411 for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {
412 my $name = $c->actions->{compiled}->[$i]->[0];
413 my $regex = $c->actions->{compiled}->[$i]->[1];
66d9e175 414 if ( $action =~ $regex ) {
415 my @snippets;
416 for my $i ( 1 .. 9 ) {
417 no strict 'refs';
418 last unless ${$i};
419 push @snippets, ${$i};
420 }
421 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
422 }
423 }
424 }
425 return [];
426}
427
b76d7db8 428=item $c->handler( $class, $r )
fc7ec1d9 429
430Handles the request.
431
432=cut
433
6dc87a0f 434sub handler {
435 my ( $class, $engine ) = @_;
fc7ec1d9 436
437 # Always expect worst case!
438 my $status = -1;
439 eval {
d41516b2 440 my @stats = ();
fc7ec1d9 441 my $handler = sub {
6dc87a0f 442 my $c = $class->prepare($engine);
d41516b2 443 $c->{stats} = \@stats;
eb9ff8f4 444 my $action = $c->req->action;
445 my $namespace = '';
ac733264 446 $namespace = ( join( '/', @{ $c->req->args } ) || '/' )
447 if $action eq 'default';
eb9ff8f4 448 unless ($namespace) {
0169d3a8 449 if ( my $result = $c->get_action($action) ) {
eb9ff8f4 450 $namespace = _class2prefix( $result->[0]->[0]->[0] );
7833fdfc 451 }
87e67021 452 }
ac733264 453 my $default = $action eq 'default' ? $namespace : undef;
454 my $results = $c->get_action( $action, $default );
455 $namespace ||= '/';
b768faa3 456 if ( @{$results} ) {
ac733264 457 for my $begin ( @{ $c->get_action( 'begin', $namespace ) } ) {
970cc51d 458 $c->state( $c->execute( @{ $begin->[0] } ) );
b768faa3 459 }
98dcf439 460 for my $result ( @{ $c->get_action( $action, $default ) }[-1] )
461 {
970cc51d 462 $c->state( $c->execute( @{ $result->[0] } ) );
ac733264 463 last unless $default;
b768faa3 464 }
98dcf439 465 for my $end ( reverse @{ $c->get_action( 'end', $namespace ) } )
466 {
970cc51d 467 $c->state( $c->execute( @{ $end->[0] } ) );
b768faa3 468 }
fc7ec1d9 469 }
470 else {
87e67021 471 my $path = $c->req->path;
472 my $error = $path
473 ? qq/Unknown resource "$path"/
7833fdfc 474 : "No default action defined";
fc7ec1d9 475 $c->log->error($error) if $c->debug;
a554cc3b 476 $c->error($error);
fc7ec1d9 477 }
478 return $c->finalize;
479 };
480 if ( $class->debug ) {
481 my $elapsed;
482 ( $elapsed, $status ) = $class->benchmark($handler);
483 $elapsed = sprintf '%f', $elapsed;
484 my $av = sprintf '%.3f', 1 / $elapsed;
d41516b2 485 $class->log->info( "Request took $elapsed" . "s ($av/s)", @stats );
fc7ec1d9 486 }
487 else { $status = &$handler }
488 };
489 if ( my $error = $@ ) {
490 chomp $error;
491 $class->log->error(qq/Caught exception in engine "$error"/);
492 }
493 $COUNT++;
494 return $status;
495}
496
23f9d934 497=item $c->prepare($r)
fc7ec1d9 498
a554cc3b 499Turns the engine-specific request( Apache, CGI ... )
500into a Catalyst context .
fc7ec1d9 501
502=cut
503
504sub prepare {
505 my ( $class, $r ) = @_;
506 my $c = bless {
507 request => Catalyst::Request->new(
508 {
509 arguments => [],
510 cookies => {},
511 headers => HTTP::Headers->new,
512 parameters => {},
513 snippets => [],
514 uploads => {}
515 }
516 ),
517 response => Catalyst::Response->new(
518 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
519 ),
b768faa3 520 stash => {},
521 state => 0
fc7ec1d9 522 }, $class;
523 if ( $c->debug ) {
524 my $secs = time - $START || 1;
525 my $av = sprintf '%.3f', $COUNT / $secs;
526 $c->log->debug('********************************');
527 $c->log->debug("* Request $COUNT ($av/s) [$$]");
528 $c->log->debug('********************************');
529 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
530 }
531 $c->prepare_request($r);
532 $c->prepare_path;
ac733264 533 $c->prepare_headers;
1a80619d 534 $c->prepare_cookies;
0556eb49 535 $c->prepare_connection;
536 my $method = $c->req->method || '';
537 my $path = $c->req->path || '';
538 my $hostname = $c->req->hostname || '';
539 my $address = $c->req->address || '';
540 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
541 if $c->debug;
fc7ec1d9 542 $c->prepare_action;
543 $c->prepare_parameters;
c85ff642 544
545 if ( $c->debug && keys %{ $c->req->params } ) {
546 my @params;
547 for my $key ( keys %{ $c->req->params } ) {
b5524568 548 my $value = $c->req->params->{$key} || '';
32e4cad6 549 push @params, " $key=$value";
c85ff642 550 }
3dafb279 551 $c->log->debug( 'Parameters are', @params );
c85ff642 552 }
fc7ec1d9 553 $c->prepare_uploads;
554 return $c;
555}
556
23f9d934 557=item $c->prepare_action
fc7ec1d9 558
559Prepare action.
560
561=cut
562
563sub prepare_action {
564 my $c = shift;
565 my $path = $c->req->path;
566 my @path = split /\//, $c->req->path;
567 $c->req->args( \my @args );
568 while (@path) {
7833fdfc 569 $path = join '/', @path;
0169d3a8 570 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 571
572 # It's a regex
573 if ($#$result) {
7e5adedd 574 my $match = $result->[1];
575 my @snippets = @{ $result->[2] };
fc7ec1d9 576 $c->log->debug(qq/Requested action "$path" matched "$match"/)
577 if $c->debug;
578 $c->log->debug(
579 'Snippets are "' . join( ' ', @snippets ) . '"' )
580 if ( $c->debug && @snippets );
581 $c->req->action($match);
582 $c->req->snippets( \@snippets );
583 }
584 else {
585 $c->req->action($path);
586 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
587 }
588 $c->req->match($path);
fc7ec1d9 589 last;
590 }
591 unshift @args, pop @path;
592 }
593 unless ( $c->req->action ) {
ac733264 594 $c->req->action('default');
87e67021 595 $c->req->match('');
fc7ec1d9 596 }
5783a9a5 597 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
598 if ( $c->debug && @args );
fc7ec1d9 599}
600
c9afa5fc 601=item $c->prepare_connection
0556eb49 602
603Prepare connection.
604
605=cut
606
607sub prepare_connection { }
608
c9afa5fc 609=item $c->prepare_cookies
fc7ec1d9 610
611Prepare cookies.
612
613=cut
614
6dc87a0f 615sub prepare_cookies {
616 my $c = shift;
617
618 if ( my $header = $c->request->header('Cookie') ) {
619 $c->req->cookies( { CGI::Cookie->parse($header) } );
620 }
621}
fc7ec1d9 622
23f9d934 623=item $c->prepare_headers
fc7ec1d9 624
625Prepare headers.
626
627=cut
628
629sub prepare_headers { }
630
23f9d934 631=item $c->prepare_parameters
fc7ec1d9 632
633Prepare parameters.
634
635=cut
636
637sub prepare_parameters { }
638
23f9d934 639=item $c->prepare_path
fc7ec1d9 640
641Prepare path and base.
642
643=cut
644
645sub prepare_path { }
646
23f9d934 647=item $c->prepare_request
fc7ec1d9 648
649Prepare the engine request.
650
651=cut
652
653sub prepare_request { }
654
23f9d934 655=item $c->prepare_uploads
fc7ec1d9 656
657Prepare uploads.
658
659=cut
660
661sub prepare_uploads { }
662
c9afa5fc 663=item $c->run
664
665Starts the engine.
666
667=cut
668
669sub run { }
670
23f9d934 671=item $c->request
672
673=item $c->req
fc7ec1d9 674
675Returns a C<Catalyst::Request> object.
676
677 my $req = $c->req;
678
23f9d934 679=item $c->response
680
681=item $c->res
fc7ec1d9 682
683Returns a C<Catalyst::Response> object.
684
685 my $res = $c->res;
686
ac733264 687=item $c->set_action( $action, $code, $namespace, $attrs )
66d9e175 688
689Set an action in a given namespace.
690
691=cut
692
693sub set_action {
ac733264 694 my ( $c, $method, $code, $namespace, $attrs ) = @_;
695
6372237c 696 my $prefix = _class2prefix($namespace) || '';
697 my %flags;
ac733264 698
699 for my $attr ( @{$attrs} ) {
98dcf439 700 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
701 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
702 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
703 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
1d4ea19d 704 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
66d9e175 705 }
ac733264 706
6372237c 707 return unless keys %flags;
ac733264 708
709 my $parent = $c->tree;
710 my $visitor = Tree::Simple::Visitor::FindByPath->new;
711 for my $part ( split '/', $prefix ) {
712 $visitor->setSearchPath($part);
713 $parent->accept($visitor);
714 my $child = $visitor->getResult;
715 unless ($child) {
716 $child = $parent->addChild( Tree::Simple->new($part) );
66d9e175 717 $visitor->setSearchPath($part);
718 $parent->accept($visitor);
ac733264 719 $child = $visitor->getResult;
66d9e175 720 }
ac733264 721 $parent = $child;
66d9e175 722 }
ac733264 723 my $uid = $parent->getUID;
724 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
725 my $forward = $prefix ? "$prefix/$method" : $method;
ac733264 726
6372237c 727 if ( $flags{path} ) {
728 $flags{path} =~ s/^\w+//;
729 $flags{path} =~ s/\w+$//;
730 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
731 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
732 }
733 if ( $flags{regex} ) {
734 $flags{regex} =~ s/^\w+//;
735 $flags{regex} =~ s/\w+$//;
736 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
737 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
738 }
ac733264 739
fee92828 740 my $reverse = $prefix ? "$prefix/$method" : $method;
ac733264 741
6372237c 742 if ( $flags{local} || $flags{global} || $flags{path} ) {
743 my $path = $flags{path} || $method;
744 my $absolute = 0;
745 if ( $path =~ /^\/(.+)/ ) {
746 $path = $1;
747 $absolute = 1;
ac733264 748 }
8702d594 749 $absolute = 1 if $flags{global};
6372237c 750 my $name = $absolute ? $path : "$prefix/$path";
ac733264 751 $c->actions->{plain}->{$name} = [ $namespace, $code ];
ac733264 752 }
6372237c 753 if ( my $regex = $flags{regex} ) {
754 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
755 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
ac733264 756 }
757
758 $c->actions->{reverse}->{"$code"} = $reverse;
66d9e175 759}
760
23f9d934 761=item $class->setup
fc7ec1d9 762
763Setup.
764
765 MyApp->setup;
766
767=cut
768
769sub setup {
770 my $self = shift;
771 $self->setup_components;
772 if ( $self->debug ) {
773 my $name = $self->config->{name} || 'Application';
774 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
775 }
776}
777
ac733264 778=item $class->setup_actions($component)
779
780Setup actions for a component.
781
782=cut
783
784sub setup_actions {
785 my ( $self, $comp ) = @_;
786 $comp = ref $comp || $comp;
787 for my $action ( @{ $comp->_cache } ) {
788 my ( $code, $attrs ) = @{$action};
789 my $name = '';
790 no strict 'refs';
98dcf439 791 my @cache = ( $comp, @{"$comp\::ISA"} );
bb6823f2 792 my %namespaces;
98dcf439 793 while ( my $namespace = shift @cache ) {
bb6823f2 794 $namespaces{$namespace}++;
98dcf439 795 for my $isa ( @{"$comp\::ISA"} ) {
bb6823f2 796 next if $namespaces{$isa};
98dcf439 797 push @cache, $isa;
bb6823f2 798 $namespaces{$isa}++;
98dcf439 799 }
800 }
bb6823f2 801 for my $namespace ( keys %namespaces ) {
98dcf439 802 for my $sym ( values %{ $namespace . '::' } ) {
803 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
804 $name = *{$sym}{NAME};
805 $self->set_action( $name, $code, $comp, $attrs );
806 last;
807 }
ac733264 808 }
809 }
810 }
811}
812
23f9d934 813=item $class->setup_components
fc7ec1d9 814
815Setup components.
816
817=cut
818
819sub setup_components {
820 my $self = shift;
821
822 # Components
823 my $class = ref $self || $self;
824 eval <<"";
825 package $class;
826 import Module::Pluggable::Fast
827 name => '_components',
828 search => [
829 '$class\::Controller', '$class\::C',
830 '$class\::Model', '$class\::M',
831 '$class\::View', '$class\::V'
832 ];
833
834 if ( my $error = $@ ) {
835 chomp $error;
836 $self->log->error(
837 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
838 }
ac733264 839 $self->setup_actions($self);
fc7ec1d9 840 $self->components( {} );
ac733264 841 for my $comp ( $self->_components($self) ) {
842 $self->components->{ ref $comp } = $comp;
843 $self->setup_actions($comp);
fc7ec1d9 844 }
af2ad181 845 my @comps;
32e4cad6 846 push @comps, " $_" for keys %{ $self->components };
4cf083b1 847 $self->log->debug( 'Loaded components', @comps )
848 if ( @comps && $self->debug );
849 my $actions = $self->actions;
850 my @messages = ('Loaded private actions');
851 my $walker = sub {
852 my ( $walker, $parent, $messages, $prefix ) = @_;
853 $prefix .= $parent->getNodeValue || '';
854 $prefix .= '/' unless $prefix =~ /\/$/;
855 my $uid = $parent->getUID;
856 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
857 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
af2ad181 858 push @$messages, _prettify( "$prefix$action", $class, $code );
4cf083b1 859 }
860 $walker->( $walker, $_, $messages, $prefix )
861 for $parent->getAllChildren;
862 };
863 $walker->( $walker, $self->tree, \@messages, '' );
864 $self->log->debug(@messages) if ( $#messages && $self->debug );
865 @messages = ('Loaded plain actions');
6655e7d4 866 for my $plain ( sort keys %{ $actions->{plain} } ) {
4cf083b1 867 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
92a3f2c9 868 push @messages, _prettify( "/$plain", $class, $code );
4cf083b1 869 }
870 $self->log->debug(@messages) if ( $#messages && $self->debug );
871 @messages = ('Loaded regex actions');
6655e7d4 872 for my $regex ( sort keys %{ $actions->{regex} } ) {
4cf083b1 873 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
af2ad181 874 push @messages, _prettify( $regex, $class, $code );
4cf083b1 875 }
876 $self->log->debug(@messages) if ( $#messages && $self->debug );
fc7ec1d9 877}
878
23f9d934 879=item $c->stash
fc7ec1d9 880
881Returns a hashref containing all your data.
882
883 $c->stash->{foo} ||= 'yada';
884 print $c->stash->{foo};
885
886=cut
887
888sub stash {
889 my $self = shift;
890 if ( $_[0] ) {
891 my $stash = $_[1] ? {@_} : $_[0];
892 while ( my ( $key, $val ) = each %$stash ) {
893 $self->{stash}->{$key} = $val;
894 }
895 }
896 return $self->{stash};
897}
898
899sub _prefix {
900 my ( $class, $name ) = @_;
7833fdfc 901 my $prefix = _class2prefix($class);
902 $name = "$prefix/$name" if $prefix;
903 return $name;
904}
905
906sub _class2prefix {
b768faa3 907 my $class = shift || '';
0434eec1 908 my $prefix;
98dcf439 909 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
910 $prefix = lc $2;
911 $prefix =~ s/\:\:/\//g;
0434eec1 912 }
7833fdfc 913 return $prefix;
fc7ec1d9 914}
915
af2ad181 916sub _prettify {
fee92828 917 my ( $val1, $val2, $val3 ) = @_;
af2ad181 918 formline
b5c7bbe3 919' @<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>>>>>>> ',
fee92828 920 $val1, $val2, $val3;
af2ad181 921 my $formatted = $^A;
922 $^A = '';
923 return $formatted;
924}
925
23f9d934 926=back
927
fc7ec1d9 928=head1 AUTHOR
929
930Sebastian Riedel, C<sri@cpan.org>
931
932=head1 COPYRIGHT
933
934This program is free software, you can redistribute it and/or modify it under
935the same terms as Perl itself.
936
937=cut
938
9391;