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