File Coverage

blib/lib/App/TinyMVC.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1              
2             package App::TinyMVC;
3              
4 2     2   45511 use warnings;
  2         6  
  2         68  
5 2     2   11 use strict;
  2         4  
  2         88  
6              
7             use constant {
8 2         160 NO_CACHE => -1,
9             CACHE_VIEW => 0,
10             CACHE_DATA => 1,
11 2     2   11 };
  2         7  
12              
13 2     2   907 use App::TinyMVC::Cache;
  2         9  
  2         73  
14 2     2   1268 use App::TinyMVC::Scheduler;
  2         4  
  2         48  
15 2     2   2135 use Data::Dumper;
  2         26761  
  2         171  
16 2     2   2344 use YAML::AppConfig;
  0            
  0            
17             use Template;
18             use Template::Constants;
19              
20             =head1 NAME
21              
22             App::TinyMVC - A lightweight MVC framework for dynamic content
23              
24             =head1 VERSION
25              
26             Version 0.01
27              
28             =cut
29              
30             our $VERSION = '0.01_2';
31              
32             =head1 SYNOPSIS
33              
34             use App::TinyMVC;
35              
36             my $tinymvc = new App::TinyMVC (
37             controller => 'books',
38             action => 'list',
39             args => [@args],
40             context => {
41             params => {...},
42             }
43             );
44              
45             $tinymvc->process;
46              
47             =head1 FUNCTIONS
48              
49             =head2 new
50              
51             Create a new App::TinyMVC object.
52              
53             =cut
54              
55             sub new {
56             my ($class) = shift;
57             my $self = { @_ };
58             $self = bless($self, $class);
59              
60             # Set defaults
61             $self->{'controller'} = 'index' unless $self->{'controller'};
62             $self->{'action'} = 'index' unless $self->{'action'};
63             $self->{'template'} = $self->{'controller'}.'/'.$self->{'action'} unless $self->{'template'};
64             $self->{'context'}->{'params'} = {} unless $self->{'context'}->{'params'};
65             $self->{'args'} = [] unless $self->{'args'};
66             $self->{'siteEnclosure'} = 0 unless $self->{'siteEnclosure'};
67              
68             # Read config files
69             my $confdir = '';
70             if ($App::TinyMVC::CONFDIR) {
71             $confdir = $App::TinyMVC::CONFDIR;
72             unless ($confdir and $confdir =~ m/\/$/) {
73             $confdir .= '/';
74             }
75             }
76             $self->{'confdir'} = $confdir;
77              
78             $self->{'config'} = new YAML::AppConfig(file => $confdir."tinymvc.yaml")->config;
79              
80             $self->{'context'}->{'query_string'} = {} unless ( $self->{'context'}->{'query_string'}->{'key'} and $self->{'context'}->{'query_string'}->{'key'} eq $self->{'config'}->{'query_string_key'} );
81             $self->{'cache'} = undef;
82              
83             $self->log("info","controller: ".$self->{'controller'}." | action: ".$self->{'action'}." | args: ".(join ',',@{$self->{'args'}})." | template: ".$self->{'template'}." | siteEnclosure: ".$self->{'siteEnclosure'});
84             $self->log('info', 'App::TinyMVC::new() ended..');
85              
86             return $self;
87             }
88              
89             =head2 process
90              
91             Process requested action from a controller.
92              
93             =cut
94              
95             sub process {
96              
97             my $self = shift;
98              
99             $self->log('info',"App::TinyMVC::process() entering..");
100              
101             my $output;
102             my $cache = App::TinyMVC::Cache->new;
103             my $scheduler = App::TinyMVC::Scheduler->new;
104              
105             # validate routes in request XXX
106              
107             #unless ($self->validate_routes) {
108             # return "ERRO";
109             #}
110              
111             # create new controller instance
112             my $contName = "App::TinyMVC::Controller::".ucfirst($self->controller);
113             my $controller = $contName->new; # XXX
114              
115             # let controller decide cache type and cache id
116             # also let controller validate args if needed
117             my($cache_id,$cache_type,$cache_expire) = $controller->auto($self);
118              
119             $self->log('info',"App::TinyMVC::process(): Controller said: cache_id: $cache_id, cache_type: $cache_type, cache_expire: ".(defined($cache_expire)?$cache_expire:"no-expire"));
120              
121             # schedule requests
122             my $workers = $scheduler->workers($self, $cache_id);
123             if ($workers) {
124             my $waiting_for = 10;
125             while ($scheduler->workers($self, $cache_id) and $waiting_for) {
126             sleep 1 and $waiting_for--;
127             }
128             }
129             else {
130             # no workers, start processing
131             $scheduler->processing($self, $cache_id);
132             }
133              
134             # Can i use view cache ?
135             if ($cache_type eq CACHE_VIEW and $cache_id) {
136              
137             # try to return cached view for request
138             $output = $cache->get($self,$cache_type,$cache_id);
139             if ( defined $output ) {
140             $self->log('info',"App::TinyMVC::process(): Have cached view, returning..");
141             $scheduler->finished($self, $cache_id);
142             return $output;
143             }
144             }
145              
146             # Can i use data cache ?
147             my $return = '';
148             if ($cache_type eq CACHE_DATA and $cache_id) {
149             $self->{'stash'} = $cache->get($self,$cache_type,$cache_id);
150             }
151             unless ($self->{'stash'}) {
152             my $action = $self->action;
153              
154             # XXX run index if called from handler only
155             # XXX or if we need to build entire site
156             if ($self->{'siteEnclosure'} or $self->controller eq 'index') {
157             my $zbr = App::TinyMVC::Controller::Index->new;
158             $zbr->index($self);
159             }
160              
161             # run action
162             if ($action) {
163             $return = $controller->$action($self);
164             }
165             else {
166             $controller->index($self);
167             }
168              
169             # store data cache
170             if ($cache_type eq CACHE_DATA and $cache_id) {
171             $cache->set($self,$cache_type,$cache_id,$self->{'stash'},$cache_expire);
172             }
173             }
174              
175             if ($return eq '404') {
176             $scheduler->finished($self, $cache_id);
177             return $return;
178             }
179              
180             my $template_dir = $self->{'config'}->{'templates'}->{'dir'} || 'templates/App::TinyMVC';
181             $self->log('info',"App::TinyMVC::process(): Using template dir: $template_dir");
182              
183             my $template = Template->new({
184             CACHE_SIZE => 0,
185             INCLUDE_PATH => $template_dir,
186             });
187             #if ($self->controller eq 'index') {
188             # $self->{'content'} = 'src/homepage';
189             #}
190             #$self->log('info','calling template '.$vars->{'template'});
191              
192             # handle stash and some needed stuff for templates
193             my $vars = $self->{'stash'};
194             if ($self->{'context'}) {
195             $vars->{'context'} = $self->{'context'};
196             }
197             $vars->{'config'} = $self->{'config'};
198              
199             #$vars->{'make_url'} =
200             # sub {
201             # '/mspapp_handler/'.join '/', @_; # XXX
202             # };
203              
204             # process template and save output
205             if($self->{'siteEnclosure'}) {
206             $vars->{'template'} = $self->{'template'};
207             $template->process('index', $vars, \$output);
208             }
209             else {
210             $template->process($self->{'template'}, $vars, \$output);
211             }
212              
213             # cache view before returning
214             if ($cache_type eq CACHE_VIEW and $cache_id and $output) {
215             $cache->set($self,$cache_type,$cache_id,$output,$cache_expire);
216             }
217              
218             $self->log('info',"App::TinyMVC::process() leaving..");
219              
220             # return output to handler
221              
222             $scheduler->finished($self, $cache_id);
223             $output;
224             }
225              
226             =head2 validate_args
227              
228             Validate arguments.
229              
230             =cut
231              
232             sub validate_args {
233             my $self = shift;
234              
235             # check controller name
236             my $found = grep {$self->{'controller'} eq $_} keys %{$self->{'config'}->{'controllers'}};
237             unless ($found) {
238             $self->log('error',"controller not found: ".$self->controller);
239             return 0;
240             }
241              
242             # check action name
243             unless ($self->controller eq 'index') {
244             $found = grep {$self->{'action'} eq $_} keys %{$self->{'config'}->{'controllers'}->{$self->{'controller'}}};
245             unless ($found) {
246             $self->log('error',"action not found for controller ".$self->controller.": ".$self->action);
247             return 0;
248             }
249             }
250              
251             # check number of args
252             unless ($self->{'config'}->{'controllers'}->{$self->controller}->{$self->action}->{'args'} == @{$self->args}) {
253             $self->log('error',"invalid number of args for controller ".$self->controller." action ".$self->action." args: ".(join ',',@{$self->{'args'}}));
254             return 0;
255             }
256              
257             1;
258             }
259              
260             =head2 log
261              
262             Log information somewhere...
263              
264             =cut
265              
266             sub log {
267             my($self,$level,$msg) = @_;
268              
269             my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime time;
270             my $timestamp = sprintf("%s-%02d-%s %02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
271              
272             my $str = "[$timestamp] [".(uc $level)."] $msg";
273              
274             if ($self->{'config'}->{'log'}->{'enabled'}) {
275             print STDERR "App::TinyMVC $str\n";
276             }
277             }
278              
279             =head2 controller
280              
281             Returns requested controller.
282              
283             =cut
284              
285             sub controller {
286             my $self = shift;
287              
288             $self->{'controller'}
289             }
290              
291             =head2 action
292              
293             Returns requested action.
294              
295             =cut
296              
297             sub action {
298             my $self = shift;
299              
300             $self->{'action'}
301             }
302              
303             =head2 args
304              
305             Returns arguments given by request.
306              
307             =cut
308              
309             sub args {
310             my $self = shift;
311              
312             $self->{'args'} = shift if @_;
313             $self->{'args'};
314             }
315              
316             =head2 sapo
317              
318             Returns SAPO object.
319              
320             =cut
321              
322             sub sapo {
323             my $self = shift;
324              
325             $self->{'context'}->{'sapo'};
326             }
327              
328             =head2 stash
329              
330             XXX
331              
332             =cut
333              
334             sub stash {
335             my($self, $key, $value) = @_;
336              
337             if ($key and $value) {
338             $self->{'stash'}->{$key} = $value;
339             }
340             }
341              
342             =head1 AUTHOR
343              
344             Nuno Carvalho, C<< >>
345             David Oliveira, C<< >>
346              
347             =head1 BUGS
348              
349             Please report any bugs or feature requests to C, or through
350             the web interface at L. I will be notified, and then you'll
351             automatically be notified of progress on your bug as I make changes.
352              
353              
354              
355              
356             =head1 SUPPORT
357              
358             You can find documentation for this module with the perldoc command.
359              
360             perldoc App::TinyMVC
361              
362              
363             You can also look for information at:
364              
365             =over 4
366              
367             =item * RT: CPAN's request tracker
368              
369             L
370              
371             =item * AnnoCPAN: Annotated CPAN documentation
372              
373             L
374              
375             =item * CPAN Ratings
376              
377             L
378              
379             =item * Search CPAN
380              
381             L
382              
383             =back
384              
385              
386             =head1 ACKNOWLEDGEMENTS
387              
388              
389             =head1 COPYRIGHT & LICENSE
390              
391             Copyright 2010 Nuno Carvalho, all rights reserved.
392              
393             This program is free software; you can redistribute it and/or modify it
394             under the same terms as Perl itself.
395              
396              
397             =cut
398              
399             1; # End of App::TinyMVC