File Coverage

blib/lib/Prancer.pm
Criterion Covered Total %
statement 51 152 33.6
branch 0 32 0.0
condition 0 18 0.0
subroutine 17 33 51.5
pod 6 7 85.7
total 74 242 30.6


line stmt bran cond sub pod time code
1             package Prancer;
2              
3 2     2   30521 use strict;
  2         4  
  2         64  
4 2     2   4 use warnings FATAL => 'all';
  2         1  
  2         52  
5              
6 2     2   383 use version;
  2         1480  
  2         6  
7             our $VERSION = "0.02";
8              
9 2     2   97 use Exporter;
  2         2  
  2         81  
10 2     2   335 use parent qw(Exporter);
  2         393  
  2         5  
11              
12             our @EXPORT_OK = qw(config logger template database);
13             our %EXPORT_TAGS = ('all' => [ @EXPORT_OK ]);
14              
15 2     2   121 use Carp;
  2         2  
  2         103  
16 2     2   382 use Module::Load ();
  2         1458  
  2         35  
17 2     2   643 use Storable qw(dclone);
  2         4116  
  2         109  
18 2     2   459 use Try::Tiny;
  2         3519  
  2         84  
19              
20 2     2   429 use Prancer::Config;
  2         3  
  2         66  
21 2     2   449 use Prancer::Logger;
  2         2  
  2         40  
22 2     2   415 use Prancer::Request;
  2         3  
  2         53  
23 2     2   432 use Prancer::Response;
  2         3  
  2         49  
24 2     2   413 use Prancer::Session;
  2         2  
  2         40  
25 2     2   388 use Prancer::Context;
  2         2  
  2         93  
