second try
[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/;
0f7ecc53 12use Text::ASCIITable;
87e67021 13use Tree::Simple;
14use Tree::Simple::Visitor::FindByPath;
fc7ec1d9 15use Catalyst::Request;
16use Catalyst::Response;
17
18require Module::Pluggable::Fast;
19
20$Data::Dumper::Terse = 1;
21
87e67021 22__PACKAGE__->mk_classdata($_) for qw/actions components tree/;
b768faa3 23__PACKAGE__->mk_accessors(qw/request response state/);
fc7ec1d9 24
25__PACKAGE__->actions(
ac733264 26 { plain => {}, private => {}, regex => {}, compiled => [], reverse => {} }
87e67021 27);
28__PACKAGE__->tree( Tree::Simple->new( 0, Tree::Simple->ROOT ) );
fc7ec1d9 29
30*comp = \&component;
31*req = \&request;
32*res = \&response;
33
34our $COUNT = 1;
35our $START = time;
36
87e67021 37memoize('_class2prefix');
38
fc7ec1d9 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 {
86 my ( $c, $name ) = @_;
87 if ( my $component = $c->components->{$name} ) {
88 return $component;
89 }
90 else {
91 for my $component ( keys %{ $c->components } ) {
92 return $c->components->{$component} if $component =~ /$name/i;
93 }
94 }
95}
96
a554cc3b 97=item $c->error
23f9d934 98
a554cc3b 99=item $c->error($error, ...)
23f9d934 100
a554cc3b 101=item $c->error($arrayref)
fc7ec1d9 102
a554cc3b 103Returns an arrayref containing error messages.
fc7ec1d9 104
a554cc3b 105 my @error = @{ $c->error };
fc7ec1d9 106
107Add a new error.
108
a554cc3b 109 $c->error('Something bad happened');
fc7ec1d9 110
111=cut
112
a554cc3b 113sub error {
fc7ec1d9 114 my $c = shift;
a554cc3b 115 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
116 push @{ $c->{error} }, @$error;
117 return $c->{error};
fc7ec1d9 118}
119
6dc87a0f 120=item $c->execute($class, $coderef)
121
122Execute a coderef in given class and catch exceptions.
123Errors are available via $c->error.
124
125=cut
126
127sub execute {
128 my ( $c, $class, $code ) = @_;
129 $class = $c->comp($class) || $class;
130 $c->state(0);
39de91b0 131 my $callsub = ( caller(1) )[3];
6dc87a0f 132 eval {
133 if ( $c->debug )
134 {
135 my $action = $c->actions->{reverse}->{"$code"};
136 $action = "/$action" unless $action =~ /\-\>/;
0f7ecc53 137 $action = " $action" if $callsub =~ /forward$/;
6dc87a0f 138 my ( $elapsed, @state ) =
139 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
0f7ecc53 140 push @{ $c->{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 ) = @_;
f6e054bb 391 return [] unless $action;
66d9e175 392 $namespace ||= '';
ac733264 393 if ($namespace) {
394 $namespace = '' if $namespace eq '/';
66d9e175 395 my $parent = $c->tree;
396 my @results;
397 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
398 push @results, [$result] if $result;
399 my $visitor = Tree::Simple::Visitor::FindByPath->new;
400 for my $part ( split '/', $namespace ) {
401 $visitor->setSearchPath($part);
402 $parent->accept($visitor);
403 my $child = $visitor->getResult;
404 my $uid = $child->getUID if $child;
405 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
6d9a6748 406 push @results, [$match] if $match;
66d9e175 407 $parent = $child if $child;
408 }
409 return \@results;
410 }
411 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
412 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
413 else {
ac733264 414 for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {
415 my $name = $c->actions->{compiled}->[$i]->[0];
416 my $regex = $c->actions->{compiled}->[$i]->[1];
66d9e175 417 if ( $action =~ $regex ) {
418 my @snippets;
419 for my $i ( 1 .. 9 ) {
420 no strict 'refs';
421 last unless ${$i};
422 push @snippets, ${$i};
423 }
424 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
425 }
426 }
427 }
428 return [];
429}
430
b76d7db8 431=item $c->handler( $class, $r )
fc7ec1d9 432
433Handles the request.
434
435=cut
436
6dc87a0f 437sub handler {
438 my ( $class, $engine ) = @_;
fc7ec1d9 439
440 # Always expect worst case!
441 my $status = -1;
442 eval {
d41516b2 443 my @stats = ();
fc7ec1d9 444 my $handler = sub {
6dc87a0f 445 my $c = $class->prepare($engine);
d41516b2 446 $c->{stats} = \@stats;
eb9ff8f4 447 my $action = $c->req->action;
448 my $namespace = '';
ac733264 449 $namespace = ( join( '/', @{ $c->req->args } ) || '/' )
450 if $action eq 'default';
eb9ff8f4 451 unless ($namespace) {
0169d3a8 452 if ( my $result = $c->get_action($action) ) {
eb9ff8f4 453 $namespace = _class2prefix( $result->[0]->[0]->[0] );
7833fdfc 454 }
87e67021 455 }
ac733264 456 my $default = $action eq 'default' ? $namespace : undef;
457 my $results = $c->get_action( $action, $default );
458 $namespace ||= '/';
b768faa3 459 if ( @{$results} ) {
ac733264 460 for my $begin ( @{ $c->get_action( 'begin', $namespace ) } ) {
970cc51d 461 $c->state( $c->execute( @{ $begin->[0] } ) );
b768faa3 462 }
f6e054bb 463 if ( my $action = $c->req->action ) {
464 for my $result (
465 @{ $c->get_action( $action, $default ) }[-1] )
466 {
467 $c->state( $c->execute( @{ $result->[0] } ) );
468 last unless $default;
469 }
b768faa3 470 }
98dcf439 471 for my $end ( reverse @{ $c->get_action( 'end', $namespace ) } )
472 {
970cc51d 473 $c->state( $c->execute( @{ $end->[0] } ) );
b768faa3 474 }
fc7ec1d9 475 }
476 else {
87e67021 477 my $path = $c->req->path;
478 my $error = $path
479 ? qq/Unknown resource "$path"/
7833fdfc 480 : "No default action defined";
fc7ec1d9 481 $c->log->error($error) if $c->debug;
a554cc3b 482 $c->error($error);
fc7ec1d9 483 }
484 return $c->finalize;
485 };
486 if ( $class->debug ) {
487 my $elapsed;
488 ( $elapsed, $status ) = $class->benchmark($handler);
489 $elapsed = sprintf '%f', $elapsed;
490 my $av = sprintf '%.3f', 1 / $elapsed;
0f7ecc53 491 my $t = Text::ASCIITable->new;
492 $t->setCols( 'Action', 'Time' );
3f36a3a3 493 $t->setColWidth( 'Action', 64, 1 );
494 $t->setColWidth( 'Time', 9, 1 );
0822f9a4 495
0f7ecc53 496 for my $stat (@stats) {
497 $t->addRow(@$stat);
498 }
499 $class->log->info( "Request took $elapsed" . "s ($av/s)",
500 $t->draw );
fc7ec1d9 501 }
502 else { $status = &$handler }
503 };
504 if ( my $error = $@ ) {
505 chomp $error;
506 $class->log->error(qq/Caught exception in engine "$error"/);
507 }
508 $COUNT++;
509 return $status;
510}
511
23f9d934 512=item $c->prepare($r)
fc7ec1d9 513
a554cc3b 514Turns the engine-specific request( Apache, CGI ... )
515into a Catalyst context .
fc7ec1d9 516
517=cut
518
519sub prepare {
520 my ( $class, $r ) = @_;
521 my $c = bless {
522 request => Catalyst::Request->new(
523 {
524 arguments => [],
525 cookies => {},
526 headers => HTTP::Headers->new,
527 parameters => {},
528 snippets => [],
529 uploads => {}
530 }
531 ),
532 response => Catalyst::Response->new(
533 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
534 ),
b768faa3 535 stash => {},
536 state => 0
fc7ec1d9 537 }, $class;
538 if ( $c->debug ) {
539 my $secs = time - $START || 1;
540 my $av = sprintf '%.3f', $COUNT / $secs;
1a0250cb 541 $c->log->debug('**********************************');
fc7ec1d9 542 $c->log->debug("* Request $COUNT ($av/s) [$$]");
1a0250cb 543 $c->log->debug('**********************************');
fc7ec1d9 544 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
545 }
546 $c->prepare_request($r);
547 $c->prepare_path;
ac733264 548 $c->prepare_headers;
1a80619d 549 $c->prepare_cookies;
0556eb49 550 $c->prepare_connection;
551 my $method = $c->req->method || '';
552 my $path = $c->req->path || '';
553 my $hostname = $c->req->hostname || '';
554 my $address = $c->req->address || '';
555 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
556 if $c->debug;
fc7ec1d9 557 $c->prepare_action;
558 $c->prepare_parameters;
c85ff642 559
560 if ( $c->debug && keys %{ $c->req->params } ) {
0f7ecc53 561 my $t = Text::ASCIITable->new;
562 $t->setCols( 'Key', 'Value' );
0822f9a4 563 $t->setColWidth( 'Key', 37, 1 );
564 $t->setColWidth( 'Value', 36, 1 );
c85ff642 565 for my $key ( keys %{ $c->req->params } ) {
b5524568 566 my $value = $c->req->params->{$key} || '';
0f7ecc53 567 $t->addRow( $key, $value );
c85ff642 568 }
0f7ecc53 569 $c->log->debug( 'Parameters are', $t->draw );
c85ff642 570 }
fc7ec1d9 571 $c->prepare_uploads;
572 return $c;
573}
574
23f9d934 575=item $c->prepare_action
fc7ec1d9 576
577Prepare action.
578
579=cut
580
581sub prepare_action {
582 my $c = shift;
583 my $path = $c->req->path;
584 my @path = split /\//, $c->req->path;
585 $c->req->args( \my @args );
586 while (@path) {
7833fdfc 587 $path = join '/', @path;
0169d3a8 588 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 589
590 # It's a regex
591 if ($#$result) {
7e5adedd 592 my $match = $result->[1];
593 my @snippets = @{ $result->[2] };
81f6fc50 594 $c->log->debug(
595 qq/Requested action is "$path" and matched "$match"/)
fc7ec1d9 596 if $c->debug;
597 $c->log->debug(
598 'Snippets are "' . join( ' ', @snippets ) . '"' )
599 if ( $c->debug && @snippets );
600 $c->req->action($match);
601 $c->req->snippets( \@snippets );
602 }
603 else {
604 $c->req->action($path);
81f6fc50 605 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
fc7ec1d9 606 }
607 $c->req->match($path);
fc7ec1d9 608 last;
609 }
610 unshift @args, pop @path;
611 }
612 unless ( $c->req->action ) {
ac733264 613 $c->req->action('default');
87e67021 614 $c->req->match('');
fc7ec1d9 615 }
5783a9a5 616 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
617 if ( $c->debug && @args );
fc7ec1d9 618}
619
c9afa5fc 620=item $c->prepare_connection
0556eb49 621
622Prepare connection.
623
624=cut
625
626sub prepare_connection { }
627
c9afa5fc 628=item $c->prepare_cookies
fc7ec1d9 629
630Prepare cookies.
631
632=cut
633
6dc87a0f 634sub prepare_cookies {
635 my $c = shift;
636
637 if ( my $header = $c->request->header('Cookie') ) {
638 $c->req->cookies( { CGI::Cookie->parse($header) } );
639 }
640}
fc7ec1d9 641
23f9d934 642=item $c->prepare_headers
fc7ec1d9 643
644Prepare headers.
645
646=cut
647
648sub prepare_headers { }
649
23f9d934 650=item $c->prepare_parameters
fc7ec1d9 651
652Prepare parameters.
653
654=cut
655
656sub prepare_parameters { }
657
23f9d934 658=item $c->prepare_path
fc7ec1d9 659
660Prepare path and base.
661
662=cut
663
664sub prepare_path { }
665
23f9d934 666=item $c->prepare_request
fc7ec1d9 667
668Prepare the engine request.
669
670=cut
671
672sub prepare_request { }
673
23f9d934 674=item $c->prepare_uploads
fc7ec1d9 675
676Prepare uploads.
677
678=cut
679
680sub prepare_uploads { }
681
c9afa5fc 682=item $c->run
683
684Starts the engine.
685
686=cut
687
688sub run { }
689
23f9d934 690=item $c->request
691
692=item $c->req
fc7ec1d9 693
694Returns a C<Catalyst::Request> object.
695
696 my $req = $c->req;
697
23f9d934 698=item $c->response
699
700=item $c->res
fc7ec1d9 701
702Returns a C<Catalyst::Response> object.
703
704 my $res = $c->res;
705
ac733264 706=item $c->set_action( $action, $code, $namespace, $attrs )
66d9e175 707
708Set an action in a given namespace.
709
710=cut
711
712sub set_action {
ac733264 713 my ( $c, $method, $code, $namespace, $attrs ) = @_;
714
6372237c 715 my $prefix = _class2prefix($namespace) || '';
716 my %flags;
ac733264 717
718 for my $attr ( @{$attrs} ) {
98dcf439 719 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
720 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
721 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
722 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
1d4ea19d 723 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
66d9e175 724 }
ac733264 725
6372237c 726 return unless keys %flags;
ac733264 727
728 my $parent = $c->tree;
729 my $visitor = Tree::Simple::Visitor::FindByPath->new;
730 for my $part ( split '/', $prefix ) {
731 $visitor->setSearchPath($part);
732 $parent->accept($visitor);
733 my $child = $visitor->getResult;
734 unless ($child) {
735 $child = $parent->addChild( Tree::Simple->new($part) );
66d9e175 736 $visitor->setSearchPath($part);
737 $parent->accept($visitor);
ac733264 738 $child = $visitor->getResult;
66d9e175 739 }
ac733264 740 $parent = $child;
66d9e175 741 }
ac733264 742 my $uid = $parent->getUID;
743 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
744 my $forward = $prefix ? "$prefix/$method" : $method;
ac733264 745
6372237c 746 if ( $flags{path} ) {
747 $flags{path} =~ s/^\w+//;
748 $flags{path} =~ s/\w+$//;
749 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
750 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
751 }
752 if ( $flags{regex} ) {
753 $flags{regex} =~ s/^\w+//;
754 $flags{regex} =~ s/\w+$//;
755 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
756 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
757 }
ac733264 758
fee92828 759 my $reverse = $prefix ? "$prefix/$method" : $method;
ac733264 760
6372237c 761 if ( $flags{local} || $flags{global} || $flags{path} ) {
762 my $path = $flags{path} || $method;
763 my $absolute = 0;
764 if ( $path =~ /^\/(.+)/ ) {
765 $path = $1;
766 $absolute = 1;
ac733264 767 }
8702d594 768 $absolute = 1 if $flags{global};
6372237c 769 my $name = $absolute ? $path : "$prefix/$path";
ac733264 770 $c->actions->{plain}->{$name} = [ $namespace, $code ];
ac733264 771 }
6372237c 772 if ( my $regex = $flags{regex} ) {
773 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
774 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
ac733264 775 }
776
777 $c->actions->{reverse}->{"$code"} = $reverse;
66d9e175 778}
779
23f9d934 780=item $class->setup
fc7ec1d9 781
782Setup.
783
784 MyApp->setup;
785
786=cut
787
788sub setup {
789 my $self = shift;
790 $self->setup_components;
791 if ( $self->debug ) {
792 my $name = $self->config->{name} || 'Application';
793 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
794 }
795}
796
ac733264 797=item $class->setup_actions($component)
798
799Setup actions for a component.
800
801=cut
802
803sub setup_actions {
804 my ( $self, $comp ) = @_;
805 $comp = ref $comp || $comp;
806 for my $action ( @{ $comp->_cache } ) {
807 my ( $code, $attrs ) = @{$action};
808 my $name = '';
809 no strict 'refs';
98dcf439 810 my @cache = ( $comp, @{"$comp\::ISA"} );
bb6823f2 811 my %namespaces;
98dcf439 812 while ( my $namespace = shift @cache ) {
bb6823f2 813 $namespaces{$namespace}++;
98dcf439 814 for my $isa ( @{"$comp\::ISA"} ) {
bb6823f2 815 next if $namespaces{$isa};
98dcf439 816 push @cache, $isa;
bb6823f2 817 $namespaces{$isa}++;
98dcf439 818 }
819 }
bb6823f2 820 for my $namespace ( keys %namespaces ) {
98dcf439 821 for my $sym ( values %{ $namespace . '::' } ) {
822 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
823 $name = *{$sym}{NAME};
824 $self->set_action( $name, $code, $comp, $attrs );
825 last;
826 }
ac733264 827 }
828 }
829 }
830}
831
23f9d934 832=item $class->setup_components
fc7ec1d9 833
834Setup components.
835
836=cut
837
838sub setup_components {
839 my $self = shift;
840
841 # Components
842 my $class = ref $self || $self;
843 eval <<"";
844 package $class;
845 import Module::Pluggable::Fast
846 name => '_components',
847 search => [
848 '$class\::Controller', '$class\::C',
849 '$class\::Model', '$class\::M',
850 '$class\::View', '$class\::V'
851 ];
852
853 if ( my $error = $@ ) {
854 chomp $error;
855 $self->log->error(
856 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
857 }
ac733264 858 $self->setup_actions($self);
fc7ec1d9 859 $self->components( {} );
ac733264 860 for my $comp ( $self->_components($self) ) {
861 $self->components->{ ref $comp } = $comp;
862 $self->setup_actions($comp);
fc7ec1d9 863 }
0f7ecc53 864 my $t = Text::ASCIITable->new;
865 $t->setCols('Class');
0822f9a4 866 $t->setColWidth( 'Class', 75, 1 );
0f7ecc53 867 $t->addRow($_) for keys %{ $self->components };
868 $self->log->debug( 'Loaded components', $t->draw )
869 if ( @{ $t->{tbl_rows} } && $self->debug );
4cf083b1 870 my $actions = $self->actions;
0f7ecc53 871 my $privates = Text::ASCIITable->new;
872 $privates->setCols( 'Action', 'Class', 'Code' );
0822f9a4 873 $privates->setColWidth( 'Action', 28, 1 );
874 $privates->setColWidth( 'Class', 28, 1 );
875 $privates->setColWidth( 'Code', 14, 1 );
0f7ecc53 876 my $walker = sub {
877 my ( $walker, $parent, $prefix ) = @_;
4cf083b1 878 $prefix .= $parent->getNodeValue || '';
879 $prefix .= '/' unless $prefix =~ /\/$/;
880 my $uid = $parent->getUID;
881 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
882 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
0f7ecc53 883 $privates->addRow( "$prefix$action", $class, $code );
4cf083b1 884 }
0f7ecc53 885 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
4cf083b1 886 };
0f7ecc53 887 $walker->( $walker, $self->tree, '' );
888 $self->log->debug( 'Loaded private actions', $privates->draw )
889 if ( @{ $privates->{tbl_rows} } && $self->debug );
890 my $publics = Text::ASCIITable->new;
891 $publics->setCols( 'Action', 'Class', 'Code' );
0822f9a4 892 $publics->setColWidth( 'Action', 28, 1 );
893 $publics->setColWidth( 'Class', 28, 1 );
894 $publics->setColWidth( 'Code', 14, 1 );
895
6655e7d4 896 for my $plain ( sort keys %{ $actions->{plain} } ) {
4cf083b1 897 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
0f7ecc53 898 $publics->addRow( "/$plain", $class, $code );
4cf083b1 899 }
0f7ecc53 900 $self->log->debug( 'Loaded public actions', $publics->draw )
901 if ( @{ $publics->{tbl_rows} } && $self->debug );
902 my $regexes = Text::ASCIITable->new;
903 $regexes->setCols( 'Action', 'Class', 'Code' );
0822f9a4 904 $regexes->setColWidth( 'Action', 28, 1 );
905 $regexes->setColWidth( 'Class', 28, 1 );
906 $regexes->setColWidth( 'Code', 14, 1 );
6655e7d4 907 for my $regex ( sort keys %{ $actions->{regex} } ) {
4cf083b1 908 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
0f7ecc53 909 $regexes->addRow( $regex, $class, $code );
4cf083b1 910 }
0f7ecc53 911 $self->log->debug( 'Loaded regex actions', $regexes->draw )
912 if ( @{ $regexes->{tbl_rows} } && $self->debug );
fc7ec1d9 913}
914
23f9d934 915=item $c->stash
fc7ec1d9 916
917Returns a hashref containing all your data.
918
919 $c->stash->{foo} ||= 'yada';
920 print $c->stash->{foo};
921
922=cut
923
924sub stash {
925 my $self = shift;
926 if ( $_[0] ) {
927 my $stash = $_[1] ? {@_} : $_[0];
928 while ( my ( $key, $val ) = each %$stash ) {
929 $self->{stash}->{$key} = $val;
930 }
931 }
932 return $self->{stash};
933}
934
935sub _prefix {
936 my ( $class, $name ) = @_;
7833fdfc 937 my $prefix = _class2prefix($class);
938 $name = "$prefix/$name" if $prefix;
939 return $name;
940}
941
942sub _class2prefix {
b768faa3 943 my $class = shift || '';
0434eec1 944 my $prefix;
98dcf439 945 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
946 $prefix = lc $2;
947 $prefix =~ s/\:\:/\//g;
0434eec1 948 }
7833fdfc 949 return $prefix;
fc7ec1d9 950}
951
23f9d934 952=back
953
fc7ec1d9 954=head1 AUTHOR
955
956Sebastian Riedel, C<sri@cpan.org>
957
958=head1 COPYRIGHT
959
960This program is free software, you can redistribute it and/or modify it under
961the same terms as Perl itself.
962
963=cut
964
9651;