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