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