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