File Coverage

blib/lib/Cake.pm
Criterion Covered Total %
statement 156 370 42.1
branch 33 142 23.2
condition 7 30 23.3
subroutine 34 68 50.0
pod 2 42 4.7
total 232 652 35.5


line stmt bran cond sub pod time code
1             package Cake;
2            
3 8     8   257421 use warnings;
  8         18  
  8         269  
4 8     8   45 use strict;
  8         17  
  8         255  
5 8     8   42 use Carp;
  8         18  
  8         612  
6 8     8   7762 use NEXT;
  8         38207  
  8         239  
7 8     8   67 use File::Find;
  8         15  
  8         691  
8 8     8   8712 use Encode;
  8         129167  
  8         775  
9 8     8   7653 use utf8;
  8         84  
  8         53  
10 8     8   5622 use Data::Dumper;
  8         36340  
  8         590  
11 8     8   4809 use Cake::Controllers;
  8         79  
  8         483  
12 8     8   4538 use Cake::Exception;
  8         22  
  8         616  
13 8     8   4275 use Cake::Utils::Accessor;
  8         24  
  8         375  
14 8     8   4300 use Cake::Utils;
  8         25  
  8         602  
15 8     8   4519 use Cake::URI;
  8         22  
  8         559  
16 8     8   46 use base qw/Exporter Cake::Dispatcher Cake::Engine/;
  8         14  
  8         4527  
17 8     8   7111 use FindBin qw($Bin);
  8         9189  
  8         39414  
