typo
[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 ) = @_;
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;
0f7ecc53 487 my $t = Text::ASCIITable->new;
488 $t->setCols( 'Action', 'Time' );
489 for my $stat (@stats) {
490 $t->addRow(@$stat);
491 }
492 $class->log->info( "Request took $elapsed" . "s ($av/s)",
493 $t->draw );
fc7ec1d9 494 }
495 else { $status = &$handler }
496 };
497 if ( my $error = $@ ) {
498 chomp $error;
499 $class->log->error(qq/Caught exception in engine "$error"/);
500 }
501 $COUNT++;
502 return $status;
503}
504
23f9d934 505=item $c->prepare($r)
fc7ec1d9 506
a554cc3b 507Turns the engine-specific request( Apache, CGI ... )
508into a Catalyst context .
fc7ec1d9 509
510=cut
511
512sub prepare {
513 my ( $class, $r ) = @_;
514 my $c = bless {
515 request => Catalyst::Request->new(
516 {
517 arguments => [],
518 cookies => {},
519 headers => HTTP::Headers->new,
520 parameters => {},
521 snippets => [],
522 uploads => {}
523 }
524 ),
525 response => Catalyst::Response->new(
526 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
527 ),
b768faa3 528 stash => {},
529 state => 0
fc7ec1d9 530 }, $class;
531 if ( $c->debug ) {
532 my $secs = time - $START || 1;
533 my $av = sprintf '%.3f', $COUNT / $secs;
534 $c->log->debug('********************************');
535 $c->log->debug("* Request $COUNT ($av/s) [$$]");
536 $c->log->debug('********************************');
537 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
538 }
539 $c->prepare_request($r);
540 $c->prepare_path;
ac733264 541 $c->prepare_headers;
1a80619d 542 $c->prepare_cookies;
0556eb49 543 $c->prepare_connection;
544 my $method = $c->req->method || '';
545 my $path = $c->req->path || '';
546 my $hostname = $c->req->hostname || '';
547 my $address = $c->req->address || '';
548 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
549 if $c->debug;
fc7ec1d9 550 $c->prepare_action;
551 $c->prepare_parameters;
c85ff642 552
553 if ( $c->debug && keys %{ $c->req->params } ) {
0f7ecc53 554 my $t = Text::ASCIITable->new;
555 $t->setCols( 'Key', 'Value' );
c85ff642 556 for my $key ( keys %{ $c->req->params } ) {
b5524568 557 my $value = $c->req->params->{$key} || '';
0f7ecc53 558 $t->addRow( $key, $value );
c85ff642 559 }
0f7ecc53 560 $c->log->debug( 'Parameters are', $t->draw );
c85ff642 561 }
fc7ec1d9 562 $c->prepare_uploads;
563 return $c;
564}
565
23f9d934 566=item $c->prepare_action
fc7ec1d9 567
568Prepare action.
569
570=cut
571
572sub prepare_action {
573 my $c = shift;
574 my $path = $c->req->path;
575 my @path = split /\//, $c->req->path;
576 $c->req->args( \my @args );
577 while (@path) {
7833fdfc 578 $path = join '/', @path;
0169d3a8 579 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 580
581 # It's a regex
582 if ($#$result) {
7e5adedd 583 my $match = $result->[1];
584 my @snippets = @{ $result->[2] };
81f6fc50 585 $c->log->debug(
586 qq/Requested action is "$path" and matched "$match"/)
fc7ec1d9 587 if $c->debug;
588 $c->log->debug(
589 'Snippets are "' . join( ' ', @snippets ) . '"' )
590 if ( $c->debug && @snippets );
591 $c->req->action($match);
592 $c->req->snippets( \@snippets );
593 }
594 else {
595 $c->req->action($path);
81f6fc50 596 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
fc7ec1d9 597 }
598 $c->req->match($path);
fc7ec1d9 599 last;
600 }
601 unshift @args, pop @path;
602 }
603 unless ( $c->req->action ) {
ac733264 604 $c->req->action('default');
87e67021 605 $c->req->match('');
fc7ec1d9 606 }
5783a9a5 607 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
608 if ( $c->debug && @args );
fc7ec1d9 609}
610
c9afa5fc 611=item $c->prepare_connection
0556eb49 612
613Prepare connection.
614
615=cut
616
617sub prepare_connection { }
618
c9afa5fc 619=item $c->prepare_cookies
fc7ec1d9 620
621Prepare cookies.
622
623=cut
624
6dc87a0f 625sub prepare_cookies {
626 my $c = shift;
627
628 if ( my $header = $c->request->header('Cookie') ) {
629 $c->req->cookies( { CGI::Cookie->parse($header) } );
630 }
631}
fc7ec1d9 632
23f9d934 633=item $c->prepare_headers
fc7ec1d9 634
635Prepare headers.
636
637=cut
638
639sub prepare_headers { }
640
23f9d934 641=item $c->prepare_parameters
fc7ec1d9 642
643Prepare parameters.
644
645=cut
646
647sub prepare_parameters { }
648
23f9d934 649=item $c->prepare_path
fc7ec1d9 650
651Prepare path and base.
652
653=cut
654
655sub prepare_path { }
656
23f9d934 657=item $c->prepare_request
fc7ec1d9 658
659Prepare the engine request.
660
661=cut
662
663sub prepare_request { }
664
23f9d934 665=item $c->prepare_uploads
fc7ec1d9 666
667Prepare uploads.
668
669=cut
670
671sub prepare_uploads { }
672
c9afa5fc 673=item $c->run
674
675Starts the engine.
676
677=cut
678
679sub run { }
680
23f9d934 681=item $c->request
682
683=item $c->req
fc7ec1d9 684
685Returns a C<Catalyst::Request> object.
686
687 my $req = $c->req;
688
23f9d934 689=item $c->response
690
691=item $c->res
fc7ec1d9 692
693Returns a C<Catalyst::Response> object.
694
695 my $res = $c->res;
696
ac733264 697=item $c->set_action( $action, $code, $namespace, $attrs )
66d9e175 698
699Set an action in a given namespace.
700
701=cut
702
703sub set_action {
ac733264 704 my ( $c, $method, $code, $namespace, $attrs ) = @_;
705
6372237c 706 my $prefix = _class2prefix($namespace) || '';
707 my %flags;
ac733264 708
709 for my $attr ( @{$attrs} ) {
98dcf439 710 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
711 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
712 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
713 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
1d4ea19d 714 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
66d9e175 715 }
ac733264 716
6372237c 717 return unless keys %flags;
ac733264 718
719 my $parent = $c->tree;
720 my $visitor = Tree::Simple::Visitor::FindByPath->new;
721 for my $part ( split '/', $prefix ) {
722 $visitor->setSearchPath($part);
723 $parent->accept($visitor);
724 my $child = $visitor->getResult;
725 unless ($child) {
726 $child = $parent->addChild( Tree::Simple->new($part) );
66d9e175 727 $visitor->setSearchPath($part);
728 $parent->accept($visitor);
ac733264 729 $child = $visitor->getResult;
66d9e175 730 }
ac733264 731 $parent = $child;
66d9e175 732 }
ac733264 733 my $uid = $parent->getUID;
734 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
735 my $forward = $prefix ? "$prefix/$method" : $method;
ac733264 736
6372237c 737 if ( $flags{path} ) {
738 $flags{path} =~ s/^\w+//;
739 $flags{path} =~ s/\w+$//;
740 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
741 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
742 }
743 if ( $flags{regex} ) {
744 $flags{regex} =~ s/^\w+//;
745 $flags{regex} =~ s/\w+$//;
746 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
747 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
748 }
ac733264 749
fee92828 750 my $reverse = $prefix ? "$prefix/$method" : $method;
ac733264 751
6372237c 752 if ( $flags{local} || $flags{global} || $flags{path} ) {
753 my $path = $flags{path} || $method;
754 my $absolute = 0;
755 if ( $path =~ /^\/(.+)/ ) {
756 $path = $1;
757 $absolute = 1;
ac733264 758 }
8702d594 759 $absolute = 1 if $flags{global};
6372237c 760 my $name = $absolute ? $path : "$prefix/$path";
ac733264 761 $c->actions->{plain}->{$name} = [ $namespace, $code ];
ac733264 762 }
6372237c 763 if ( my $regex = $flags{regex} ) {
764 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
765 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
ac733264 766 }
767
768 $c->actions->{reverse}->{"$code"} = $reverse;
66d9e175 769}
770
23f9d934 771=item $class->setup
fc7ec1d9 772
773Setup.
774
775 MyApp->setup;
776
777=cut
778
779sub setup {
780 my $self = shift;
781 $self->setup_components;
782 if ( $self->debug ) {
783 my $name = $self->config->{name} || 'Application';
784 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
785 }
786}
787
ac733264 788=item $class->setup_actions($component)
789
790Setup actions for a component.
791
792=cut
793
794sub setup_actions {
795 my ( $self, $comp ) = @_;
796 $comp = ref $comp || $comp;
797 for my $action ( @{ $comp->_cache } ) {
798 my ( $code, $attrs ) = @{$action};
799 my $name = '';
800 no strict 'refs';
98dcf439 801 my @cache = ( $comp, @{"$comp\::ISA"} );
bb6823f2 802 my %namespaces;
98dcf439 803 while ( my $namespace = shift @cache ) {
bb6823f2 804 $namespaces{$namespace}++;
98dcf439 805 for my $isa ( @{"$comp\::ISA"} ) {
bb6823f2 806 next if $namespaces{$isa};
98dcf439 807 push @cache, $isa;
bb6823f2 808 $namespaces{$isa}++;
98dcf439 809 }
810 }
bb6823f2 811 for my $namespace ( keys %namespaces ) {
98dcf439 812 for my $sym ( values %{ $namespace . '::' } ) {
813 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
814 $name = *{$sym}{NAME};
815 $self->set_action( $name, $code, $comp, $attrs );
816 last;
817 }
ac733264 818 }
819 }
820 }
821}
822
23f9d934 823=item $class->setup_components
fc7ec1d9 824
825Setup components.
826
827=cut
828
829sub setup_components {
830 my $self = shift;
831
832 # Components
833 my $class = ref $self || $self;
834 eval <<"";
835 package $class;
836 import Module::Pluggable::Fast
837 name => '_components',
838 search => [
839 '$class\::Controller', '$class\::C',
840 '$class\::Model', '$class\::M',
841 '$class\::View', '$class\::V'
842 ];
843
844 if ( my $error = $@ ) {
845 chomp $error;
846 $self->log->error(
847 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
848 }
ac733264 849 $self->setup_actions($self);
fc7ec1d9 850 $self->components( {} );
ac733264 851 for my $comp ( $self->_components($self) ) {
852 $self->components->{ ref $comp } = $comp;
853 $self->setup_actions($comp);
fc7ec1d9 854 }
0f7ecc53 855 my $t = Text::ASCIITable->new;
856 $t->setCols('Class');
857 $t->addRow($_) for keys %{ $self->components };
858 $self->log->debug( 'Loaded components', $t->draw )
859 if ( @{ $t->{tbl_rows} } && $self->debug );
4cf083b1 860 my $actions = $self->actions;
0f7ecc53 861 my $privates = Text::ASCIITable->new;
862 $privates->setCols( 'Action', 'Class', 'Code' );
863 my $walker = sub {
864 my ( $walker, $parent, $prefix ) = @_;
4cf083b1 865 $prefix .= $parent->getNodeValue || '';
866 $prefix .= '/' unless $prefix =~ /\/$/;
867 my $uid = $parent->getUID;
868 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
869 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
0f7ecc53 870 $privates->addRow( "$prefix$action", $class, $code );
4cf083b1 871 }
0f7ecc53 872 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
4cf083b1 873 };
0f7ecc53 874 $walker->( $walker, $self->tree, '' );
875 $self->log->debug( 'Loaded private actions', $privates->draw )
876 if ( @{ $privates->{tbl_rows} } && $self->debug );
877 my $publics = Text::ASCIITable->new;
878 $publics->setCols( 'Action', 'Class', 'Code' );
6655e7d4 879 for my $plain ( sort keys %{ $actions->{plain} } ) {
4cf083b1 880 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
0f7ecc53 881 $publics->addRow( "/$plain", $class, $code );
4cf083b1 882 }
0f7ecc53 883 $self->log->debug( 'Loaded public actions', $publics->draw )
884 if ( @{ $publics->{tbl_rows} } && $self->debug );
885 my $regexes = Text::ASCIITable->new;
886 $regexes->setCols( 'Action', 'Class', 'Code' );
6655e7d4 887 for my $regex ( sort keys %{ $actions->{regex} } ) {
4cf083b1 888 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
0f7ecc53 889 $regexes->addRow( $regex, $class, $code );
4cf083b1 890 }
0f7ecc53 891 $self->log->debug( 'Loaded regex actions', $regexes->draw )
892 if ( @{ $regexes->{tbl_rows} } && $self->debug );
fc7ec1d9 893}
894
23f9d934 895=item $c->stash
fc7ec1d9 896
897Returns a hashref containing all your data.
898
899 $c->stash->{foo} ||= 'yada';
900 print $c->stash->{foo};
901
902=cut
903
904sub stash {
905 my $self = shift;
906 if ( $_[0] ) {
907 my $stash = $_[1] ? {@_} : $_[0];
908 while ( my ( $key, $val ) = each %$stash ) {
909 $self->{stash}->{$key} = $val;
910 }
911 }
912 return $self->{stash};
913}
914
915sub _prefix {
916 my ( $class, $name ) = @_;
7833fdfc 917 my $prefix = _class2prefix($class);
918 $name = "$prefix/$name" if $prefix;
919 return $name;
920}
921
922sub _class2prefix {
b768faa3 923 my $class = shift || '';
0434eec1 924 my $prefix;
98dcf439 925 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
926 $prefix = lc $2;
927 $prefix =~ s/\:\:/\//g;
0434eec1 928 }
7833fdfc 929 return $prefix;
fc7ec1d9 930}
931
23f9d934 932=back
933
fc7ec1d9 934=head1 AUTHOR
935
936Sebastian Riedel, C<sri@cpan.org>
937
938=head1 COPYRIGHT
939
940This program is free software, you can redistribute it and/or modify it under
941the same terms as Perl itself.
942
943=cut
944
9451;