Coverage Summary

# File Coverage

blib/lib/Prancer/Application.pm
Criterion | Covered | Total | %
--- | --- | --- | ---
statement | 33 | 68 | 48.5
branch | 0 | 6 | 0.0
condition | 0 | 6 | 0.0
subroutine | 11 | 21 | 52.4
pod | 0 | 2 | 0.0
total | 44 | 103 | 42.7

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | package Prancer::Application;
2 |  |  |  |  |  |  |
3 | 4 |  |  | 4 |  | 4007 | use strict;
| 4 |  |  |  |  | 4 |
| 4 |  |  |  |  | 116 |
4 | 4 |  |  | 4 |  | 12 | use warnings FATAL => 'all';
| 4 |  |  |  |  | 3 |
| 4 |  |  |  |  | 98 |
5 |  |  |  |  |  |  |
6 | 4 |  |  | 4 |  | 11 | use Exporter;
| 4 |  |  |  |  | 1 |
| 4 |  |  |  |  | 128 |
7 | 4 |  |  | 4 |  | 9 | use parent qw(Exporter);
| 4 |  |  |  |  | 3 |
| 4 |  |  |  |  | 16 |
8 |  |  |  |  |  |  |
9 | 4 |  |  | 4 |  | 165 | use Carp;
| 4 |  |  |  |  | 4 |
| 4 |  |  |  |  | 158 |
10 | 4 |  |  | 4 |  | 10 | use Try::Tiny;
| 4 |  |  |  |  | 4 |
| 4 |  |  |  |  | 150 |
11 | 4 |  |  | 4 |  | 10 | use Module::Load ();
| 4 |  |  |  |  | 6 |
| 4 |  |  |  |  | 56 |
12 | 4 |  |  | 4 |  | 781 | use Router::Boom::Method;
| 4 |  |  |  |  | 13916 |
| 4 |  |  |  |  | 94 |
13 | 4 |  |  | 4 |  | 15 | use Prancer;
| 4 |  |  |  |  | 4 |
| 4 |  |  |  |  | 371 |
14 |  |  |  |  |  |  |
15 |  |  |  |  |  |  | our @EXPORT_OK = qw(context mount dispatch config logger database template);
16 |  |  |  |  |  |  | our %EXPORT_TAGS = ('all' => [ @EXPORT_OK ]);
17 |  |  |  |  |  |  |
18 |  |  |  |  |  |  | sub new {
19 | 0 |  |  | 0 | 0 |  | my ($class, $context) = @_;
20 | 0 |  |  |  |  |  | my $self = bless({ '_context' => $context }, $class);
21 | 0 |  |  |  |  |  | $self->{'_dispatcher'} = Router::Boom::Method->new();
22 |  |  |  |  |  |  |
23 |  |  |  |  |  |  | # because the actual method needs a reference to the object it belongs to,
24 |  |  |  |  |  |  | # it will be wrapped to provide $self as the first argument. we assume that
25 |  |  |  |  |  |  | # the exported methods are actually implemented by a method with the same
26 |  |  |  |  |  |  | # name but prefixed with an underscore. this loop dynamically creates the
27 |  |  |  |  |  |  | # exported method and makes it call the actual method.
28 | 0 |  |  |  |  |  | for my $method (@EXPORT_OK) {
29 | 4 |  |  | 4 |  | 11 | no strict 'refs';
| 4 |  |  |  |  | 4 |
| 4 |  |  |  |  | 84 |
30 | 4 |  |  | 4 |  | 9 | no warnings 'redefine';
| 4 |  |  |  |  | 4 |
| 4 |  |  |  |  | 1586 |
31 | 0 |  |  |  |  |  | my $exported = __PACKAGE__ . "::${method}";
32 | 0 |  |  |  |  |  | *{"${exported}"} = sub {
33 | 0 |  |  | 0 |  |  | my $internal = "_${method}";
34 | 0 |  |  |  |  |  | &$internal($self, @_);
35 | 0 |  |  |  |  |  | };
36 |  |  |  |  |  |  | }
37 |  |  |  |  |  |  |
38 | 0 |  |  |  |  |  | return $self;
39 |  |  |  |  |  |  | }
40 |  |  |  |  |  |  |
41 |  |  |  |  |  |  | sub handle {
42 | 0 |  |  | 0 | 0 |  | my ($self, $env) = @_;
43 | 0 |  |  |  |  |  | croak "method 'handle' must be implemented by subclass to " . __PACKAGE__;
44 |  |  |  |  |  |  | }
45 |  |  |  |  |  |  |
46 |  |  |  |  |  |  | ## no critic (ProhibitUnusedPrivateSubroutines)
47 |  |  |  |  |  |  | sub _config {
48 | 0 |  |  | 0 |  |  | my $self = shift;
49 | 0 |  |  |  |  |  | return Prancer::config(@_);
50 |  |  |  |  |  |  | }
51 |  |  |  |  |  |  |
52 |  |  |  |  |  |  | ## no critic (ProhibitUnusedPrivateSubroutines)
53 |  |  |  |  |  |  | sub _logger {
54 | 0 |  |  | 0 |  |  | my $self = shift;
55 | 0 |  |  |  |  |  | return Prancer::logger(@_);
56 |  |  |  |  |  |  | }
57 |  |  |  |  |  |  |
58 |  |  |  |  |  |  | ## no critic (ProhibitUnusedPrivateSubroutines)
59 |  |  |  |  |  |  | sub _database {
60 | 0 |  |  | 0 |  |  | my $self = shift;
61 | 0 |  |  |  |  |  | return Prancer::database(@_);
62 |  |  |  |  |  |  | }
63 |  |  |  |  |  |  |
64 |  |  |  |  |  |  | ## no critic (ProhibitUnusedPrivateSubroutines)
65 |  |  |  |  |  |  | sub _template {
66 | 0 |  |  | 0 |  |  | my $self = shift;
67 | 0 |  |  |  |  |  | return Prancer::template(@_);
68 |  |  |  |  |  |  | }
69 |  |  |  |  |  |  |
70 |  |  |  |  |  |  | ## no critic (ProhibitUnusedPrivateSubroutines)
71 |  |  |  |  |  |  | sub _context {
72 | 0 |  |  | 0 |  |  | my $self = shift;
73 | 0 |  |  |  |  |  | return $self->{'_context'};
74 |  |  |  |  |  |  | }
75 |  |  |  |  |  |  |
76 |  |  |  |  |  |  | ## no critic (ProhibitUnusedPrivateSubroutines)
77 |  |  |  |  |  |  | sub _mount {
78 | 0 |  |  | 0 |  |  | my ($self, $method, $path, $sub) = @_;
79 |  |  |  |  |  |  |
80 | 0 | 0 | 0 |  |  |  | unless ($sub && ref($sub) && ref($sub) eq "CODE") {
|  |  | 0 |  |  |  |
81 | 0 |  |  |  |  |  | croak "can only dispatch to a sub";
82 |  |  |  |  |  |  | }
83 |  |  |  |  |  |  |
84 | 0 |  |  |  |  |  | $self->{'_dispatcher'}->add($method, $path, $sub);
85 | 0 |  |  |  |  |  | return;
86 |  |  |  |  |  |  | }
87 |  |  |  |  |  |  |
88 |  |  |  |  |  |  | ## no critic (ProhibitUnusedPrivateSubroutines)
89 |  |  |  |  |  |  | sub _dispatch {
90 | 0 |  |  | 0 |  |  | my $self = shift;
91 |  |  |  |  |  |  |
92 | 0 |  |  |  |  |  | my $env = $self->{'_context'}->env();
93 | 0 |  |  |  |  |  | my $path = $env->{PATH_INFO};
94 | 0 |  |  |  |  |  | my $method = $env->{REQUEST_METHOD};
95 | 0 |  |  |  |  |  | my ($matched, $captured, $is_method_not_allowed) = $self->{'_dispatcher'}->match($method, $path);
96 |  |  |  |  |  |  |
97 | 0 | 0 |  |  |  |  | return [405, ['Content-Type', 'text/plain'], ['method not allowed']] if ($is_method_not_allowed);
98 | 0 | 0 |  |  |  |  | return [404, ['Content-Type', 'text/plain'], ['not found']] unless defined($matched);
99 | 0 |  |  |  |  |  | return $matched->($captured);
100 |  |  |  |  |  |  | }
101 |  |  |  |  |  |  |
102 |  |  |  |  |  |  | 1;
103 |  |  |  |  |  |  |
104 |  |  |  |  |  |  | =head1 NAME
105 |  |  |  |  |  |  |
106 |  |  |  |  |  |  | Prancer::Application
107 |  |  |  |  |  |  |
108 |  |  |  |  |  |  | =head1 SYNOPSIS
109 |  |  |  |  |  |  |
110 |  |  |  |  |  |  | This package is where your application should start.
111 |  |  |  |  |  |  |
112 |  |  |  |  |  |  | package MyApp;
113 |  |  |  |  |  |  |
114 |  |  |  |  |  |  | use Prancer::Application qw(:all);
115 |  |  |  |  |  |  | use parent qw(Prancer::Application);
116 |  |  |  |  |  |  |
117 |  |  |  |  |  |  | sub new {
118 |  |  |  |  |  |  | my $class = shift;
119 |  |  |  |  |  |  | my $self = $class->SUPER::new(shift);
120 |  |  |  |  |  |  |
121 |  |  |  |  |  |  | # when prancer is instantiated, the programmer has the option to pass
122 |  |  |  |  |  |  | # extra arguments after the handler class name. those arguments will end
123 |  |  |  |  |  |  | # up in here!
124 |  |  |  |  |  |  | #
125 |  |  |  |  |  |  | # of course, you don't have to write a ->new method if you don't want to as
126 |  |  |  |  |  |  | # one is created automatically in Prancer::Application. but that means that
127 |  |  |  |  |  |  | # if you DO create one you must call to ->SUPER::new first and you MUST
128 |  |  |  |  |  |  | # pass the first argument to ->new (after $class) to ->SUPER::new in order
129 |  |  |  |  |  |  | # for Prancer to work correctly.
130 |  |  |  |  |  |  |
131 |  |  |  |  |  |  | return $self;
132 |  |  |  |  |  |  | }
133 |  |  |  |  |  |  |
134 |  |  |  |  |  |  | sub handle {
135 |  |  |  |  |  |  | my ($self, $env) = @_;
136 |  |  |  |  |  |  |
137 |  |  |  |  |  |  | mount('GET', '/', sub {
138 |  |  |  |  |  |  | context->header(set => 'Content-Type', value => 'text/plain');
139 |  |  |  |  |  |  | context->body("hello world");
140 |  |  |  |  |  |  | context->finalize(200);
141 |  |  |  |  |  |  | });
142 |  |  |  |  |  |  |
143 |  |  |  |  |  |  | return dispatch;
144 |  |  |  |  |  |  | }
145 |  |  |  |  |  |  |
146 |  |  |  |  |  |  | A new instance of this package is created on every request so that request
147 |  |  |  |  |  |  | specific fields may be filled in and available. It also means that your code
148 |  |  |  |  |  |  | should be as lightweight as possible.
149 |  |  |  |  |  |  |
150 |  |  |  |  |  |  | Your class should implement C<handle>. Two arguments are passed to this method:
151 |  |  |  |  |  |  | the instance of your class created for this request and C<$env> for the
152 |  |  |  |  |  |  | request. You probably don't need to use C<$env> because the methods detailed
153 |  |  |  |  |  |  | below should give you everything you need, especially C<context>.
154 |  |  |  |  |  |  |
155 |  |  |  |  |  |  | =head1 EXPORTABLE
156 |  |  |  |  |  |  |
157 |  |  |  |  |  |  | The following methods are exportable: C<context>, C<mount>, C<dispatch>,
158 |  |  |  |  |  |  | C<config>, C<logger>, C<database>, C<template>. They can all be exported at
159 |  |  |  |  |  |  | once with C<:all>.
160 |  |  |  |  |  |  |
161 |  |  |  |  |  |  | =head1 METHODS
162 |  |  |  |  |  |  |
163 |  |  |  |  |  |  | By default this package exports nothing. But that makes it difficult to use.
164 |  |  |  |  |  |  | You should probably export C<:all>. That will give you quick access to these
165 |  |  |  |  |  |  | methods.
166 |  |  |  |  |  |  |
167 |  |  |  |  |  |  | =over 4
168 |  |  |  |  |  |  |
169 |  |  |  |  |  |  | =item config
170 |  |  |  |  |  |  |
171 |  |  |  |  |  |  | Passes through to C<Prancer::config>. This is made available for your
172 |  |  |  |  |  |  | application.
173 |  |  |  |  |  |  |
174 |  |  |  |  |  |  | =item logger
175 |  |  |  |  |  |  |
176 |  |  |  |  |  |  | Passes through to C<Prancer::logger>. This is made available for your
177 |  |  |  |  |  |  | application.
178 |  |  |  |  |  |  |
179 |  |  |  |  |  |  | =item database
180 |  |  |  |  |  |  |
181 |  |  |  |  |  |  | Passes through to C<Prancer::database>. This is made available for your
182 |  |  |  |  |  |  | application.
183 |  |  |  |  |  |  |
184 |  |  |  |  |  |  | =item template
185 |  |  |  |  |  |  |
186 |  |  |  |  |  |  | Passes through to C<Prancer::template>. This is made available for your
187 |  |  |  |  |  |  | application.
188 |  |  |  |  |  |  |
189 |  |  |  |  |  |  | =item context
190 |  |  |  |  |  |  |
191 |  |  |  |  |  |  | This gives access to the request context. See L<Prancer::Context> for more
192 |  |  |  |  |  |  | information about what that makes available. But here is a short example:
193 |  |  |  |  |  |  |
194 |  |  |  |  |  |  | context->header(set => 'Content-Type', value => 'text/plain');
195 |  |  |  |  |  |  | context->body("hello world");
196 |  |  |  |  |  |  | context->finalize(200);
197 |  |  |  |  |  |  |
198 |  |  |  |  |  |  | =item mount METHOD, PATH, SUB
199 |  |  |  |  |  |  |
200 |  |  |  |  |  |  | This adds a routed path. Prancer uses L<Router::Boom::Method> to handle
201 |  |  |  |  |  |  | routing. If it is not installed then calls to this method will croak. The first
202 |  |  |  |  |  |  | argument will always be the method or methods that should match. For example:
203 |  |  |  |  |  |  |
204 |  |  |  |  |  |  | mount('GET', ..., ...);
205 |  |  |  |  |  |  | mount(['GET','POST'], ..., ...);
206 |  |  |  |  |  |  |
207 |  |  |  |  |  |  | The second argument should be the path that will match. For example:
208 |  |  |  |  |  |  |
209 |  |  |  |  |  |  | mount(..., '/', ...);
210 |  |  |  |  |  |  | mount(..., '/:user', ...);
211 |  |  |  |  |  |  | mount(..., '/blog/{year}', ...);
212 |  |  |  |  |  |  | mount(..., '/blog/{year}/{month:\d+}', ...);
213 |  |  |  |  |  |  | mount(..., '/download/*', ...);
214 |  |  |  |  |  |  |
215 |  |  |  |  |  |  | The last argument should be a sub that will run on a successful match. For
216 |  |  |  |  |  |  | example:
217 |  |  |  |  |  |  |
218 |  |  |  |  |  |  | mount(..., ..., sub {
219 |  |  |  |  |  |  | my $captured = shift;
220 |  |  |  |  |  |  |
221 |  |  |  |  |  |  | context->header(set => 'Content-Type', value => 'text/plain');
222 |  |  |  |  |  |  | context->body("hello world");
223 |  |  |  |  |  |  | context->finalize(200);
224 |  |  |  |  |  |  | });
225 |  |  |  |  |  |  |
226 |  |  |  |  |  |  | Of course the sub doesn't have to be anonymous and could point to anything. The
227 |  |  |  |  |  |  | only argument that gets passed to a sub is a hashref containing what, if
228 |  |  |  |  |  |  | anything, was captured in the route. For example:
229 |  |  |  |  |  |  |
230 |  |  |  |  |  |  | mount(..., '/', sub {
231 |  |  |  |  |  |  | my $captured = shift;
232 |  |  |  |  |  |  | # captured = {}
233 |  |  |  |  |  |  | });
234 |  |  |  |  |  |  |
235 |  |  |  |  |  |  | # :user matches qr{([^/]+)}
236 |  |  |  |  |  |  | mount(..., '/:user', sub {
237 |  |  |  |  |  |  | my $captured = shift;
238 |  |  |  |  |  |  | print $captured->{'user'};
239 |  |  |  |  |  |  | });
240 |  |  |  |  |  |  |
241 |  |  |  |  |  |  | # {year} matches qr{([^/]+)}
242 |  |  |  |  |  |  | mount(..., '/blog/{year}/{month:\d+}', sub {
243 |  |  |  |  |  |  | my $captured = shift;
244 |  |  |  |  |  |  | print $captured->{'year'};
245 |  |  |  |  |  |  | print $captured->{'month'};
246 |  |  |  |  |  |  | });
247 |  |  |  |  |  |  |
248 |  |  |  |  |  |  | # * matches qr{(.+)}
249 |  |  |  |  |  |  | mount(..., '/download/*', sub {
250 |  |  |  |  |  |  | my $captured = shift;
251 |  |  |  |  |  |  | print $captured->{'*'};
252 |  |  |  |  |  |  | });
253 |  |  |  |  |  |  |
254 |  |  |  |  |  |  | Further documentation on how to use routes can be found by reading the docs
255 |  |  |  |  |  |  | for L<Router::Boom> and L<Router::Boom::Method>.
256 |  |  |  |  |  |  |
257 |  |  |  |  |  |  | =item dispatch
258 |  |  |  |  |  |  |
259 |  |  |  |  |  |  | This should be called at the end of your implementation to C<handle>. It will
260 |  |  |  |  |  |  | run the configured routes and return a valid PSGI response to the application
261 |  |  |  |  |  |  | server. If you do not have L<Router::Boom> installed then calling this method
262 |  |  |  |  |  |  | will croak. If you are not using L<Router::Boom> then you should not use this
263 |  |  |  |  |  |  | method but should instead have your implementation of C<handle> return a valid
264 |  |  |  |  |  |  | PSGI response.
265 |  |  |  |  |  |  |
266 |  |  |  |  |  |  | =back
267 |  |  |  |  |  |  |
268 |  |  |  |  |  |  | =cut
269 |  |  |  |  |  |  |