added dependency to Text::ASCITable for some eyecandy in logs,
[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] };
fc7ec1d9 585 $c->log->debug(qq/Requested action "$path" matched "$match"/)
586 if $c->debug;
587 $c->log->debug(
588 'Snippets are "' . join( ' ', @snippets ) . '"' )
589 if ( $c->debug && @snippets );
590 $c->req->action($match);
591 $c->req->snippets( \@snippets );
592 }
593 else {
594 $c->req->action($path);
595 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
596 }
597 $c->req->match($path);
fc7ec1d9 598 last;
599 }
600 unshift @args, pop @path;
601 }
602 unless ( $c->req->action ) {
ac733264 603 $c->req->action('default');
87e67021 604 $c->req->match('');
fc7ec1d9 605 }
5783a9a5 606 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
607 if ( $c->debug && @args );
fc7ec1d9 608}
609
c9afa5fc 610=item $c->prepare_connection
0556eb49 611
612Prepare connection.
613
614=cut
615
616sub prepare_connection { }
617
c9afa5fc 618=item $c->prepare_cookies
fc7ec1d9 619
620Prepare cookies.
621
622=cut
623
6dc87a0f 624sub prepare_cookies {
625 my $c = shift;
626
627 if ( my $header = $c->request->header('Cookie') ) {
628 $c->req->cookies( { CGI::Cookie->parse($header) } );
629 }
630}
fc7ec1d9 631
23f9d934 632=item $c->prepare_headers
fc7ec1d9 633
634Prepare headers.
635
636=cut
637
638sub prepare_headers { }
639
23f9d934 640=item $c->prepare_parameters
fc7ec1d9 641
642Prepare parameters.
643
644=cut
645
646sub prepare_parameters { }
647
23f9d934 648=item $c->prepare_path
fc7ec1d9 649
650Prepare path and base.
651
652=cut
653
654sub prepare_path { }
655
23f9d934 656=item $c->prepare_request
fc7ec1d9 657
658Prepare the engine request.
659
660=cut
661
662sub prepare_request { }
663
23f9d934 664=item $c->prepare_uploads
fc7ec1d9 665
666Prepare uploads.
667
668=cut
669
670sub prepare_uploads { }
671
c9afa5fc 672=item $c->run
673
674Starts the engine.
675
676=cut
677
678sub run { }
679
23f9d934 680=item $c->request
681
682=item $c->req
fc7ec1d9 683
684Returns a C<Catalyst::Request> object.
685
686 my $req = $c->req;
687
23f9d934 688=item $c->response
689
690=item $c->res
fc7ec1d9 691
692Returns a C<Catalyst::Response> object.
693
694 my $res = $c->res;
695
ac733264 696=item $c->set_action( $action, $code, $namespace, $attrs )
66d9e175 697
698Set an action in a given namespace.
699
700=cut
701
702sub set_action {
ac733264 703 my ( $c, $method, $code, $namespace, $attrs ) = @_;
704
6372237c 705 my $prefix = _class2prefix($namespace) || '';
706 my %flags;
ac733264 707
708 for my $attr ( @{$attrs} ) {
98dcf439 709 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
710 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
711 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
712 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
1d4ea19d 713 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
66d9e175 714 }
ac733264 715
6372237c 716 return unless keys %flags;
ac733264 717
718 my $parent = $c->tree;
719 my $visitor = Tree::Simple::Visitor::FindByPath->new;
720 for my $part ( split '/', $prefix ) {
721 $visitor->setSearchPath($part);
722 $parent->accept($visitor);
723 my $child = $visitor->getResult;
724 unless ($child) {
725 $child = $parent->addChild( Tree::Simple->new($part) );
66d9e175 726 $visitor->setSearchPath($part);
727 $parent->accept($visitor);
ac733264 728 $child = $visitor->getResult;
66d9e175 729 }
ac733264 730 $parent = $child;
66d9e175 731 }
ac733264 732 my $uid = $parent->getUID;
733 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
734 my $forward = $prefix ? "$prefix/$method" : $method;
ac733264 735
6372237c 736 if ( $flags{path} ) {
737 $flags{path} =~ s/^\w+//;
738 $flags{path} =~ s/\w+$//;
739 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
740 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
741 }
742 if ( $flags{regex} ) {
743 $flags{regex} =~ s/^\w+//;
744 $flags{regex} =~ s/\w+$//;
745 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
746 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
747 }
ac733264 748
fee92828 749 my $reverse = $prefix ? "$prefix/$method" : $method;
ac733264 750
6372237c 751 if ( $flags{local} || $flags{global} || $flags{path} ) {
752 my $path = $flags{path} || $method;
753 my $absolute = 0;
754 if ( $path =~ /^\/(.+)/ ) {
755 $path = $1;
756 $absolute = 1;
ac733264 757 }
8702d594 758 $absolute = 1 if $flags{global};
6372237c 759 my $name = $absolute ? $path : "$prefix/$path";
ac733264 760 $c->actions->{plain}->{$name} = [ $namespace, $code ];
ac733264 761 }
6372237c 762 if ( my $regex = $flags{regex} ) {
763 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
764 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
ac733264 765 }
766
767 $c->actions->{reverse}->{"$code"} = $reverse;
66d9e175 768}
769
23f9d934 770=item $class->setup
fc7ec1d9 771
772Setup.
773
774 MyApp->setup;
775
776=cut
777
778sub setup {
779 my $self = shift;
780 $self->setup_components;
781 if ( $self->debug ) {
782 my $name = $self->config->{name} || 'Application';
783 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
784 }
785}
786
ac733264 787=item $class->setup_actions($component)
788
789Setup actions for a component.
790
791=cut
792
793sub setup_actions {
794 my ( $self, $comp ) = @_;
795 $comp = ref $comp || $comp;
796 for my $action ( @{ $comp->_cache } ) {
797 my ( $code, $attrs ) = @{$action};
798 my $name = '';
799 no strict 'refs';
98dcf439 800 my @cache = ( $comp, @{"$comp\::ISA"} );
bb6823f2 801 my %namespaces;
98dcf439 802 while ( my $namespace = shift @cache ) {
bb6823f2 803 $namespaces{$namespace}++;
98dcf439 804 for my $isa ( @{"$comp\::ISA"} ) {
bb6823f2 805 next if $namespaces{$isa};
98dcf439 806 push @cache, $isa;
bb6823f2 807 $namespaces{$isa}++;
98dcf439 808 }
809 }
bb6823f2 810 for my $namespace ( keys %namespaces ) {
98dcf439 811 for my $sym ( values %{ $namespace . '::' } ) {
812 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
813 $name = *{$sym}{NAME};
814 $self->set_action( $name, $code, $comp, $attrs );
815 last;
816 }
ac733264 817 }
818 }
819 }
820}
821
23f9d934 822=item $class->setup_components
fc7ec1d9 823
824Setup components.
825
826=cut
827
828sub setup_components {
829 my $self = shift;
830
831 # Components
832 my $class = ref $self || $self;
833 eval <<"";
834 package $class;
835 import Module::Pluggable::Fast
836 name => '_components',
837 search => [
838 '$class\::Controller', '$class\::C',
839 '$class\::Model', '$class\::M',
840 '$class\::View', '$class\::V'
841 ];
842
843 if ( my $error = $@ ) {
844 chomp $error;
845 $self->log->error(
846 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
847 }
ac733264 848 $self->setup_actions($self);
fc7ec1d9 849 $self->components( {} );
ac733264 850 for my $comp ( $self->_components($self) ) {
851 $self->components->{ ref $comp } = $comp;
852 $self->setup_actions($comp);
fc7ec1d9 853 }
0f7ecc53 854 my $t = Text::ASCIITable->new;
855 $t->setCols('Class');
856 $t->addRow($_) for keys %{ $self->components };
857 $self->log->debug( 'Loaded components', $t->draw )
858 if ( @{ $t->{tbl_rows} } && $self->debug );
4cf083b1 859 my $actions = $self->actions;
0f7ecc53 860 my $privates = Text::ASCIITable->new;
861 $privates->setCols( 'Action', 'Class', 'Code' );
862 my $walker = sub {
863 my ( $walker, $parent, $prefix ) = @_;
4cf083b1 864 $prefix .= $parent->getNodeValue || '';
865 $prefix .= '/' unless $prefix =~ /\/$/;
866 my $uid = $parent->getUID;
867 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
868 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
0f7ecc53 869 $privates->addRow( "$prefix$action", $class, $code );
4cf083b1 870 }
0f7ecc53 871 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
4cf083b1 872 };
0f7ecc53 873 $walker->( $walker, $self->tree, '' );
874 $self->log->debug( 'Loaded private actions', $privates->draw )
875 if ( @{ $privates->{tbl_rows} } && $self->debug );
876 my $publics = Text::ASCIITable->new;
877 $publics->setCols( 'Action', 'Class', 'Code' );
6655e7d4 878 for my $plain ( sort keys %{ $actions->{plain} } ) {
4cf083b1 879 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
0f7ecc53 880 $publics->addRow( "/$plain", $class, $code );
4cf083b1 881 }
0f7ecc53 882 $self->log->debug( 'Loaded public actions', $publics->draw )
883 if ( @{ $publics->{tbl_rows} } && $self->debug );
884 my $regexes = Text::ASCIITable->new;
885 $regexes->setCols( 'Action', 'Class', 'Code' );
6655e7d4 886 for my $regex ( sort keys %{ $actions->{regex} } ) {
4cf083b1 887 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
0f7ecc53 888 $regexes->addRow( $regex, $class, $code );
4cf083b1 889 }
0f7ecc53 890 $self->log->debug( 'Loaded regex actions', $regexes->draw )
891 if ( @{ $regexes->{tbl_rows} } && $self->debug );
fc7ec1d9 892}
893
23f9d934 894=item $c->stash
fc7ec1d9 895
896Returns a hashref containing all your data.
897
898 $c->stash->{foo} ||= 'yada';
899 print $c->stash->{foo};
900
901=cut
902
903sub stash {
904 my $self = shift;
905 if ( $_[0] ) {
906 my $stash = $_[1] ? {@_} : $_[0];
907 while ( my ( $key, $val ) = each %$stash ) {
908 $self->{stash}->{$key} = $val;
909 }
910 }
911 return $self->{stash};
912}
913
914sub _prefix {
915 my ( $class, $name ) = @_;
7833fdfc 916 my $prefix = _class2prefix($class);
917 $name = "$prefix/$name" if $prefix;
918 return $name;
919}
920
921sub _class2prefix {
b768faa3 922 my $class = shift || '';
0434eec1 923 my $prefix;
98dcf439 924 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
925 $prefix = lc $2;
926 $prefix =~ s/\:\:/\//g;
0434eec1 927 }
7833fdfc 928 return $prefix;
fc7ec1d9 929}
930
72596f03 931sub _prettify_action {
fee92828 932 my ( $val1, $val2, $val3 ) = @_;
91dc9907 933 formline ' + @<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<'
934 . ' @>>>>>>>>>>>>>> ', $val1, $val2, $val3;
af2ad181 935 my $formatted = $^A;
936 $^A = '';
937 return $formatted;
938}
939
72596f03 940sub _prettify_stats {
941 my ( $val1, $val2 ) = @_;
91dc9907 942 formline ' + @<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<< ',
72596f03 943 $val1, $val2;
944 my $formatted = $^A;
945 $^A = '';
946 return $formatted;
947}
948
23f9d934 949=back
950
fc7ec1d9 951=head1 AUTHOR
952
953Sebastian Riedel, C<sri@cpan.org>
954
955=head1 COPYRIGHT
956
957This program is free software, you can redistribute it and/or modify it under
958the same terms as Perl itself.
959
960=cut
961
9621;