| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Blog::Blosxom; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
29635
|
use warnings; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
40
|
|
|
4
|
1
|
|
|
1
|
|
7
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
39
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
1186
|
use FindBin; |
|
|
1
|
|
|
|
|
1522
|
|
|
|
1
|
|
|
|
|
48
|
|
|
7
|
1
|
|
|
1
|
|
1232
|
use FileHandle; |
|
|
1
|
|
|
|
|
14500
|
|
|
|
1
|
|
|
|
|
7
|
|
|
8
|
1
|
|
|
1
|
|
448
|
use File::Find; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
63
|
|
|
9
|
1
|
|
|
1
|
|
6
|
use File::Spec; |
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
20
|
|
|
10
|
1
|
|
|
1
|
|
1061
|
use File::stat; |
|
|
1
|
|
|
|
|
15206
|
|
|
|
1
|
|
|
|
|
10
|
|
|
11
|
1
|
|
|
1
|
|
1436
|
use Time::localtime; |
|
|
1
|
|
|
|
|
3053
|
|
|
|
1
|
|
|
|
|
72
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
1
|
|
|
1
|
|
9
|
use List::Util qw(min); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
1370
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Blog::Blosxom - A module version of the apparently inactive blosxom.cgi |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 VERSION |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
Version 0.01 |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=cut |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
our $VERSION = '0.10'; |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use Blog::Blosxom; |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
... |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $blog = Blog::Blosxom->new(%params); |
|
34
|
|
|
|
|
|
|
$blog->run($path, $flavour); |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
A path comes in from somewhere - usually an HTTP request. This is applied to the |
|
37
|
|
|
|
|
|
|
blog directory. A configurable file extension is added to the path and if it matches |
|
38
|
|
|
|
|
|
|
a file that file is served as the matched entry. If the path matches a directory, |
|
39
|
|
|
|
|
|
|
all entries in that directory and in subdirectories are served. If the path looks |
|
40
|
|
|
|
|
|
|
like a date, all posts that match that date are served. A string is returned, which |
|
41
|
|
|
|
|
|
|
is usually printed back to CGI. The string is the matched entries, served in the |
|
42
|
|
|
|
|
|
|
specified output format, or flavour. The flavour is determined by the file extension |
|
43
|
|
|
|
|
|
|
on the incoming path, or a GET parameter, or a configured default. |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Blosxom is a blog engine. It is a rewrite of a CGI script found at www.blosxom.com. |
|
48
|
|
|
|
|
|
|
Blosxom uses the filesystem as the database for blog entries. Blosxom's run() |
|
49
|
|
|
|
|
|
|
method takes two parameters: the path and the flavour. |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
The CGI script that ships with the module is an example of how to use this module |
|
52
|
|
|
|
|
|
|
to reproduce the behaviour of the original Blosxom script, but the idea here is |
|
53
|
|
|
|
|
|
|
that it is up to you how to get this data. |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
Every file that ends in a configurable file extension and is placed somewhere within |
|
56
|
|
|
|
|
|
|
the blog root directory is, in the default situation, served up on a big page, in |
|
57
|
|
|
|
|
|
|
date order, having been processed and turned into blog posts. The set of files chosen |
|
58
|
|
|
|
|
|
|
for display is pared down by specifying a path to limit the set to only entries under |
|
59
|
|
|
|
|
|
|
that path, the narrowest possible filter of course being when the path actually |
|
60
|
|
|
|
|
|
|
matches a single blog entry. |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Alternatively, the path may be a date in the format YYYY[/MM[/DD]], with the brackets |
|
63
|
|
|
|
|
|
|
denoting an optional section. This will be used to filter the posts by date instead |
|
64
|
|
|
|
|
|
|
of by location. All posts under the blog root are candidates for being returned. A |
|
65
|
|
|
|
|
|
|
TODO is to concatenate a date-style filter and a directory-style filter. |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
The module is designed to be extensible. That is, there are several methods in it |
|
68
|
|
|
|
|
|
|
that are designed to be overridden by subclasses to extend the functionality of |
|
69
|
|
|
|
|
|
|
Blog::Blosxom. You can see the L section below for details on these, and |
|
70
|
|
|
|
|
|
|
examples. |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head1 TERMS |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head2 entry |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Entry is used to mean both the individual article (its title, content and any |
|
77
|
|
|
|
|
|
|
metadata) and the file that contains that data. The entry is a filename with a |
|
78
|
|
|
|
|
|
|
customisable file extension. The file name and file extension have two differnent |
|
79
|
|
|
|
|
|
|
purposes. |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
The file extension is used to decide what is a blog post and what isn't. Files that |
|
82
|
|
|
|
|
|
|
have the defined file extension will be found by the blog engine and displayed, so |
|
83
|
|
|
|
|
|
|
long as they are within the filter. |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
The entry's filename is used to find a single entry. The path you provide to |
|
86
|
|
|
|
|
|
|
C is given the file extension defined for blog entries and then applied to the |
|
87
|
|
|
|
|
|
|
root directory. If this is a file, that is served. If not, it is tested without the |
|
88
|
|
|
|
|
|
|
file extension to be a directory. If it is a directory, all files ending with this |
|
89
|
|
|
|
|
|
|
extension and within that directory are served. |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=head2 story |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
The story is the formatted version of an entry. The file story.$flavour is used to |
|
94
|
|
|
|
|
|
|
insert the various parts of the blog entry into a template, which is then concatenated |
|
95
|
|
|
|
|
|
|
to a string which is itself returned from the C method. See below for what I |
|
96
|
|
|
|
|
|
|
mean by $flavour. |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head2 flavour |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
The flavour of the blog is simply the format in which the blog entry is served as a |
|
101
|
|
|
|
|
|
|
story. The flavour is determined by you. The CGI script takes the file extension |
|
102
|
|
|
|
|
|
|
from the request URI, or the C GET parameter. |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
If neither is provided, the default flavour is used. This is passed as a parameter |
|
105
|
|
|
|
|
|
|
to C and defaults to C. |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=head2 component |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
A component is one of the five (currently) sections of the page: head, foot, story, |
|
110
|
|
|
|
|
|
|
date and content-type. The story and date components appear zero-to-many times on |
|
111
|
|
|
|
|
|
|
each page and the other three appear exactly once. |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head2 template |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
A template is a flavoured component. It is defined as a file in the blog root whose |
|
116
|
|
|
|
|
|
|
filename is the component and whose extension is the flavour. E.g. C is |
|
117
|
|
|
|
|
|
|
the HTML-flavoured head component's template. |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=head1 METHODS |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head2 new(%params) |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Create a new blog. Parameters are provided in a hash and are: |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=over |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=item blog_title |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
This will be available in your templates as $blog_title, and by default appears in the |
|
130
|
|
|
|
|
|
|
page title and at the top of the page body. |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
This parameter is required. |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item blog_description |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
This will be available in your templates as $blog_description. This does not appear in |
|
137
|
|
|
|
|
|
|
the default templates. |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=item blog_language |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
This is used for the RSS feed, as well as any other flavours you specify that have a |
|
142
|
|
|
|
|
|
|
language parameter. |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=item datadir |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
This is where blosxom will look for the blog's entries. A relative path will be relative |
|
147
|
|
|
|
|
|
|
to the script that is using this module. |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
This parameter is required. |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=item url |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
This will override the base URL for the blog, which is automatic if you do not provide. |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=item depth |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
This is how far down subdirectories of datadir to look for more blog entries. 0 is the |
|
158
|
|
|
|
|
|
|
default, which means to look down indefinitely. 1, therefore, means to look only in the |
|
159
|
|
|
|
|
|
|
datadir itself, up to n, which will look n-1 subdirectories down. |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=item num_entries |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
This is the maximum number of stories to display when multiple are found in the filter. |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=item file_extension |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
By default, Blosxom will treat .txt files as blog entries. Change this to use a |
|
168
|
|
|
|
|
|
|
different file extension. Do not provide the dot that separates the filename and the |
|
169
|
|
|
|
|
|
|
file extension. |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=item default_flavour |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
The flavour simply determines which set of templates to use to draw the blog. This |
|
174
|
|
|
|
|
|
|
defines which flavour to use by default. Vanilla blosxom has HTML and RSS flavours, |
|
175
|
|
|
|
|
|
|
of which RSS sucks really hard so really only HTML is available by default. |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=item show_future_entries |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
This is a bit of a strange one, since by default, having a future date on an entry |
|
180
|
|
|
|
|
|
|
is a filesystem error, but if you want to override how the date of a template is |
|
181
|
|
|
|
|
|
|
defined, this will be helpful to you. |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=item plugin_dir |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Tells blosxom where to look for plugins. This is empty by default, which means it won't |
|
186
|
|
|
|
|
|
|
look for plugins. Relative paths will be taken relative to the script that uses this |
|
187
|
|
|
|
|
|
|
module. |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=item plugin_state_dir |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
Some plugins wish to store state. This is where the state data will be stored. It will |
|
192
|
|
|
|
|
|
|
need to be writable. Defaults to plugin_dir/state if you specify a plugin_dir. |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=item static_dir |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Blosxom can publish your files statically, which means you run the script and it creates |
|
197
|
|
|
|
|
|
|
HTML files (for example) for each of your entries, instead of loading them dynamically. |
|
198
|
|
|
|
|
|
|
This defines where those files should go. I haven't actually implemented this because I |
|
199
|
|
|
|
|
|
|
don't really want to. |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=item static_password |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
You have to provide a password if you want to use static rendering, as a security |
|
204
|
|
|
|
|
|
|
measure or something. |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=item static_flavours |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
An arrayref of the flavours that Blosxom should generate statically. By default this is |
|
209
|
|
|
|
|
|
|
html and rss. |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=item static_entries |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
Set this to a true value to turn on static generation of individual entries. Generally |
|
214
|
|
|
|
|
|
|
there is no point because your entries are static files already, but you may be using a |
|
215
|
|
|
|
|
|
|
plugin to alter them before rendering. |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=back |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=cut |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub new { |
|
222
|
0
|
|
|
0
|
1
|
|
my ($class, %params) = @_; |
|
223
|
|
|
|
|
|
|
|
|
224
|
0
|
|
|
|
|
|
for (qw(blog_title datadir)) { |
|
225
|
0
|
0
|
0
|
|
|
|
die "Required parameter $_ not provided to Blog::Blosxom->new" |
|
226
|
|
|
|
|
|
|
unless exists $params{$_} && $params{$_}; |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
0
|
0
|
|
|
|
|
die $params{datadir} . " does not exist!" unless -d $params{datadir}; |
|
230
|
|
|
|
|
|
|
|
|
231
|
0
|
|
|
|
|
|
my %defaults = ( |
|
232
|
|
|
|
|
|
|
blog_description => "", |
|
233
|
|
|
|
|
|
|
blog_language => "en", |
|
234
|
|
|
|
|
|
|
url => "", |
|
235
|
|
|
|
|
|
|
depth => 0, |
|
236
|
|
|
|
|
|
|
num_entries => 40, |
|
237
|
|
|
|
|
|
|
file_extension => "txt", |
|
238
|
|
|
|
|
|
|
default_flavour => "html", |
|
239
|
|
|
|
|
|
|
show_future_entries => 0, |
|
240
|
|
|
|
|
|
|
plugin_dir => "", |
|
241
|
|
|
|
|
|
|
plugin_state_dir => "", |
|
242
|
|
|
|
|
|
|
static_dir => "", |
|
243
|
|
|
|
|
|
|
static_password => "", |
|
244
|
|
|
|
|
|
|
static_flavours => [qw(html rss)], |
|
245
|
|
|
|
|
|
|
static_entries => 0, |
|
246
|
|
|
|
|
|
|
require_namespace => 0, |
|
247
|
|
|
|
|
|
|
); |
|
248
|
|
|
|
|
|
|
|
|
249
|
0
|
|
|
|
|
|
%params = (%defaults, %params); |
|
250
|
|
|
|
|
|
|
|
|
251
|
0
|
0
|
0
|
|
|
|
$params{plugin_state_dir} ||= $params{plugin_dir} if $params{plugin_dir}; |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
# Absolutify relative paths |
|
254
|
0
|
|
|
|
|
|
for my $key (qw(plugin_dir datadir static_dir plugin_state_dir)) { |
|
255
|
0
|
|
|
|
|
|
my $dir = $params{$key}; |
|
256
|
|
|
|
|
|
|
|
|
257
|
0
|
0
|
|
|
|
|
unless (File::Spec->file_name_is_absolute( $dir )) { |
|
258
|
0
|
|
|
|
|
|
$dir = File::Spec->catdir($FindBin::Bin, $dir); |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
|
|
261
|
0
|
|
|
|
|
|
$params{$key} = $dir; |
|
262
|
|
|
|
|
|
|
} |
|
263
|
|
|
|
|
|
|
|
|
264
|
0
|
|
|
|
|
|
my $self = bless \%params, $class; |
|
265
|
0
|
|
|
|
|
|
$self->_load_plugins; |
|
266
|
0
|
|
|
|
|
|
$self->_load_templates; |
|
267
|
|
|
|
|
|
|
|
|
268
|
0
|
|
|
|
|
|
return $self; |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head2 run ($path, $flavour) |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
It is now the responsibility of the user to provide the correct path and |
|
274
|
|
|
|
|
|
|
flavour. That is because there are several ways that you can gain this |
|
275
|
|
|
|
|
|
|
information, and it is not up to this engine to decide what they are. That is, |
|
276
|
|
|
|
|
|
|
this information comes from the request URL and, possibly, the parameter string, |
|
277
|
|
|
|
|
|
|
POST, cookies, what-have-you. |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Therefore: |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=over |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=item |
|
284
|
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
The path is the entire path up to the filename. The filename shall not include |
|
286
|
|
|
|
|
|
|
a file extension. The filename is optional, and if omitted, the directory given |
|
287
|
|
|
|
|
|
|
will be searched for all entries and an index page generated. |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=item |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
The flavour can be gathered in any manner you desire. The original Blosxom |
|
292
|
|
|
|
|
|
|
script would use either a parameter string, C, or simply by using |
|
293
|
|
|
|
|
|
|
the flavour as the file extension for the requested path. |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=back |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
No flavour provided will result in the default being used, obviously. No path |
|
298
|
|
|
|
|
|
|
being provided will result in the root path being used, since these are |
|
299
|
|
|
|
|
|
|
equivalent. |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=cut |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
sub run { |
|
304
|
0
|
|
|
0
|
1
|
|
my ($self, $path, $flavour) = @_; |
|
305
|
|
|
|
|
|
|
|
|
306
|
0
|
|
0
|
|
|
|
$path ||= ""; |
|
307
|
0
|
|
0
|
|
|
|
$flavour ||= $self->{default_flavour}; |
|
308
|
|
|
|
|
|
|
|
|
309
|
0
|
|
|
|
|
|
$path =~ s|^/||; |
|
310
|
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
|
my @entries; |
|
312
|
|
|
|
|
|
|
|
|
313
|
0
|
|
|
|
|
|
$self->{path_info} = $path; |
|
314
|
0
|
|
|
|
|
|
$self->{flavour} = $flavour; |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# Build an index page for the path |
|
317
|
0
|
|
|
|
|
|
@entries = $self->entries_for_path($path); |
|
318
|
0
|
|
|
|
|
|
@entries = $self->filter(@entries); |
|
319
|
0
|
|
|
|
|
|
@entries = $self->sort(@entries); |
|
320
|
|
|
|
|
|
|
|
|
321
|
0
|
|
|
|
|
|
@entries = @entries[0 .. min($#entries, $self->{num_entries}-1) ]; |
|
322
|
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
|
$self->{entries} = []; |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# A special template. The user is going to need to know this, but not print it. |
|
326
|
0
|
|
|
|
|
|
$self->{content_type} = $self->template($path, "content_type", $flavour); |
|
327
|
|
|
|
|
|
|
|
|
328
|
0
|
|
|
|
|
|
my @templates; |
|
329
|
|
|
|
|
|
|
|
|
330
|
0
|
|
|
|
|
|
my $date = ""; |
|
331
|
0
|
|
|
|
|
|
for my $entry (@entries) { |
|
332
|
|
|
|
|
|
|
# TODO: Here is an opportunity to style the entries in the style |
|
333
|
|
|
|
|
|
|
# of the subdir they came from. |
|
334
|
0
|
|
|
|
|
|
my $entry_data = $self->entry_data($entry); |
|
335
|
0
|
|
|
|
|
|
push @{$self->{entries}}, $entry_data; |
|
|
0
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
|
|
337
|
0
|
|
|
|
|
|
my $entry_date = join " ", @{$entry_data}{qw(da mo yr)}; # To create a date entry when it changes. |
|
|
0
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
|
|
339
|
0
|
0
|
|
|
|
|
if ($date ne $entry_date) { |
|
340
|
0
|
|
|
|
|
|
$date = $entry_date; |
|
341
|
0
|
|
|
|
|
|
my $date_data = { map { $_ => $entry_data->{$_} } qw( yr mo mo_num dw da hr min ) }; |
|
|
0
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
|
|
343
|
0
|
|
|
|
|
|
push @templates, $self->interpolate($self->template($path, "date", $flavour), $date_data); |
|
344
|
|
|
|
|
|
|
} |
|
345
|
|
|
|
|
|
|
|
|
346
|
0
|
|
|
|
|
|
push @templates, $self->interpolate($self->template($path, "story", $flavour), $entry_data); |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# If we do head and foot last, we let plugins use data about the contents in them. |
|
350
|
0
|
|
|
|
|
|
unshift @templates, $self->interpolate($self->template($path, "head", $flavour), $self->head_data()); |
|
351
|
0
|
|
|
|
|
|
push @templates, $self->interpolate($self->template($path, "foot", $flavour), $self->foot_data()); |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# A skip plugin will stop processing just before anything is output. |
|
354
|
|
|
|
|
|
|
# Not sure why. |
|
355
|
0
|
0
|
|
|
|
|
return if $self->_check_plugins('skip'); |
|
356
|
|
|
|
|
|
|
|
|
357
|
0
|
|
|
|
|
|
return join "\n", @templates; |
|
358
|
|
|
|
|
|
|
} |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=head2 template($path, $component, $flavour) |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
Returns a chunk of markup for the requested component in the requested flavour |
|
363
|
|
|
|
|
|
|
for the requested path. The path will be the one given to C. |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
By default the template file chosen is the file C<$component.$flavour> within |
|
366
|
|
|
|
|
|
|
the C<$path> provided, and if not found, upwards from there to the blog root. |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
The templates used are I, I, I, I and I, |
|
369
|
|
|
|
|
|
|
so the HTML template for the head would be C. |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=cut |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub template { |
|
374
|
0
|
|
|
0
|
1
|
|
my ($self, $path, $comp, $flavour) = @_; |
|
375
|
|
|
|
|
|
|
|
|
376
|
0
|
|
|
|
|
|
my $template; |
|
377
|
|
|
|
|
|
|
|
|
378
|
0
|
0
|
|
|
|
|
unless ($template = $self->_check_plugins('template', @_)) { |
|
379
|
0
|
|
|
|
|
|
my $fn = File::Spec->catfile($self->{datadir}, $path, "$comp.$flavour"); |
|
380
|
|
|
|
|
|
|
|
|
381
|
0
|
|
|
|
|
|
while (1) { |
|
382
|
|
|
|
|
|
|
# Return the contents of this template if the file exists. If it is empty, |
|
383
|
|
|
|
|
|
|
# we have defaults set up. |
|
384
|
0
|
0
|
|
|
|
|
if (-e $fn) { |
|
385
|
0
|
|
|
|
|
|
open my $fh, "<", $fn; |
|
386
|
0
|
|
|
|
|
|
$template = join '', <$fh>; |
|
387
|
|
|
|
|
|
|
} |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
# Stop looking when there is no $path to go between datadir and the |
|
390
|
|
|
|
|
|
|
# template file. For portability, we can't check whether it is a "/" |
|
391
|
0
|
0
|
0
|
|
|
|
last if !$path or $path eq File::Spec->rootdir; |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# Look one dir higher and go again. |
|
394
|
0
|
|
|
|
|
|
my @dir = File::Spec->splitdir($path); |
|
395
|
0
|
|
|
|
|
|
pop @dir; |
|
396
|
0
|
|
|
|
|
|
$path = File::Spec->catdir(@dir); |
|
397
|
0
|
|
|
|
|
|
$fn = File::Spec->catfile($self->{datadir}, $path, "$comp.$flavour"); |
|
398
|
|
|
|
|
|
|
} |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
|
|
401
|
0
|
|
0
|
|
|
|
$template ||= $self->{template}{$flavour}{$comp} || $self->{template}{error}{$comp}; |
|
|
|
|
0
|
|
|
|
|
|
402
|
|
|
|
|
|
|
|
|
403
|
0
|
|
|
|
|
|
return $template; |
|
404
|
|
|
|
|
|
|
} |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=head2 entries_for_path |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Given a path, find the entries that should be returned. This may be overridden |
|
409
|
|
|
|
|
|
|
by a plugin defining the function "entries", or this "entries_for_path" function. |
|
410
|
|
|
|
|
|
|
They are synonymous. See L for information on overriding this method. |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
The path will not include C. |
|
413
|
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
It implements two behaviours. If the path requested is a real path then it is |
|
415
|
|
|
|
|
|
|
searched for all blog entries, honouring the depth parameter that limits how far |
|
416
|
|
|
|
|
|
|
below the C we should look for blog entries. |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
If it is not then it is expected to be a date, being in 1, 2 or 3 parts, in one |
|
419
|
|
|
|
|
|
|
true date ISO format. This version will return all entries filtered by this date |
|
420
|
|
|
|
|
|
|
specification. See also L, which determines the date on which the |
|
421
|
|
|
|
|
|
|
post was made and can be overridden in plugins. |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=cut |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
sub entries_for_path { |
|
426
|
0
|
|
|
0
|
1
|
|
my ($self, $path) = @_; |
|
427
|
|
|
|
|
|
|
|
|
428
|
0
|
|
|
|
|
|
my @entries; |
|
429
|
|
|
|
|
|
|
|
|
430
|
0
|
0
|
|
|
|
|
return @entries if @entries = $self->_check_plugins('entries', @_); |
|
431
|
|
|
|
|
|
|
|
|
432
|
0
|
|
|
|
|
|
my $abs_path = File::Spec->catdir( $self->{datadir}, $path ); |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# If this is an entry, return it. |
|
435
|
0
|
0
|
|
|
|
|
if (-f $abs_path . "." . $self->{file_extension}) { |
|
436
|
0
|
|
|
|
|
|
my $date = $self->date_of_post($abs_path . "." . $self->{file_extension}); |
|
437
|
0
|
|
|
|
|
|
return [ $path . "." . $self->{file_extension}, { date => $date } ]; |
|
438
|
|
|
|
|
|
|
} |
|
439
|
|
|
|
|
|
|
|
|
440
|
0
|
0
|
|
|
|
|
if (-d $abs_path) { |
|
441
|
|
|
|
|
|
|
# We use File::Find on a real path |
|
442
|
|
|
|
|
|
|
my $find = sub { |
|
443
|
0
|
0
|
|
0
|
|
|
return unless -f; |
|
444
|
|
|
|
|
|
|
|
|
445
|
0
|
|
|
|
|
|
my $rel_path = File::Spec->abs2rel( $File::Find::dir, $self->{datadir} ); |
|
446
|
0
|
|
|
|
|
|
my $curdepth = File::Spec->splitdir($rel_path); |
|
447
|
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
|
my $fex = $self->{file_extension}; |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
# not specifying a file extension is a bit silly. |
|
451
|
0
|
0
|
0
|
|
|
|
if (!$fex || /\.$fex$/) { |
|
452
|
1
|
|
|
1
|
|
8
|
no warnings "once"; # File::Find::name causes a warning. |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
889
|
|
|
453
|
|
|
|
|
|
|
|
|
454
|
0
|
|
|
|
|
|
my $rel_file = File::Spec->catfile( $rel_path, $_ ); |
|
455
|
0
|
|
|
|
|
|
$rel_file = File::Spec->canonpath($rel_file); # This removes any artefacts like ./ |
|
456
|
0
|
|
|
|
|
|
my $date = $self->date_of_post($File::Find::name); |
|
457
|
0
|
|
|
|
|
|
my $file_info = { date => $date }; |
|
458
|
|
|
|
|
|
|
|
|
459
|
0
|
|
|
|
|
|
push @entries, [$rel_file, $file_info ]; |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
|
|
462
|
0
|
|
0
|
|
|
|
$File::Find::prune = ($self->{depth} && $curdepth > $self->{depth}); |
|
463
|
0
|
|
|
|
|
|
}; |
|
464
|
|
|
|
|
|
|
|
|
465
|
0
|
|
|
|
|
|
File::Find::find( $find, $abs_path ); |
|
466
|
|
|
|
|
|
|
} |
|
467
|
|
|
|
|
|
|
else { |
|
468
|
|
|
|
|
|
|
# We use date stuff on a fake path. |
|
469
|
|
|
|
|
|
|
# TODO: see whether we can split the path into a date section and a real section. |
|
470
|
0
|
|
|
|
|
|
my @ymd = File::Spec->splitdir( $path ); |
|
471
|
0
|
|
|
|
|
|
my @all_entries = $self->entries_for_path( "" ); |
|
472
|
|
|
|
|
|
|
|
|
473
|
0
|
|
|
|
|
|
my @entries = grep { |
|
474
|
0
|
|
|
|
|
|
my $post_date = localtime( $_->[1]{date} ); |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
# requested year |
|
477
|
0
|
0
|
0
|
|
|
|
($ymd[0] == ($post_date->year+1900)) |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
478
|
|
|
|
|
|
|
and |
|
479
|
|
|
|
|
|
|
# matches month, or moth not requested |
|
480
|
|
|
|
|
|
|
(!$ymd[1] || $ymd[1] == ($post_date->mon+1)) |
|
481
|
|
|
|
|
|
|
and |
|
482
|
|
|
|
|
|
|
# matches day, or day not rquested |
|
483
|
|
|
|
|
|
|
(!$ymd[2] || $ymd[2] == $post_date->mday) |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
? $_ : () |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
} @all_entries; |
|
488
|
|
|
|
|
|
|
} |
|
489
|
|
|
|
|
|
|
|
|
490
|
0
|
|
|
|
|
|
return @entries; |
|
491
|
|
|
|
|
|
|
} |
|
492
|
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head2 date_of_post ($fn) |
|
494
|
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Return a unix timestamp defining the date of the post. The filename provided to |
|
496
|
|
|
|
|
|
|
the method is an absolute filename. |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=cut |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
sub date_of_post { |
|
501
|
0
|
|
|
0
|
1
|
|
my ($self, $fn) = @_; |
|
502
|
|
|
|
|
|
|
|
|
503
|
0
|
|
|
|
|
|
my $dop; |
|
504
|
0
|
0
|
|
|
|
|
return $dop if $dop = $self->_check_plugins("date_of_post", @_); |
|
505
|
|
|
|
|
|
|
|
|
506
|
0
|
|
|
|
|
|
return stat($fn)->mtime; |
|
507
|
|
|
|
|
|
|
} |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=head2 filter (@entries) |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
This function returns only the desired entries from the array passed in. By |
|
512
|
|
|
|
|
|
|
default it just returns the array back, so is just a place to check for plugins. |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
This can be overridden by plugins in order to alter the way the module filters |
|
515
|
|
|
|
|
|
|
the files. See L for more details. |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=cut |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
sub filter { |
|
520
|
0
|
|
|
0
|
1
|
|
my ($self, @entries) = @_; |
|
521
|
|
|
|
|
|
|
|
|
522
|
0
|
|
|
|
|
|
my @remaining_files = $self->_check_plugins("filter", @_); |
|
523
|
|
|
|
|
|
|
|
|
524
|
0
|
|
0
|
|
|
|
return @remaining_files || @entries; |
|
525
|
|
|
|
|
|
|
} |
|
526
|
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
=head2 sort (@entries) |
|
528
|
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
Sort @entries and return the new list. |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
Default behaviour is to sort by date. |
|
532
|
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
=cut |
|
534
|
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
sub sort { |
|
536
|
0
|
|
|
0
|
1
|
|
my ($self, @entries) = @_; |
|
537
|
|
|
|
|
|
|
|
|
538
|
0
|
|
|
|
|
|
my @sorted_entries; |
|
539
|
0
|
0
|
|
|
|
|
return @sorted_entries if @sorted_entries = $self->_check_plugins("sort", @_); |
|
540
|
|
|
|
|
|
|
|
|
541
|
0
|
|
|
|
|
|
@sorted_entries = sort { $a->[1]->{date} <=> $b->[1]->{date} } @entries; |
|
|
0
|
|
|
|
|
|
|
|
542
|
0
|
|
|
|
|
|
return @sorted_entries; |
|
543
|
|
|
|
|
|
|
} |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=head2 static_mode($password, $on) |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
Sets static mode. Pass in the password to turn it on. Turns it off if it is already on. |
|
548
|
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=cut |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
sub static_mode { |
|
552
|
0
|
|
|
0
|
1
|
|
my ($self, $password, $on) = @_; |
|
553
|
|
|
|
|
|
|
|
|
554
|
0
|
0
|
|
|
|
|
die "No static dir defined" unless $self->{static_dir}; |
|
555
|
0
|
0
|
|
|
|
|
die "No static publishing password defined" unless $self->{static_password}; |
|
556
|
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
# Set it to toggle if we don't specify. |
|
558
|
0
|
0
|
|
|
|
|
$on = !$self->{static_mode} if !defined $on; |
|
559
|
|
|
|
|
|
|
|
|
560
|
0
|
0
|
0
|
|
|
|
if ($self->{static_mode} && !$on) { |
|
561
|
0
|
|
|
|
|
|
$self->{static_mode} = 0; |
|
562
|
0
|
|
|
|
|
|
$blosxom::static_or_dynamic = 'dynamic'; |
|
563
|
0
|
|
|
|
|
|
return; |
|
564
|
|
|
|
|
|
|
} |
|
565
|
|
|
|
|
|
|
|
|
566
|
0
|
0
|
0
|
|
|
|
if ($on && $password eq $self->{static_password}) { |
|
567
|
0
|
|
|
|
|
|
$self->{static_mode} = 1; |
|
568
|
0
|
|
|
|
|
|
$blosxom::static_or_dynamic = 'static'; |
|
569
|
|
|
|
|
|
|
} |
|
570
|
|
|
|
|
|
|
} |
|
571
|
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
=head2 interpolate($template, $extra_data) |
|
573
|
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
Each template is interpolated, which means that variables are swapped out if |
|
575
|
|
|
|
|
|
|
they exist. Each template may have template-specific variables; e.g. the story |
|
576
|
|
|
|
|
|
|
template has a title and a body. Those are provided in the extra data, which is |
|
577
|
|
|
|
|
|
|
a hashref with the variable name to be replaced (without the $) as the key, and |
|
578
|
|
|
|
|
|
|
the corresponding value as the value. |
|
579
|
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
By default, a different set of variables are available to each template: |
|
581
|
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=head3 All templates |
|
583
|
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
These are defined by you when you provide them to new() or run() |
|
585
|
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=over |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
=item blog_title |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=item blog_description |
|
591
|
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
=item blog_language |
|
593
|
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
=item url |
|
595
|
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=item path_info |
|
597
|
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=item flavour |
|
599
|
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
=back |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=head3 Story (entry) template |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
These are defined by the entry. |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=over |
|
607
|
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=item title |
|
609
|
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
Post title |
|
611
|
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
=item body |
|
613
|
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
The body of the post |
|
615
|
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
=item yr |
|
617
|
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
=item mo |
|
619
|
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
=item mo_num |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=item da |
|
623
|
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
=item dw |
|
625
|
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
=item hr |
|
627
|
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=item min |
|
629
|
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
Timestamp of entry. mo = month name; dw = day name |
|
631
|
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=item path |
|
633
|
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
The folder in which the post lives, relative to the blog's base URL. |
|
635
|
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=item fn |
|
637
|
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
The filename of the post, sans extension. |
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=back |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=head3 Head template |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=over |
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=item title |
|
647
|
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
This method can be overridden by a plugin. |
|
649
|
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=cut |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
sub interpolate { |
|
653
|
0
|
|
|
0
|
1
|
|
my ($self, $template, $extra_data) = @_; |
|
654
|
|
|
|
|
|
|
|
|
655
|
0
|
|
|
|
|
|
my $done; |
|
656
|
0
|
0
|
|
|
|
|
return $done if $done = $self->_check_plugins("interpolate", @_); |
|
657
|
|
|
|
|
|
|
|
|
658
|
0
|
|
|
|
|
|
for my $var (keys %$extra_data){ |
|
659
|
0
|
0
|
|
|
|
|
if($self->{require_namespace}) { |
|
660
|
0
|
|
|
|
|
|
$template =~ s/\$blosxom::$var\b/$extra_data->{$var}/g; |
|
661
|
|
|
|
|
|
|
} |
|
662
|
|
|
|
|
|
|
else { |
|
663
|
0
|
|
|
|
|
|
$template =~ s/\$(?:blosxom::)?$var\b/$extra_data->{$var}/g; |
|
664
|
|
|
|
|
|
|
} |
|
665
|
|
|
|
|
|
|
} |
|
666
|
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# The blosxom docs say these are global vars, so let's mimic that. |
|
668
|
0
|
|
|
|
|
|
for my $var (qw(blog_title blog_description blog_language url path_info flavour)) { |
|
669
|
|
|
|
|
|
|
# You can set this option so that only $blosxom::foo variables are interpolated |
|
670
|
0
|
0
|
|
|
|
|
if($self->{require_namespace}) { |
|
671
|
0
|
|
|
|
|
|
$template =~ s/\$blosxom::$var\b/$self->{$var}/g; |
|
672
|
|
|
|
|
|
|
} |
|
673
|
|
|
|
|
|
|
else { |
|
674
|
0
|
|
|
|
|
|
$template =~ s/\$(?:blosxom::)?$var\b/$self->{$var}/g; |
|
675
|
|
|
|
|
|
|
} |
|
676
|
|
|
|
|
|
|
} |
|
677
|
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
{ |
|
679
|
1
|
|
|
1
|
|
8
|
no strict 'vars'; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
1306
|
|
|
|
0
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
# Non-blosxom:: variables must be namespaced. I can't be bothered |
|
681
|
|
|
|
|
|
|
# making it work with more than one :: in it just yet, sorry. |
|
682
|
0
|
|
|
|
|
|
$template =~ s/\$(\w+::\w+)/"defined \$$1 ? \$$1 : '\$$1'"/gee; |
|
|
0
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
} |
|
684
|
|
|
|
|
|
|
|
|
685
|
0
|
|
|
|
|
|
return $template; |
|
686
|
|
|
|
|
|
|
} |
|
687
|
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
=head2 entry_data ($entry) |
|
689
|
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
Provided with the entry data, which is an arrayref with the entry filename, |
|
691
|
|
|
|
|
|
|
relative to datadir, in the first slot and a hashref in the second. The hashref |
|
692
|
|
|
|
|
|
|
will have at least a date entry, being a UNIX timestamp for the entry. See |
|
693
|
|
|
|
|
|
|
the section on plugin entries. |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
Returns a hashref containing the following keys: |
|
696
|
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
=over |
|
698
|
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
=item title |
|
700
|
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
Post title |
|
702
|
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
=item body |
|
704
|
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
The body of the post |
|
706
|
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
=item yr |
|
708
|
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
=item mo |
|
710
|
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
=item mo_num |
|
712
|
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
=item da |
|
714
|
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=item dw |
|
716
|
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
=item hr |
|
718
|
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=item min |
|
720
|
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
Timestamp of entry. mo = month name; dw = day name |
|
722
|
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=item path |
|
724
|
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
The folder in which the post lives, relative to the blog's base URL. |
|
726
|
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
=item fn |
|
728
|
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
The filename of the post, sans extension. |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=back |
|
732
|
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
These should be returned such that it is true that |
|
734
|
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
$path . "/" . $fn . "." . $flavour eq $request_url |
|
736
|
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
i.e. these components together are what was originally asked for. (Note that |
|
738
|
|
|
|
|
|
|
flavour is a variable available to templates but not returned by this method.) |
|
739
|
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
=cut |
|
741
|
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
sub entry_data { |
|
743
|
0
|
|
|
0
|
1
|
|
my ($self, $entry) = @_; |
|
744
|
|
|
|
|
|
|
|
|
745
|
0
|
|
|
|
|
|
my $entry_data = {}; |
|
746
|
|
|
|
|
|
|
|
|
747
|
0
|
|
|
|
|
|
my $fn = $entry->[0]; |
|
748
|
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
{ |
|
750
|
0
|
|
|
|
|
|
open my $fh, "<", File::Spec->catfile($self->{datadir}, $fn); |
|
|
0
|
|
|
|
|
|
|
|
751
|
0
|
|
|
|
|
|
my $title = <$fh>; chomp $title; |
|
|
0
|
|
|
|
|
|
|
|
752
|
0
|
|
|
|
|
|
$entry_data->{title} = $title; |
|
753
|
|
|
|
|
|
|
|
|
754
|
0
|
|
|
|
|
|
$entry_data->{body} = join "", <$fh>; |
|
755
|
0
|
|
|
|
|
|
close $fh; |
|
756
|
|
|
|
|
|
|
} |
|
757
|
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
{ |
|
759
|
0
|
|
|
|
|
|
my @path = (File::Spec->splitpath($fn)); |
|
|
0
|
|
|
|
|
|
|
|
760
|
0
|
|
|
|
|
|
$fn = pop @path; |
|
761
|
0
|
|
|
|
|
|
$fn =~ s/\.$self->{file_extension}$//; |
|
762
|
0
|
|
|
|
|
|
$entry_data->{fn} = $fn; |
|
763
|
0
|
|
|
|
|
|
$entry_data->{path} = File::Spec->catpath(@path); |
|
764
|
|
|
|
|
|
|
} |
|
765
|
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
{ |
|
767
|
0
|
|
|
|
|
|
my $i = 1; |
|
|
0
|
|
|
|
|
|
|
|
768
|
0
|
|
|
|
|
|
my %month2num = map {$_, $i++} qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); |
|
|
0
|
|
|
|
|
|
|
|
769
|
0
|
|
|
|
|
|
my $c_time = ctime($entry->[1]->{date}); |
|
770
|
|
|
|
|
|
|
|
|
771
|
0
|
|
|
|
|
|
my($dw,$mo,$da,$hr,$min,$yr) = ( $c_time =~ /(\w{3}) +(\w{3}) +(\d{1,2}) +(\d{2}):(\d{2}):\d{2} +(\d{4})$/ ); |
|
772
|
|
|
|
|
|
|
|
|
773
|
0
|
|
|
|
|
|
$da = sprintf("%02d", $da); |
|
774
|
0
|
|
|
|
|
|
my $mo_num = $month2num{$mo}; |
|
775
|
0
|
|
|
|
|
|
$mo_num = sprintf("%02d", $mo_num); |
|
776
|
|
|
|
|
|
|
|
|
777
|
0
|
|
|
|
|
|
@{$entry_data}{qw(dw mo da yr mo_num hr min)} = ($dw, $mo, $da, $yr, $mo_num, $hr, $min); |
|
|
0
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
# Keep track of the latest date we find. |
|
780
|
0
|
0
|
|
|
|
|
$self->{latest_entry_date} = 0 if !exists $self->{latest_entry_date}; |
|
781
|
0
|
0
|
|
|
|
|
$self->{latest_entry_date} = $entry->[1]->{date} |
|
782
|
|
|
|
|
|
|
if $entry->[1]->{date} > $self->{latest_entry_date}; |
|
783
|
|
|
|
|
|
|
} |
|
784
|
|
|
|
|
|
|
|
|
785
|
0
|
|
|
|
|
|
return $entry_data; |
|
786
|
|
|
|
|
|
|
} |
|
787
|
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=head2 head_data () |
|
789
|
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
Return the data you want to be available to the head template. The head is |
|
791
|
|
|
|
|
|
|
attached to the top of the output I the entries have been run through, |
|
792
|
|
|
|
|
|
|
so you have the data for all the entry data available to you in the arrayref |
|
793
|
|
|
|
|
|
|
$self->{entries}. |
|
794
|
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
Example: |
|
796
|
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
my $self = shift; |
|
798
|
|
|
|
|
|
|
my $data = {}; |
|
799
|
|
|
|
|
|
|
if(@{$self->{entries}} == 1) { |
|
800
|
|
|
|
|
|
|
$data->{title} = $self->{entries}->[0]->{title} |
|
801
|
|
|
|
|
|
|
} |
|
802
|
|
|
|
|
|
|
return $data; |
|
803
|
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
=cut |
|
805
|
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
sub head_data { |
|
807
|
0
|
|
|
0
|
1
|
|
+{}; |
|
808
|
|
|
|
|
|
|
} |
|
809
|
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
=head2 foot_data () |
|
811
|
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
Return the data you want to be available in your foot template. This is attached |
|
813
|
|
|
|
|
|
|
to the output after everything else, as you'd expect. |
|
814
|
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
=cut |
|
816
|
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
sub foot_data { |
|
818
|
0
|
|
|
0
|
1
|
|
+{}; |
|
819
|
|
|
|
|
|
|
} |
|
820
|
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
## PRIVATE FUNCTIONS |
|
822
|
|
|
|
|
|
|
# Purposely not in the POD |
|
823
|
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
## _load_plugins |
|
825
|
|
|
|
|
|
|
# Trawl the plugins directory and look for plugins. Put them in the object hash. |
|
826
|
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
sub _load_plugins { |
|
828
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
829
|
|
|
|
|
|
|
|
|
830
|
0
|
|
|
|
|
|
my $dir = $self->{plugins_dir}; |
|
831
|
0
|
0
|
|
|
|
|
return unless $dir; |
|
832
|
|
|
|
|
|
|
|
|
833
|
0
|
|
|
|
|
|
opendir my($plugins), $dir; |
|
834
|
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
# blosxom docs say modules ending in _ will not be loaded. |
|
836
|
0
|
0
|
0
|
|
|
|
for my $plugin (grep { /^\w+$/ && !/_$/ && -f File::Spec->join($dir, $_) } |
|
|
0
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
sort readdir $plugins) { |
|
838
|
|
|
|
|
|
|
# blosxom docs say you can order modules by prefixing numbers. |
|
839
|
0
|
|
|
|
|
|
$plugin =~ s/^\d+//; |
|
840
|
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
# This will blow up if your package name is not the same as your file name. |
|
842
|
0
|
|
|
|
|
|
require "$dir/$plugin"; |
|
843
|
0
|
0
|
|
|
|
|
if ($plugin->start()) { |
|
844
|
0
|
|
|
|
|
|
$self->{active_plugins}->{$plugin} = 1; |
|
845
|
|
|
|
|
|
|
|
|
846
|
0
|
|
0
|
|
|
|
$self->{plugins_ordered} ||= []; |
|
847
|
0
|
|
|
|
|
|
push @{$self->{plugins_ordered}}, $plugin; |
|
|
0
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
} |
|
849
|
|
|
|
|
|
|
} |
|
850
|
|
|
|
|
|
|
|
|
851
|
0
|
|
|
|
|
|
closedir $plugins; |
|
852
|
|
|
|
|
|
|
} |
|
853
|
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
## _load_templates |
|
855
|
|
|
|
|
|
|
# Read the default templates from DATA. Later the plugins get an opportunity to |
|
856
|
|
|
|
|
|
|
# override what happens when the real templates are read in, so we don't do that here. |
|
857
|
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
sub _load_templates { |
|
859
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
860
|
|
|
|
|
|
|
|
|
861
|
0
|
|
|
|
|
|
while () { |
|
862
|
0
|
0
|
|
|
|
|
last if /^(__END__)?$/; |
|
863
|
0
|
|
|
|
|
|
my($flavour, $comp, $txt) = /^(\S+)\s(\S+)\s(.*)$/; |
|
864
|
0
|
|
|
|
|
|
$txt =~ s/\\n/\n/mg; |
|
865
|
0
|
|
|
|
|
|
$self->{template}{$flavour}{$comp} = $txt; |
|
866
|
|
|
|
|
|
|
} |
|
867
|
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
} |
|
869
|
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
## _check_plugins |
|
871
|
|
|
|
|
|
|
# Look for plugins that can do the first arg, and pass them the rest of the args. |
|
872
|
|
|
|
|
|
|
# Return the first value returned by a plugin. |
|
873
|
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
sub _check_plugins { |
|
875
|
0
|
|
|
0
|
|
|
my ($self, $method, @underscore) = @_; |
|
876
|
|
|
|
|
|
|
|
|
877
|
0
|
0
|
|
|
|
|
return unless $self->{plugins_ordered}; |
|
878
|
0
|
0
|
|
|
|
|
return if $self->{no_plugins}; |
|
879
|
|
|
|
|
|
|
|
|
880
|
0
|
|
|
|
|
|
for my $plugin (@{$self->{plugins_ordered}}) { |
|
|
0
|
|
|
|
|
|
|
|
881
|
0
|
|
|
|
|
|
local $self->{no_plugins} = 1; |
|
882
|
0
|
|
|
|
|
|
my @return; |
|
883
|
0
|
0
|
|
|
|
|
@return = $plugin->$method($self, @underscore) if $plugin->can($method); |
|
884
|
|
|
|
|
|
|
|
|
885
|
0
|
0
|
|
|
|
|
return @return if @return; |
|
886
|
|
|
|
|
|
|
} |
|
887
|
|
|
|
|
|
|
} |
|
888
|
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
=head1 USAGE |
|
890
|
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
=head2 Quick start |
|
892
|
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
To quick start, first you need to create a Blosxom object. You have to provide |
|
894
|
|
|
|
|
|
|
three parameters to the C method: |
|
895
|
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
datadir |
|
897
|
|
|
|
|
|
|
blog_title |
|
898
|
|
|
|
|
|
|
blog_description |
|
899
|
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
The latter is likely to be dropped as a requirement soon. |
|
901
|
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
Then you need to find some way of collecting a path and a flavour from the user. |
|
903
|
|
|
|
|
|
|
The original script used the URL provided by the web server. You provide these |
|
904
|
|
|
|
|
|
|
to the C method. |
|
905
|
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
use Blog::Blosxom; |
|
907
|
|
|
|
|
|
|
use CGI qw(standard); |
|
908
|
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
my $blog = Blog::Blosxom->new( |
|
910
|
|
|
|
|
|
|
datadir => '/var/www/blosxom/entries', |
|
911
|
|
|
|
|
|
|
blog_title => 'Descriptive blog title.', |
|
912
|
|
|
|
|
|
|
blog_description => 'Descriptive blog description.', |
|
913
|
|
|
|
|
|
|
); |
|
914
|
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
my $path = path_info() || param('path'); |
|
916
|
|
|
|
|
|
|
my ($flavour) = $path =~ s/(\.\w+)$// || param('flav'); |
|
917
|
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
print header, |
|
919
|
|
|
|
|
|
|
$blog->run($path, $flavour); |
|
920
|
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
The above is a complete CGI script that will run your blog. Note that C |
|
922
|
|
|
|
|
|
|
is a CGI function. Don't print that if you're not using CGI! |
|
923
|
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
Now that we know how to run Blosxom we can look at how to make entries. |
|
925
|
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
=head2 Entries |
|
927
|
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
To create an entry, create a plaintext file in your C with the .txt |
|
929
|
|
|
|
|
|
|
extension. The first line of this file is the title of the post and the rest |
|
930
|
|
|
|
|
|
|
is the body. |
|
931
|
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
This post will be displayed if it is somewhere under the C<$path> you provided |
|
933
|
|
|
|
|
|
|
to C, unless it is the 41st such file, because by default only 40 are |
|
934
|
|
|
|
|
|
|
displayed at once. |
|
935
|
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
The C part of all this is configurable in C. |
|
937
|
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=head2 Flavour |
|
939
|
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
The flavour that you provide determines which set of templates are used to |
|
941
|
|
|
|
|
|
|
compose the output blog. |
|
942
|
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
You may be wondering about the fact that the blog entry ends with .txt, but in |
|
944
|
|
|
|
|
|
|
the CGI script we have used the extension to determine the flavour. The file |
|
945
|
|
|
|
|
|
|
extension is ignored when mapping the path to the file system, so your path |
|
946
|
|
|
|
|
|
|
could feasibly match a single entry, which will of course be served on its own. |
|
947
|
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
The template to be chosen is a file whose file extension is the current flavour |
|
949
|
|
|
|
|
|
|
and whose file name is the template in question. Have a look at the docs for the |
|
950
|
|
|
|
|
|
|
C function. |
|
951
|
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
Writing these templates is the primary way you make your blog entries show up |
|
953
|
|
|
|
|
|
|
as decent stories. The other way is when you override the C method |
|
954
|
|
|
|
|
|
|
and have the content of your entries moulded into some slightly better markup. |
|
955
|
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
=head2 More information |
|
957
|
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
The best source of information on this is the documentation for the methods |
|
959
|
|
|
|
|
|
|
themselves. Therefore, we provide the execution order so you can see exactly |
|
960
|
|
|
|
|
|
|
what is going on and figure stuff out that way. |
|
961
|
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=over |
|
963
|
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
=item new |
|
965
|
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
Blosxom is an object-oriented thing now. This is so that you can subclass it to |
|
967
|
|
|
|
|
|
|
override any or all of the default functionality, which is kind of the point |
|
968
|
|
|
|
|
|
|
of Blosxom in the first place. |
|
969
|
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
=item run |
|
971
|
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
The original script found the path and flavour for you but this one lets you |
|
973
|
|
|
|
|
|
|
decide where they should come from, so you can integrate them into other |
|
974
|
|
|
|
|
|
|
applications if you wish. |
|
975
|
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
=item entries_for_path |
|
977
|
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
The next thing that happens is the path is searched for all entries, and this |
|
979
|
|
|
|
|
|
|
function simply returns them all. This function returns an array of each entry's |
|
980
|
|
|
|
|
|
|
filename and a bit of extra data alongside. |
|
981
|
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
=item date_of_post |
|
983
|
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
C calls C to get that little bit of extra data. |
|
985
|
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
=item filter |
|
987
|
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
Then the entries list is filtered. This function is empty by default, intended |
|
989
|
|
|
|
|
|
|
to be overridden by plugins or subclasses. |
|
990
|
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
=item sort |
|
992
|
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
The remaining list is sorted. This is done by date by default, the date being |
|
994
|
|
|
|
|
|
|
ascertained during C. |
|
995
|
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
=item entry_data |
|
997
|
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
This is one of the more powerful functions to reimplement. It returns as much |
|
999
|
|
|
|
|
|
|
data about the provided entry as is necessary for your use of Blosxom. The |
|
1000
|
|
|
|
|
|
|
required return data are defined in the docs for this function; see also the |
|
1001
|
|
|
|
|
|
|
PLUGINS section. |
|
1002
|
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
This is called for each entry that remains after C is done and C |
|
1004
|
|
|
|
|
|
|
has ordered them. |
|
1005
|
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
=item template |
|
1007
|
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
This is called to get the data to give to C. It simply returns the |
|
1009
|
|
|
|
|
|
|
contents of the template file. |
|
1010
|
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
=item interpolate |
|
1012
|
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
This is the second of the more powerful functions. In this function, the raw |
|
1014
|
|
|
|
|
|
|
templates have their variables replaced with their values. How that works is |
|
1015
|
|
|
|
|
|
|
documented in the method's own documentation! |
|
1016
|
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
Each entry's data is given to its template through this function; occasionally |
|
1018
|
|
|
|
|
|
|
while this is happening the date template is also given its data too. |
|
1019
|
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
=item head_data |
|
1021
|
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
This gets data to give to C for the C template. |
|
1023
|
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
=item foot_data |
|
1025
|
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
This gets data to give to C for the C template. |
|
1027
|
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=back |
|
1029
|
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
The interpolated templates are aggregated into an array, onto which the head |
|
1031
|
|
|
|
|
|
|
and foot templates are added at the end, allowing info about the whole page to |
|
1032
|
|
|
|
|
|
|
be available to both the top and bottom of the page. |
|
1033
|
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
=head1 PLUGINS |
|
1035
|
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
Writing plugins for this new version of Blosxom is easy. If you know exactly |
|
1037
|
|
|
|
|
|
|
what you want from your plugin, you can simply subclass this one and override |
|
1038
|
|
|
|
|
|
|
the methods below. Alternatively, you can create files in some directory, and |
|
1039
|
|
|
|
|
|
|
then configure your Blosxom object to use that directory as your plugins |
|
1040
|
|
|
|
|
|
|
directory. |
|
1041
|
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
In order to use a plugin directory, the package name in the plugin file must |
|
1043
|
|
|
|
|
|
|
be identical to the filename itself. That is because the blosxom engine uses |
|
1044
|
|
|
|
|
|
|
the filename to know which package to give to C. The only thing that |
|
1045
|
|
|
|
|
|
|
deviates from this rule is that you can prepend the filename with any number of |
|
1046
|
|
|
|
|
|
|
digits, and these will be used to load the plugins in order. The order is that |
|
1047
|
|
|
|
|
|
|
returned by the sort function, so it is recommended all your numbers have the |
|
1048
|
|
|
|
|
|
|
same number of digits. |
|
1049
|
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
In order to disable a plugin, simply alter its C subroutine to return a |
|
1051
|
|
|
|
|
|
|
false value instead of a true one. |
|
1052
|
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
=head2 Starting a plugin |
|
1054
|
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
As mentioned, it is necessary that your plugin's filename is the same as the |
|
1056
|
|
|
|
|
|
|
package defined in the plugin. Please also include a bit of a comment about |
|
1057
|
|
|
|
|
|
|
what your plugin does, plus author information. The community would appreciate |
|
1058
|
|
|
|
|
|
|
it if you would use an open licence for your copyrighting, since the community |
|
1059
|
|
|
|
|
|
|
is built on this attitude. However, the licence you use is, of course, up to |
|
1060
|
|
|
|
|
|
|
you. |
|
1061
|
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
The smallest possible plugin (comments aside) is shown below, and its filename |
|
1063
|
|
|
|
|
|
|
would be C. |
|
1064
|
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
## Blosxom plugin myplugin |
|
1066
|
|
|
|
|
|
|
## Makes blosxom not suck |
|
1067
|
|
|
|
|
|
|
## Author: Altreus |
|
1068
|
|
|
|
|
|
|
## Licence: X11/MIT |
|
1069
|
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
package myplugin; |
|
1071
|
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
sub start{1} |
|
1073
|
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
1; |
|
1075
|
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
=head2 Hooks |
|
1077
|
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
Every single hook in the plugin will be passed the I object as the |
|
1079
|
|
|
|
|
|
|
first argument, effectively running the function as a method on the object |
|
1080
|
|
|
|
|
|
|
itself. This is shown as the C<$self> argument. |
|
1081
|
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
In all cases, the first plugin that defines a hook is the one that gets to do |
|
1083
|
|
|
|
|
|
|
it. For this reason you may find that you want to use the method above to |
|
1084
|
|
|
|
|
|
|
decide the order in which the plugins are loaded. |
|
1085
|
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
Also in all cases, except where a true/false value is expected, simply not |
|
1087
|
|
|
|
|
|
|
returning anything is the way to go about deciding you don't want to alter the |
|
1088
|
|
|
|
|
|
|
default behaviour. For example, if you wanted to take the date of a post from |
|
1089
|
|
|
|
|
|
|
the filename, then in the cases where the filename does not define a date, you |
|
1090
|
|
|
|
|
|
|
can simply C and processing will continue as if it had not defined |
|
1091
|
|
|
|
|
|
|
this functionality in the first place. |
|
1092
|
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
Also also in all cases, you can get the return value of the default method by |
|
1094
|
|
|
|
|
|
|
simply calling $self->method. This is helpful if you want to slightly but not |
|
1095
|
|
|
|
|
|
|
wildly alter the output, such as adding extra information to the same set of |
|
1096
|
|
|
|
|
|
|
data. Obviously this is not true of C and C, since these are not |
|
1097
|
|
|
|
|
|
|
methods on the class in the first place. |
|
1098
|
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
=head3 start ($self) |
|
1100
|
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
The C subroutine is required in your module and will return either a |
|
1102
|
|
|
|
|
|
|
true or a false value to decide whether the plugin should be used. |
|
1103
|
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
You can use the values on the Blosxom object if you need to make a decision. |
|
1105
|
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
sub start { |
|
1107
|
|
|
|
|
|
|
return shift->{static_mode}; # Only active in static mode |
|
1108
|
|
|
|
|
|
|
} |
|
1109
|
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
=head3 template ($self, $path, $comp, $flavour) |
|
1111
|
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
$path: path of request, filename removed |
|
1113
|
|
|
|
|
|
|
$comp: component e.g. head, story |
|
1114
|
|
|
|
|
|
|
$flavour: quite obviously the flavour of the request |
|
1115
|
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
This returns the template to use in the given path for the given component for |
|
1117
|
|
|
|
|
|
|
the given flavour. The requested filename will not be part of the path, if the |
|
1118
|
|
|
|
|
|
|
requested path matched an individual entry. |
|
1119
|
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
The default template procedure is to check the given path and all parent |
|
1121
|
|
|
|
|
|
|
directories of that path, up to the blog root, for the file $comp.$flavour, |
|
1122
|
|
|
|
|
|
|
and to use the first one found. |
|
1123
|
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
Since it is pretty easy to find this file based on just the filename, you'd |
|
1125
|
|
|
|
|
|
|
think this method has something a bit more difficult to do. In fact this |
|
1126
|
|
|
|
|
|
|
function returns the I of that file, ready for interpolation. |
|
1127
|
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
This function implements the functionality of both the C hook in the |
|
1129
|
|
|
|
|
|
|
original blosxom script, as well as the hooks for the individual templates |
|
1130
|
|
|
|
|
|
|
themselves. That means that if your original plugin defined a new template for, |
|
1131
|
|
|
|
|
|
|
e.g., the date.html in certain situations, this is where you should now return |
|
1132
|
|
|
|
|
|
|
that magic HTML. |
|
1133
|
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
=head3 entries_for_path ($self, $path) |
|
1135
|
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
$path: The path as provided to run() |
|
1137
|
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
This returns an array of items. Each item is itself an arrayref, whose first |
|
1139
|
|
|
|
|
|
|
entry is the filename and whose second entry is a hashref. The hashref is |
|
1140
|
|
|
|
|
|
|
required to contain the 'date' key, which specifies the date of the file. |
|
1141
|
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
That's pretty complicated. Here's some more info. When Blosxom is converting |
|
1143
|
|
|
|
|
|
|
entries into stories it needs to know what entries exist under a given path, |
|
1144
|
|
|
|
|
|
|
and the date of each entry. It therefore needs an array of arrayrefs, each |
|
1145
|
|
|
|
|
|
|
arrayref representing one of the entries found under the given path, in the |
|
1146
|
|
|
|
|
|
|
format C<[$filename, $date]>. |
|
1147
|
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
However, since it is envisioned you might want to add more metadata about the |
|
1149
|
|
|
|
|
|
|
entry, the C part is a hashref, so you can add more stuff to it if you |
|
1150
|
|
|
|
|
|
|
want to. So now the format is C<[$filename, { date => $date }]>. |
|
1151
|
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
The filename returned is the I of the entry file, I
|
|
1153
|
|
|
|
|
|
|
the datadir>. The input $path is not necessarily relative to anything, because |
|
1154
|
|
|
|
|
|
|
it will be the path the user requested. Thus, please note, it may contain the |
|
1155
|
|
|
|
|
|
|
year, month and day of the requested post(s) and not a path to any real file or |
|
1156
|
|
|
|
|
|
|
directory at all. |
|
1157
|
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
It is worth noting that you can override how Blosxom decides the date of the |
|
1159
|
|
|
|
|
|
|
file by implementing the C method instead of this one. |
|
1160
|
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
=head3 date_of_post ($self, $post) |
|
1162
|
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
$post: path and filename relative to blog root |
|
1164
|
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
You should return an arrayref where [0] is the 4-digit year, [1] is the 2-digit |
|
1166
|
|
|
|
|
|
|
month, and [2] is the 2-digit day. This is not checked for validity but will |
|
1167
|
|
|
|
|
|
|
probably cause something to blow up somewhere if it is not a real date. |
|
1168
|
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
=head3 filter ($self, @entries) |
|
1170
|
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
@entries: an array of all entries, exactly as returned by entries_for_path |
|
1172
|
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
This function does nothing by default and is a hook by which you can |
|
1174
|
|
|
|
|
|
|
scrupulously filter out posts one way or another. You are given the output of |
|
1175
|
|
|
|
|
|
|
the C method above, and you should return an array in exactly |
|
1176
|
|
|
|
|
|
|
the same format, except having removed any entries you do not want to show up on |
|
1177
|
|
|
|
|
|
|
the generated page. |
|
1178
|
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
=head3 sort ($self, @entries) |
|
1180
|
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
@entries: an array of all entries, exactly as returned by entries_for_path |
|
1182
|
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
You can override the default sorting mechanism, which is by date by default, |
|
1184
|
|
|
|
|
|
|
by implementing this method. It is advisable to empty your date template if |
|
1185
|
|
|
|
|
|
|
you do this, because the date template is inserted every time the date changes |
|
1186
|
|
|
|
|
|
|
as processing goes down the list. |
|
1187
|
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
A future release may see the date template replaced by a divider template, which |
|
1189
|
|
|
|
|
|
|
would be configurable and merely default to the date. |
|
1190
|
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
=head3 skip |
|
1192
|
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
The skip function is a feature from the original blosxom. Setting it to return |
|
1194
|
|
|
|
|
|
|
a true value will cause the Blosxom object to stop just before anything is |
|
1195
|
|
|
|
|
|
|
actually output. That is, it will find all the entries and pull in the templates |
|
1196
|
|
|
|
|
|
|
but not do anything with them if any active plugin makes this function return |
|
1197
|
|
|
|
|
|
|
true. This is useful if, e.g., your plugin issues a redirect header. |
|
1198
|
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
=head3 interpolate ($self, $template, $extra_data) |
|
1200
|
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
This is where you can override the default way in which the variables are |
|
1202
|
|
|
|
|
|
|
interpolated into the template. |
|
1203
|
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
The extra data will be a hashref of var => val, var being the variable name to |
|
1205
|
|
|
|
|
|
|
interpolate, without its $. |
|
1206
|
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
If you don't call the parent function for this, be aware that there is an |
|
1208
|
|
|
|
|
|
|
option called $self->{require_namespace}, which means that only fully-qualified |
|
1209
|
|
|
|
|
|
|
variables will be interpolated. You should honour this if you intend anyone |
|
1210
|
|
|
|
|
|
|
else to use your plugin. |
|
1211
|
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
The three functions C, C and C return simple |
|
1213
|
|
|
|
|
|
|
hashrefs that are interpolated in this function. |
|
1214
|
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
You should also be aware that there are several "global" variables, available |
|
1216
|
|
|
|
|
|
|
to all templates, that are not returned by any of those functions. They are |
|
1217
|
|
|
|
|
|
|
hard-coded in the default implementation of C, so it is probably |
|
1218
|
|
|
|
|
|
|
for the best if you defer to this method. |
|
1219
|
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
The section on usage will probably help here. |
|
1221
|
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
=head3 entry_data ($entry) |
|
1223
|
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
This is provided with an arrayref as returned by entries_for_path, and should |
|
1225
|
|
|
|
|
|
|
return a hashref with the keys as described above, in the method's own |
|
1226
|
|
|
|
|
|
|
documentation. Briefly, they are |
|
1227
|
|
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
title body yr mo mo_name da dw hr min path fn |
|
1229
|
|
|
|
|
|
|
|
|
1230
|
|
|
|
|
|
|
You may also provide any extra keys that your own templates may want. It is |
|
1231
|
|
|
|
|
|
|
recommended that you use next::method to get the default hashref, and add more |
|
1232
|
|
|
|
|
|
|
things to it. |
|
1233
|
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
See the section on usage for how all this works and thus to get a better idea |
|
1235
|
|
|
|
|
|
|
of what you should or should not be overriding. |
|
1236
|
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
=head3 head_data |
|
1238
|
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
This function is called to get the data required for the head template. By |
|
1240
|
|
|
|
|
|
|
default, only the global variables are available in the head template. |
|
1241
|
|
|
|
|
|
|
|
|
1242
|
|
|
|
|
|
|
You may override any global variable by returning it as a key in this hashref, |
|
1243
|
|
|
|
|
|
|
or you can add to the set of available variables instead. See the section on |
|
1244
|
|
|
|
|
|
|
usage for how all this works. |
|
1245
|
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
=head3 foot_data |
|
1247
|
|
|
|
|
|
|
|
|
1248
|
|
|
|
|
|
|
This function is called to provide data to the foot template. By default it |
|
1249
|
|
|
|
|
|
|
returns an empty hashref. |
|
1250
|
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
You may override any global variable by returning it as a key in this hashref, |
|
1252
|
|
|
|
|
|
|
or you can add to the set of available variables instead. |
|
1253
|
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
See the section on usage for how all this works. |
|
1255
|
|
|
|
|
|
|
|
|
1256
|
|
|
|
|
|
|
=head1 AUTHOR |
|
1257
|
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
Altreus, C<< >> |
|
1259
|
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
=head1 TODO |
|
1261
|
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
Most existing plugins won't work because I've changed the way it works to the |
|
1263
|
|
|
|
|
|
|
extent that the same functions don't necessarily exist. However, existing |
|
1264
|
|
|
|
|
|
|
plugins should be fairly easy to tailor to the new plugin system. I didn't |
|
1265
|
|
|
|
|
|
|
think this was too important a feature because a good number of the plugins at |
|
1266
|
|
|
|
|
|
|
the blosxom plugin repository are 404s anyway. |
|
1267
|
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
The plugin system is like the original blosxom's, where the first plugin to |
|
1269
|
|
|
|
|
|
|
define a function is the boss of that function. Some functions may benefit |
|
1270
|
|
|
|
|
|
|
from the combined effort of all plugins, such as filtering. That is something |
|
1271
|
|
|
|
|
|
|
to think about in the future. |
|
1272
|
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
Static rendering is not yet implemented. Frankly I don't think I can be |
|
1274
|
|
|
|
|
|
|
bothered. |
|
1275
|
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
A comment system is common on many blogs and I think I will write a separate |
|
1277
|
|
|
|
|
|
|
plugin to make this easy. |
|
1278
|
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
=head1 BUGS |
|
1280
|
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
Bug reports on github, please! http://github.com/Altreus/Blog-Blosxom/issues |
|
1282
|
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
You can also get the latest version from here. |
|
1284
|
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
=head1 SUPPORT |
|
1286
|
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
You are reading the only documentation for this module. |
|
1288
|
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
You should check out the examples folder. If you don't know where that is, |
|
1290
|
|
|
|
|
|
|
either check $HOME/.cpan/build or browse/clone the Git repository |
|
1291
|
|
|
|
|
|
|
http://github.com/Altreus/Blog-Blosxom |
|
1292
|
|
|
|
|
|
|
|
|
1293
|
|
|
|
|
|
|
If you're brave you can see if I'm around in #perl on irc.freenode.com. Your |
|
1294
|
|
|
|
|
|
|
use case will help me refine the module, since in its initial state it was |
|
1295
|
|
|
|
|
|
|
merely a rewrite of the original script for my own sanity. |
|
1296
|
|
|
|
|
|
|
|
|
1297
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
|
1298
|
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
Thanks to the original author of blosxom.cgi for writing it and giving me code |
|
1300
|
|
|
|
|
|
|
to do much of the stuff it did. |
|
1301
|
|
|
|
|
|
|
|
|
1302
|
|
|
|
|
|
|
http://blosxom.com |
|
1303
|
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
Thanks to f00li5h on that irc.freenode.com (and many others!) for being the |
|
1305
|
|
|
|
|
|
|
first person I mean cat other than me to use it and therefore have lots of |
|
1306
|
|
|
|
|
|
|
things to say about it. |
|
1307
|
|
|
|
|
|
|
|
|
1308
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
|
1309
|
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
This module is released under the X11/MIT licence, which is the one where you |
|
1311
|
|
|
|
|
|
|
use it as you wish and don't blame me for it. I hope the author of the original |
|
1312
|
|
|
|
|
|
|
script does not take this badly; if the author sees this and wishes me to |
|
1313
|
|
|
|
|
|
|
change the licence I am happy to do so. |
|
1314
|
|
|
|
|
|
|
|
|
1315
|
|
|
|
|
|
|
=cut |
|
1316
|
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
1; |
|
1318
|
|
|
|
|
|
|
|
|
1319
|
|
|
|
|
|
|
__DATA__ |