File Coverage

blib/lib/Minima/App.pm
Criterion Covered Total %
statement 28 28 100.0
branch 4 4 100.0
condition n/a
subroutine 9 9 100.0
pod n/a
total 41 41 100.0


line stmt bran cond sub pod time code
1 10     10   1184959 use v5.40;
  10         36  
2 10     10   5213 use experimental 'class';
  10         36926  
  10         72  
3              
4             class Minima::App;
5              
6 10     10   2598 use Carp;
  10         22  
  10         784  
7 10     10   5873 use Minima::Router;
  10         30  
  10         464  
8 10     10   78 use Path::Tiny ();
  10         24  
  10         209  
9 10     10   5971 use Plack::Util;
  10         127693  
  10         494  
10 10     10   5495 use FindBin;
  10         15108  
  10         728  
11              
12 10     10   95 use constant DEFAULT_VERSION => 'prototype';
  10         29  
  10         20592  
13              
14             field $env :param(environment) :reader = undef;
15             field $config :param(configuration) :reader = {};
16              
17             field $router = Minima::Router->new;
18              
19             ADJUST {
20             $self->_read_config;
21             }
22              
23             method set_env ($e) { $env = $e }
24             method set_config ($c) { $config = $c; $self->_read_config }
25              
26             method development
27             {
28             return 1 if not defined $ENV{PLACK_ENV};
29              
30             $ENV{PLACK_ENV} eq 'development'
31             }
32              
33             method path ($p)
34             {
35             Path::Tiny::path($p)->absolute($config->{base_dir})->stringify;
36             }
37              
38             method run
39             {
40             croak "Can't run without an environment.\n" unless defined $env;
41              
42             my $m = $router->match($env);
43              
44             return $self->_not_found unless $m;
45              
46             my $class = $m->{controller};
47             my $method = $m->{action};
48              
49             $self->_load_class($class);
50              
51             my $controller = $class->new(
52             app => $self,
53             route => $m,
54             );
55              
56             my $response;
57              
58             try {
59             # before_action
60             if ($controller->can('before_action')) {
61             my $res = $controller->before_action($method);
62             return $res if $res; # halt
63             }
64             # actual action
65             $response = $controller->$method;
66             # after action
67             if ($controller->can('after_action')) {
68             $controller->after_action($response);
69             }
70             } catch ($e) {
71             my $err = $router->error_route;
72             # Something failed. If we're in production
73             # and there is a server_error route, try it.
74             if (!$self->development && $err) {
75             $class = $err->{controller};
76             $method = $err->{action};
77             $self->_load_class($class);
78             $controller = $class->new(
79             app => $self,
80             route => $err,
81             );
82             $response = $controller->$method($e);
83             } else {
84             # Nothing can be done, re-throw
85             die $e;
86             }
87             }
88              
89             # Delete body on HEAD requests
90             my $auto_head = $config->{automatic_head} // 1;
91             if ( $auto_head
92             && length $env->{REQUEST_METHOD}
93             && $env->{REQUEST_METHOD} eq 'HEAD'
94             ) {
95             return Plack::Util::response_cb($response, sub {
96 2     2   29 my $res = shift;
97 2 100       24 if ($res->[2]) {
98 1         5 $res->[2] = [];
99             } else {
100 1 100       7 return sub { defined $_[0] ? '' : undef };
  2         44  
101             }
102             });
103             }
104              
105             return $response;
106             }
107              
108             method _not_found
109             {
110             [
111             404,
112             [ 'Content-Type' => 'text/plain' ],
113             [ "not found\n" ]
114             ]
115             }
116              
117             method _load_class ($class)
118             {
119             try {
120             my $file = $class;
121             $file =~ s|::|/|g;
122             require "$file.pm";
123             } catch ($e) {
124             croak "Could not load `$class`: $e\n";
125             }
126             }
127              
128             method _read_config
129             {
130             # Ensure base_dir is set and absolute
131             my $base = $config->{base_dir} // '.';
132             $config->{base_dir} = Path::Tiny::path($base)->absolute;
133              
134             $self->_load_routes;
135             $self->_set_version;
136             }
137              
138             method _load_routes
139             {
140             $router->clear_routes;
141              
142             my $file = $config->{routes};
143             unless (defined $file) {
144             # No file passed. Attempt the default route.
145             $file = $self->path('etc/routes.map');
146             # If it does not exist, setup a basic route
147             # for the default controller only.
148             unless (-e $file) {
149             $router->_connect(
150             '/',
151             {
152             controller => 'Minima::Controller',
153             action => 'hello',
154             },
155             );
156             return;
157             }
158             }
159              
160             # Controller prefix
161             my $prefix = $config->{controller_prefix};
162             $router->set_prefix($prefix) if defined $prefix;
163              
164             # Read routes
165             $file = $self->path($file);
166             $router->read_file($file);
167             }
168              
169             method _set_version
170             {
171             return if defined $config->{VERSION};
172              
173             if (defined $config->{version_from}) {
174             my $class = $config->{version_from};
175             try {
176             $self->_load_class($class);
177             } catch ($e) {
178             croak "Failed to load version from class.\n$e\n";
179             }
180             $config->{VERSION} = $class->VERSION // DEFAULT_VERSION;
181             } else {
182             $config->{VERSION} = DEFAULT_VERSION;
183             }
184             }
185              
186             __END__