fixed forward
[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] } );
b9ffe28b 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] } );
b9ffe28b 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] } );
b9ffe28b 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 = $@ ) {
b9ffe28b 206
207 unless ( ref $error ) {
208 chomp $error;
209 $error = qq/Caught exception "$error"/;
210 }
211
6dc87a0f 212 $c->log->error($error);
b9ffe28b 213 $c->error($error);
6dc87a0f 214 $c->state(0);
215 }
216 return $c->state;
217}
218
23f9d934 219=item $c->finalize
fc7ec1d9 220
ca39d576 221Finalize request.
fc7ec1d9 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
ca39d576 279Finalize error.
969647fd 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
ca39d576 382Finalize headers.
fc7ec1d9 383
384=cut
385
386sub finalize_headers { }
387
23f9d934 388=item $c->finalize_output
fc7ec1d9 389
ca39d576 390Finalize output.
fc7ec1d9 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) || '/' }
ca39d576 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] } );
b9ffe28b 443 return if scalar @{ $c->error };
444 last unless $c->state;
2deb0d7c 445 }
b768faa3 446 return $c->state;
fc7ec1d9 447}
448
ca39d576 449=item $c->get_action( $action, $namespace )
66d9e175 450
451Get an action in a given namespace.
452
453=cut
454
455sub get_action {
ca39d576 456 my ( $c, $action, $namespace ) = @_;
f6e054bb 457 return [] unless $action;
66d9e175 458 $namespace ||= '';
ac733264 459 if ($namespace) {
ca39d576 460 $namespace = '' if $namespace eq '/';
461 my $parent = $c->tree;
462 my @results;
463 my %allowed = ( begin => 1, auto => 1, default => 1, end => 1 );
464 if ( $allowed{$action} ) {
1927c219 465 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
466 push @results, [$result] if $result;
467 my $visitor = Tree::Simple::Visitor::FindByPath->new;
468 for my $part ( split '/', $namespace ) {
469 $visitor->setSearchPath($part);
470 $parent->accept($visitor);
471 my $child = $visitor->getResult;
472 my $uid = $child->getUID if $child;
473 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
474 push @results, [$match] if $match;
475 $parent = $child if $child;
476 }
66d9e175 477 }
ca39d576 478 else {
479 if ($namespace) {
480 my $visitor = Tree::Simple::Visitor::FindByPath->new;
481 $visitor->setSearchPath( split '/', $namespace );
482 $parent->accept($visitor);
483 my $child = $visitor->getResult;
484 my $uid = $child->getUID if $child;
485 my $match = $c->actions->{private}->{$uid}->{$action}
486 if $uid;
487 push @results, [$match] if $match;
488 }
489 else {
490 my $result =
491 $c->actions->{private}->{ $parent->getUID }->{$action};
492 push @results, [$result] if $result;
493 }
494 }
495 return \@results;
66d9e175 496 }
497 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
498 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
499 else {
ac733264 500 for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {
501 my $name = $c->actions->{compiled}->[$i]->[0];
502 my $regex = $c->actions->{compiled}->[$i]->[1];
66d9e175 503 if ( $action =~ $regex ) {
504 my @snippets;
505 for my $i ( 1 .. 9 ) {
506 no strict 'refs';
507 last unless ${$i};
508 push @snippets, ${$i};
509 }
510 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
511 }
512 }
513 }
514 return [];
515}
516
b76d7db8 517=item $c->handler( $class, $r )
fc7ec1d9 518
ca39d576 519Handles the request.
fc7ec1d9 520
521=cut
522
6dc87a0f 523sub handler {
524 my ( $class, $engine ) = @_;
fc7ec1d9 525
526 # Always expect worst case!
527 my $status = -1;
528 eval {
d41516b2 529 my @stats = ();
fc7ec1d9 530 my $handler = sub {
6dc87a0f 531 my $c = $class->prepare($engine);
d41516b2 532 $c->{stats} = \@stats;
63b763c5 533 $c->dispatch;
fc7ec1d9 534 return $c->finalize;
535 };
536 if ( $class->debug ) {
537 my $elapsed;
538 ( $elapsed, $status ) = $class->benchmark($handler);
539 $elapsed = sprintf '%f', $elapsed;
540 my $av = sprintf '%.3f', 1 / $elapsed;
0f7ecc53 541 my $t = Text::ASCIITable->new;
542 $t->setCols( 'Action', 'Time' );
3f36a3a3 543 $t->setColWidth( 'Action', 64, 1 );
544 $t->setColWidth( 'Time', 9, 1 );
0822f9a4 545
0f7ecc53 546 for my $stat (@stats) {
55c388c1 547 $t->addRow( wrap( $stat->[0], 64 ), wrap( $stat->[1], 9 ) );
0f7ecc53 548 }
549 $class->log->info( "Request took $elapsed" . "s ($av/s)",
550 $t->draw );
fc7ec1d9 551 }
552 else { $status = &$handler }
553 };
554 if ( my $error = $@ ) {
555 chomp $error;
556 $class->log->error(qq/Caught exception in engine "$error"/);
557 }
558 $COUNT++;
559 return $status;
560}
561
23f9d934 562=item $c->prepare($r)
fc7ec1d9 563
a554cc3b 564Turns the engine-specific request( Apache, CGI ... )
565into a Catalyst context .
fc7ec1d9 566
567=cut
568
569sub prepare {
570 my ( $class, $r ) = @_;
571 my $c = bless {
572 request => Catalyst::Request->new(
573 {
574 arguments => [],
575 cookies => {},
576 headers => HTTP::Headers->new,
577 parameters => {},
578 snippets => [],
579 uploads => {}
580 }
581 ),
582 response => Catalyst::Response->new(
583 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
584 ),
b768faa3 585 stash => {},
586 state => 0
fc7ec1d9 587 }, $class;
588 if ( $c->debug ) {
589 my $secs = time - $START || 1;
590 my $av = sprintf '%.3f', $COUNT / $secs;
1a0250cb 591 $c->log->debug('**********************************');
fc7ec1d9 592 $c->log->debug("* Request $COUNT ($av/s) [$$]");
1a0250cb 593 $c->log->debug('**********************************');
fc7ec1d9 594 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
595 }
596 $c->prepare_request($r);
597 $c->prepare_path;
ac733264 598 $c->prepare_headers;
1a80619d 599 $c->prepare_cookies;
0556eb49 600 $c->prepare_connection;
601 my $method = $c->req->method || '';
602 my $path = $c->req->path || '';
603 my $hostname = $c->req->hostname || '';
604 my $address = $c->req->address || '';
605 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
606 if $c->debug;
fc7ec1d9 607 $c->prepare_action;
608 $c->prepare_parameters;
c85ff642 609
610 if ( $c->debug && keys %{ $c->req->params } ) {
0f7ecc53 611 my $t = Text::ASCIITable->new;
612 $t->setCols( 'Key', 'Value' );
0822f9a4 613 $t->setColWidth( 'Key', 37, 1 );
614 $t->setColWidth( 'Value', 36, 1 );
c85ff642 615 for my $key ( keys %{ $c->req->params } ) {
b5524568 616 my $value = $c->req->params->{$key} || '';
55c388c1 617 $t->addRow( wrap( $key, 37 ), wrap( $value, 36 ) );
c85ff642 618 }
0f7ecc53 619 $c->log->debug( 'Parameters are', $t->draw );
c85ff642 620 }
fc7ec1d9 621 $c->prepare_uploads;
622 return $c;
623}
624
23f9d934 625=item $c->prepare_action
fc7ec1d9 626
ca39d576 627Prepare action.
fc7ec1d9 628
629=cut
630
631sub prepare_action {
632 my $c = shift;
633 my $path = $c->req->path;
634 my @path = split /\//, $c->req->path;
635 $c->req->args( \my @args );
636 while (@path) {
7833fdfc 637 $path = join '/', @path;
0169d3a8 638 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 639
640 # It's a regex
641 if ($#$result) {
7e5adedd 642 my $match = $result->[1];
643 my @snippets = @{ $result->[2] };
81f6fc50 644 $c->log->debug(
645 qq/Requested action is "$path" and matched "$match"/)
fc7ec1d9 646 if $c->debug;
647 $c->log->debug(
648 'Snippets are "' . join( ' ', @snippets ) . '"' )
649 if ( $c->debug && @snippets );
650 $c->req->action($match);
651 $c->req->snippets( \@snippets );
652 }
653 else {
654 $c->req->action($path);
81f6fc50 655 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
fc7ec1d9 656 }
657 $c->req->match($path);
fc7ec1d9 658 last;
659 }
660 unshift @args, pop @path;
661 }
662 unless ( $c->req->action ) {
ac733264 663 $c->req->action('default');
87e67021 664 $c->req->match('');
fc7ec1d9 665 }
5783a9a5 666 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
667 if ( $c->debug && @args );
fc7ec1d9 668}
669
c9afa5fc 670=item $c->prepare_connection
0556eb49 671
ca39d576 672Prepare connection.
0556eb49 673
674=cut
675
676sub prepare_connection { }
677
c9afa5fc 678=item $c->prepare_cookies
fc7ec1d9 679
ca39d576 680Prepare cookies.
fc7ec1d9 681
682=cut
683
6dc87a0f 684sub prepare_cookies {
685 my $c = shift;
686
687 if ( my $header = $c->request->header('Cookie') ) {
688 $c->req->cookies( { CGI::Cookie->parse($header) } );
689 }
690}
fc7ec1d9 691
23f9d934 692=item $c->prepare_headers
fc7ec1d9 693
ca39d576 694Prepare headers.
fc7ec1d9 695
696=cut
697
698sub prepare_headers { }
699
23f9d934 700=item $c->prepare_parameters
fc7ec1d9 701
ca39d576 702Prepare parameters.
fc7ec1d9 703
704=cut
705
706sub prepare_parameters { }
707
23f9d934 708=item $c->prepare_path
fc7ec1d9 709
ca39d576 710Prepare path and base.
fc7ec1d9 711
712=cut
713
714sub prepare_path { }
715
23f9d934 716=item $c->prepare_request
fc7ec1d9 717
ca39d576 718Prepare the engine request.
fc7ec1d9 719
720=cut
721
722sub prepare_request { }
723
23f9d934 724=item $c->prepare_uploads
fc7ec1d9 725
ca39d576 726Prepare uploads.
fc7ec1d9 727
728=cut
729
730sub prepare_uploads { }
731
c9afa5fc 732=item $c->run
733
ca39d576 734Starts the engine.
c9afa5fc 735
736=cut
737
738sub run { }
739
61b1e958 740=item $c->request
fc7ec1d9 741
ca39d576 742=item $c->req
23f9d934 743
ca39d576 744Returns a C<Catalyst::Request> object.
fc7ec1d9 745
ca39d576 746 my $req = $c->req;
61b1e958 747
748=item $c->response
749
ca39d576 750=item $c->res
751
fc7ec1d9 752Returns a C<Catalyst::Response> object.
753
754 my $res = $c->res;
755
ac733264 756=item $c->set_action( $action, $code, $namespace, $attrs )
66d9e175 757
ca39d576 758Set an action in a given namespace.
66d9e175 759
760=cut
761
762sub set_action {
ac733264 763 my ( $c, $method, $code, $namespace, $attrs ) = @_;
764
6372237c 765 my $prefix = _class2prefix($namespace) || '';
766 my %flags;
ac733264 767
768 for my $attr ( @{$attrs} ) {
98dcf439 769 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
770 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
771 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
772 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
1d4ea19d 773 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
66d9e175 774 }
ac733264 775
6372237c 776 return unless keys %flags;
ac733264 777
778 my $parent = $c->tree;
779 my $visitor = Tree::Simple::Visitor::FindByPath->new;
780 for my $part ( split '/', $prefix ) {
781 $visitor->setSearchPath($part);
782 $parent->accept($visitor);
783 my $child = $visitor->getResult;
784 unless ($child) {
785 $child = $parent->addChild( Tree::Simple->new($part) );
66d9e175 786 $visitor->setSearchPath($part);
787 $parent->accept($visitor);
ac733264 788 $child = $visitor->getResult;
66d9e175 789 }
ac733264 790 $parent = $child;
66d9e175 791 }
ac733264 792 my $uid = $parent->getUID;
793 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
794 my $forward = $prefix ? "$prefix/$method" : $method;
ac733264 795
6372237c 796 if ( $flags{path} ) {
797 $flags{path} =~ s/^\w+//;
798 $flags{path} =~ s/\w+$//;
799 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
800 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
801 }
802 if ( $flags{regex} ) {
803 $flags{regex} =~ s/^\w+//;
804 $flags{regex} =~ s/\w+$//;
805 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
806 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
807 }
ac733264 808
fee92828 809 my $reverse = $prefix ? "$prefix/$method" : $method;
ac733264 810
6372237c 811 if ( $flags{local} || $flags{global} || $flags{path} ) {
812 my $path = $flags{path} || $method;
813 my $absolute = 0;
814 if ( $path =~ /^\/(.+)/ ) {
815 $path = $1;
816 $absolute = 1;
ac733264 817 }
8702d594 818 $absolute = 1 if $flags{global};
384698de 819 my $name = $absolute ? $path : $prefix ? "$prefix/$path" : $path;
ac733264 820 $c->actions->{plain}->{$name} = [ $namespace, $code ];
ac733264 821 }
6372237c 822 if ( my $regex = $flags{regex} ) {
823 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
824 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
ac733264 825 }
826
827 $c->actions->{reverse}->{"$code"} = $reverse;
66d9e175 828}
829
23f9d934 830=item $class->setup
fc7ec1d9 831
ca39d576 832Setup.
fc7ec1d9 833
834 MyApp->setup;
835
836=cut
837
838sub setup {
839 my $self = shift;
840 $self->setup_components;
841 if ( $self->debug ) {
842 my $name = $self->config->{name} || 'Application';
843 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
844 }
845}
846
ac733264 847=item $class->setup_actions($component)
848
849Setup actions for a component.
850
851=cut
852
853sub setup_actions {
854 my ( $self, $comp ) = @_;
855 $comp = ref $comp || $comp;
856 for my $action ( @{ $comp->_cache } ) {
857 my ( $code, $attrs ) = @{$action};
858 my $name = '';
859 no strict 'refs';
98dcf439 860 my @cache = ( $comp, @{"$comp\::ISA"} );
bb6823f2 861 my %namespaces;
98dcf439 862 while ( my $namespace = shift @cache ) {
bb6823f2 863 $namespaces{$namespace}++;
98dcf439 864 for my $isa ( @{"$comp\::ISA"} ) {
bb6823f2 865 next if $namespaces{$isa};
98dcf439 866 push @cache, $isa;
bb6823f2 867 $namespaces{$isa}++;
98dcf439 868 }
869 }
bb6823f2 870 for my $namespace ( keys %namespaces ) {
98dcf439 871 for my $sym ( values %{ $namespace . '::' } ) {
872 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
873 $name = *{$sym}{NAME};
874 $self->set_action( $name, $code, $comp, $attrs );
875 last;
876 }
ac733264 877 }
878 }
879 }
880}
881
23f9d934 882=item $class->setup_components
fc7ec1d9 883
ca39d576 884Setup components.
fc7ec1d9 885
886=cut
887
888sub setup_components {
889 my $self = shift;
890
891 # Components
892 my $class = ref $self || $self;
893 eval <<"";
894 package $class;
895 import Module::Pluggable::Fast
896 name => '_components',
897 search => [
898 '$class\::Controller', '$class\::C',
899 '$class\::Model', '$class\::M',
900 '$class\::View', '$class\::V'
901 ];
902
903 if ( my $error = $@ ) {
904 chomp $error;
905 $self->log->error(
906 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
907 }
ac733264 908 $self->setup_actions($self);
fc7ec1d9 909 $self->components( {} );
ac733264 910 for my $comp ( $self->_components($self) ) {
911 $self->components->{ ref $comp } = $comp;
912 $self->setup_actions($comp);
fc7ec1d9 913 }
63b763c5 914 my $t = Text::ASCIITable->new( { hide_HeadRow => 1, hide_HeadLine => 1 } );
0f7ecc53 915 $t->setCols('Class');
0822f9a4 916 $t->setColWidth( 'Class', 75, 1 );
55c388c1 917 $t->addRow( wrap( $_, 75 ) ) for keys %{ $self->components };
0f7ecc53 918 $self->log->debug( 'Loaded components', $t->draw )
919 if ( @{ $t->{tbl_rows} } && $self->debug );
4cf083b1 920 my $actions = $self->actions;
0f7ecc53 921 my $privates = Text::ASCIITable->new;
d2d570d4 922 $privates->setCols( 'Private', 'Class', 'Code' );
923 $privates->setColWidth( 'Private', 28, 1 );
924 $privates->setColWidth( 'Class', 28, 1 );
925 $privates->setColWidth( 'Code', 14, 1 );
0f7ecc53 926 my $walker = sub {
927 my ( $walker, $parent, $prefix ) = @_;
4cf083b1 928 $prefix .= $parent->getNodeValue || '';
929 $prefix .= '/' unless $prefix =~ /\/$/;
930 my $uid = $parent->getUID;
931 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
932 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
55c388c1 933 $privates->addRow(
934 wrap( "$prefix$action", 28 ),
935 wrap( $class, 28 ),
936 wrap( $code, 14 )
937 );
4cf083b1 938 }
0f7ecc53 939 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
4cf083b1 940 };
0f7ecc53 941 $walker->( $walker, $self->tree, '' );
942 $self->log->debug( 'Loaded private actions', $privates->draw )
943 if ( @{ $privates->{tbl_rows} } && $self->debug );
944 my $publics = Text::ASCIITable->new;
d2d570d4 945 $publics->setCols( 'Public', 'Private' );
946 $publics->setColWidth( 'Public', 37, 1 );
947 $publics->setColWidth( 'Private', 36, 1 );
0822f9a4 948
6655e7d4 949 for my $plain ( sort keys %{ $actions->{plain} } ) {
4cf083b1 950 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
d2d570d4 951 $publics->addRow( wrap( "/$plain", 37 ),
952 wrap( $self->actions->{reverse}->{$code} || $code, 36 ) );
4cf083b1 953 }
0f7ecc53 954 $self->log->debug( 'Loaded public actions', $publics->draw )
955 if ( @{ $publics->{tbl_rows} } && $self->debug );
956 my $regexes = Text::ASCIITable->new;
d2d570d4 957 $regexes->setCols( 'Regex', 'Private' );
958 $regexes->setColWidth( 'Regex', 37, 1 );
959 $regexes->setColWidth( 'Private', 36, 1 );
6655e7d4 960 for my $regex ( sort keys %{ $actions->{regex} } ) {
4cf083b1 961 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
d2d570d4 962 $regexes->addRow( wrap( $regex, 37 ),
963 wrap( $self->actions->{reverse}->{$class} || $class, 36 ) );
4cf083b1 964 }
0f7ecc53 965 $self->log->debug( 'Loaded regex actions', $regexes->draw )
966 if ( @{ $regexes->{tbl_rows} } && $self->debug );
fc7ec1d9 967}
968
63b763c5 969=item $c->state
970
971Contains the return value of the last executed action.
972
23f9d934 973=item $c->stash
fc7ec1d9 974
ca39d576 975Returns a hashref containing all your data.
fc7ec1d9 976
977 $c->stash->{foo} ||= 'yada';
978 print $c->stash->{foo};
979
980=cut
981
982sub stash {
983 my $self = shift;
984 if ( $_[0] ) {
985 my $stash = $_[1] ? {@_} : $_[0];
986 while ( my ( $key, $val ) = each %$stash ) {
987 $self->{stash}->{$key} = $val;
988 }
989 }
990 return $self->{stash};
991}
992
993sub _prefix {
994 my ( $class, $name ) = @_;
7833fdfc 995 my $prefix = _class2prefix($class);
996 $name = "$prefix/$name" if $prefix;
997 return $name;
998}
999
1000sub _class2prefix {
b768faa3 1001 my $class = shift || '';
0434eec1 1002 my $prefix;
98dcf439 1003 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
1004 $prefix = lc $2;
1005 $prefix =~ s/\:\:/\//g;
0434eec1 1006 }
7833fdfc 1007 return $prefix;
fc7ec1d9 1008}
1009
23f9d934 1010=back
1011
fc7ec1d9 1012=head1 AUTHOR
1013
1014Sebastian Riedel, C<sri@cpan.org>
1015
1016=head1 COPYRIGHT
1017
1018This program is free software, you can redistribute it and/or modify it under
1019the same terms as Perl itself.
1020
1021=cut
1022
10231;