18            
19             our $VERSION = '0.006_2';
20            
21             my @controller_export = qw(
22             get
23             post
24             any
25             args
26             chained
27             Action
28             route
29             auto
30             );
31            
32             my @plugin_export = qw(
33             loadSettings
34             settings
35             register
36             );
37            
38             my @extra_export = qw(
39             bake
40             plugins
41             context
42             );
43            
44             our @EXPORT = (@controller_export,@plugin_export,@extra_export);
45            
46             __PACKAGE__->Accessor('env','app','engine','action','stash','uri');
47            
48             my ($DEBUG,$ENGINE,$SELF);
49             my $SETTINGS = {};
50             my $COUNTER = 0;
51 0     0 0 0 sub clear_counter {$COUNTER = 0}
52 0     0 0 0 sub counter {$COUNTER}
53 3     3 0 14 sub debug {return $DEBUG;}
54             #============================================================================
55             # import on app start
56             #============================================================================
57             sub import {
58 15     15   124 my ($class, @options) = @_;
59 15         55 my ($package,$script) = caller;
60 15         29 my $engine;
61            
62             ###import these to app by default
63 15         298 strict->import;
64 15         259 warnings->import;
65 15         113 utf8->import;
66            
67 15         72 foreach (@options) {
68 4 50       26 if (/^Plugin$/){
    0          
69 4         460 $class->export_to_level(1, $class, @plugin_export);
70 4         736 return;
71             } elsif (/^Controller$/){
72 0         0 $class->export_to_level(1, $class, @controller_export);
73 0         0 return;
74             }
75            
76             # Import engine, load only one engine
77 0 0 0     0 if ( /^:Engine=(\S+)/ && !$ENGINE) {
    0          
78 0         0 $ENGINE = $1;
79 0         0 $engine = Cake::Utils::Require($ENGINE,'Cake::Engine');
80 0 0       0 if ( $@ ) { die qq/can't load engine "$ENGINE", "$@"/ }
  0         0  
81 0         0 unshift(@Cake::ISA,$engine);
82             } elsif (/^:Debug=(\S+)/){
83 0         0 $DEBUG = $1;
84             }
85             }
86            
87 11 100       68 if (!$SELF){
88 8         25 $SELF->{'basename'} = $package;
89 8         19 $package .='.pm';
90 8   33     66 ( $SELF->{'dir'} = $INC{$package} || $Bin) =~ s/\.pm//;
91 8         23 push @INC, $SELF->{'dir'};
92             }
93            
94 11         7521 $class->export_to_level(1, $class, @EXPORT);
95             }
96            
97             #============================================================================
98             # Load Settings from outer file / must be a valid json file
99             #============================================================================
100             sub loadSettings {
101 0     0 0 0 my $file = shift;
102 0         0 my $conf;
103 0         0 eval {
104 0         0 $conf = Cake::Utils::get_file($file);
105 0 0       0 }; if ($@){
106 0         0 die "can't open file $file";
107             }
108             ##json to perl
109 0         0 return Cake::Utils->serialize($conf)->to_perl;
110             }
111            
112             #============================================================================
113             # settings : get/set application Settings
114             #============================================================================
115             sub settings {
116 4 100 66 4 0 39 if (@_ > 1 || ref $_[0] eq 'HASH'){
117 1 50       4 if (!ref $_[0]){
118 0         0 my $package = shift;
119 0 0       0 $SETTINGS->{$package} = @_ > 1 ? {@_} : $_[0];
120             } else {
121 1         3 $SETTINGS = {%{$SETTINGS},%{$_[0]}};
  1         4  
  1         4  
122            
123 1 50       8 if (my $plugins = delete $SETTINGS->{plugins}){
124 0 0       0 if (ref $plugins eq 'ARRAY'){
125 0         0 &plugins($plugins);
126             }
127             }
128             }
129             } else {
130 3   66     15 my $package = $_[0] || caller;
131 3 50       19 $SETTINGS->{$package} ? return $SETTINGS->{$package} : return $SETTINGS;
132             }
133 1         2 return $SETTINGS;
134             }
135            
136             sub config {
137 0     0 0 0 my $self = shift;
138 0 0       0 if ($_[0]){
139 0         0 return $self->{settings}->{$_[0]};
140             }
141 0         0 return $self->{settings};
142             }
143            
144             #============================================================================
145             # load plugins
146             #============================================================================
147             sub plugins {
148 2     2 0 28 my @plugins = @{$_[0]};
  2         7  
149 2         6 my @pluginsRequire;
150 2 50       9 return if !@plugins;
151 2         5 my $withOptions;
152 2         11 for (my $i= 0; $i < @plugins; $i++) {
153 4 50       12 if (!ref $plugins[$i]){
154 4         16 my $module = Cake::Utils::noRequire($plugins[$i],'Cake::Plugins');
155 4         9 push(@pluginsRequire,$plugins[$i]);
156 4 50       17 if (ref ( my $next = $plugins[$i+1] )){
157 4         10 $withOptions->{$module} = $next;
158 4         20 splice(@plugins, $i+1, 1);
159             }
160             }
161             }
162 2         6 map {Cake::Utils::Require($_,'Cake::Plugins')} @pluginsRequire;
  4         17  
163 2         4 $SETTINGS = {%{$SETTINGS},%{$withOptions}};
  2         6  
  2         17  
164             }
165            
166             #============================================================================
167             # Cake Context
168             #============================================================================
169             sub context {
170 0     0 0 0 my $caller = shift;
171             #$ENV{'REQUEST_URI'} = shift || '';
172 0   0     0 $ENV{SCRIPT_NAME} = (caller)[0] || '';
173 0         0 my $c = $caller->bake($_[0],1);
174 0         0 $c->finalize();
175 0         0 return $c;
176             }
177            
178             #============================================================================
179             # bakery: bake the cake
180             #============================================================================
181             sub bake {
182 3     3 0 34 my $class = shift;
183 3         20 my $self = bless({}, __PACKAGE__);
184             ##load settings
185 3         25 $self->app(bless($SELF, $class));
186 3         18 $self->{pid} = $$;
187 3   50     47 $self->env(bless($_[0] || \%ENV, 'Cake::ENV'));
188 3         8 $self->{COUNT} = $COUNTER;
189 3         18 $self->{response}->{headers} = ["X-Framework: PerlCake"];
190 3         13 $self->{settings} = $SETTINGS;
191 3         13 $self->loadOnce();
192 3 50       23 return $self->_runner() if !$_[1];
193 0         0 return $self;
194             }
195            
196             sub loadOnce {
197 3     3 0 13 my $self = shift;
198 3 50       16 return if $COUNTER;
199 3         17 $self->loadControllers();
200 3         30 $self->cando();
201             }
202            
203             #============================================================================
204             # run app
205             #============================================================================
206             sub _runner {
207 3     3   9 my $self = shift;
208 3         14 local $SIG{__DIE__};
209 3         7 eval {
210 3         35 $self->init();
211 3 50       514 if ($self->app->{can}->{begin}){
212 0         0 $self->app->begin($self);
213             }
214            
215 3         10 ++$self->{_count};
216 3 50       16 croak('Infinte Loop Detected') if $self->{_count} > '20';
217 3         32 $self->setup();
218 3 50       25 if ($self->app->{can}->{end}){
219 0         0 $self->app->end($self);
220             }
221            
222 3         48 $self->finalize();
223             };
224            
225 3 50       11 if ($@){
226 0         0 $self->error($@);
227             }
228            
229 3         4 $COUNTER++;
230 3         64 return $self;
231             }
232            
233             #============================================================================
234             # Run code on destruction ??!
235             #============================================================================
236             sub DESTROY {
237 2     2   7 my $self = shift;
238 2 50       11 return if !$self->{pid};
239 2 50       12 return if $$ != $self->{pid};
240 2 50       255 if ( exists $self->{on_destroy} ){
241 0 0       0 map {
242 0         0 $_->($self) if ref $_ eq "CODE";
243 0         0 } @{$self->{on_destroy}};
244            
245 0         0 $self->{on_destroy} = [];
246             }
247             }
248            
249             #============================================================================
250             # load controllers
251             #============================================================================
252             sub loadControllers {
253 3 50   3 0 18 return if $COUNTER;
254 3         8 my $self = shift;
255 3         14 my $dir = $self->app->{'dir'}.'/Controllers';
256             #warn Dumper $dir;
257 3 50       103 return if !-d $dir;
258            
259             find(sub {
260 6 100   6   359 if ($_ =~ m/\.pm$/){
261 3         6 my $file = $File::Find::name;
262            
263 3         219 eval "require '$file'";
264            
265 3 50       170 if ($@) {
266 0         0 die("can't load controller $file");
267             }
268             }
269 3         385 }, $dir);
270             }
271            
272             sub cando {
273 3     3 0 9 my $self = shift;
274 3 50       18 $self->app->{can} = {
    50          
    50          
    50          
275             begin => $self->app->can('begin') ? 1 : undef,
276             end => $self->app->can('end') ? 1 : undef,
277             error => $self->app->can('error') ? 1 : undef,
278             notfound => $self->app->can('notfound') ? 1 : undef
279             };
280             }
281            
282             #============================================================================
283             # load app model
284             #============================================================================
285             sub model {
286 0     0 0 0 my $self = shift;
287 0         0 my $model = shift;
288 0         0 my $module = $self->app->{'dir'}."::Model::".$model;
289 0         0 $module =~ s/::/\//g;
290 0         0 $module .= '.pm';
291            
292 0         0 require "$module";
293 0         0 $model = $self->app->{'basename'}."::Model::".$model;
294            
295 0         0 my $return;
296 0         0 eval {
297 0         0 $return = $model->init($self);
298             };
299            
300             ##if the model has init sub return it
301             ##other wise bless and return model class
302 0   0     0 return $return || bless({
303             c => $self
304             },$model);
305             }
306            
307             #============================================================================
308             # load Plugins
309             #============================================================================
310             sub loadPlugins {
311 0     0 0 0 my $self = shift;
312 0         0 my $dir = shift;
313 0         0 foreach my $module (@{$SELF->{plugins}}) {
  0         0  
314 0         0 Cake::Utils::Require($module,'Cake::Plugins');
315             ####maybe we should register plugins internally, but let's first test
316             #$self->register($module);
317             }
318             }
319            
320             #============================================================================
321             # register plugins
322             #============================================================================
323             sub register {
324 4     4 0 491 my @attr = @_;
325 4         13 my $caller = caller(0);
326 4         111 unshift @Cake::ISA,$caller;
327 4         18 return;
328             }
329            
330             #============================================================================
331             # server - get server name if set
332             #============================================================================
333             sub server {
334 0     0 1 0 return shift->env->{'cake.server'};
335             }
336            
337             #============================================================================
338             # controllers routing
339             #============================================================================
340 5     5 0 1654 sub get { Cake::Controllers->dispatch('get',@_); }
341 0     0 0 0 sub post { Cake::Controllers->dispatch('post',@_); }
342 0     0 1 0 sub any { Cake::Controllers->dispatch('any',@_); }
343 0     0 0 0 sub route { Cake::Controllers->dispatch('route',@_); }
344 0     0 0 0 sub auto { Cake::Controllers->auto(@_); }
345            
346             #maybe to be implemented later
347             #sub after { Cake::Controllers->after('after',@_); }
348             #sub before { Cake::Controllers->before('before',@_); }
349            
350             #============================================================================
351             # Custom Action Class Loader
352             #============================================================================
353             sub Action {
354 0     0 0 0 my $class = shift;
355 0         0 my $caller = (caller)[0];
356 0         0 $class = Cake::Utils::Require($class,'Cake::Actions');
357 0         0 my $self = {};
358 0 0       0 if (@_ == 1){
    0          
359 0         0 $self = $_[0];
360             } elsif (@_){
361 0         0 $self = \@_;
362             }
363            
364             ##bless action class
365 0         0 $class = bless($self,$class);
366             return sub {
367 0     0   0 my $dispatch = shift;
368 0         0 $dispatch->Action->{ActionClass} = $class;
369 0         0 };
370             }
371            
372             #============================================================================
373             # args
374             #============================================================================
375             sub args {
376 0     0 0 0 my $args = $_[0];
377 0         0 my $num = $args;
378            
379 0 0       0 if (ref $args eq 'ARRAY'){
380 0         0 $num = @{$args};
  0         0  
381             }
382            
383             return sub {
384 0     0   0 my $dispatch = shift;
385 0         0 my $path = $dispatch->Action->{path};
386 0 0       0 if (my $chain = $dispatch->{chains}->{$path}){
387 0         0 $dispatch->{chains}->{$path}->{path} = $path.'('.$num.')';
388 0         0 $dispatch->{chains}->{$path}->{args} = $num;
389             }
390            
391 0 0       0 if (ref $path eq 'Regexp'){
392 0         0 $dispatch->Action->{path} = qr{$path(/.*?)(/.*?)$};
393             } else {
394 0         0 $dispatch->Action->{path} .= '('.$num.')';
395             }
396            
397 0         0 $dispatch->Action->{args} = $args;
398 0         0 };
399             }
400            
401             #============================================================================
402             # chained controllers
403             #============================================================================
404             sub chained {
405 0     0 0 0 my $chain_path = $_[0];
406             return sub {
407 0     0   0 my $dispatch = shift;
408 0         0 my $path = $dispatch->Action->{path};
409             ##tell dispatcher this can be called on chains only
410 0         0 $dispatch->Action->{chain} = 1;
411            
412 0         0 my $class;
413             my $namespace;
414 0         0 my $abs_path = $chain_path;
415            
416 0         0 $class = $dispatch->Action->{class};
417 0         0 ($class) = $class =~ m/Controllers(::.*)$/;
418 0         0 ($class = lc $class) =~ s/::/\//g;
419            
420 0         0 $namespace = $dispatch->Action->{namespace};
421 0         0 my $to_chain = $chain_path;
422 0 0       0 unless ($chain_path =~ m/^\// ){
423 0         0 $to_chain = lc $class.'/'.$chain_path;
424             }
425            
426 0 0       0 if (!$abs_path){
427 0         0 push @{$dispatch->{chains_index}},$path;
  0         0  
428             }
429            
430 0         0 my ($dir) = $path =~ m/^$namespace(.*?)$/;
431 0         0 $dispatch->{chains}->{$path}->{dir} = $dir;
432 0         0 $dispatch->{chains}->{$path}->{path} = $path;
433 0         0 $dispatch->{chains}->{$path}->{namespace} = $namespace;
434 0         0 push @{$dispatch->{chains}->{$to_chain}->{chained_by}},$path;
  0         0  
435 0         0 };
436             }
437            
438             #============================================================================
439             # some short cuts
440             #============================================================================
441             sub capture {
442 0 0   0 0 0 if (@_ > 1){
443 0         0 return $_[0]->action->{args}->[$_[1]];
444             }
445 0         0 return $_[0]->action->{args}
446             }
447            
448             sub ActionClass {
449 3     3 0 18 return shift->action->{ActionClass};
450             }
451             #============================================================================
452             # return controller class object
453             #============================================================================
454             sub controller {
455 9     9 0 31 return shift->action->{controller};
456             }
457             #============================================================================
458             # return current action code
459             #============================================================================
460             sub code {
461 3     3 0 12 return shift->action->{code};
462             }
463             #============================================================================
464             # set body content
465             #============================================================================
466             sub body {
467 4     4 0 144 my ($self,$content) = @_;
468 4 50       14 if (@_ == 2){
469 4         5 my $body;
470 4 50       13 if (ref $content eq ('CODE' || 'ARRAY' || 'GLOB')){
471 0         0 $body = $content;
472             } else {
473             #truncates and open for reading and writing
474 4         1679 open($body, "+>", undef);
475 4         51 $body->write($content);
476             }
477            
478 4         135 $self->{response}->{'body'} = $body;
479 4         125 return $self;
480             }
481 0         0 my $body = $self->{response}->{'body'};
482 0         0 return $body;
483             }
484            
485             sub getBody {
486 1     1 0 8 my ($self) = @_;
487 1         3 my $body = $self->{response}->{'body'};
488 1 50       6 if (ref $body eq 'GLOB'){
489 1         16 $body->seek(0,0);
490 1         34 local $/;
491 1         23 return <$body>;
492             } else {
493 0         0 return $body;
494             }
495             }
496            
497             ##append content to the body
498             sub write {
499 0     0 0 0 my ($self,$chunk) = @_;
500 0         0 my $fh = $self->body();
501 0 0 0     0 if ($fh && ref $fh eq 'GLOB'){
502 0         0 $fh->write($chunk);
503             } else {
504 0         0 $self->body($chunk);
505             }
506             }
507            
508             #============================================================================
509             # dump data
510             #============================================================================
511             sub dumper {
512 0     0 0 0 my $self = shift;
513 0         0 my $data = shift;
514 0         0 $self->body(Dumper $data);
515             }
516            
517             sub json {
518 0     0 0 0 my $self = shift;
519 0         0 my $data = shift;
520 0 0       0 if (ref $data eq 'HASH'){
521 0         0 $data = $self->serialize($data)->to_json;
522             }
523 0         0 $self->content_type('application/javascript; charset=utf-8');
524 0         0 $self->body($data);
525             }
526            
527             sub detach {
528 0     0 0 0 my $self = shift;
529 0         0 $self->finalize();
530 0         0 Cake::Exception::Mercy_Killing($self);
531             }
532             #============================================================================
533             # param : set/get param
534             # copied form catalyst param method :P that's why it looks sophisticated :))
535             #============================================================================
536             sub param {
537 0     0 0 0 my $self = shift;
538 0 0       0 if ( @_ == 0 ) {
539 0         0 return keys %{ $self->parameters };
  0         0  
540             }
541            
542 0 0       0 if (ref($_[0]) eq 'HASH'){
    0          
    0          
543 0         0 my $hash = shift;
544 0         0 while (my ($key,$value) = each(%{$hash})){
  0         0  
545 0         0 $self->parameters->{$key} = $value;
546             }
547            
548             } elsif ( @_ == 1 ) {
549 0         0 my $param = shift;
550 0 0       0 unless ( exists $self->parameters->{$param} ) {
551 0 0       0 return wantarray ? () : undef;
552             }
553            
554 0 0       0 if ( ref $self->parameters->{$param} eq 'ARRAY' ) {
555             return (wantarray)
556 0 0       0 ? @{ $self->parameters->{$param} }
  0         0  
557             : $self->parameters->{$param}->[0];
558             }
559             else {
560             return (wantarray)
561 0 0       0 ? ( $self->parameters->{$param} )
562             : $self->parameters->{$param};
563             }
564            
565             } elsif ( @_ > 1 ) {
566 0         0 my $field = shift;
567 0 0       0 $self->parameters->{$field} = @_ >= 2 ? [@_] : $_[0] ;
568             }
569 0         0 return $self->parameters();
570             }
571            
572             #============================================================================
573             # params : alias for parameters
574             #============================================================================
575             #============================================================================
576             # parameters : Implemented in Cake::Engine
577             #============================================================================
578             sub params {
579 0     0 0 0 return shift->parameters(@_);
580             }
581            
582             #============================================================================
583             # push_header : add header
584             #============================================================================
585             sub push_header {
586 0     0 0 0 my $self = shift;
587 0         0 my ($header) = @_;
588 0 0       0 if (ref $header eq 'HASH'){
    0          
589 0         0 foreach my $key (keys %{$header}){
  0         0  
590 0         0 my $head = $key.': '.$header->{$key};
591 0         0 $self->push_header($head);
592             }
593 0         0 return;
594             } elsif (ref $header eq 'ARRAY'){
595 0         0 map { $self->push_header($_) } @{$header};
  0         0  
  0         0  
596 0         0 return;
597             }
598            
599 0 0       0 if (@_ > 1){
600 0         0 $header = $_[0].': '.$_[1];
601             }
602            
603 0 0 0     0 croak 'Headers accept a Hash ref, Array of Hash refs or scalar'
604             if ref $header || $header !~ /(.*?):(.*?)/;
605            
606 0 0       0 if ($header =~ s/^content-type:\s*//i){
    0          
607 0         0 $self->content_type($header);
608             } elsif ($header =~ s/^status:\s*//i){
609 0         0 $self->status_code($header);
610             } else {
611 0         0 push(@{$self->{response}->{headers}}, $header);
  0         0  
612             }
613            
614 0         0 return $self;
615             }
616            
617             #============================================================================
618             # add multiple headers / get all headers
619             #============================================================================
620             sub headers {
621 0     0 0 0 my $self = shift;
622 0 0       0 if (@_){
623 0         0 foreach my $header (@_){
624 0         0 $self->push_header($header);
625             }
626 0         0 return $self;
627             }
628 0 0       0 return wantarray ? @{$self->{response}->{headers}} : $self->{response}->{headers};
  0         0  
629             }
630            
631             #============================================================================
632             # get/set content type header
633             #============================================================================
634             sub content_type {
635 0     0 0 0 my ($self,$type) = @_;
636 0 0       0 if ($type){
637 0         0 $self->{response}->{content_type} = $type;
638             }
639 0   0     0 return $self->{response}->{content_type} || 'text/html';
640             }
641            
642             #============================================================================
643             # get/set response status code header
644             #============================================================================
645             sub status_code {
646 3     3 0 5 my ($self,$code) = @_;
647 3 50       19 if ($code){
648 3         14 $self->{response}->{status_code} = $code;
649             }
650 3   50     18 return $self->{response}->{status_code} || '200';
651             }
652            
653             #============================================================================
654             # redirect
655             #============================================================================
656             sub redirect {
657 0     0 0   my $self = shift;
658 0           my $url = shift;
659 0   0       my $status = shift || 302;
660 0           $url = $self->uri_for($url);
661 0           $self->status_code($status);
662 0           $self->push_header("Location: $url");
663            
664             ##just in case ?? inject this HTNL/javascript redirect
665 0           my $html = qq~
667             This page has moved to $url~;
668 0           $self->body($html);
669 0           $self->finalize();
670             }
671            
672             #============================================================================
673             # forward
674             # to stop after forward use
675             # return $c->forward();
676             #============================================================================
677             sub forward {
678 0     0 0   my $self = shift;
679 0           my $forward_to = shift;
680 0           my $args = shift;
681 0 0         if (ref $forward_to eq 'CODE'){
    0          
682 0           $forward_to->($self->controller,$self,$args);
683             } elsif ($forward_to !~ /^\//){
684 0           $self->controller()->$forward_to($self,$args);
685             } else {
686             ####alter reguest path
687 0           $self->path($forward_to);
688 0           $self->run($args);
689             }
690             }
691            
692             package Cake::ENV;
693             our $AUTOLOAD;
694 0     0     sub ip { shift->{REMOTE_ADDR} }
695 0     0     sub host { shift->{HTTP_HOST} }
696 0     0     sub referrer { shift->{HTTP_REFERER} }
697             sub AUTOLOAD {
698 0     0     my $self = shift;
699 0           my $sub = $AUTOLOAD;
700 0           $sub =~ s/.*:://;
701 0           while (my ($key,$val) = each %{$self} ){
  0            
702 0 0         if ($key =~ m/$sub/i){
703 0           return $val;
704             }
705             }
706 0           return '';
707             }
708            
709             1;
710            
711             __END__