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 76     76   465 use Mouse;
  76         155  
  76         486  
3              
4 60     60   52723 use Ark::Context;
  60         5355  
  60         2755  
5 60     60   36070 use Ark::Request;
  60         210  
  60         2080  
6 60     60   34469 use Ark::Response;
  60         6288  
  60         1999  
7              
8 60     60   52920 use Exporter::AutoClean;
  60         822709  
  60         2050  
9 60     60   42790 use Path::Class qw/file dir/;
  60         1647508  
  60         4685  
10 60     60   64148 use Path::AttrRouter;
  60         678942  
  60         37475  
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 60     60   658 no Mouse;
  60         162  
  60         391  
136              
137             sub EXPORT {
138 67     67 0 164 my ($class, $target) = @_;
139              
140 67         341 my $load_plugins = $class->can('load_plugins');
141 67         345 my $use_model = $class->can('use_model');
142 67         314 my $config = $class->can('config');
143 67     12   332 my $config_sub = sub { $config->( $target, @_ ) };
  12         194  
144              
145             Exporter::AutoClean->export(
146             $target,
147 13     13   711 use_plugins => sub { $load_plugins->( $target, @_ ) },
148 1     1   12 use_model => sub { $use_model->( $target, @_ ) },
149 67         951 config => $config_sub,
150             conf => $config_sub, # backward compatibility
151             );
152             }
153              
154             sub config {
155 1578     1578 0 2562 my $class = shift;
156 1578 100       4138 my $config = @_ > 1 ? {@_} : $_[0];
157              
158 1578 100       6165 $class->configdata({}) unless $class->configdata;
159              
160 1578 100       16960 if ($config) {
161 14 50       34 for my $key (keys %{ $config || {} }) {
  14         75  
162 13         54 $class->configdata->{$key} = $config->{$key};
163             }
164             }
165              
166 1578         4500 $class->configdata;
167             }
168              
169             sub class_wrapper {
170 312     312 0 543 my $self = shift;
171 312 50       1857 my $args = @_ > 1 ? {@_} : $_[0];
172              
173 312   33     1051 my $pkg = ref($self) || $self;
174              
175 312 50 33     2337 $self->log( fatal => q["name" and "base" parameters are required] )
176             unless $args->{name} and $args->{base};
177              
178 312         938 my $classname = "${pkg}::Ark::$args->{name}";
179 312 100 66     6437 return $classname
180             if Mouse::is_class_loaded($classname) && $classname->isa($args->{base});
181              
182             {
183 75         181 local $@;
  75         135  
184 75     59   11441 eval qq{
  59         433  
  59         123  
  59         481  
185             package ${classname};
186             use Mouse;
187             extends '$args->{base}';
188             1;
189             };
190 75 50       422 die $@ if $@;
191             }
192              
193 75 100       173 for my $plugin (@{ $self->lazy_roles->{ $args->{name} } || [] }) {
  75         1158  
194 18 50       13594 $plugin->meta->apply( $classname->meta )
195             unless $classname->meta->does_role( $plugin );
196             }
197 75 100       7644 $classname->meta->make_immutable if $self->context_class_detected;
198              
199 75         1833 $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 13     13 0 87 my ($class, @names) = @_;
209              
210 13 50       207 $class->plugins([]) unless $class->plugins;
211              
212 35 100       344 my @plugins =
213 13         720 map { $_ =~ /^\+(.+)/ ? $1 : 'Ark::Plugin::' . $_ } grep {$_} @names;
  35         76  
214              
215 13         33 push @{ $class->plugins }, @plugins;
  13         56  
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 1172 my ($self, %option) = @_;
246              
247 54 50       3354 $self->setup_debug_mode if $self->debug;
248              
249 54         1044 $self->setup_home;
250 54         4059 $self->setup_plugins;
251              
252             # cache
253 54 50       12817 $self->action_cache( $self->path_to($option{action_cache}) )
254             if $option{action_cache};
255              
256 54         441 $self->use_cache(1);
257 54         588 $self->setup_actions;
258              
259 54         451 $self->log( debug => 'Minimal setup finished');
260 54         513 $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 285     285 0 840 my $self = shift;
273 285 100       1787 return if $self->config->{home};
274              
275 65         690 my $class = ref $self;
276 65         313 (my $file = "${class}.pm") =~ s!::!/!g;
277              
278 65 100       341 if (my $path = $INC{$file}) {
279 10         189 $path =~ s/$file$//;
280              
281 10         70 $path = dir($path);
282              
283 10 50       1882 if (-d $path) {
284 10         753 $path = $path->absolute;
285 10         398 while ($path->dir_list(-1) =~ /^b?lib$/) {
286 10         226 $path = $path->parent;
287             }
288              
289 10         1199 $self->config->{home} = $path;
290             }
291             }
292             }
293              
294             sub setup_plugin {
295 134     134 0 327 my ($self, $plugin) = @_;
296              
297 134         538 $self->ensure_class_loaded($plugin);
298              
299 134 100       1418 if (my $target_context = $plugin->plugin_context) {
300 43 100       123 if ($target_context eq 'Core') {
301 1 50       5 $plugin->meta->apply( $self->meta )
302             unless $self->meta->does_role($plugin);
303             }
304             else {
305 42         64 push @{ $self->lazy_roles->{ $target_context } }, $plugin;
  42         215  
306             }
307 43         3009 return;
308             }
309 91 100       926 $plugin->meta->apply( $self->context_class->meta )
310             unless $self->context_class->meta->does_role($plugin);
311             }
312              
313             sub setup_plugins {
314 231     231 0 690 my $self = shift;
315              
316 231         2319 $self->meta->make_mutable;
317              
318 231 100       10231 for my $plugin (@{ $self->plugins || [] }) {
  231         1864  
319 71         19653 $self->setup_plugin($plugin);
320             }
321              
322 231         20043 $self->setup_default_plugins;
323              
324 231         179176 $self->meta->make_immutable;
325             }
326              
327             sub setup_default_plugins {
328 231     231 0 441 my $self = shift;
329              
330 231         443 my $encoding_filter_required = 1;
331 231         445 for my $role (@{ $self->context_class->meta->roles }) {
  231         2460  
332 201 100       7427 $encoding_filter_required = 0 if $role->name =~ /::Encoding::/;
333             }
334              
335 231 100       2028 $self->setup_plugin('Ark::Plugin::Encoding::Unicode') if $encoding_filter_required;
336             }
337              
338             sub setup_actions {
339 231     231 0 506 my $self = shift;
340              
341 231 100       6679 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 231         1338916 $self->router($router);
347              
348 231 50       3444 if ($self->debug) {
349 0         0 $self->log( debug => $router->routing_table->draw );
350             }
351             }
352              
353             sub load_component {
354 328     328 0 639 my ($self, $component) = @_;
355              
356 328 100       2023 if ($self->components->{ $component }) {
357 17         82 return $self->components->{ $component };
358             }
359              
360 311 50       1076 $self->ensure_class_loaded($component) or return;
361 311 100       4416 $component->isa('Ark::Component') or return;
362              
363             # merge config
364 23         129 $component->config( $self->config->{ $component->component_name } );
365              
366 23         158 my $instance = $component->new( app => $self, %{ $component->config } );
  23         77  
367 23 100       1762 if ($instance->can('ARK_DELEGATE')) {
368 3         13 $instance = $instance->ARK_DELEGATE($self);
369             }
370              
371 23         189 $self->components->{ $component } = $instance;
372             }
373              
374             sub component {
375 23     23 0 46 my ($self, $name) = @_;
376 23 50       65 return unless $name;
377              
378 23 50       81 if ($name =~ /^\+/) {
379 0         0 $name =~ s/^\+//;
380             }
381             else {
382 23         74 $name = ref($self) . '::' . $name;
383             }
384              
385 23         79 $self->ensure_class_loaded($name);
386 23   66     920 $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 123 my ($self, $name) = @_;
397              
398 5 100       34 if (my $class = $self->external_model_class) {
399 2 50       40 return @_ >= 2 ? $class->get($name) : $class;
400             }
401              
402 3 50       30 return unless $name;
403 3         21 $self->component('Model::' . $name);
404             }
405              
406             sub view {
407 20     20 0 930 my ($self, $name) = @_;
408 20 100       58 unless (defined $name) {
409 1 50       5 $name = $self->config->{default_view} or return;
410             }
411 20         124 $self->component('View::' . $name);
412             }
413              
414             sub use_model {
415 1     1 0 4 my ($self, $model_class) = @_;
416 1         17 $self->ensure_class_loaded( $model_class );
417 1         14 $self->external_model_class( $model_class );
418 1 50       91 $model_class->initialize if $model_class->can('initialize');
419              
420 1         9 my $conf = $self->model('conf');
421 1 50       4 if ($conf) {
422 1         13 $self->config($conf);
423             }
424             }
425              
426             sub log {
427 363     363 0 22183 my $self = shift;
428              
429 363 100       1283 unless (@_) {
430 2         19 return $self->logger;
431             }
432             else {
433             # keep backward compatibility
434 361         2924 $self->logger->log(@_);
435             }
436             }
437              
438             sub ensure_class_loaded {
439 1194     1194 0 3157 my ($self, $class) = @_;
440 1194 100       9369 Mouse::load_class($class) unless Mouse::is_class_loaded($class);
441             }
442              
443             sub path_to {
444 149     149 0 1009 my ($self, @path) = @_;
445              
446 149 50       450 die qq[Can't call path_to method before setup_home]
447             unless $self->config->{home};
448              
449 149         2419 my $path = dir( $self->config->{home}, @path );
450 149 100       25046 return $path if -d $path;
451 148         9116 return file($path);
452             }
453              
454             sub handle_request {
455 280     280 0 698 my ($self, $env) = @_;
456 280         3603 my $req = Ark::Request->new($env);
457              
458 280         15335 my $context = $self->context_class->new( app => $self, request => $req );
459 280         13130 $self->context($context)->process;
460 280         1321 $self->context(undef);
461              
462 280 100       4795 if ( my $error = $context->error->[0] ) {
463 1         3 chomp $error;
464 1         4 $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         19 my $res = $context->response;
468 1         3 $res->status(500);
469 1         4 $res->body('Internal Server Error');
470             }
471             }
472              
473 280         2365 return $context->response;
474             }
475              
476             __PACKAGE__->meta->make_immutable;