helper generates ::V::, not ::View::
[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
61b1e958 50This is the core of catalyst. The various drivers are subclasses
51of this class.
52
23f9d934 53=head1 METHODS
fc7ec1d9 54
23f9d934 55=over 4
56
23f9d934 57=item $c->benchmark($coderef)
fc7ec1d9 58
59Takes a coderef with arguments and returns elapsed time as float.
60
61 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
62 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
63
64=cut
65
66sub benchmark {
67 my $c = shift;
68 my $code = shift;
69 my $time = [gettimeofday];
70 my @return = &$code(@_);
71 my $elapsed = tv_interval $time;
72 return wantarray ? ( $elapsed, @return ) : $elapsed;
73}
74
23f9d934 75=item $c->comp($name)
76
61b1e958 77Shortcut for $c->component
78
23f9d934 79=item $c->component($name)
fc7ec1d9 80
81Get a component object by name.
82
83 $c->comp('MyApp::Model::MyModel')->do_stuff;
84
85Regex search for a component.
86
87 $c->comp('mymodel')->do_stuff;
88
89=cut
90
91sub component {
92 my ( $c, $name ) = @_;
93 if ( my $component = $c->components->{$name} ) {
94 return $component;
95 }
96 else {
97 for my $component ( keys %{ $c->components } ) {
98 return $c->components->{$component} if $component =~ /$name/i;
99 }
100 }
101}
102
63b763c5 103=item $c->dispatch
104
105Dispatch request to actions.
106
107=cut
108
109sub dispatch {
110 my $c = shift;
111 my $action = $c->req->action;
112 my $namespace = '';
113 $namespace = ( join( '/', @{ $c->req->args } ) || '/' )
114 if $action eq 'default';
115 unless ($namespace) {
116 if ( my $result = $c->get_action($action) ) {
117 $namespace = _class2prefix( $result->[0]->[0]->[0] );
118 }
119 }
120 my $default = $action eq 'default' ? $namespace : undef;
121 my $results = $c->get_action( $action, $default );
122 $namespace ||= '/';
123 if ( @{$results} ) {
5bf31738 124
125 # Execute last begin
126 $c->state(1);
127 if ( my $begin = @{ $c->get_action( 'begin', $namespace ) }[-1] ) {
a135d186 128 $c->execute( @{ $begin->[0] } );
b9ffe28b 129 return if scalar @{ $c->error };
63b763c5 130 }
5bf31738 131
132 # Execute the auto chain
133 for my $auto ( @{ $c->get_action( 'auto', $namespace ) } ) {
134 $c->execute( @{ $auto->[0] } );
b9ffe28b 135 return if scalar @{ $c->error };
1c470b06 136 last unless $c->state;
63b763c5 137 }
5bf31738 138
139 # Execute the action or last default
140 if ( ( my $action = $c->req->action ) && $c->state ) {
141 if ( my $result = @{ $c->get_action( $action, $default ) }[-1] ) {
142 $c->execute( @{ $result->[0] } );
143 }
144 }
145
146 # Execute last end
147 if ( my $end = @{ $c->get_action( 'end', $namespace ) }[-1] ) {
a135d186 148 $c->execute( @{ $end->[0] } );
b9ffe28b 149 return if scalar @{ $c->error };
63b763c5 150 }
151 }
152 else {
153 my $path = $c->req->path;
154 my $error = $path
155 ? qq/Unknown resource "$path"/
156 : "No default action defined";
157 $c->log->error($error) if $c->debug;
158 $c->error($error);
159 }
160}
161
a554cc3b 162=item $c->error
23f9d934 163
a554cc3b 164=item $c->error($error, ...)
23f9d934 165
a554cc3b 166=item $c->error($arrayref)
fc7ec1d9 167
a554cc3b 168Returns an arrayref containing error messages.
fc7ec1d9 169
a554cc3b 170 my @error = @{ $c->error };
fc7ec1d9 171
172Add a new error.
173
a554cc3b 174 $c->error('Something bad happened');
fc7ec1d9 175
176=cut
177
a554cc3b 178sub error {
fc7ec1d9 179 my $c = shift;
a554cc3b 180 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
181 push @{ $c->{error} }, @$error;
182 return $c->{error};
fc7ec1d9 183}
184
6dc87a0f 185=item $c->execute($class, $coderef)
186
187Execute a coderef in given class and catch exceptions.
188Errors are available via $c->error.
189
190=cut
191
192sub execute {
193 my ( $c, $class, $code ) = @_;
194 $class = $c->comp($class) || $class;
195 $c->state(0);
39de91b0 196 my $callsub = ( caller(1) )[3];
6dc87a0f 197 eval {
198 if ( $c->debug )
199 {
200 my $action = $c->actions->{reverse}->{"$code"};
201 $action = "/$action" unless $action =~ /\-\>/;
fb13403c 202 $action = "-> $action" if $callsub =~ /forward$/;
6dc87a0f 203 my ( $elapsed, @state ) =
204 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
0f7ecc53 205 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
6dc87a0f 206 $c->state(@state);
207 }
208 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
209 };
210 if ( my $error = $@ ) {
b9ffe28b 211
212 unless ( ref $error ) {
213 chomp $error;
214 $error = qq/Caught exception "$error"/;
215 }
216
6dc87a0f 217 $c->log->error($error);
b9ffe28b 218 $c->error($error);
6dc87a0f 219 $c->state(0);
220 }
221 return $c->state;
222}
223
23f9d934 224=item $c->finalize
fc7ec1d9 225
61b1e958 226Finalize request. This function can typically be overloaded with
227NEXT by plugins that need to do something at the end of the request.
fc7ec1d9 228
229=cut
230
231sub finalize {
232 my $c = shift;
23f9d934 233
6dc87a0f 234 $c->finalize_cookies;
235
49490aab 236 if ( my $location = $c->response->redirect ) {
23f9d934 237 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
6dc87a0f 238 $c->response->header( Location => $location );
63b763c5 239 $c->response->status(302) if $c->response->status !~ /3\d\d$/;
6dc87a0f 240 }
241
969647fd 242 if ( $#{ $c->error } >= 0 ) {
243 $c->finalize_error;
23f9d934 244 }
245
36b3abcb 246 if ( !$c->response->output && $c->response->status !~ /^(1|3)\d\d$/ ) {
969647fd 247 $c->finalize_error;
248 }
fc7ec1d9 249
c4695f3a 250 if ( $c->response->output && !$c->response->content_length ) {
39de91b0 251 use bytes; # play safe with a utf8 aware perl
49490aab 252 $c->response->content_length( length $c->response->output );
fc7ec1d9 253 }
969647fd 254
fc7ec1d9 255 my $status = $c->finalize_headers;
256 $c->finalize_output;
257 return $status;
258}
259
6dc87a0f 260=item $c->finalize_cookies
261
262Finalize cookies.
263
264=cut
265
266sub finalize_cookies {
267 my $c = shift;
268
269 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
270 my $cookie = CGI::Cookie->new(
271 -name => $name,
272 -value => $cookie->{value},
273 -expires => $cookie->{expires},
274 -domain => $cookie->{domain},
275 -path => $cookie->{path},
276 -secure => $cookie->{secure} || 0
277 );
278
279 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
280 }
281}
282
969647fd 283=item $c->finalize_error
284
61b1e958 285This is the default error screen displayed from finalize. Override
286with your own output if you need something special.
969647fd 287
288=cut
289
290sub finalize_error {
291 my $c = shift;
292
293 $c->res->headers->content_type('text/html');
294 my $name = $c->config->{name} || 'Catalyst Application';
295
296 my ( $title, $error, $infos );
297 if ( $c->debug ) {
298 $error = join '<br/>', @{ $c->error };
299 $error ||= 'No output';
300 $title = $name = "$name on Catalyst $Catalyst::VERSION";
301 my $req = encode_entities Dumper $c->req;
302 my $res = encode_entities Dumper $c->res;
303 my $stash = encode_entities Dumper $c->stash;
304 $infos = <<"";
305<br/>
306<b><u>Request</u></b><br/>
307<pre>$req</pre>
308<b><u>Response</u></b><br/>
309<pre>$res</pre>
310<b><u>Stash</u></b><br/>
311<pre>$stash</pre>
312
313 }
314 else {
315 $title = $name;
316 $error = '';
317 $infos = <<"";
318<pre>
319(en) Please come back later
320(de) Bitte versuchen sie es spaeter nocheinmal
321(nl) Gelieve te komen later terug
322(no) Vennligst prov igjen senere
323(fr) Veuillez revenir plus tard
324(es) Vuelto por favor mas adelante
325(pt) Voltado por favor mais tarde
326(it) Ritornato prego più successivamente
327</pre>
328
329 $name = '';
330 }
331 $c->res->output( <<"" );
332<html>
333<head>
334 <title>$title</title>
335 <style type="text/css">
336 body {
337 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
338 Tahoma, Arial, helvetica, sans-serif;
339 color: #ddd;
340 background-color: #eee;
341 margin: 0px;
342 padding: 0px;
343 }
344 div.box {
345 background-color: #ccc;
346 border: 1px solid #aaa;
347 padding: 4px;
348 margin: 10px;
349 -moz-border-radius: 10px;
350 }
351 div.error {
352 background-color: #977;
353 border: 1px solid #755;
354 padding: 8px;
355 margin: 4px;
356 margin-bottom: 10px;
357 -moz-border-radius: 10px;
358 }
359 div.infos {
360 background-color: #797;
361 border: 1px solid #575;
362 padding: 8px;
363 margin: 4px;
364 margin-bottom: 10px;
365 -moz-border-radius: 10px;
366 }
367 div.name {
368 background-color: #779;
369 border: 1px solid #557;
370 padding: 8px;
371 margin: 4px;
372 -moz-border-radius: 10px;
373 }
374 </style>
375</head>
376<body>
377 <div class="box">
378 <div class="error">$error</div>
379 <div class="infos">$infos</div>
380 <div class="name">$name</div>
381 </div>
382</body>
383</html>
384
385}
386
23f9d934 387=item $c->finalize_headers
fc7ec1d9 388
61b1e958 389Finalize headers. Null action by default.
fc7ec1d9 390
391=cut
392
393sub finalize_headers { }
394
23f9d934 395=item $c->finalize_output
fc7ec1d9 396
61b1e958 397Finalize output. Null action by default
fc7ec1d9 398
399=cut
400
401sub finalize_output { }
402
23f9d934 403=item $c->forward($command)
fc7ec1d9 404
ac733264 405Forward processing to a private action or a method from a class.
fc7ec1d9 406If you define a class without method it will default to process().
407
6196207f 408 $c->forward('/foo');
61b1e958 409 $c->forward('/controller/action');
ac733264 410 $c->forward('index');
fc7ec1d9 411 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
412 $c->forward('MyApp::View::TT');
413
414=cut
415
416sub forward {
417 my $c = shift;
418 my $command = shift;
419 unless ($command) {
420 $c->log->debug('Nothing to forward to') if $c->debug;
421 return 0;
422 }
ac733264 423 my $caller = caller(0);
1927c219 424 my $global = $command =~ /^\// ? 0 : 1;
ac733264 425 my $namespace = '/';
6196207f 426 if ( $command =~ /^\// ) {
89c5fe2d 427 $command =~ /^(.*)\/(\w+)$/;
428 $namespace = $1 || '/';
429 $command = $2;
430 }
ac733264 431 else { $namespace = _class2prefix($caller) || '/' }
1927c219 432 my $results = $c->get_action( $command, $namespace, $global );
ac733264 433 unless ( @{$results} ) {
b768faa3 434 my $class = $command;
fc7ec1d9 435 if ( $class =~ /[^\w\:]/ ) {
436 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
437 return 0;
438 }
439 my $method = shift || 'process';
b768faa3 440 if ( my $code = $class->can($method) ) {
fc7ec1d9 441 $c->actions->{reverse}->{"$code"} = "$class->$method";
2deb0d7c 442 $results = [ [ [ $class, $code ] ] ];
fc7ec1d9 443 }
444 else {
445 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
446 if $c->debug;
447 return 0;
448 }
449 }
2deb0d7c 450 for my $result ( @{$results} ) {
a135d186 451 $c->execute( @{ $result->[0] } );
b9ffe28b 452 return if scalar @{ $c->error };
453 last unless $c->state;
2deb0d7c 454 }
b768faa3 455 return $c->state;
fc7ec1d9 456}
457
1927c219 458=item $c->get_action( $action, $namespace, $global )
66d9e175 459
460Get an action in a given namespace.
461
462=cut
463
464sub get_action {
1927c219 465 my ( $c, $action, $namespace, $global ) = @_;
f6e054bb 466 return [] unless $action;
66d9e175 467 $namespace ||= '';
ac733264 468 if ($namespace) {
1927c219 469 if ($global) {
470 my @results;
471 for my $uid ( keys %{ $c->actions->{private} } ) {
472 if ( my $result = $c->actions->{private}->{$uid}->{$action} ) {
473 push @results, [$result];
474 }
475 }
476 return \@results;
477 }
478 else {
479 $namespace = '' if $namespace eq '/';
480 my $parent = $c->tree;
481 my @results;
482 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
483 push @results, [$result] if $result;
484 my $visitor = Tree::Simple::Visitor::FindByPath->new;
485 for my $part ( split '/', $namespace ) {
486 $visitor->setSearchPath($part);
487 $parent->accept($visitor);
488 my $child = $visitor->getResult;
489 my $uid = $child->getUID if $child;
490 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
491 push @results, [$match] if $match;
492 $parent = $child if $child;
493 }
494 return \@results;
66d9e175 495 }
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
61b1e958 519The main request handler.
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
61b1e958 627Prepare action for processing.
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
61b1e958 672Prepare connection. Null action by default
0556eb49 673
674=cut
675
676sub prepare_connection { }
677
c9afa5fc 678=item $c->prepare_cookies
fc7ec1d9 679
61b1e958 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
61b1e958 694Prepare headers. Null action by default
fc7ec1d9 695
696=cut
697
698sub prepare_headers { }
699
23f9d934 700=item $c->prepare_parameters
fc7ec1d9 701
61b1e958 702Prepare parameters. Null action by default
fc7ec1d9 703
704=cut
705
706sub prepare_parameters { }
707
23f9d934 708=item $c->prepare_path
fc7ec1d9 709
61b1e958 710Prepare path and base. Null action by default
fc7ec1d9 711
712=cut
713
714sub prepare_path { }
715
23f9d934 716=item $c->prepare_request
fc7ec1d9 717
61b1e958 718Prepare the engine request. Null action by default
fc7ec1d9 719
720=cut
721
722sub prepare_request { }
723
23f9d934 724=item $c->prepare_uploads
fc7ec1d9 725
61b1e958 726Prepare uploads. Null action by default
fc7ec1d9 727
728=cut
729
730sub prepare_uploads { }
731
c9afa5fc 732=item $c->run
733
61b1e958 734Starts the engine. Null action by default
c9afa5fc 735
736=cut
737
738sub run { }
739
23f9d934 740=item $c->req
fc7ec1d9 741
61b1e958 742Shortcut for $c->request
fc7ec1d9 743
61b1e958 744=item $c->request
fc7ec1d9 745
61b1e958 746Returns a C<Catalyst::Request> object.
747
748 my $req = $c->request;
23f9d934 749
750=item $c->res
fc7ec1d9 751
61b1e958 752Shortcut for $c->response
753
754=item $c->response
755
fc7ec1d9 756Returns a C<Catalyst::Response> object.
757
758 my $res = $c->res;
759
ac733264 760=item $c->set_action( $action, $code, $namespace, $attrs )
66d9e175 761
61b1e958 762Set an action in a given namespace. Used to defined the actions
763in the attribute handlers.
66d9e175 764
765=cut
766
767sub set_action {
ac733264 768 my ( $c, $method, $code, $namespace, $attrs ) = @_;
769
6372237c 770 my $prefix = _class2prefix($namespace) || '';
771 my %flags;
ac733264 772
773 for my $attr ( @{$attrs} ) {
98dcf439 774 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
775 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
776 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
777 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
1d4ea19d 778 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
66d9e175 779 }
ac733264 780
6372237c 781 return unless keys %flags;
ac733264 782
783 my $parent = $c->tree;
784 my $visitor = Tree::Simple::Visitor::FindByPath->new;
785 for my $part ( split '/', $prefix ) {
786 $visitor->setSearchPath($part);
787 $parent->accept($visitor);
788 my $child = $visitor->getResult;
789 unless ($child) {
790 $child = $parent->addChild( Tree::Simple->new($part) );
66d9e175 791 $visitor->setSearchPath($part);
792 $parent->accept($visitor);
ac733264 793 $child = $visitor->getResult;
66d9e175 794 }
ac733264 795 $parent = $child;
66d9e175 796 }
ac733264 797 my $uid = $parent->getUID;
798 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
799 my $forward = $prefix ? "$prefix/$method" : $method;
ac733264 800
6372237c 801 if ( $flags{path} ) {
802 $flags{path} =~ s/^\w+//;
803 $flags{path} =~ s/\w+$//;
804 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
805 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
806 }
807 if ( $flags{regex} ) {
808 $flags{regex} =~ s/^\w+//;
809 $flags{regex} =~ s/\w+$//;
810 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
811 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
812 }
ac733264 813
fee92828 814 my $reverse = $prefix ? "$prefix/$method" : $method;
ac733264 815
6372237c 816 if ( $flags{local} || $flags{global} || $flags{path} ) {
817 my $path = $flags{path} || $method;
818 my $absolute = 0;
819 if ( $path =~ /^\/(.+)/ ) {
820 $path = $1;
821 $absolute = 1;
ac733264 822 }
8702d594 823 $absolute = 1 if $flags{global};
384698de 824 my $name = $absolute ? $path : $prefix ? "$prefix/$path" : $path;
ac733264 825 $c->actions->{plain}->{$name} = [ $namespace, $code ];
ac733264 826 }
6372237c 827 if ( my $regex = $flags{regex} ) {
828 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
829 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
ac733264 830 }
831
832 $c->actions->{reverse}->{"$code"} = $reverse;
66d9e175 833}
834
23f9d934 835=item $class->setup
fc7ec1d9 836
61b1e958 837Setup the application. required to initialize actions.
fc7ec1d9 838
839 MyApp->setup;
840
841=cut
842
843sub setup {
844 my $self = shift;
845 $self->setup_components;
846 if ( $self->debug ) {
847 my $name = $self->config->{name} || 'Application';
848 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
849 }
850}
851
ac733264 852=item $class->setup_actions($component)
853
854Setup actions for a component.
855
856=cut
857
858sub setup_actions {
859 my ( $self, $comp ) = @_;
860 $comp = ref $comp || $comp;
861 for my $action ( @{ $comp->_cache } ) {
862 my ( $code, $attrs ) = @{$action};
863 my $name = '';
864 no strict 'refs';
98dcf439 865 my @cache = ( $comp, @{"$comp\::ISA"} );
bb6823f2 866 my %namespaces;
98dcf439 867 while ( my $namespace = shift @cache ) {
bb6823f2 868 $namespaces{$namespace}++;
98dcf439 869 for my $isa ( @{"$comp\::ISA"} ) {
bb6823f2 870 next if $namespaces{$isa};
98dcf439 871 push @cache, $isa;
bb6823f2 872 $namespaces{$isa}++;
98dcf439 873 }
874 }
bb6823f2 875 for my $namespace ( keys %namespaces ) {
98dcf439 876 for my $sym ( values %{ $namespace . '::' } ) {
877 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
878 $name = *{$sym}{NAME};
879 $self->set_action( $name, $code, $comp, $attrs );
880 last;
881 }
ac733264 882 }
883 }
884 }
885}
886
23f9d934 887=item $class->setup_components
fc7ec1d9 888
61b1e958 889Setup all the components in YourApp::(M|V|C|Model|View|Controller)::*
fc7ec1d9 890
891=cut
892
893sub setup_components {
894 my $self = shift;
895
896 # Components
897 my $class = ref $self || $self;
898 eval <<"";
899 package $class;
900 import Module::Pluggable::Fast
901 name => '_components',
902 search => [
903 '$class\::Controller', '$class\::C',
904 '$class\::Model', '$class\::M',
905 '$class\::View', '$class\::V'
906 ];
907
908 if ( my $error = $@ ) {
909 chomp $error;
910 $self->log->error(
911 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
912 }
ac733264 913 $self->setup_actions($self);
fc7ec1d9 914 $self->components( {} );
ac733264 915 for my $comp ( $self->_components($self) ) {
916 $self->components->{ ref $comp } = $comp;
917 $self->setup_actions($comp);
fc7ec1d9 918 }
63b763c5 919 my $t = Text::ASCIITable->new( { hide_HeadRow => 1, hide_HeadLine => 1 } );
0f7ecc53 920 $t->setCols('Class');
0822f9a4 921 $t->setColWidth( 'Class', 75, 1 );
55c388c1 922 $t->addRow( wrap( $_, 75 ) ) for keys %{ $self->components };
0f7ecc53 923 $self->log->debug( 'Loaded components', $t->draw )
924 if ( @{ $t->{tbl_rows} } && $self->debug );
4cf083b1 925 my $actions = $self->actions;
0f7ecc53 926 my $privates = Text::ASCIITable->new;
d2d570d4 927 $privates->setCols( 'Private', 'Class', 'Code' );
928 $privates->setColWidth( 'Private', 28, 1 );
929 $privates->setColWidth( 'Class', 28, 1 );
930 $privates->setColWidth( 'Code', 14, 1 );
0f7ecc53 931 my $walker = sub {
932 my ( $walker, $parent, $prefix ) = @_;
4cf083b1 933 $prefix .= $parent->getNodeValue || '';
934 $prefix .= '/' unless $prefix =~ /\/$/;
935 my $uid = $parent->getUID;
936 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
937 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
55c388c1 938 $privates->addRow(
939 wrap( "$prefix$action", 28 ),
940 wrap( $class, 28 ),
941 wrap( $code, 14 )
942 );
4cf083b1 943 }
0f7ecc53 944 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
4cf083b1 945 };
0f7ecc53 946 $walker->( $walker, $self->tree, '' );
947 $self->log->debug( 'Loaded private actions', $privates->draw )
948 if ( @{ $privates->{tbl_rows} } && $self->debug );
949 my $publics = Text::ASCIITable->new;
d2d570d4 950 $publics->setCols( 'Public', 'Private' );
951 $publics->setColWidth( 'Public', 37, 1 );
952 $publics->setColWidth( 'Private', 36, 1 );
0822f9a4 953
6655e7d4 954 for my $plain ( sort keys %{ $actions->{plain} } ) {
4cf083b1 955 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
d2d570d4 956 $publics->addRow( wrap( "/$plain", 37 ),
957 wrap( $self->actions->{reverse}->{$code} || $code, 36 ) );
4cf083b1 958 }
0f7ecc53 959 $self->log->debug( 'Loaded public actions', $publics->draw )
960 if ( @{ $publics->{tbl_rows} } && $self->debug );
961 my $regexes = Text::ASCIITable->new;
d2d570d4 962 $regexes->setCols( 'Regex', 'Private' );
963 $regexes->setColWidth( 'Regex', 37, 1 );
964 $regexes->setColWidth( 'Private', 36, 1 );
6655e7d4 965 for my $regex ( sort keys %{ $actions->{regex} } ) {
4cf083b1 966 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
d2d570d4 967 $regexes->addRow( wrap( $regex, 37 ),
968 wrap( $self->actions->{reverse}->{$class} || $class, 36 ) );
4cf083b1 969 }
0f7ecc53 970 $self->log->debug( 'Loaded regex actions', $regexes->draw )
971 if ( @{ $regexes->{tbl_rows} } && $self->debug );
fc7ec1d9 972}
973
63b763c5 974=item $c->state
975
976Contains the return value of the last executed action.
977
23f9d934 978=item $c->stash
fc7ec1d9 979
61b1e958 980The stash is a global hash which can be used to pass around data
981between your components.
fc7ec1d9 982
983 $c->stash->{foo} ||= 'yada';
984 print $c->stash->{foo};
985
986=cut
987
988sub stash {
989 my $self = shift;
990 if ( $_[0] ) {
991 my $stash = $_[1] ? {@_} : $_[0];
992 while ( my ( $key, $val ) = each %$stash ) {
993 $self->{stash}->{$key} = $val;
994 }
995 }
996 return $self->{stash};
997}
998
999sub _prefix {
1000 my ( $class, $name ) = @_;
7833fdfc 1001 my $prefix = _class2prefix($class);
1002 $name = "$prefix/$name" if $prefix;
1003 return $name;
1004}
1005
1006sub _class2prefix {
b768faa3 1007 my $class = shift || '';
0434eec1 1008 my $prefix;
98dcf439 1009 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
1010 $prefix = lc $2;
1011 $prefix =~ s/\:\:/\//g;
0434eec1 1012 }
7833fdfc 1013 return $prefix;
fc7ec1d9 1014}
1015
23f9d934 1016=back
1017
61b1e958 1018=head1 SEE ALSO
1019
1020=over 4
1021
1022=item L<Catalyst::Engine::Apache> - Apache Engines for MP1/2
1023=item L<Catalyst::Engine::CGI> - CGI Engine
1024=item L<Catalyst::Engine::FCGI> - FastCGI Engine
1025=item L<Catalyst::Engine::HTTP> - Standalone Catalyst Server
1026=item L<Catalyst::Engine::Test> - Engine for testing
1027
1028=back
1029
fc7ec1d9 1030=head1 AUTHOR
1031
1032Sebastian Riedel, C<sri@cpan.org>
61b1e958 1033Marcus Ramberg, C<mramberg@cpan.org>
fc7ec1d9 1034
1035=head1 COPYRIGHT
1036
1037This program is free software, you can redistribute it and/or modify it under
1038the same terms as Perl itself.
1039
1040=cut
1041
10421;