File Coverage

blib/lib/Ark/Core.pm
Criterion Covered Total %
statement 170 181 93.9
branch 71 98 72.4
condition 7 15 46.6
subroutine 31 34 91.1
pod 0 22 0.0
total 279 350 79.7


line stmt bran cond sub pod time code
1             package Ark::Core;
2 78     78   554 use Mouse;
  78         152  
  78         410  
3              
4 61     61   44397 use Ark::Context;
  61         4910  
  61         2331  
5 61     61   23834 use Ark::Request;
  61         220  
  61         2173  
6 61     61   27671 use Ark::Response;
  61         5483  
  61         1989  
7              
8 61     61   28243 use Exporter::AutoClean;
  61         557313  
  61         2072  
9 61     61   21525 use Path::Class qw/file dir/;
  61         831584  
  61         4110  
10 61     61   28432 use Path::AttrRouter 0.03;
  61         584745  
  61         32925  
11              
12             extends 'Class::Data::Inheritable';
13              
14             __PACKAGE__->mk_classdata($_)
15             for qw/context configdata plugins _class_stash external_model_class/;
16              
17             has handler => (
18             is => 'rw',
19             isa => 'CodeRef',
20             lazy => 1,
21             default => sub {
22             my $self = shift;
23              
24             sub {
25             my $res = $self->handle_request(shift);
26             $res->finalize;
27             };
28             },
29             predicate => 'is_psgi',
30             );
31              
32             has logger_class => (
33             is => 'rw',
34             isa => 'Str',
35             lazy => 1,
36             default => sub { 'Ark::Logger' },
37             );
38              
39             has logger => (
40             is => 'rw',
41             isa => 'Object',
42             lazy => 1,
43             default => sub {
44             my $self = shift;
45             my $class = $self->logger_class;
46             $self->ensure_class_loaded($class);
47             $class->new( log_level => $self->log_level );
48             },
49             );
50              
51             has log_level => (
52             is => 'rw',
53             isa => 'Str',
54             default => sub {
55             # XXX: how detect plackup -E env in application?
56             if ($INC{'Plack/Middleware/StackTrace.pm'}) {
57             $ENV{ARK_DEBUG} =1;
58             }
59             $ENV{ARK_DEBUG} ? 'debug' : 'error';
60             },
61             );
62              
63             has debug => (
64             is => 'rw',
65             isa => 'Bool',
66             lazy => 1,
67             default => sub {
68             shift->log_level eq 'debug';
69             },
70             );
71              
72             has components => (
73             is => 'rw',
74             isa => 'HashRef',
75             default => sub { {} },
76             );
77              
78             has context_class => (
79             is => 'rw',
80             isa => 'Str',
81             default => sub {
82             my $self = shift;
83              
84             # create application specific context class for mod_perl
85             my $class = $self->class_wrapper(
86             name => 'Context',
87             base => 'Ark::Context',
88             );
89             },
90             predicate => 'context_class_detected',
91             );
92              
93             has setup_finished => (
94             is => 'rw',
95             isa => 'Bool',
96             default => 0,
97             );
98              
99             after setup => sub {
100             my $self = shift;
101              
102             $self->log( debug => 'Setup finished' );
103             $self->setup_finished(1);
104             };
105              
106             has lazy_roles => (
107             is => 'rw',
108             isa => 'HashRef',
109             lazy => 1,
110             default => sub { {} },
111             );
112              
113             has action_cache => (
114             is => 'rw',
115             isa => 'Object',
116             lazy => 1,
117             default => sub {
118             my $self = shift;
119             $self->path_to('action.cache');
120             },
121             );
122              
123             has use_cache => (
124             is => 'rw',
125             isa => 'Bool',
126             default => 0,
127             );
128              
129             has router => (
130             is => 'rw',
131             isa => 'Path::AttrRouter',
132             handles => ['get_action', 'get_actions'],
133             );
134              
135 61     61   523 no Mouse;
  61         134  
  61         284  
