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