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   551 use Mouse;
  78         142  
  78         402  
3              
4 61     61   44240 use Ark::Context;
  61         5127  
  61         2422  
5 61     61   24029 use Ark::Request;
  61         286  
  61         2276  
6 61     61   27821 use Ark::Response;
  61         6501  
  61         2022  
7              
8 61     61   26479 use Exporter::AutoClean;
  61         560426  
  61         2040  
9 61     61   21466 use Path::Class qw/file dir/;
  61         833701  
  61         3855  
10 61     61   28768 use Path::AttrRouter;
  61         585825  
  61         31956  
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   534 no Mouse;
  61         147  
  61         361  
136              
137             sub EXPORT {
138 68     68 0 161 my ($class, $target) = @_;
139              
140 68         238 my $load_plugins = $class->can('load_plugins');
141 68         213 my $use_model = $class->can('use_model');
142 68         212 my $config = $class->can('config');
143 68     13   290 my $config_sub = sub { $config->( $target, @_ ) };
  13         317  
144              
145             Exporter::AutoClean->export(
146             $target,
147 14     14   2308 use_plugins => sub { $load_plugins->( $target, @_ ) },
148 1     1   168 use_model => sub { $use_model->( $target, @_ ) },
149 68         644 config => $config_sub,
150             conf => $config_sub, # backward compatibility
151             );
152             }
153              
154             sub config {
155 1722     1722 0 2852 my $class = shift;
156 1722 100       3865 my $config = @_ > 1 ? {@_} : $_[0];
157              
158 1722 100       5107 $class->configdata({}) unless $class->configdata;
159              
160 1722 100       18689 if ($config) {
161 15 50       33 for my $key (keys %{ $config || {} }) {
  15         109  
162 14         155 $class->configdata->{$key} = $config->{$key};
163             }
164             }
165              
166 1722         3511 $class->configdata;
167             }
168              
169             sub class_wrapper {
170 314     314 0 574 my $self = shift;
171 314 50       1741 my $args = @_ > 1 ? {@_} : $_[0];
172              
173 314   33     1065 my $pkg = ref($self) || $self;
174              
175             $self->log( fatal => q["name" and "base" parameters are required] )
176 314 50 33     1939 unless $args->{name} and $args->{base};
177              
178 314         945 my $classname = "${pkg}::Ark::$args->{name}";
179             return $classname
180 314 100 66     4514 if Mouse::is_class_loaded($classname) && $classname->isa($args->{base});
181              
182             {
183 77         206 local $@;
  77         146  
184 77     60   6063 eval qq{
  60         474  
  60         138  
  60         397  
185             package ${classname};
186             use Mouse;
187             extends '$args->{base}';
188             1;
189             };
190 77 50       422 die $@ if $@;
191             }
192              
193 77 100       186 for my $plugin (@{ $self->lazy_roles->{ $args->{name} } || [] }) {
  77         853  
194 20 50       21227 $plugin->meta->apply( $classname->meta )
195             unless $classname->meta->does_role( $plugin );
196             }
197 77 100       7953 $classname->meta->make_immutable if $self->context_class_detected;
198              
199 77         1866 $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 71 my ($class, @names) = @_;
209              
210 14 50       198 $class->plugins([]) unless $class->plugins;
211              
212             my @plugins =
213 14 100       891 map { $_ =~ /^\+(.+)/ ? $1 : 'Ark::Plugin::' . $_ } grep {$_} @names;
  38         184  
  38         131  
214              
215 14         37 push @{ $class->plugins }, @plugins;
  14         51  
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 801 my ($self, %option) = @_;
246              
247 54 50       4351 $self->setup_debug_mode if $self->debug;
248              
249 54         1136 $self->setup_home;
250 54         4663 $self->setup_plugins;
251              
252             # cache
253             $self->action_cache( $self->path_to($option{action_cache}) )
254 54 50       14838 if $option{action_cache};
255              
256 54         687 $self->use_cache(1);
257 54         531 $self->setup_actions;
258              
259 54         634 $self->log( debug => 'Minimal setup finished');
260 54         413 $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 649 my $self = shift;
273 286 100       1540 return if $self->config->{home};
274              
275 66         683 my $class = ref $self;
276 66         318 (my $file = "${class}.pm") =~ s!::!/!g;
277              
278 66 50       270 if (my $path = $INC{$file}) {
279 66         1335 $path =~ s/$file$//;
280              
281 66         390 $path = dir($path);
282              
283 66 100       8894 if (-d $path) {
284 10         638 $path = $path->absolute;
285 10         409 while ($path->dir_list(-1) =~ /^b?lib$/) {
286 10         307 $path = $path->parent;
287             }
288              
289 10         1220 $self->config->{home} = $path;
290             }
291             }
292             }
293              
294             sub setup_plugin {
295 138     138 0 394 my ($self, $plugin) = @_;
296              
297 138         575 $self->ensure_class_loaded($plugin);
298              
299 138 100       1326 if (my $target_context = $plugin->plugin_context) {
300 45 100       141 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         68 push @{ $self->lazy_roles->{ $target_context } }, $plugin;
  44         185  
306             }
307 45         2643 return;
308             }
309 93 100       802 $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 793 my $self = shift;
315              
316 232         1905 $self->meta->make_mutable;
317              
318 232 100       9870 for my $plugin (@{ $self->plugins || [] }) {
  232         1323  
319 74         22821 $self->setup_plugin($plugin);
320             }
321              
322 232         19098 $self->setup_default_plugins;
323              
324 232         143278 $self->meta->make_immutable;
325             }
326              
327             sub setup_default_plugins {
328 232     232 0 556 my $self = shift;
329              
330 232         429 my $encoding_filter_required = 1;
331 232         439 for my $role (@{ $self->context_class->meta->roles }) {
  232         2085  
332 202 100       5407 $encoding_filter_required = 0 if $role->name =~ /::Encoding::/;
333             }
334              
335 232 100       1949 $self->setup_plugin('Ark::Plugin::Encoding::Unicode') if $encoding_filter_required;
336             }
337              
338             sub setup_actions {
339 232     232 0 579 my $self = shift;
340              
341 232 100       4563 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         1076931 $self->router($router);
347              
348 232 50       2658 if ($self->debug) {
349 0         0 $self->log( debug => $router->routing_table->draw );
350             }
351             }
352              
353             sub load_component {
354 329     329 0 781 my ($self, $component) = @_;
355              
356 329 100       1278 if ($self->components->{ $component }) {
357 17         69 return $self->components->{ $component };
358             }
359              
360 312 50       845 $self->ensure_class_loaded($component) or return;
361 312 100       2833 $component->isa('Ark::Component') or return;
362              
363             # merge config
364 23         89 $component->config( $self->config->{ $component->component_name } );
365              
366 23         188 my $instance = $component->new( app => $self, %{ $component->config } );
  23         59  
367 23 100       1632 if ($instance->can('ARK_DELEGATE')) {
368 3         11 $instance = $instance->ARK_DELEGATE($self);
369             }
370              
371 23         158 $self->components->{ $component } = $instance;
372             }
373              
374             sub component {
375 23     23 0 69 my ($self, $name) = @_;
376 23 50       56 return unless $name;
377              
378 23 50       96 if ($name =~ /^\+/) {
379 0         0 $name =~ s/^\+//;
380             }
381             else {
382 23         72 $name = ref($self) . '::' . $name;
383             }
384              
385 23         75 $self->ensure_class_loaded($name);
386 23   66     685 $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 158 my ($self, $name) = @_;
397              
398 5 100       25 if (my $class = $self->external_model_class) {
399 2 50       43 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 1574 my ($self, $name) = @_;
408 20 100       79 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 3 my ($self, $model_class) = @_;
416 1         11 $self->ensure_class_loaded( $model_class );
417 1         9 $self->external_model_class( $model_class );
418 1 50       67 $model_class->initialize if $model_class->can('initialize');
419              
420 1         5 my $conf = $self->model('conf');
421 1 50       4 if ($conf) {
422 1         8 $self->config($conf);
423             }
424             }
425              
426             sub log {
427 364     364 0 8690 my $self = shift;
428              
429 364 100       1409 unless (@_) {
430 2         21 return $self->logger;
431             }
432             else {
433             # keep backward compatibility
434 362         2418 $self->logger->log(@_);
435             }
436             }
437              
438             sub ensure_class_loaded {
439 1207     1207 0 3727 my ($self, $class) = @_;
440 1207 100       7932 Mouse::load_class($class) unless Mouse::is_class_loaded($class);
441             }
442              
443             sub path_to {
444 149     149 0 1119 my ($self, @path) = @_;
445              
446             die qq[Can't call path_to method before setup_home]
447 149 50       458 unless $self->config->{home};
448              
449 149         2074 my $path = dir( $self->config->{home}, @path );
450 149 100       21106 return $path if -d $path;
451 148         10046 return file($path);
452             }
453              
454             sub handle_request {
455 281     281 0 720 my ($self, $env) = @_;
456 281         2907 my $req = Ark::Request->new($env);
457              
458 281         11019 my $context = $self->context_class->new( app => $self, request => $req );
459 281         12837 $self->context($context)->process;
460 281         1059 $self->context(undef);
461              
462 281 100       3545 if ( my $error = $context->error->[0] ) {
463 1         3 chomp $error;
464 1         6 $self->log( error => 'Caught exception in engine "%s"', $error );
465              
466 1 50 33     13 unless ($self->debug or $context->response->status eq '500') {
467 1         2 my $res = $context->response;
468 1         10 $res->status(500);
469 1         4 $res->body('Internal Server Error');
470             }
471             }
472              
473 281         2046 return $context->response;
474             }
475              
476             __PACKAGE__->meta->make_immutable;