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