improved 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;
fc7ec1d9 6use Data::Dumper;
7use HTML::Entities;
8use HTTP::Headers;
87e67021 9use Memoize;
fc7ec1d9 10use Time::HiRes qw/gettimeofday tv_interval/;
87e67021 11use Tree::Simple;
12use Tree::Simple::Visitor::FindByPath;
fc7ec1d9 13use Catalyst::Request;
14use Catalyst::Response;
15
16require Module::Pluggable::Fast;
17
18$Data::Dumper::Terse = 1;
19
87e67021 20__PACKAGE__->mk_classdata($_) for qw/actions components tree/;
b768faa3 21__PACKAGE__->mk_accessors(qw/request response state/);
fc7ec1d9 22
23__PACKAGE__->actions(
87e67021 24 { plain => {}, private => {}, regex => {}, compiled => {}, reverse => {} }
25);
26__PACKAGE__->tree( Tree::Simple->new( 0, Tree::Simple->ROOT ) );
fc7ec1d9 27
28*comp = \&component;
29*req = \&request;
30*res = \&response;
31
32our $COUNT = 1;
33our $START = time;
34
87e67021 35memoize('_class2prefix');
36
fc7ec1d9 37=head1 NAME
38
39Catalyst::Engine - The Catalyst Engine
40
41=head1 SYNOPSIS
42
43See L<Catalyst>.
44
45=head1 DESCRIPTION
46
23f9d934 47=head1 METHODS
fc7ec1d9 48
23f9d934 49=over 4
50
51=item $c->action( $name => $coderef, ... )
52
fc7ec1d9 53Add one or more actions.
54
55 $c->action( '!foo' => sub { $_[1]->res->output('Foo!') } );
56
fc7ec1d9 57It also automatically calls setup() if needed.
58
59See L<Catalyst::Manual::Intro> for more informations about actions.
60
61=cut
62
63sub action {
64 my $self = shift;
65 $self->setup unless $self->components;
66 $self->actions( {} ) unless $self->actions;
67 my $action;
68 $_[1] ? ( $action = {@_} ) : ( $action = shift );
69 if ( ref $action eq 'HASH' ) {
70 while ( my ( $name, $code ) = each %$action ) {
0169d3a8 71 $self->set_action( $name, $code, caller(0) );
fc7ec1d9 72 }
73 }
87e67021 74 return 1;
75}
76
23f9d934 77=item $c->benchmark($coderef)
fc7ec1d9 78
79Takes a coderef with arguments and returns elapsed time as float.
80
81 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
82 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
83
84=cut
85
86sub benchmark {
87 my $c = shift;
88 my $code = shift;
89 my $time = [gettimeofday];
90 my @return = &$code(@_);
91 my $elapsed = tv_interval $time;
92 return wantarray ? ( $elapsed, @return ) : $elapsed;
93}
94
23f9d934 95=item $c->comp($name)
96
97=item $c->component($name)
fc7ec1d9 98
99Get a component object by name.
100
101 $c->comp('MyApp::Model::MyModel')->do_stuff;
102
103Regex search for a component.
104
105 $c->comp('mymodel')->do_stuff;
106
107=cut
108
109sub component {
110 my ( $c, $name ) = @_;
111 if ( my $component = $c->components->{$name} ) {
112 return $component;
113 }
114 else {
115 for my $component ( keys %{ $c->components } ) {
116 return $c->components->{$component} if $component =~ /$name/i;
117 }
118 }
119}
120
23f9d934 121=item $c->errors
122
123=item $c->errors($error, ...)
124
125=item $c->errors($arrayref)
fc7ec1d9 126
127Returns an arrayref containing errors messages.
128
129 my @errors = @{ $c->errors };
130
131Add a new error.
132
133 $c->errors('Something bad happened');
134
135=cut
136
137sub errors {
138 my $c = shift;
139 my $errors = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
140 push @{ $c->{errors} }, @$errors;
141 return $c->{errors};
142}
143
23f9d934 144=item $c->finalize
fc7ec1d9 145
146Finalize request.
147
148=cut
149
150sub finalize {
151 my $c = shift;
23f9d934 152
153 if ( my $location = $c->res->redirect ) {
154 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
155 $c->res->headers->header( Location => $location );
156 $c->res->status(302);
157 }
158
fc7ec1d9 159 if ( !$c->res->output || $#{ $c->errors } >= 0 ) {
160 $c->res->headers->content_type('text/html');
161 my $name = $c->config->{name} || 'Catalyst Application';
162 my ( $title, $errors, $infos );
163 if ( $c->debug ) {
164 $errors = join '<br/>', @{ $c->errors };
165 $errors ||= 'No output';
166 $title = $name = "$name on Catalyst $Catalyst::VERSION";
167 my $req = encode_entities Dumper $c->req;
168 my $res = encode_entities Dumper $c->res;
169 my $stash = encode_entities Dumper $c->stash;
170 $infos = <<"";
171<br/>
172<b><u>Request</u></b><br/>
173<pre>$req</pre>
174<b><u>Response</u></b><br/>
175<pre>$res</pre>
176<b><u>Stash</u></b><br/>
177<pre>$stash</pre>
178
179 }
180 else {
181 $title = $name;
182 $errors = '';
183 $infos = <<"";
184<pre>
185(en) Please come back later
186(de) Bitte versuchen sie es spaeter nocheinmal
187(nl) Gelieve te komen later terug
188(no) Vennligst prov igjen senere
189(fr) Veuillez revenir plus tard
190(es) Vuelto por favor mas adelante
191(pt) Voltado por favor mais tarde
192(it) Ritornato prego piĆ¹ successivamente
193</pre>
194
195 $name = '';
196 }
197 $c->res->{output} = <<"";
198<html>
199 <head>
200 <title>$title</title>
201 <style type="text/css">
202 body {
203 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
204 Tahoma, Arial, helvetica, sans-serif;
205 color: #ddd;
206 background-color: #eee;
207 margin: 0px;
208 padding: 0px;
209 }
210 div.box {
211 background-color: #ccc;
212 border: 1px solid #aaa;
213 padding: 4px;
214 margin: 10px;
215 -moz-border-radius: 10px;
216 }
217 div.errors {
218 background-color: #977;
219 border: 1px solid #755;
220 padding: 8px;
221 margin: 4px;
222 margin-bottom: 10px;
223 -moz-border-radius: 10px;
224 }
225 div.infos {
226 background-color: #797;
227 border: 1px solid #575;
228 padding: 8px;
229 margin: 4px;
230 margin-bottom: 10px;
231 -moz-border-radius: 10px;
232 }
233 div.name {
234 background-color: #779;
235 border: 1px solid #557;
236 padding: 8px;
237 margin: 4px;
238 -moz-border-radius: 10px;
239 }
240 </style>
241 </head>
242 <body>
243 <div class="box">
244 <div class="errors">$errors</div>
245 <div class="infos">$infos</div>
246 <div class="name">$name</div>
247 </div>
248 </body>
249</html>
250
251 }
fc7ec1d9 252 $c->res->headers->content_length( length $c->res->output );
253 my $status = $c->finalize_headers;
254 $c->finalize_output;
255 return $status;
256}
257
23f9d934 258=item $c->finalize_headers
fc7ec1d9 259
260Finalize headers.
261
262=cut
263
264sub finalize_headers { }
265
23f9d934 266=item $c->finalize_output
fc7ec1d9 267
268Finalize output.
269
270=cut
271
272sub finalize_output { }
273
23f9d934 274=item $c->forward($command)
fc7ec1d9 275
276Forward processing to a private/public action or a method from a class.
277If you define a class without method it will default to process().
278
279 $c->forward('!foo');
280 $c->forward('index.html');
281 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
282 $c->forward('MyApp::View::TT');
283
284=cut
285
286sub forward {
287 my $c = shift;
288 my $command = shift;
2deb0d7c 289 $c->state(0);
fc7ec1d9 290 unless ($command) {
291 $c->log->debug('Nothing to forward to') if $c->debug;
292 return 0;
293 }
87e67021 294 my $caller = caller(0);
fc7ec1d9 295 if ( $command =~ /^\?(.*)$/ ) {
296 $command = $1;
fc7ec1d9 297 $command = _prefix( $caller, $command );
298 }
87e67021 299 my $namespace = '';
300 if ( $command =~ /^\!/ ) {
301 $namespace = _class2prefix($caller);
7242d068 302 }
66d9e175 303 my $results = $c->get_action( $command, $namespace );
304 if ( @{$results} ) {
2deb0d7c 305 unless ( $command =~ /^\!/ ) {
306 $results = [ pop @{$results} ];
307 if ( $results->[0]->[2] ) {
b768faa3 308 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
309 if $c->debug;
310 return 0;
311 }
7e5adedd 312 }
fc7ec1d9 313 }
314 else {
b768faa3 315 my $class = $command;
fc7ec1d9 316 if ( $class =~ /[^\w\:]/ ) {
317 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
318 return 0;
319 }
320 my $method = shift || 'process';
b768faa3 321 if ( my $code = $class->can($method) ) {
fc7ec1d9 322 $c->actions->{reverse}->{"$code"} = "$class->$method";
2deb0d7c 323 $results = [ [ [ $class, $code ] ] ];
fc7ec1d9 324 }
325 else {
326 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
327 if $c->debug;
328 return 0;
329 }
330 }
2deb0d7c 331 for my $result ( @{$results} ) {
332 my ( $class, $code ) = @{ $result->[0] };
333 $class = $c->comp->{$class} || $class;
334 $c->state( $c->process( $class, $code ) );
335 }
b768faa3 336 return $c->state;
fc7ec1d9 337}
338
66d9e175 339=item $c->get_action( $action, $namespace )
340
341Get an action in a given namespace.
342
343=cut
344
345sub get_action {
346 my ( $c, $action, $namespace ) = @_;
347 $namespace ||= '';
348 if ( $action =~ /^\!(.*)/ ) {
349 $action = $1;
350 my $parent = $c->tree;
351 my @results;
352 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
353 push @results, [$result] if $result;
354 my $visitor = Tree::Simple::Visitor::FindByPath->new;
2aaa2e5b 355 my $local;
66d9e175 356 for my $part ( split '/', $namespace ) {
2aaa2e5b 357 $local = undef;
66d9e175 358 $visitor->setSearchPath($part);
359 $parent->accept($visitor);
360 my $child = $visitor->getResult;
361 my $uid = $child->getUID if $child;
362 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
57221a47 363 return [ [$match] ] if ( $match && $match =~ /^?.*/ );
2aaa2e5b 364 $local = $c->actions->{private}->{$uid}->{"?$action"} if $uid;
66d9e175 365 push @results, [$match] if $match;
366 $parent = $child if $child;
367 }
2aaa2e5b 368 return [ [$local] ] if $local;
66d9e175 369 return \@results;
370 }
371 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
372 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
373 else {
374 for my $regex ( keys %{ $c->actions->{compiled} } ) {
375 my $name = $c->actions->{compiled}->{$regex};
376 if ( $action =~ $regex ) {
377 my @snippets;
378 for my $i ( 1 .. 9 ) {
379 no strict 'refs';
380 last unless ${$i};
381 push @snippets, ${$i};
382 }
383 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
384 }
385 }
386 }
387 return [];
388}
389
b76d7db8 390=item $c->handler( $class, $r )
fc7ec1d9 391
392Handles the request.
393
394=cut
395
b76d7db8 396sub handler ($$) {
fc7ec1d9 397 my ( $class, $r ) = @_;
398
399 # Always expect worst case!
400 my $status = -1;
401 eval {
402 my $handler = sub {
eb9ff8f4 403 my $c = $class->prepare($r);
404 my $action = $c->req->action;
405 my $namespace = '';
406 $namespace = join '/', @{ $c->req->args } if $action eq '!default';
407 unless ($namespace) {
0169d3a8 408 if ( my $result = $c->get_action($action) ) {
eb9ff8f4 409 $namespace = _class2prefix( $result->[0]->[0]->[0] );
7833fdfc 410 }
87e67021 411 }
0169d3a8 412 my $results = $c->get_action( $action, $namespace );
b768faa3 413 if ( @{$results} ) {
0169d3a8 414 for my $begin ( @{ $c->get_action( '!begin', $namespace ) } ) {
d6f060b7 415 $c->state( $c->process( @{ $begin->[0] } ) );
b768faa3 416 }
0169d3a8 417 for my $result ( @{ $c->get_action( $action, $namespace ) } ) {
d6f060b7 418 $c->state( $c->process( @{ $result->[0] } ) );
b768faa3 419 }
0169d3a8 420 for my $end ( @{ $c->get_action( '!end', $namespace ) } ) {
d6f060b7 421 $c->state( $c->process( @{ $end->[0] } ) );
b768faa3 422 }
fc7ec1d9 423 }
424 else {
87e67021 425 my $path = $c->req->path;
426 my $error = $path
427 ? qq/Unknown resource "$path"/
7833fdfc 428 : "No default action defined";
fc7ec1d9 429 $c->log->error($error) if $c->debug;
430 $c->errors($error);
431 }
432 return $c->finalize;
433 };
434 if ( $class->debug ) {
435 my $elapsed;
436 ( $elapsed, $status ) = $class->benchmark($handler);
437 $elapsed = sprintf '%f', $elapsed;
438 my $av = sprintf '%.3f', 1 / $elapsed;
439 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
440 }
441 else { $status = &$handler }
442 };
443 if ( my $error = $@ ) {
444 chomp $error;
445 $class->log->error(qq/Caught exception in engine "$error"/);
446 }
447 $COUNT++;
448 return $status;
449}
450
23f9d934 451=item $c->prepare($r)
fc7ec1d9 452
23f9d934 453Turns the engine-specific request (Apache, CGI...) into a Catalyst context.
fc7ec1d9 454
455=cut
456
457sub prepare {
458 my ( $class, $r ) = @_;
459 my $c = bless {
460 request => Catalyst::Request->new(
461 {
462 arguments => [],
463 cookies => {},
464 headers => HTTP::Headers->new,
465 parameters => {},
466 snippets => [],
467 uploads => {}
468 }
469 ),
470 response => Catalyst::Response->new(
471 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
472 ),
b768faa3 473 stash => {},
474 state => 0
fc7ec1d9 475 }, $class;
476 if ( $c->debug ) {
477 my $secs = time - $START || 1;
478 my $av = sprintf '%.3f', $COUNT / $secs;
479 $c->log->debug('********************************');
480 $c->log->debug("* Request $COUNT ($av/s) [$$]");
481 $c->log->debug('********************************');
482 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
483 }
484 $c->prepare_request($r);
485 $c->prepare_path;
c679d242 486 $c->prepare_cookies;
2aaa2e5b 487 $c->prepare_headers;
0556eb49 488 $c->prepare_connection;
489 my $method = $c->req->method || '';
490 my $path = $c->req->path || '';
491 my $hostname = $c->req->hostname || '';
492 my $address = $c->req->address || '';
493 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
494 if $c->debug;
fc7ec1d9 495 $c->prepare_action;
496 $c->prepare_parameters;
c85ff642 497
498 if ( $c->debug && keys %{ $c->req->params } ) {
499 my @params;
500 for my $key ( keys %{ $c->req->params } ) {
b5524568 501 my $value = $c->req->params->{$key} || '';
c85ff642 502 push @params, "$key=$value";
503 }
504 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
505 }
fc7ec1d9 506 $c->prepare_uploads;
507 return $c;
508}
509
23f9d934 510=item $c->prepare_action
fc7ec1d9 511
512Prepare action.
513
514=cut
515
516sub prepare_action {
517 my $c = shift;
518 my $path = $c->req->path;
519 my @path = split /\//, $c->req->path;
520 $c->req->args( \my @args );
521 while (@path) {
7833fdfc 522 $path = join '/', @path;
0169d3a8 523 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 524
525 # It's a regex
526 if ($#$result) {
7e5adedd 527 my $match = $result->[1];
528 my @snippets = @{ $result->[2] };
fc7ec1d9 529 $c->log->debug(qq/Requested action "$path" matched "$match"/)
530 if $c->debug;
531 $c->log->debug(
532 'Snippets are "' . join( ' ', @snippets ) . '"' )
533 if ( $c->debug && @snippets );
534 $c->req->action($match);
535 $c->req->snippets( \@snippets );
536 }
537 else {
538 $c->req->action($path);
539 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
540 }
541 $c->req->match($path);
fc7ec1d9 542 last;
543 }
544 unshift @args, pop @path;
545 }
546 unless ( $c->req->action ) {
87e67021 547 $c->req->action('!default');
548 $c->req->match('');
fc7ec1d9 549 }
5783a9a5 550 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
551 if ( $c->debug && @args );
fc7ec1d9 552}
553
c9afa5fc 554=item $c->prepare_connection
0556eb49 555
556Prepare connection.
557
558=cut
559
560sub prepare_connection { }
561
c9afa5fc 562=item $c->prepare_cookies
fc7ec1d9 563
564Prepare cookies.
565
566=cut
567
568sub prepare_cookies { }
569
23f9d934 570=item $c->prepare_headers
fc7ec1d9 571
572Prepare headers.
573
574=cut
575
576sub prepare_headers { }
577
23f9d934 578=item $c->prepare_parameters
fc7ec1d9 579
580Prepare parameters.
581
582=cut
583
584sub prepare_parameters { }
585
23f9d934 586=item $c->prepare_path
fc7ec1d9 587
588Prepare path and base.
589
590=cut
591
592sub prepare_path { }
593
23f9d934 594=item $c->prepare_request
fc7ec1d9 595
596Prepare the engine request.
597
598=cut
599
600sub prepare_request { }
601
23f9d934 602=item $c->prepare_uploads
fc7ec1d9 603
604Prepare uploads.
605
606=cut
607
608sub prepare_uploads { }
609
23f9d934 610=item $c->process($class, $coderef)
fc7ec1d9 611
612Process a coderef in given class and catch exceptions.
613Errors are available via $c->errors.
614
615=cut
616
617sub process {
618 my ( $c, $class, $code ) = @_;
619 my $status;
620 eval {
621 if ( $c->debug )
622 {
623 my $action = $c->actions->{reverse}->{"$code"} || "$code";
624 my $elapsed;
625 ( $elapsed, $status ) =
626 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
627 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
628 if $c->debug;
629 }
630 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
631 };
632 if ( my $error = $@ ) {
633 chomp $error;
634 $error = qq/Caught exception "$error"/;
635 $c->log->error($error);
636 $c->errors($error) if $c->debug;
637 return 0;
638 }
639 return $status;
640}
641
c9afa5fc 642=item $c->run
643
644Starts the engine.
645
646=cut
647
648sub run { }
649
23f9d934 650=item $c->request
651
652=item $c->req
fc7ec1d9 653
654Returns a C<Catalyst::Request> object.
655
656 my $req = $c->req;
657
23f9d934 658=item $c->response
659
660=item $c->res
fc7ec1d9 661
662Returns a C<Catalyst::Response> object.
663
664 my $res = $c->res;
665
66d9e175 666=item $c->set_action( $action, $code, $namespace )
667
668Set an action in a given namespace.
669
670=cut
671
672sub set_action {
673 my ( $c, $action, $code, $namespace ) = @_;
66d9e175 674 my $prefix = '';
675 if ( $action =~ /^\?(.*)$/ ) {
676 my $prefix = $1 || '';
677 $action = $2;
678 $action = $prefix . _prefix( $namespace, $action );
679 $c->actions->{plain}->{$action} = [ $namespace, $code ];
680 }
2aaa2e5b 681 elsif ( $action =~ /^\/(.*)\/$/ ) {
66d9e175 682 my $regex = $1;
683 $c->actions->{compiled}->{qr#$regex#} = $action;
684 $c->actions->{regex}->{$action} = [ $namespace, $code ];
685 }
686 elsif ( $action =~ /^\!(.*)$/ ) {
687 $action = $1;
688 my $parent = $c->tree;
689 my $visitor = Tree::Simple::Visitor::FindByPath->new;
690 $prefix = _class2prefix($namespace);
691 for my $part ( split '/', $prefix ) {
692 $visitor->setSearchPath($part);
693 $parent->accept($visitor);
694 my $child = $visitor->getResult;
695 unless ($child) {
696 $child = $parent->addChild( Tree::Simple->new($part) );
697 $visitor->setSearchPath($part);
698 $parent->accept($visitor);
699 $child = $visitor->getResult;
700 }
701 $parent = $child;
702 }
703 my $uid = $parent->getUID;
704 $c->actions->{private}->{$uid}->{$action} = [ $namespace, $code ];
705 $action = "!$action";
706 }
2aaa2e5b 707 else { $c->actions->{plain}->{$action} = [ $namespace, $code ] }
66d9e175 708 my $reverse = $prefix ? "$action ($prefix)" : $action;
709 $c->actions->{reverse}->{"$code"} = $reverse;
2aaa2e5b 710 $c->log->debug(qq/"$namespace" defined "$action" as "$code"/) if $c->debug;
66d9e175 711}
712
23f9d934 713=item $class->setup
fc7ec1d9 714
715Setup.
716
717 MyApp->setup;
718
719=cut
720
721sub setup {
722 my $self = shift;
723 $self->setup_components;
724 if ( $self->debug ) {
725 my $name = $self->config->{name} || 'Application';
726 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
727 }
728}
729
23f9d934 730=item $class->setup_components
fc7ec1d9 731
732Setup components.
733
734=cut
735
736sub setup_components {
737 my $self = shift;
738
739 # Components
740 my $class = ref $self || $self;
741 eval <<"";
742 package $class;
743 import Module::Pluggable::Fast
744 name => '_components',
745 search => [
746 '$class\::Controller', '$class\::C',
747 '$class\::Model', '$class\::M',
748 '$class\::View', '$class\::V'
749 ];
750
751 if ( my $error = $@ ) {
752 chomp $error;
753 $self->log->error(
754 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
755 }
756 $self->components( {} );
757 for my $component ( $self->_components($self) ) {
758 $self->components->{ ref $component } = $component;
759 }
760 $self->log->debug( 'Initialized components "'
761 . join( ' ', keys %{ $self->components } )
762 . '"' )
763 if $self->debug;
764}
765
23f9d934 766=item $c->stash
fc7ec1d9 767
768Returns a hashref containing all your data.
769
770 $c->stash->{foo} ||= 'yada';
771 print $c->stash->{foo};
772
773=cut
774
775sub stash {
776 my $self = shift;
777 if ( $_[0] ) {
778 my $stash = $_[1] ? {@_} : $_[0];
779 while ( my ( $key, $val ) = each %$stash ) {
780 $self->{stash}->{$key} = $val;
781 }
782 }
783 return $self->{stash};
784}
785
786sub _prefix {
787 my ( $class, $name ) = @_;
7833fdfc 788 my $prefix = _class2prefix($class);
2aaa2e5b 789 warn "$class - $name - $prefix";
7833fdfc 790 $name = "$prefix/$name" if $prefix;
791 return $name;
792}
793
794sub _class2prefix {
b768faa3 795 my $class = shift || '';
796 $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/;
87e67021 797 my $prefix = lc $2 || '';
798 $prefix =~ s/\:\:/\//g;
7833fdfc 799 return $prefix;
fc7ec1d9 800}
801
23f9d934 802=back
803
fc7ec1d9 804=head1 AUTHOR
805
806Sebastian Riedel, C<sri@cpan.org>
807
808=head1 COPYRIGHT
809
810This program is free software, you can redistribute it and/or modify it under
811the same terms as Perl itself.
812
813=cut
814
8151;