26              
27             sub new {
28 0     0 1       my ($class, $config_path, $handler, @args) = @_;
29              
30             # already got an object
31 0 0             return $class if ref($class);
32              
33             # this is a singleton
34 0               my $instance = undef;
35                 {
36 2     2   4         no strict 'refs';
  2         2  
  2         211  
  0            
37 0                   $instance = \${"$class\::_instance"};
  0            
38 0 0                 return $$instance if defined($$instance);
39                 }
40              
41 0               my $self = bless({
42                     '_handler' => $handler, # the name of the class that will implement the handler
43                     '_handler_args' => \@args, # any arguments that should be passed to the handler on creation
44                 }, $class);
45              
46             # load configuration
47 0               $self->{'_config'} = Prancer::Config->load($config_path);
48              
49             # load the configured logger
50 0               $self->{'_logger'} = Prancer::Logger->load($self->{'_config'}->remove('logger'));
51              
52 0               $$instance = $self;
53 0               return $self;
54             }
55              
56             # return an already created instance of ourselves or croak if one doesn't exist
57             sub instance {
58 0     0 0       my $class = __PACKAGE__;
59              
60                 {
61 2     2   4         no strict 'refs';
  2         2  
  2         2054  
  0            
62 0                   my $instance = \${"$class\::_instance"};
  0            
63 0 0                 return $$instance if defined($$instance);
64                 }
65              
66 0               croak "must create an instance of " . __PACKAGE__ . " before it may be used";
67             }
68              
69             sub logger {
70 0     0 1       my $self = instance();
71 0               return $self->{'_logger'};
72             }
73              
74             sub config {
75 0     0 1       my $self = instance();
76 0               return $self->{'_config'};
77             }
78              
79             sub template {
80 0     0 1       my $self = instance();
81              
82             # if the template object hasn't been initialized do it now
83             # this will make this work well with CLI apps
84 0               require Prancer::Template;
85 0 0             $self->{'_template'} = Prancer::Template->load(config->remove('template')) unless defined($self->{'_template'});
86              
87 0               return $self->{'_template'}->render(@_);
88             }
89              
90             sub database {
91 0     0 1       my $self = instance();
92 0   0           my $connection = shift || "default";
93              
94             # if the database object hasn't been initialized do it now
95             # this will make this work well with CLI apps
96 0               require Prancer::Database;
97 0 0             $self->{'_database'} = Prancer::Database->load(config->remove('database')) unless defined($self->{'_database'});
98              
99 0 0             if (!defined($connection)) {
100 0                   logger->fatal("could not get connection to database: no connection name given");
101 0                   croak;
102                 }
103 0 0             if (!exists($self->{'_database'}->{$connection})) {
104 0                   logger->fatal("could not get connection to database: no connection named '${connection}'");
105 0                   croak;
106                 }
107              
108 0               return $self->{'_database'}->{$connection}->handle();
109             }
110              
111             sub run {
112 0     0 1       my $self = shift;
113              
114                 try {
115 0     0             Module::Load::load($self->{'_handler'});
116                 } catch {
117 0 0   0             logger->fatal("could not initialize handler: " . (defined($_) ? $_ : "unknown"));
118 0                   croak;
119 0               };
120              
121             # pre-load the template engine
122 0               require Prancer::Template;
123 0               $self->{'_template'} = Prancer::Template->load(config->remove('template'));
124              
125             # pre-load the database engine
126 0               require Prancer::Database;
127 0               $self->{'_database'} = Prancer::Database->load(config->remove('database'));
128              
129                 my $app = sub {
130 0     0             my $env = shift;
131              
132             # create a context to pass to the request
133 0                   my $context = Prancer::Context->new(
134                         'env' => $env,
135                         'request' => Prancer::Request->new($env),
136                         'response' => Prancer::Response->new($env),
137                         'session' => Prancer::Session->new($env),
138                     );
139              
140 0                   my $handler = $self->{'_handler'};
141 0                   my $copy = $handler->new($context, @{$self->{'_handler_args'}});
  0            
142 0                   return $copy->handle($env);
143 0               };
144              
145             # capture warnings and logging messages and send them to the configured logger
146 0               require Prancer::Middleware::Logger;
147 0               $app = Prancer::Middleware::Logger->wrap($app);
148              
149             # enable user sessions
150 0               $app = $self->_enable_sessions($app);
151              
152             # serve up static files if configured to do so
153 0               $app = $self->_enable_static($app);
154              
155 0               return $app;
156             }
157              
158             sub _enable_sessions {
159 0     0         my ($self, $app) = @_;
160              
161 0               my $config = config->remove('session');
162 0 0             if ($config) {
163                     try {
164             # load the session state module first
165             # this will probably be a cookie
166 0     0                 my $state_module = undef;
167 0                       my $state_options = undef;
168 0 0 0                   if (ref($config->{'state'}) && ref($config->{'state'}) eq "HASH") {
169 0                           $state_module = $config->{'state'}->{'driver'};
170 0                           $state_options = $config->{'state'}->{'options'};
171                         }
172              
173             # set defaults and then load the state module
174 0   0                   $state_options ||= {};
175 0   0                   $state_module ||= "Prancer::Session::State::Cookie";
176 0                       Module::Load::load($state_module);
177              
178             # set the default for the session name because the plack
179             # default is stupid
180 0   0                   $state_options->{'session_key'} ||= "PSESSION";
181              
182             # load the store module second
183 0                       my $store_module = undef;
184 0                       my $store_options = undef;
185 0 0 0                   if (ref($config->{'store'}) && ref($config->{'store'}) eq "HASH") {
186 0                           $store_module = $config->{'store'}->{'driver'};
187 0                           $store_options = $config->{'store'}->{'options'};
188                         }
189              
190             # set defaults and then load the store module
191 0   0                   $store_options ||= {};
192 0   0                   $store_module ||= "Prancer::Session::Store::Memory";
193 0                       Module::Load::load($store_module);
194              
195 0                       require Plack::Middleware::Session;
196 0                       $app = Plack::Middleware::Session->wrap($app,
197                             'state' => $state_module->new($state_options),
198                             'store' => $store_module->new($store_options),
199                         );
200 0                       logger->info("initialized session handler with state module ${state_module} and store module ${store_module}");
201                     } catch {
202 0 0   0                 my $error = (defined($_) ? $_ : "unknonw");
203 0                       logger->warn("could not initialize session handler: initialization error: ${error}");
204 0                   };
205                 } else {
206 0                   logger->warn("could not initialize session handler: no session handler configured");
207                 }
208              
209 0               return $app;
210             }
211              
212             sub _enable_static {
213 0     0         my ($self, $app) = @_;
214              
215 0               my $config = config->remove('static');
216 0 0             if ($config) {
217                     try {
218             # this intercepts requests for /static/* and checks to see if
219             # the requested file exists in the configured path. if it does
220             # it is served up. if it doesn't then the request will pass
221             # through to the handler.
222 0 0   0                 die "no path is configured\n" unless defined($config->{'path'});
223 0                       my $path = Cwd::realpath($config->{'path'});
224 0 0                     die "${path} does not exist\n" unless (-e $path);
225 0 0                     die "${path} is not readable\n" unless (-r $path);
226              
227 0                       require Plack::Middleware::Static;
228                         $app = Plack::Middleware::Static->wrap($app,
229 0                           'path' => sub { s!^/static/!!x },
230 0                           'root' => $path,
231                             'pass_through' => 1,
232                         );
233 0                       logger->info("serving static files from ${path} at /static");
234                     } catch {
235 0     0                 logger->warn("could not initialize static file loader: initialization error: $_");
236 0                   };
237                 } else {
238 0                   logger->warn("could not initialize static file loader: not configured");
239                 }
240              
241 0               return $app;
242             }
243              
244             1;
245              
246             =head1 NAME
247            
248             Prancer - Another PSGI Framework
249            
250             =head1 SYNOPSIS
251            
252             Prancer is yet another PSGI framework. This one is designed to be a bit smaller
253             and more out of the way than others but it could probably be described best as
254             project derived from L<NIH syndrome|https://en.wikipedia.org/wiki/Not_Invented_Here>.
255            
256             Here's how it might be used:
257            
258             ==> myapp.psgi
259            
260             use Prancer;
261             my $app = Prancer->new("/path/to/confdir", "MyApp");
262             $app->run();
263            
264             ==> MyApp.pm
265            
266             package MyApp;
267            
268             use Prancer::Application qw(:all);
269             use parent qw(Prancer::Application);
270            
271             sub handle {
272             my $self = shift;
273            
274             mount('GET', '/', sub {
275             context->header(set => 'Content-Type', value => 'text/plain');
276             context->body("hello world");
277             context->finalize(200);
278             });
279            
280             return dispatch;
281             }
282            
283             Full documentation can be found in L<Prancer::Manual>.
284            
285             =head1 INSTALLATION
286            
287             To install this module, run the following commands:
288            
289             perl Makefile.PL
290             make
291             make test
292             make install
293            
294             If this ever makes it to CPAN you can install it with this simple command:
295            
296             perl -MCPAN -e 'install Prancer'
297            
298             These optional libraries will enhance the functionality of Prancer:
299            
300             =over 4
301            
302             =item L<Template>
303            
304             Without this the Prancer template interface will not work.
305            
306             =item L<DBI>
307            
308             Without this the Prancer database interface will not work. You also will need
309             a database driver like L<DBD::Pg>.
310            
311             =item L<Plack::Middleware::Session>
312            
313             Without this the Prancer session support will not work. If you want to use the
314             YAML session storage you will also need to have L<YAML> (preferably
315             L<YAML::XS>) installed. If you want support to write sessions do the database
316             you will also need L<DBI> installed along with a database driver like
317             L<DBD::Pg>.
318            
319             =back
320            
321             =head1 EXPORTABLE
322            
323             The following methods are exportable: C<config>, C<logger>, C<database>,
324             C<template>. They can all be exported at once using C<:all>.
325            
326             =head1 METHODS
327            
328             With the exception of C<-E<gt>new> and C<-E<gt>run>, all methods should be
329             called in a static context. Additionally, with the same exception, all methods
330             are exportable individually or with C<qw(:all)>.
331            
332             =over 4
333            
334             =item ->new CONFIG PACKAGE ARGS
335            
336             This will create your application. It takes two arguments:
337            
338             =over 4
339            
340             =item CONFIG
341            
342             This a path to a directory containing configuration files. How configuration
343             files are loaded is detailed below.
344            
345             =item PACKAGE
346            
347             This is the name of a package that implements your application. The package
348             named should extend L<Prancer::Application> though this is not enforced.
349            
350             =item ARGS
351            
352             After the name of the package, any number of arguments may be added. Any extra
353             arguments are passed directly to the C<new> method on the named package when it
354             is created for a request.
355            
356             =back
357            
358             =item ->run
359            
360             This starts your application. It takes no arguments.
361            
362             =item logger
363            
364             This gives access to the logger. For example:
365            
366             logger->info("Hello");
367             logger->fatal("uh oh");
368             logger->debug("here we are");
369            
370             =item config
371            
372             This gives access to the configuration. For example:
373            
374             config->has('foo');
375             config->get('foo');
376             config->set('foo', value => 'bar');
377             config->remove('foo');
378            
379             Any changes to the configuration do not persist back to the actual
380             configuration file. Additionally they do not persist between threads or
381             processes.
382            
383             Whenever this method is used to get a configuration option and that option
384             is reference, the reference will be cloned by Storable to prevent changes to
385             one copy from affecting other uses. But this could have performance
386             implications if you are routinely getting large data structures out if your
387             configuration files.
388            
389             =item template
390            
391             This gives access to the configured template engine. For example:
392            
393             print template("foo.tt", {
394             'title' => 'foobar',
395             'var1' => 'val2',
396             });
397            
398             If no template engines are configured then this method will always return
399             C<undef>.
400            
401             =item database
402            
403             This gives access to the configured databases. For example:
404            
405             # handle to the database configured as 'default'
406             my $dbh = database;
407            
408             # handle to the database configured as 'foo'
409             my $dbh = database('foo');
410            
411             # prepare a statement on connection 'default'
412             my $sth = database->prepare("SELECT * FROM foo");
413            
414             In all cases, C<$dbh> will be a reference to a L<DBI> handle and anything that
415             can be done with L<DBI> can be done here.
416            
417             If no databases are configured then this method will always return C<undef>.
418            
419             =item session
420            
421             Configures the session handler. For example:
422            
423             session:
424             state:
425             driver: Prancer::Session::State::Cookie
426             options:
427             key: PSESSION
428             store:
429             driver: Prancer::Session::Store::YAML
430             options:
431             path: /tmp/prancer/sessions
432            
433             See L<Prancer::Session::State::Cookie>, L<Prancer::Session::Store::Memory>,
434             L<Prancer::Session::Store::YAML> and L<Prancer::Session::Store::Database> for
435             more options.
436            
437             =back
438            
439             =head1 CONFIGURATION
440            
441             One doesn't need to create any configuration to use Prancer but then Prancer
442             wouldn't be very useful. Prancer uses L<Config::Any> to process configuration
443             files so anything supported by that will be supported by this. It will load
444             configuration files from given path set when your application initialized.
445             First it will look for a file named C<config.ext> where C<ext> is something
446             like C<yml> or C<ini>. Then it will look for a file named after the current
447             environment like C<develoment.ext> or C<production.ext>. The environment is
448             derived by looking first for an environment variable called C<ENVIRONMENT>,
449             then for an environment variable called C<PLACK_ENV>. If neither of those exist
450             then the default is C<development>. Configuration files will be merged such
451             that the environment configuration file will take precedence over the global
452             configuration file.
453            
454             Arbitrary configuration directives can be put into your configuration files
455             and they can be accessed like this:
456            
457             config(get => 'foo');
458            
459             The configuration accessors will only give you configuration directives found
460             at the root of the configuration file. So if you use any data structures you
461             will have to decode them yourself. For example, if you create a YAML file like
462             this:
463            
464             foo:
465             bar1: asdf
466             bar2: fdsa
467            
468             Then you will only be able to get the value to C<bar1> like this:
469            
470             my $foo = config(get => 'foo')->{'bar1'};
471            
472             =head2 Reserved Configuration Options
473            
474             To support the components of Prancer, these keys are used:
475            
476             =over 4
477            
478             =item logger
479            
480             Configures the logging system. For example:
481            
482             logger:
483             driver: Prancer::Logger::WhateverLogger
484             options:
485             level: debug
486            
487             For the console logger, see L<Prancer::Logger::Console> for more options.
488            
489             =item template
490            
491             Configures the templating system. For example:
492            
493             template:
494             driver: Prancer::Template::WhateverEngine
495             options:
496             template_dir: /srv/www/site/templates
497             encoding: utf8
498             start_tag: "<%"
499             end_tag: "%>"
500            
501             For the Template Toolkit plugin, see L<Prancer::Template::TemplateToolkit> for
502             more options.
503            
504             =item database
505            
506             Configures database connections. For example:
507            
508             database:
509             default:
510             driver: Prancer::Database::Driver::WhateverDriver
511             options:
512             username: test
513             password: test
514             database: test
515            
516             See L<Prancer::Database> for more options.
517            
518             =item static
519            
520             Configures a directory where static documents can be found and served using
521             L<Plack::Middleware::Static>. For example:
522            
523             static:
524             path: /srv/www/site/static
525            
526             The only configuration option for static documents is C<path>. If this path
527             is not defined your application will not start. If this path does not point
528             to a directory that is readable your application will not start.
529            
530             =back
531            
532             =head1 CREDITS
533            
534             Large portions of this library were taken from the following locations and
535             projects:
536            
537             =over 4
538            
539             =item
540            
541             HTTP status code documentation taken from L<Wikipedia|http://www.wikipedia.org>.
542            
543             =item
544            
545             L<Prancer::Config> is derived directly from L<Dancer2::Core::Role::Config>.
546             Thank you to the L<Dancer2|https://github.com/PerlDancer/Dancer2> team.
547            
548             =item
549            
550             L<Prancer::Request>, L<Prancer::Request::Upload> and L<Prancer::Response> are
551             but thin wrappers to and reimplementations of L<Plack::Request>,
552             L<Plack::Request::Upload> and L<Prancer::Response>. Thank you to Tatsuhiko
553             Miyagawa.
554            
555             =item
556            
557             L<Prancer::Database> is derived directly from L<Dancer::Plugin::Database>.
558             Thank you to David Precious.
559            
560             =back
561            
562             =head1 COPYRIGHT
563            
564             Copyright 2013, 2014 Paul Lockaby. All rights reserved.
565            
566             This library is free software; you can redistribute it and/or modify it under
567             the same terms as Perl itself.
568            
569             =cut
570