File Coverage

blib/lib/Statocles/Theme.pm
Criterion Covered Total %
statement 44 48 91.6
branch 7 10 70.0
condition 7 12 58.3
subroutine 10 10 100.0
pod 6 6 100.0
total 74 86 86.0


line stmt bran cond sub pod time code
1             package Statocles::Theme;
2             our $VERSION = '0.086';
3             # ABSTRACT: Templates, headers, footers, and navigation
4              
5 59     59   12891 use Statocles::Base 'Class';
  59         242  
  59         729  
6 59     59   437242 use File::Share qw( dist_dir );
  59         31034  
  59         3329  
7 59     59   415 use Scalar::Util qw( blessed );
  59         134  
  59         2653  
8 59     59   1253 use Statocles::Template;
  59         134  
  59         62240  
9             with 'Statocles::App::Role::Store';
10              
11             #pod =attr url_root
12             #pod
13             #pod The root URL for this application. Defaults to C</theme>.
14             #pod
15             #pod =cut
16              
17             has '+url_root' => ( default => sub { '/theme' } );
18              
19             #pod =attr store
20             #pod
21             #pod The source L<store|Statocles::Store> for this theme.
22             #pod
23             #pod If the path begins with ::, will pull one of the Statocles default
24             #pod themes from the Statocles share directory.
25             #pod
26             #pod =cut
27              
28             #pod =attr include_stores
29             #pod
30             #pod An array of L<stores|Statocles::Store> to look for includes. The L</store> is
31             #pod added at the end of this list.
32             #pod
33             #pod =cut
34              
35             has include_stores => (
36             is => 'ro',
37             isa => ArrayRef[Store],
38             default => sub { [] },
39             coerce => sub {
40             my ( $thing ) = @_;
41             if ( ref $thing eq 'ARRAY' ) {
42             return [ map { Store->coercion->( $_ ) } @$thing ];
43             }
44             return [ Store->coercion->( $thing ) ];
45             },
46             );
47              
48             #pod =attr _templates
49             #pod
50             #pod The cached template objects for this theme.
51             #pod
52             #pod =cut
53              
54             has '+_templates' => (
55             is => 'ro',
56             isa => HashRef[InstanceOf['Statocles::Template']],
57             default => sub { {} },
58             lazy => 1, # Must be lazy or the clearer won't re-init the default
59             clearer => '_clear_templates',
60             );
61              
62             #pod =attr _includes
63             #pod
64             #pod The cached template objects for the includes.
65             #pod
66             #pod =cut
67              
68             has _includes => (
69             is => 'ro',
70             isa => HashRef[InstanceOf['Statocles::Template']],
71             default => sub { {} },
72             lazy => 1, # Must be lazy or the clearer won't re-init the default
73             clearer => '_clear_includes',
74             );
75              
76             # The helpers added to this theme.
77             has _helpers => (
78             is => 'ro',
79             isa => HashRef[CodeRef],
80             default => sub { {} },
81             init_arg => 'helpers', # Allow initialization via config file
82             );
83              
84             #pod =method BUILDARGS
85             #pod
86             #pod Handle the path :: share theme.
87             #pod
88             #pod =cut
89              
90             around BUILDARGS => sub {
91             my ( $orig, $self, @args ) = @_;
92             my $args = $self->$orig( @args );
93             if ( $args->{store} && !ref $args->{store} && $args->{store} =~ /^::/ ) {
94             my $name = substr $args->{store}, 2;
95             $args->{store} = Path::Tiny->new( dist_dir( 'Statocles' ) )->child( 'theme', $name );
96             }
97             return $args;
98             };
99              
100             #pod =method read
101             #pod
102             #pod my $tmpl = $theme->read( $path )
103             #pod
104             #pod Read the template for the given C<path> and create the
105             #pod L<template|Statocles::Template> object.
106             #pod
107             #pod =cut
108              
109             sub read {
110 307     307 1 6321 my ( $self, $path ) = @_;
111 307         910 $path .= '.ep';
112              
113 307         1391 my $content = eval { $self->store->read_file( $path ); };
  307         1576  
114 307 100       72587 if ( $@ ) {
115 1 50 33     23 if ( blessed $@ && $@->isa( 'Path::Tiny::Error' ) && $@->{op} =~ /^open/ ) {
      33        
116 1         9 die sprintf 'ERROR: Template "%s" does not exist in theme directory "%s"' . "\n",
117             $path, $self->store->path;
118             }
119             else {
120 0         0 die $@;
121             }
122             }
123              
124 306         1157 return $self->build_template( $path, $content );
125             }
126              
127             #pod =method build_template
128             #pod
129             #pod my $tmpl = $theme->build_template( $path, $content )
130             #pod
131             #pod Build a new L<Statocles::Template> object with the given C<path> and C<content>.
132             #pod
133             #pod =cut
134              
135             sub build_template {
136 885     885 1 60622 my ( $self, $path, $content ) = @_;
137              
138 885         19353 return Statocles::Template->new(
139             path => $path,
140             content => $content,
141             theme => $self,
142             );
143             }
144              
145             #pod =method template
146             #pod
147             #pod my $tmpl = $theme->template( $path )
148             #pod my $tmpl = $theme->template( @path_parts )
149             #pod
150             #pod Get the L<template|Statocles::Template> at the given C<path>, or with the
151             #pod given C<path_parts>.
152             #pod
153             #pod =cut
154              
155             sub template {
156 1845     1845 1 702630 my ( $self, @path ) = @_;
157 1845         5645 my $path = Path::Tiny->new( @path );
158 1845   100     78634 return $self->_templates->{ $path } ||= $self->read( $path );
159             }
160              
161             #pod =method include
162             #pod
163             #pod my $tmpl = $theme->include( $path );
164             #pod my $tmpl = $theme->include( @path_parts );
165             #pod
166             #pod Get the desired L<template|Statocles::Template> to include based on the given
167             #pod C<path> or C<path_parts>. Looks through all the
168             #pod L<include_stores|/include_stores> before looking in the L<main store|/store>.
169             #pod
170             #pod =cut
171              
172             sub include {
173 281     281 1 606 my ( $self, @path ) = @_;
174 281         386 my $render = 1;
175 281 50       591 if ( $path[0] eq '-raw' ) {
176             # Allow raw files to not be passed through the template renderer
177             # This override flag will always exist, but in the future we may
178             # add better detection to possible file types to process
179 0         0 $render = 0;
180 0         0 shift @path;
181             }
182 281         532 my $path = Path::Tiny->new( @path );
183              
184 281         6066 my @stores = ( @{ $self->include_stores }, $self->store );
  281         961  
185 281         477 for my $store ( @stores ) {
186 282 100       966 if ( $store->has_file( $path ) ) {
187 279 50       20060 if ( $render ) {
188 279   66     5454 return $self->_includes->{ $path } ||= $self->build_template( $path, $store->read_file( $path ) );
189             }
190 0         0 return $store->read_file( $path );
191             }
192             }
193              
194             die qq{Can not find include "$path" in include directories: }
195 2         106 . join( ", ", map { sprintf q{"%s"}, $_->path } @stores )
  3         28  
196             . "\n";
197             }
198              
199             #pod =method helper
200             #pod
201             #pod $theme->helper( $name, $sub );
202             #pod
203             #pod Register a helper on this theme. Helpers are functions that are added to
204             #pod the template to allow for additional features. Helpers are usually added
205             #pod by L<Statocles plugins|Statocles::Plugin>.
206             #pod
207             #pod There are a L<default set of helpers available to all
208             #pod templates|Statocles::Template/DEFAULT HELPERS> which cannot be
209             #pod overridden by this method.
210             #pod
211             #pod =cut
212              
213             sub helper {
214 5     5 1 1203 my ( $self, $name, $sub ) = @_;
215 5         18 $self->_helpers->{ $name } = $sub;
216 5         38 return;
217             }
218              
219             #pod =method clear
220             #pod
221             #pod $theme->clear;
222             #pod
223             #pod Clear out the cached templates and includes. Used by the daemon when it
224             #pod detects a change to the theme files.
225             #pod
226             #pod =cut
227              
228             sub clear {
229 1     1 1 84 my ( $self ) = @_;
230 1         18 $self->_clear_templates;
231 1         19 $self->_clear_includes;
232 1         5 return;
233             }
234              
235             #pod =method pages
236             #pod
237             #pod Get the extra, non-template files to deploy with the rest of the site, like CSS,
238             #pod JavaScript, and images.
239             #pod
240             #pod Templates, files that end in C<.ep>, will not be deployed with the rest of the
241             #pod site.
242             #pod
243             #pod =cut
244              
245             around pages => sub {
246             my ( $orig, $self, %args ) = @_;
247             my @pages = $self->$orig( %args );
248             return grep { $_->path !~ /[.]ep$/ } @pages;
249             };
250              
251             1;
252              
253             __END__
254              
255             =pod
256              
257             =encoding UTF-8
258              
259             =head1 NAME
260              
261             Statocles::Theme - Templates, headers, footers, and navigation
262              
263             =head1 VERSION
264              
265             version 0.086
266              
267             =head1 SYNOPSIS
268              
269             # Template directory layout
270             /theme/site/layout.html.ep
271             /theme/site/include/layout.html.ep
272             /theme/blog/index.html.ep
273             /theme/blog/post.html.ep
274              
275             my $theme = Statocles::Theme->new( store => '/theme' );
276             my $layout = $theme->template( qw( site include layout.html ) );
277             my $blog_index = $theme->template( blog => 'index.html' );
278             my $blog_post = $theme->template( 'blog/post.html' );
279              
280             # Clear out cached templates and includes
281             $theme->clear;
282              
283             =head1 DESCRIPTION
284              
285             A Theme contains all the L<templates|Statocles::Template> that
286             L<applications|Statocles::App> need. This class handles finding and parsing
287             files into L<template objects|Statocles::Template>.
288              
289             When the L</store> is read, the templates inside are organized based on
290             their name and their parent directory.
291              
292             =head1 ATTRIBUTES
293              
294             =head2 url_root
295              
296             The root URL for this application. Defaults to C</theme>.
297              
298             =head2 store
299              
300             The source L<store|Statocles::Store> for this theme.
301              
302             If the path begins with ::, will pull one of the Statocles default
303             themes from the Statocles share directory.
304              
305             =head2 include_stores
306              
307             An array of L<stores|Statocles::Store> to look for includes. The L</store> is
308             added at the end of this list.
309              
310             =head2 _templates
311              
312             The cached template objects for this theme.
313              
314             =head2 _includes
315              
316             The cached template objects for the includes.
317              
318             =head1 METHODS
319              
320             =head2 BUILDARGS
321              
322             Handle the path :: share theme.
323              
324             =head2 read
325              
326             my $tmpl = $theme->read( $path )
327              
328             Read the template for the given C<path> and create the
329             L<template|Statocles::Template> object.
330              
331             =head2 build_template
332              
333             my $tmpl = $theme->build_template( $path, $content )
334              
335             Build a new L<Statocles::Template> object with the given C<path> and C<content>.
336              
337             =head2 template
338              
339             my $tmpl = $theme->template( $path )
340             my $tmpl = $theme->template( @path_parts )
341              
342             Get the L<template|Statocles::Template> at the given C<path>, or with the
343             given C<path_parts>.
344              
345             =head2 include
346              
347             my $tmpl = $theme->include( $path );
348             my $tmpl = $theme->include( @path_parts );
349              
350             Get the desired L<template|Statocles::Template> to include based on the given
351             C<path> or C<path_parts>. Looks through all the
352             L<include_stores|/include_stores> before looking in the L<main store|/store>.
353              
354             =head2 helper
355              
356             $theme->helper( $name, $sub );
357              
358             Register a helper on this theme. Helpers are functions that are added to
359             the template to allow for additional features. Helpers are usually added
360             by L<Statocles plugins|Statocles::Plugin>.
361              
362             There are a L<default set of helpers available to all
363             templates|Statocles::Template/DEFAULT HELPERS> which cannot be
364             overridden by this method.
365              
366             =head2 clear
367              
368             $theme->clear;
369              
370             Clear out the cached templates and includes. Used by the daemon when it
371             detects a change to the theme files.
372              
373             =head2 pages
374              
375             Get the extra, non-template files to deploy with the rest of the site, like CSS,
376             JavaScript, and images.
377              
378             Templates, files that end in C<.ep>, will not be deployed with the rest of the
379             site.
380              
381             =head1 SEE ALSO
382              
383             =over 4
384              
385             =item L<Statocles::Help::Theme>
386              
387             =item L<Statocles::Template>
388              
389             =back
390              
391             =head1 AUTHOR
392              
393             Doug Bell <preaction@cpan.org>
394              
395             =head1 COPYRIGHT AND LICENSE
396              
397             This software is copyright (c) 2016 by Doug Bell.
398              
399             This is free software; you can redistribute it and/or modify it under
400             the same terms as the Perl 5 programming language system itself.
401              
402             =cut