made !? work
[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;
289 unless ($command) {
290 $c->log->debug('Nothing to forward to') if $c->debug;
291 return 0;
292 }
87e67021 293 my $caller = caller(0);
fc7ec1d9 294 if ( $command =~ /^\?(.*)$/ ) {
295 $command = $1;
fc7ec1d9 296 $command = _prefix( $caller, $command );
297 }
87e67021 298 my $namespace = '';
299 if ( $command =~ /^\!/ ) {
300 $namespace = _class2prefix($caller);
7242d068 301 }
66d9e175 302 my $results = $c->get_action( $command, $namespace );
303 if ( @{$results} ) {
b768faa3 304 if ( $command =~ /^\!/ ) {
305 for my $result ( @{$results} ) {
306 my ( $class, $code ) = @{ $result->[0] };
307 $c->state( $c->process( $class, $code ) );
308 }
309 }
310 else {
311 return 0 unless my $result = $results->[0];
312 if ( $result->[2] ) {
313 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
314 if $c->debug;
315 return 0;
316 }
317 my ( $class, $code ) = @{ $result->[0] };
318 $class = $c->components->{$class} || $class;
319 $c->state( $c->process( $class, $code ) );
7e5adedd 320 }
fc7ec1d9 321 }
322 else {
b768faa3 323 my $class = $command;
fc7ec1d9 324 if ( $class =~ /[^\w\:]/ ) {
325 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
326 return 0;
327 }
328 my $method = shift || 'process';
b768faa3 329 if ( my $code = $class->can($method) ) {
fc7ec1d9 330 $c->actions->{reverse}->{"$code"} = "$class->$method";
66d9e175 331 $class = $c->comp($class) || $class;
b768faa3 332 $c->state( $c->process( $class, $code ) );
fc7ec1d9 333 }
334 else {
335 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
336 if $c->debug;
337 return 0;
338 }
339 }
b768faa3 340 return $c->state;
fc7ec1d9 341}
342
66d9e175 343=item $c->get_action( $action, $namespace )
344
345Get an action in a given namespace.
346
347=cut
348
349sub get_action {
350 my ( $c, $action, $namespace ) = @_;
351 $namespace ||= '';
352 if ( $action =~ /^\!(.*)/ ) {
353 $action = $1;
354 my $parent = $c->tree;
355 my @results;
356 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
357 push @results, [$result] if $result;
358 my $visitor = Tree::Simple::Visitor::FindByPath->new;
2aaa2e5b 359 my $local;
66d9e175 360 for my $part ( split '/', $namespace ) {
2aaa2e5b 361 $local = undef;
66d9e175 362 $visitor->setSearchPath($part);
363 $parent->accept($visitor);
364 my $child = $visitor->getResult;
365 my $uid = $child->getUID if $child;
366 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
2aaa2e5b 367 $local = $c->actions->{private}->{$uid}->{"?$action"} if $uid;
66d9e175 368 push @results, [$match] if $match;
369 $parent = $child if $child;
370 }
2aaa2e5b 371 return [ [$local] ] if $local;
66d9e175 372 return \@results;
373 }
374 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
375 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
376 else {
377 for my $regex ( keys %{ $c->actions->{compiled} } ) {
378 my $name = $c->actions->{compiled}->{$regex};
379 if ( $action =~ $regex ) {
380 my @snippets;
381 for my $i ( 1 .. 9 ) {
382 no strict 'refs';
383 last unless ${$i};
384 push @snippets, ${$i};
385 }
386 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
387 }
388 }
389 }
390 return [];
391}
392
b76d7db8 393=item $c->handler( $class, $r )
fc7ec1d9 394
395Handles the request.
396
397=cut
398
b76d7db8 399sub handler ($$) {
fc7ec1d9 400 my ( $class, $r ) = @_;
401
402 # Always expect worst case!
403 my $status = -1;
404 eval {
405 my $handler = sub {
eb9ff8f4 406 my $c = $class->prepare($r);
407 my $action = $c->req->action;
408 my $namespace = '';
409 $namespace = join '/', @{ $c->req->args } if $action eq '!default';
410 unless ($namespace) {
0169d3a8 411 if ( my $result = $c->get_action($action) ) {
eb9ff8f4 412 $namespace = _class2prefix( $result->[0]->[0]->[0] );
7833fdfc 413 }
87e67021 414 }
0169d3a8 415 my $results = $c->get_action( $action, $namespace );
b768faa3 416 if ( @{$results} ) {
0169d3a8 417 for my $begin ( @{ $c->get_action( '!begin', $namespace ) } ) {
d6f060b7 418 $c->state( $c->process( @{ $begin->[0] } ) );
b768faa3 419 }
0169d3a8 420 for my $result ( @{ $c->get_action( $action, $namespace ) } ) {
d6f060b7 421 $c->state( $c->process( @{ $result->[0] } ) );
b768faa3 422 }
0169d3a8 423 for my $end ( @{ $c->get_action( '!end', $namespace ) } ) {
d6f060b7 424 $c->state( $c->process( @{ $end->[0] } ) );
b768faa3 425 }
fc7ec1d9 426 }
427 else {
87e67021 428 my $path = $c->req->path;
429 my $error = $path
430 ? qq/Unknown resource "$path"/
7833fdfc 431 : "No default action defined";
fc7ec1d9 432 $c->log->error($error) if $c->debug;
433 $c->errors($error);
434 }
435 return $c->finalize;
436 };
437 if ( $class->debug ) {
438 my $elapsed;
439 ( $elapsed, $status ) = $class->benchmark($handler);
440 $elapsed = sprintf '%f', $elapsed;
441 my $av = sprintf '%.3f', 1 / $elapsed;
442 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
443 }
444 else { $status = &$handler }
445 };
446 if ( my $error = $@ ) {
447 chomp $error;
448 $class->log->error(qq/Caught exception in engine "$error"/);
449 }
450 $COUNT++;
451 return $status;
452}
453
23f9d934 454=item $c->prepare($r)
fc7ec1d9 455
23f9d934 456Turns the engine-specific request (Apache, CGI...) into a Catalyst context.
fc7ec1d9 457
458=cut
459
460sub prepare {
461 my ( $class, $r ) = @_;
462 my $c = bless {
463 request => Catalyst::Request->new(
464 {
465 arguments => [],
466 cookies => {},
467 headers => HTTP::Headers->new,
468 parameters => {},
469 snippets => [],
470 uploads => {}
471 }
472 ),
473 response => Catalyst::Response->new(
474 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
475 ),
b768faa3 476 stash => {},
477 state => 0
fc7ec1d9 478 }, $class;
479 if ( $c->debug ) {
480 my $secs = time - $START || 1;
481 my $av = sprintf '%.3f', $COUNT / $secs;
482 $c->log->debug('********************************');
483 $c->log->debug("* Request $COUNT ($av/s) [$$]");
484 $c->log->debug('********************************');
485 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
486 }
487 $c->prepare_request($r);
488 $c->prepare_path;
c679d242 489 $c->prepare_cookies;
2aaa2e5b 490 $c->prepare_headers;
0556eb49 491 $c->prepare_connection;
492 my $method = $c->req->method || '';
493 my $path = $c->req->path || '';
494 my $hostname = $c->req->hostname || '';
495 my $address = $c->req->address || '';
496 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
497 if $c->debug;
fc7ec1d9 498 $c->prepare_action;
499 $c->prepare_parameters;
c85ff642 500
501 if ( $c->debug && keys %{ $c->req->params } ) {
502 my @params;
503 for my $key ( keys %{ $c->req->params } ) {
b5524568 504 my $value = $c->req->params->{$key} || '';
c85ff642 505 push @params, "$key=$value";
506 }
507 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
508 }
fc7ec1d9 509 $c->prepare_uploads;
510 return $c;
511}
512
23f9d934 513=item $c->prepare_action
fc7ec1d9 514
515Prepare action.
516
517=cut
518
519sub prepare_action {
520 my $c = shift;
521 my $path = $c->req->path;
522 my @path = split /\//, $c->req->path;
523 $c->req->args( \my @args );
524 while (@path) {
7833fdfc 525 $path = join '/', @path;
0169d3a8 526 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 527
528 # It's a regex
529 if ($#$result) {
7e5adedd 530 my $match = $result->[1];
531 my @snippets = @{ $result->[2] };
fc7ec1d9 532 $c->log->debug(qq/Requested action "$path" matched "$match"/)
533 if $c->debug;
534 $c->log->debug(
535 'Snippets are "' . join( ' ', @snippets ) . '"' )
536 if ( $c->debug && @snippets );
537 $c->req->action($match);
538 $c->req->snippets( \@snippets );
539 }
540 else {
541 $c->req->action($path);
542 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
543 }
544 $c->req->match($path);
fc7ec1d9 545 last;
546 }
547 unshift @args, pop @path;
548 }
549 unless ( $c->req->action ) {
87e67021 550 $c->req->action('!default');
551 $c->req->match('');
fc7ec1d9 552 }
5783a9a5 553 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
554 if ( $c->debug && @args );
fc7ec1d9 555}
556
c9afa5fc 557=item $c->prepare_connection
0556eb49 558
559Prepare connection.
560
561=cut
562
563sub prepare_connection { }
564
c9afa5fc 565=item $c->prepare_cookies
fc7ec1d9 566
567Prepare cookies.
568
569=cut
570
571sub prepare_cookies { }
572
23f9d934 573=item $c->prepare_headers
fc7ec1d9 574
575Prepare headers.
576
577=cut
578
579sub prepare_headers { }
580
23f9d934 581=item $c->prepare_parameters
fc7ec1d9 582
583Prepare parameters.
584
585=cut
586
587sub prepare_parameters { }
588
23f9d934 589=item $c->prepare_path
fc7ec1d9 590
591Prepare path and base.
592
593=cut
594
595sub prepare_path { }
596
23f9d934 597=item $c->prepare_request
fc7ec1d9 598
599Prepare the engine request.
600
601=cut
602
603sub prepare_request { }
604
23f9d934 605=item $c->prepare_uploads
fc7ec1d9 606
607Prepare uploads.
608
609=cut
610
611sub prepare_uploads { }
612
23f9d934 613=item $c->process($class, $coderef)
fc7ec1d9 614
615Process a coderef in given class and catch exceptions.
616Errors are available via $c->errors.
617
618=cut
619
620sub process {
621 my ( $c, $class, $code ) = @_;
622 my $status;
623 eval {
624 if ( $c->debug )
625 {
626 my $action = $c->actions->{reverse}->{"$code"} || "$code";
627 my $elapsed;
628 ( $elapsed, $status ) =
629 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
630 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
631 if $c->debug;
632 }
633 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
634 };
635 if ( my $error = $@ ) {
636 chomp $error;
637 $error = qq/Caught exception "$error"/;
638 $c->log->error($error);
639 $c->errors($error) if $c->debug;
640 return 0;
641 }
642 return $status;
643}
644
c9afa5fc 645=item $c->run
646
647Starts the engine.
648
649=cut
650
651sub run { }
652
23f9d934 653=item $c->request
654
655=item $c->req
fc7ec1d9 656
657Returns a C<Catalyst::Request> object.
658
659 my $req = $c->req;
660
23f9d934 661=item $c->response
662
663=item $c->res
fc7ec1d9 664
665Returns a C<Catalyst::Response> object.
666
667 my $res = $c->res;
668
66d9e175 669=item $c->set_action( $action, $code, $namespace )
670
671Set an action in a given namespace.
672
673=cut
674
675sub set_action {
676 my ( $c, $action, $code, $namespace ) = @_;
66d9e175 677 my $prefix = '';
678 if ( $action =~ /^\?(.*)$/ ) {
679 my $prefix = $1 || '';
680 $action = $2;
681 $action = $prefix . _prefix( $namespace, $action );
682 $c->actions->{plain}->{$action} = [ $namespace, $code ];
683 }
2aaa2e5b 684 elsif ( $action =~ /^\/(.*)\/$/ ) {
66d9e175 685 my $regex = $1;
686 $c->actions->{compiled}->{qr#$regex#} = $action;
687 $c->actions->{regex}->{$action} = [ $namespace, $code ];
688 }
689 elsif ( $action =~ /^\!(.*)$/ ) {
690 $action = $1;
691 my $parent = $c->tree;
692 my $visitor = Tree::Simple::Visitor::FindByPath->new;
693 $prefix = _class2prefix($namespace);
694 for my $part ( split '/', $prefix ) {
695 $visitor->setSearchPath($part);
696 $parent->accept($visitor);
697 my $child = $visitor->getResult;
698 unless ($child) {
699 $child = $parent->addChild( Tree::Simple->new($part) );
700 $visitor->setSearchPath($part);
701 $parent->accept($visitor);
702 $child = $visitor->getResult;
703 }
704 $parent = $child;
705 }
706 my $uid = $parent->getUID;
707 $c->actions->{private}->{$uid}->{$action} = [ $namespace, $code ];
708 $action = "!$action";
709 }
2aaa2e5b 710 else { $c->actions->{plain}->{$action} = [ $namespace, $code ] }
66d9e175 711 my $reverse = $prefix ? "$action ($prefix)" : $action;
712 $c->actions->{reverse}->{"$code"} = $reverse;
2aaa2e5b 713 $c->log->debug(qq/"$namespace" defined "$action" as "$code"/) if $c->debug;
66d9e175 714}
715
23f9d934 716=item $class->setup
fc7ec1d9 717
718Setup.
719
720 MyApp->setup;
721
722=cut
723
724sub setup {
725 my $self = shift;
726 $self->setup_components;
727 if ( $self->debug ) {
728 my $name = $self->config->{name} || 'Application';
729 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
730 }
731}
732
23f9d934 733=item $class->setup_components
fc7ec1d9 734
735Setup components.
736
737=cut
738
739sub setup_components {
740 my $self = shift;
741
742 # Components
743 my $class = ref $self || $self;
744 eval <<"";
745 package $class;
746 import Module::Pluggable::Fast
747 name => '_components',
748 search => [
749 '$class\::Controller', '$class\::C',
750 '$class\::Model', '$class\::M',
751 '$class\::View', '$class\::V'
752 ];
753
754 if ( my $error = $@ ) {
755 chomp $error;
756 $self->log->error(
757 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
758 }
759 $self->components( {} );
760 for my $component ( $self->_components($self) ) {
761 $self->components->{ ref $component } = $component;
762 }
763 $self->log->debug( 'Initialized components "'
764 . join( ' ', keys %{ $self->components } )
765 . '"' )
766 if $self->debug;
767}
768
23f9d934 769=item $c->stash
fc7ec1d9 770
771Returns a hashref containing all your data.
772
773 $c->stash->{foo} ||= 'yada';
774 print $c->stash->{foo};
775
776=cut
777
778sub stash {
779 my $self = shift;
780 if ( $_[0] ) {
781 my $stash = $_[1] ? {@_} : $_[0];
782 while ( my ( $key, $val ) = each %$stash ) {
783 $self->{stash}->{$key} = $val;
784 }
785 }
786 return $self->{stash};
787}
788
789sub _prefix {
790 my ( $class, $name ) = @_;
7833fdfc 791 my $prefix = _class2prefix($class);
2aaa2e5b 792 warn "$class - $name - $prefix";
7833fdfc 793 $name = "$prefix/$name" if $prefix;
794 return $name;
795}
796
797sub _class2prefix {
b768faa3 798 my $class = shift || '';
799 $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/;
87e67021 800 my $prefix = lc $2 || '';
801 $prefix =~ s/\:\:/\//g;
7833fdfc 802 return $prefix;
fc7ec1d9 803}
804
23f9d934 805=back
806
fc7ec1d9 807=head1 AUTHOR
808
809Sebastian Riedel, C<sri@cpan.org>
810
811=head1 COPYRIGHT
812
813This program is free software, you can redistribute it and/or modify it under
814the same terms as Perl itself.
815
816=cut
817
8181;