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