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
|
|
|
|
|
|
|
|