136              
137             sub EXPORT {
138 68     68 0 162 my ($class, $target) = @_;
139              
140 68         211 my $load_plugins = $class->can('load_plugins');
141 68         222 my $use_model = $class->can('use_model');
142 68         213 my $config = $class->can('config');
143 68     13   284 my $config_sub = sub { $config->( $target, @_ ) };
  13         287  
144              
145             Exporter::AutoClean->export(
146             $target,
147 14     14   1912 use_plugins => sub { $load_plugins->( $target, @_ ) },
148 1     1   137 use_model => sub { $use_model->( $target, @_ ) },
149 68         571 config => $config_sub,
150             conf => $config_sub, # backward compatibility
151             );
152             }
153              
154             sub config {
155 1722     1722 0 2932 my $class = shift;
156 1722 100       3549 my $config = @_ > 1 ? {@_} : $_[0];
157              
158 1722 100       4871 $class->configdata({}) unless $class->configdata;
159              
160 1722 100       17668 if ($config) {
161 15 50       26 for my $key (keys %{ $config || {} }) {
  15         65  
162 14         55 $class->configdata->{$key} = $config->{$key};
163             }
164             }
165              
166 1722         3178 $class->configdata;
167             }
168              
169             sub class_wrapper {
170 314     314 0 595 my $self = shift;
171 314 50       1696 my $args = @_ > 1 ? {@_} : $_[0];
172              
173 314   33     1020 my $pkg = ref($self) || $self;
174              
175             $self->log( fatal => q["name" and "base" parameters are required] )
176 314 50 33     1919 unless $args->{name} and $args->{base};
177              
178 314         988 my $classname = "${pkg}::Ark::$args->{name}";
179             return $classname
180 314 100 66     4732 if Mouse::is_class_loaded($classname) && $classname->isa($args->{base});
181              
182             {
183 77         141 local $@;
  77         143  
184 77     60   5690 eval qq{
  60         423  
  60         113  
  60         384  
185             package ${classname};
186             use Mouse;
187             extends '$args->{base}';
188             1;
189             };
190 77 50       440 die $@ if $@;
191             }
192              
193 77 100       167 for my $plugin (@{ $self->lazy_roles->{ $args->{name} } || [] }) {
  77         950  
194 20 50       17343 $plugin->meta->apply( $classname->meta )
195             unless $classname->meta->does_role( $plugin );
196             }
197 77 100       6452 $classname->meta->make_immutable if $self->context_class_detected;
198              
199 77         1657 $classname;
200             }
201              
202             sub class_stash {
203 0     0 0 0 my $self = shift;
204 0 0       0 $self->_class_stash || $self->_class_stash({});
205             }
206              
207             sub load_plugins {
208 14     14 0 57 my ($class, @names) = @_;
209              
210 14 50       144 $class->plugins([]) unless $class->plugins;
211              
212             my @plugins =
213 14 100       842 map { $_ =~ /^\+(.+)/ ? $1 : 'Ark::Plugin::' . $_ } grep {$_} @names;
  38         144  
  38         74  
214              
215 14         31 push @{ $class->plugins }, @plugins;
  14         45  
216             }
217              
218             sub setup {
219             my $self = shift;
220             my $class = ref($self) || $self;
221             my $args = @_ > 1 ? {@_} : $_[0];
222              
223             $self->setup_debug_mode if $self->debug;
224              
225             $self->setup_home;
226              
227             # setup components
228             $self->ensure_class_loaded('Module::Pluggable::Object');
229              
230             my @paths = qw/::View/;
231             my $locator = Module::Pluggable::Object->new(
232             search_path => [ map { $class . $_ } @paths ],
233             );
234              
235             my @components = $locator->plugins;
236             for my $component (@components) {
237             $self->load_component($component);
238             }
239              
240             $self->setup_plugins;
241             $self->setup_actions;
242             }
243              
244             sub setup_minimal {
245 54     54 0 807 my ($self, %option) = @_;
246              
247 54 50       4406 $self->setup_debug_mode if $self->debug;
248              
249 54         1179 $self->setup_home;
250 54         4648 $self->setup_plugins;
251              
252             # cache
253             $self->action_cache( $self->path_to($option{action_cache}) )
254 54 50       15335 if $option{action_cache};
255              
256 54         786 $self->use_cache(1);
257 54         800 $self->setup_actions;
258              
259 54         768 $self->log( debug => 'Minimal setup finished');
260 54         585 $self->setup_finished(1)
261             }
262              
263             sub setup_debug_mode {
264 0     0 0 0 my $self = shift;
265 0 0       0 return if $self->context_class->meta->does_role('Ark::Context::Debug');
266              
267 0         0 $self->ensure_class_loaded('Ark::Context::Debug');
268 0         0 Ark::Context::Debug->meta->apply( $self->context_class->meta );
269             }
270              
271             sub setup_home {
272 286     286 0 619 my $self = shift;
273 286 100       1548 return if $self->config->{home};
274              
275 66         662 my $class = ref $self;
276 66         299 (my $file = "${class}.pm") =~ s!::!/!g;
277              
278 66 50       332 if (my $path = $INC{$file}) {
279 66         1469 $path =~ s/$file$//;
280              
281 66         397 $path = dir($path);
282              
283 66 100       8557 if (-d $path) {
284 10         647 $path = $path->absolute;
285 10         425 while ($path->dir_list(-1) =~ /^b?lib$/) {
286 10         259 $path = $path->parent;
287             }
288              
289 10         1096 $self->config->{home} = $path;
290             }
291             }
292             }
293              
294             sub setup_plugin {
295 138     138 0 355 my ($self, $plugin) = @_;
296              
297 138         528 $self->ensure_class_loaded($plugin);
298              
299 138 100       1231 if (my $target_context = $plugin->plugin_context) {
300 45 100       125 if ($target_context eq 'Core') {
301 1 50       4 $plugin->meta->apply( $self->meta )
302             unless $self->meta->does_role($plugin);
303             }
304             else {
305 44         52 push @{ $self->lazy_roles->{ $target_context } }, $plugin;
  44         148  
306             }
307 45         2389 return;
308             }
309 93 100       788 $plugin->meta->apply( $self->context_class->meta )
310             unless $self->context_class->meta->does_role($plugin);
311             }
312              
313             sub setup_plugins {
314 232     232 0 786 my $self = shift;
315              
316 232         1942 $self->meta->make_mutable;
317              
318 232 100       10334 for my $plugin (@{ $self->plugins || [] }) {
  232         1518  
319 74         18521 $self->setup_plugin($plugin);
320             }
321              
322 232         17112 $self->setup_default_plugins;
323              
324 232         148080 $self->meta->make_immutable;
325             }
326              
327             sub setup_default_plugins {
328 232     232 0 467 my $self = shift;
329              
330 232         457 my $encoding_filter_required = 1;
331 232         443 for my $role (@{ $self->context_class->meta->roles }) {
  232         2030  
332 202 100       5723 $encoding_filter_required = 0 if $role->name =~ /::Encoding::/;
333             }
334              
335 232 100       1856 $self->setup_plugin('Ark::Plugin::Encoding::Unicode') if $encoding_filter_required;
336             }
337              
338             sub setup_actions {
339 232     232 0 601 my $self = shift;
340              
341 232 100       4741 my $router = Path::AttrRouter->new(
342             search_path => ref($self) . '::Controller',
343             action_class => 'Ark::Action',
344             $self->use_cache ? ( action_cache => $self->action_cache . '' ) : (),
345             );
346 232         1115134 $self->router($router);
347              
348 232 50       2567 if ($self->debug) {
349 0         0 $self->log( debug => $router->routing_table->draw );
350             }
351             }
352              
353             sub load_component {
354 329     329 0 787 my ($self, $component) = @_;
355              
356 329 100       1268 if ($self->components->{ $component }) {
357 17         66 return $self->components->{ $component };
358             }
359              
360 312 50       832 $self->ensure_class_loaded($component) or return;
361 312 100       2829 $component->isa('Ark::Component') or return;
362              
363             # merge config
364 23         73 $component->config( $self->config->{ $component->component_name } );
365              
366 23         173 my $instance = $component->new( app => $self, %{ $component->config } );
  23         60  
367 23 100       1434 if ($instance->can('ARK_DELEGATE')) {
368 3         12 $instance = $instance->ARK_DELEGATE($self);
369             }
370              
371 23         126 $self->components->{ $component } = $instance;
372             }
373              
374             sub component {
375 23     23 0 46 my ($self, $name) = @_;
376 23 50       49 return unless $name;
377              
378 23 50       70 if ($name =~ /^\+/) {
379 0         0 $name =~ s/^\+//;
380             }
381             else {
382 23         59 $name = ref($self) . '::' . $name;
383             }
384              
385 23         97 $self->ensure_class_loaded($name);
386 23   66     588 $self->components->{$name} ||= $self->load_component($name);
387             }
388              
389             sub controller {
390 0     0 0 0 my ($self, $name) = @_;
391 0 0       0 return unless $name;
392 0         0 $self->component('Controller::' . $name);
393             }
394              
395             sub model {
396 5     5 0 157 my ($self, $name) = @_;
397              
398 5 100       25 if (my $class = $self->external_model_class) {
399 2 50       37 return @_ >= 2 ? $class->get($name) : $class;
400             }
401              
402 3 50       45 return unless $name;
403 3         16 $self->component('Model::' . $name);
404             }
405              
406             sub view {
407 20     20 0 1555 my ($self, $name) = @_;
408 20 100       49 unless (defined $name) {
409 1 50       4 $name = $self->config->{default_view} or return;
410             }
411 20         79 $self->component('View::' . $name);
412             }
413              
414             sub use_model {
415 1     1 0 5 my ($self, $model_class) = @_;
416 1         13 $self->ensure_class_loaded( $model_class );
417 1         8 $self->external_model_class( $model_class );
418 1 50       75 $model_class->initialize if $model_class->can('initialize');
419              
420 1         7 my $conf = $self->model('conf');
421 1 50       4 if ($conf) {
422 1         9 $self->config($conf);
423             }
424             }
425              
426             sub log {
427 364     364 0 8772 my $self = shift;
428              
429 364 100       1303 unless (@_) {
430 2         12 return $self->logger;
431             }
432             else {
433             # keep backward compatibility
434 362         2643 $self->logger->log(@_);
435             }
436             }
437              
438             sub ensure_class_loaded {
439 1207     1207 0 3456 my ($self, $class) = @_;
440 1207 100       7897 Mouse::load_class($class) unless Mouse::is_class_loaded($class);
441             }
442              
443             sub path_to {
444 149     149 0 1492 my ($self, @path) = @_;
445              
446             die qq[Can't call path_to method before setup_home]
447 149 50       596 unless $self->config->{home};
448              
449 149         2405 my $path = dir( $self->config->{home}, @path );
450 149 100       23248 return $path if -d $path;
451 148         10792 return file($path);
452             }
453              
454             sub handle_request {
455 281     281 0 691 my ($self, $env) = @_;
456 281         3011 my $req = Ark::Request->new($env);
457              
458 281         10846 my $context = $self->context_class->new( app => $self, request => $req );
459 281         13151 $self->context($context)->process;
460 281         1088 $self->context(undef);
461              
462 281 100       3370 if ( my $error = $context->error->[0] ) {
463 1         4 chomp $error;
464 1         5 $self->log( error => 'Caught exception in engine "%s"', $error );
465              
466 1 50 33     19 unless ($self->debug or $context->response->status eq '500') {
467 1         3 my $res = $context->response;
468 1         4 $res->status(500);
469 1         3 $res->body('Internal Server Error');
470             }
471             }
472              
473 281         1915 return $context->response;
474             }
475              
476             __PACKAGE__->meta->make_immutable;