File Coverage

blib/lib/App/TemplateServer.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package App::TemplateServer;
2 2     2   63985 use 5.010;
  2         8  
  2         87  
3 2     2   13 use feature ':5.10';
  2         4  
  2         396  
4              
5 2     2   2144 use Moose;
  0            
  0            
6             use Moose::Util::TypeConstraints;
7             use MooseX::Types::Path::Class qw(File);
8              
9             use HTTP::Daemon;
10             use HTTP::Headers;
11             use HTTP::Request;
12             use HTTP::Response;
13              
14             use App::TemplateServer::Types;
15             use App::TemplateServer::Provider::TT;
16             use App::TemplateServer::Page::Index;
17             use App::TemplateServer::Context;
18              
19             use Package::FromData;
20             use Method::Signatures;
21             use URI::Escape;
22             use YAML::Syck qw(LoadFile);
23              
24             our $VERSION = '0.04';
25             our $AUTHORITY = 'cpan:JROCKWAY';
26              
27             with 'MooseX::Getopt';
28              
29             has 'port' => (
30             is => 'ro',
31             isa => 'Port',
32             default => '4000',
33             );
34              
35             has 'docroot' => (
36             is => 'ro',
37             isa => 'ArrayRef[Str]',
38             default => sub { [$ENV{PWD}] },
39             coerce => 1,
40             lazy => 1,
41             );
42              
43             has 'datafile' => ( # mocked data for the templates to use
44             isa => File,
45             is => 'ro',
46             coerce => 1,
47             required => 0,
48             );
49              
50             has '_raw_data' => (
51             isa => 'HashRef',
52             is => 'ro',
53             default => sub { eval { LoadFile($_[0]->datafile) } || {} },
54             lazy => 1,
55             );
56              
57             has '_data' => (
58             isa => 'HashRef',
59             is => 'ro',
60             default => sub {
61             my $self = shift;
62             my $raw_data = $self->_raw_data;
63             my $package_def = delete $raw_data->{packages};
64             create_package_from_data($package_def) if $package_def;
65              
66             my $to_instantiate = delete $raw_data->{instantiate};
67             foreach my $var (keys %{$to_instantiate||{}}){
68             my $class = $to_instantiate->{$var};
69             given(ref $class){
70             when('HASH'){
71             my ($package, $method) = %$class;
72             $raw_data->{$var} = $package->$method;
73             }
74             default {
75             $raw_data->{$var} = $class->new;
76             }
77             }
78             }
79              
80             return $raw_data;
81             },
82             lazy => 1,
83             );
84              
85             coerce 'ClassName'
86             => as 'Str'
87             => via { # so much code for nothing. oh well :)
88             my $loaded;
89             for ($_, "App::TemplateServer::Provider::$_"){
90             eval {
91             if(Class::MOP::load_class($_)){
92             return $loaded = $_;
93             }
94             } and last;
95             }
96             return $loaded || die "failed to coerce $_ to a provider class";
97             };
98              
99             has 'provider_class' => (
100             metaclass => 'MooseX::Getopt::Meta::Attribute',
101             cmd_arg => 'provider',
102             is => 'ro',
103             isa => 'ClassName',
104             default => 'App::TemplateServer::Provider::TT',
105             coerce => 1,
106             );
107              
108             has 'provider' => (
109             metaclass => 'NoGetopt',
110             is => 'ro',
111             isa => 'Provider',
112             lazy => 1,
113             default => sub {
114             my $self = shift;
115             $self->provider_class->new(docroot => $self->docroot);
116             },
117             );
118              
119             has '_daemon' => (
120             is => 'ro',
121             isa => 'HTTP::Daemon',
122             lazy => 1,
123             default => sub {
124             return HTTP::Daemon->new(ReuseAddr => 1, LocalPort => shift->port);
125             },
126             );
127              
128             method run {
129             print "Server started at: ". $self->_daemon->url. "\n";
130             $self->_main_loop;
131             };
132              
133             method _main_loop {
134             local $SIG{CHLD} = 'IGNORE';
135             app:
136             while(my $c = $self->_daemon->accept){
137             if(!fork){
138             req:
139             while (my $req = $c->get_request){
140             my $res = $self->_req_handler($req);
141             $c->send_response($res);
142             }
143             $c->close;
144             exit; # exit child
145             }
146             }
147             };
148              
149             method _req_handler($req) {
150             my $res = eval {
151             given($req->uri){
152             when(m{^/(?:index(?:[.]html?)?)?$}){
153             return $self->_render_index($req);
154             }
155             when(m{^/favicon.ico$}){
156             return $self->_render_favicon($req);
157             }
158             default {
159             return $self->_render_template($req);
160             }
161             }
162             };
163             if($@ || !$res){
164             my $h = HTTP::Headers->new;
165             $res = HTTP::Response->new(500, 'Internal Server Error', $h, $@);
166             }
167            
168             return $res;
169             };
170              
171             sub _success {
172             my $content = shift;
173             my $headers = HTTP::Headers->new;
174              
175             # set up utf8
176             $headers->header('content-type' => 'text/html; charset=utf8');
177             utf8::upgrade($content); # kill latin1
178             utf8::encode($content);
179              
180             return HTTP::Response->new(200, 'OK', $headers, $content);
181             }
182              
183             method _mk_context($req) {
184             return App::TemplateServer::Context->new(
185             data => $self->_data,
186             request => $req,
187             server => $self->_daemon,
188             );
189             };
190              
191             method _render_template($req) {
192             my $context = $self->_mk_context($req);
193             my $template = uri_unescape($req->uri->path);
194             $template =~ s{^/}{};
195             my $content = $self->provider->render_template($template, $context);
196             return _success($content);
197             };
198              
199             method _render_index($req) {
200              
201             my $index = App::TemplateServer::Page::Index->new(
202             provider => $self->provider,
203             );
204             my $context = $self->_mk_context($req);
205             my $content = $index->render($context);
206             return _success($content);
207             };
208              
209             method _render_favicon($req){
210             return HTTP::Response->new(404, 'Not found');
211             };
212              
213             1;
214             __END__
215              
216             =head1 NAME
217              
218             App::TemplateServer - application to serve processed templates
219              
220             =head1 SYNOPSIS
221              
222             template-server --docroot project/templates --data project/test_data.yml
223              
224             =head1 DESCRIPTION
225              
226             Occasionally you need to give HTML templates to someone to edit
227             without setting up a full perl environment for them. You can use this
228             application to serve templates to the browser and provide those
229             templates with sample data to operate on. The template editor will
230             need Perl, but not a database, Apache, Catalyst, etc. (You can build
231             a PAR and then they won't need Perl either.)
232              
233             It's also useful for experimenting with new templating engines. You
234             can start writing templates right away, without having to setup Apache
235             or a Catalyst application first. Interfacing C<App::TemplateServer>
236             to a new templating system is a quick matter of writing a few lines of
237             code. (See L<App::TemplateServer::Provider> for details.)
238              
239             As a user, you'll be interacting with C<App::TemplateServer> via the
240             included C<template-server> script.
241              
242             =head1 METHODS
243              
244             =head2 run
245              
246             Start the server. This method never returns.
247              
248             =head1 ATTRIBUTES
249              
250             =head2 port
251              
252             The port to bind the server to. Defaults to 4000.
253              
254             =head2 docroot
255              
256             The directory containing templates. Defaults to the current
257             directory.
258              
259             =head2 provider_class
260              
261             The class name of the Provider to use. Defaults to
262             C<App::TemplateServer::Provider::TT>, but you can get others from the
263             CPAN (for using templating systems other than TT).
264              
265             As of version 0.02, you can omit the
266             C<App::TemplateServer::Provider::> prefix if you prefer. The literal
267             class you pass will be loaded first; if that fails then the
268             C<App::TemplateServer::Provider::> prefix is added. Failing that, an
269             exception is thrown.
270              
271             =head2 datafile
272              
273             The YAML file containing the package and variable definitions. For
274             example:
275              
276             ---
277             foo: "bar"
278             packages:
279             Test:
280             constructors: ["new"]
281             methods:
282             map_foo_bar:
283             - ["foo"]
284             - "bar"
285             - ["bar"]
286             - "foo"
287             - "INVALID INPUT"
288             instantiate:
289             test_instance: "Test"
290             another_test_instance:
291             Test: "new"
292              
293             This makes the variables C<foo>, C<test_instance>, and
294             C<another_test_instance> available in the templates. It also creates
295             a package called C<Test> and adds a constructor called C<new>, and a
296             method called C<map_foo_bar> that returns "bar" when the argument is
297             "foo", "foo" when the argument is "bar", and "INVALID INPUT"
298             otherwise.
299              
300             =head3 DESCRIPTION
301              
302             Any key/value pair other than C<packages> and C<instantiate> is
303             treated as a literal variable to make available in the template.
304              
305             C<packages> is passed to L<Package::FromData> and is used to
306             dynamically create data, methods, static methods, and constructors
307             inside packages. See L<Package::FromData> for more details.
308              
309             C<instantiate> is a list of variables to populate with instantiated
310             classes. The key is the variable name, the value is either a class
311             name to call new on, or a hash containing a single key/value pair
312             which is treated like C<< class => method >>. This allows you to use
313             the constructors that L<Package::FromData> made for you.
314              
315             =head1 AUTHOR
316              
317             Jonathan Rockway C<< <jrockway@cpan.org> >>
318              
319             =head1 COPYRIGHT
320              
321             Copyright (c) 2008 Jonathan Rockway. You may redistribute this module
322             under the same terms as Perl itself.
323              
324