File Coverage

blib/lib/Text/PSP.pm
Criterion Covered Total %
statement 85 85 100.0
branch 30 44 68.1
condition 7 15 46.6
subroutine 10 10 100.0
pod 4 7 57.1
total 136 161 84.4


line stmt bran cond sub pod time code
1             package Text::PSP;
2             $VERSION = '1.013';
3 6     6   159793 use strict;
  6         99  
  6         233  
4              
5 6     6   33 use Carp qw(croak carp);
  6         14  
  6         503  
6 6     6   34 use File::Path qw(mkpath);
  6         16  
  6         16830  
7              
8             =pod
9              
10             =head1 NAME
11              
12             Text::PSP - Perl extension implementing a JSP-like templating system.
13              
14             =head1 SYNOPSIS
15              
16             use Text::PSP;
17            
18             my $psp_engine = Text::PSP->new(
19             template_root => 'templates',
20             workdir => '/tmp/psp_work',
21             );
22             my $template_object = $psp_engine->template('/home/joost/templates/index.psp');
23             my @out = $template_object->run(@arguments);
24              
25             print @out;
26              
27             =head1 DESCRIPTION
28              
29             The Text::PSP system consists of 3 modules: L, L and L. The parser creates perl modules from the input files, which are subclasses of Text::PSP::Template. Text::PSP is the module overseeing the creation and caching of the templates.
30              
31             You can use the basics of the JSP system:
32              
33             <%
34             my $self = shift;
35             # code mode
36             my @words = qw(zero one two three);
37             %>
38             Hello, World - this is text mode
39             <%=
40             map { $i++ . ' = ' . $_ } @words
41             %>
42             That was an expression
43             <%!
44             # define mode
45             sub method {
46             return "method called";
47             }
48             %>
49             <%= $self->method %>
50             And insert mode again
51              
52             includes
53             <%@file include="some/page.psp"%>
54              
55             and includes that search for a file upwards to the template
56             root
57             <%@file find="header.psp"%>
58              
59             For a complete description of the template constructs, see L.
60              
61             =head1 METHODS
62              
63             =head2 new
64              
65             my $psp = Text::PSP->new(
66             template_root => './templates',
67             workdir => './work',
68             );
69              
70              
71             Instantiates a new Text::PSP object.
72              
73             =head3 Parameters
74              
75             =over 4
76              
77             =item template_root
78              
79             The root directory for the template files. No templates outside the template_root can be run by this Text::PSP object. This is a required parameter.
80              
81             =item workdir
82              
83             The directory in which to store the translated templates. This is a required parameter.
84              
85             =item create_workdir
86              
87             If this parameter is true and the workdir doesn't exist, one will be created. Default is false.
88              
89             =back
90              
91              
92             =cut
93              
94             sub new {
95 8     8 1 3637 my $class = shift;
96 8         69 my $self = bless {
97             workdir => undef,
98             remove_spaces => 0, # currently unused
99             template_root => undef,
100             create_workdir => 0,
101             @_
102             },$class;
103 8 50       62 croak "No workdir given" unless defined $self->{workdir};
104 8 50       32 croak "No template_root given" unless defined $self->{template_root};
105 8 100       197 unless (-d $self->{workdir}) {
106 2 100       8 if ($self->{create_workdir}) {
107 1 50       220 mkpath $self->{workdir} or croak "Can't create workdir '$self->{workdir}': $!"
108             }
109             else {
110 1 50       244 croak "Workdir $self->{workdir} does not exist" unless (-d $self->{workdir});
111             }
112             }
113 7         25 return $self;
114             }
115              
116             =head2 template
117              
118             my $template = $psp->template("index.psp");
119             # or
120             my $template = $psp->template("index.psp", force_rebuild => 1);
121              
122              
123             Get a template object from a template file. This will translate the template file into a Text::PSP::Template module if needed.
124              
125             Optional arguments:
126              
127             =over 4
128              
129             =item force_rebuild
130              
131             Always rebuild the resulting .pm file and reload it (useful for development). Normally, the .pm file is only built if the I template file is newer than the resulting module. This can be really annoying if you're developing and are only changing some included file.
132              
133             =back
134              
135             =cut
136              
137             sub template {
138 13 50   13 1 7473 croak "Text::PSP template method takes 1+ argument" if @_ < 2;
139 13         39 my ($self,$filename,%options) = @_;
140 13         130 my ($pmfile,$classname) = $self->translate_filename($filename);
141 13 50 33     526 if ( $options{force_rebuild} or ( !-f $pmfile ) or -M _ > -M "$self->{template_root}/$filename" ) {
      33        
142 13         36 delete $INC{ $pmfile };
143 13         51 $self->write_pmfile($filename,$pmfile,$classname);
144             }
145 12         12935 require $pmfile;
146 12         269 return $classname->new( engine => $self, filename => $filename);
147             }
148              
149             =head2 find_template
150              
151             my $template = $psp->find_template("some/path/index.psp");
152             # or
153             my $template = $psp->find_template("some/path/index.psp", force_rebuild => 1);
154              
155              
156             Similar to the C method, but searches for a file starting at the specified path, working up to the template_root.
157              
158             The returned template object will behave as if it really were in the specified path, regardless of the real location of the template in the file system, so for instance any C and C directives will work from that path.
159             =cut
160              
161             sub find_template {
162 1 50   1 1 911 croak "Text::PSP find_template method takes 1+ argument" if @_ < 2;
163 1         3 my ($self,$directory,%options) = @_;
164 1 50       11 $directory =~ s#([^/]+)$## or croak "Cannot find a filename from $directory";
165 1         3 my $filename = $1;
166 1         6 $directory = $self->normalize_path($directory);
167 1         3 my $path = $directory;
168 1         2 my $found = 0;
169 1         2 while (1) {
170             # warn "testing $path/$filename";
171 3 100       12 $found =1,last if -f $self->normalize_path("$self->{template_root}/$path/$filename");
172 2 50       6 last if $path eq '';
173 2         11 $path =~ s#/?[^/]+$##;
174             }
175 1 50       13 croak "Cannot find $filename from directory $directory" unless $found;
176 1         7 my ($pmfile,$classname) = $self->translate_filename("$directory/$filename");
177 1 50 33     46 if ( $options{force_rebuild} or ( !-f $pmfile ) or -M _ > -M "$self->{template_root}/$path/$filename" ) {
      33        
178 1         3 delete $INC{ $pmfile };
179 1         6 $self->write_pmfile($filename,$pmfile,$classname,$directory);
180             }
181 1         958 require $pmfile;
182 1         24 return $classname->new( engine => $self, filename => "$path/$filename");
183             }
184              
185              
186              
187             =head2 clear_workdir
188              
189             $psp->clear_workdir();
190              
191             This will remove the entire content of the work directory, cleaning up disk space and forcing new calls to C<< $psp->template() >> to recompile the template file.
192              
193             =cut
194              
195             sub clear_workdir {
196 1     1 1 7 my ($self) = shift;
197 1         10 require File::Path;
198 1         3 my $workdir = $self->{workdir};
199 1         223 File::Path::rmtree( [ <$workdir/*> ],0);
200             }
201              
202              
203              
204              
205             # ===================================================================
206             #
207             # The following methods are private and subject to change
208             #
209             # ===================================================================
210              
211              
212              
213              
214              
215             #
216             # Translate template filename into package name & module filename
217             #
218              
219             sub translate_filename {
220 14     14 0 29 my ($self,$filename) = @_;
221 14         54 $filename = $self->normalize_path($filename);
222 14 50       56 croak "Filename $filename outsite template_root" if $filename =~ /\.\./;
223 14         67 my $classname = $self->normalize_path("$self->{template_root}/$filename");
224 14         96 $classname =~ s#[^\w/]#_#g;
225 14         30 $classname =~ s#^/#_ROOT_/#;
226 14         25 my $pmfile = $classname;
227 14         59 $classname =~ s#/#::#g;
228 14         37 $classname = "Text::PSP::Generated::$classname";
229 14         131 $pmfile = $self->normalize_path("$self->{workdir}/$pmfile.pm");
230 14         45 return ($pmfile,$classname);
231             }
232              
233             #
234             # Parse the template and write out the resulting module
235             #
236              
237             sub write_pmfile {
238 14     14 0 41 my ($self,$filename,$pmfile,$classname,$directory) = @_;
239 14 50       623 open INFILE,"< $self->{template_root}/$filename" or croak "Cannot open template file $filename: $!";
240 14         3700 require Text::PSP::Parser;
241 14         115 my $parser = Text::PSP::Parser->new($self);
242 14         29 my @dir_opts;
243 14 100       41 if (defined $directory) {
244 1         4 @dir_opts = ( directory => $directory );
245             }
246 14         69 my ($head,$define,$out) = $parser->parse_template(input => \*INFILE, classname => $classname, filename => $filename, @dir_opts);
247 13         147 close INFILE;
248 13         71 my ($outpath) = $pmfile =~ m#(.*)/#;
249 13         84 require File::Path;
250 13         1206 File::Path::mkpath([$outpath]);
251 13 50       1582 open OUTFILE,"> $pmfile" or die "Cannot open $pmfile for writing: $!";
252 13         113 print OUTFILE @$head,@$define,'sub run { my @o;',"\n",@$out,"\n",'return \@o;}',"\n1\n";
253 13         849 close OUTFILE;
254             }
255              
256             #
257             # Translate path into "canonical" equivalent. Relative paths will remain
258             # relative but things like "some/path/../other/thing" will be turned into
259             # "some/other/thing" and excess slashes will be removed.
260             #
261              
262             sub normalize_path {
263 55     55 0 467 my ($self,$inpath) = @_;
264 55         219 my @inpath = split '/',$inpath;
265 55 100 100     302 my $relative = (@inpath > 0 and $inpath[0] ne '') ? 1 : 0;
266 55         75 my @outpath;
267 55         94 for (@inpath) {
268 218 100       383 next if $_ eq '';
269 193 100       306 pop @outpath,next if $_ eq '..';
270 189         335 push @outpath,$_;
271             }
272 55         155 my $outpath = join('/',@outpath);
273 55 100       134 $outpath = "/$outpath" unless $relative;
274 55         384 return $outpath;
275             }
276              
277             1;
278              
279             =head1 COPYRIGHT
280              
281             Copyright 2002 - 2005 Joost Diepenmaat, jdiepen@cpan.org. All rights reserved.
282              
283             This library is free software; you can redistribute it and/or modify it
284             under the same terms as Perl itself.
285              
286             =head1 THANKS TO
287              
288             Christian Hansen for supplying a patch to make the force_reload option work
289             under mod_perl.
290              
291             =head1 SEE ALSO
292              
293             L, L.
294              
295             =cut
296