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