line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=pod |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
WRT - WRiting Tool |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=for HTML <a href="https://travis-ci.org/brennen/wrt"><img src="https://travis-ci.org/brennen/wrt.svg?branch=master"></a> |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 SYNOPSIS |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
$ wrt display 2016 > 2016.html |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Or: |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
$ wrt render |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Or: |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
#!/usr/bin/env perl |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
use WRT; |
22
|
|
|
|
|
|
|
my $w = WRT->new( |
23
|
|
|
|
|
|
|
entry_dir => 'archives', |
24
|
|
|
|
|
|
|
url_root => '/', |
25
|
|
|
|
|
|
|
# etc. |
26
|
|
|
|
|
|
|
); |
27
|
|
|
|
|
|
|
print $w->display(@ARGV); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 INSTALLING |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
It's possible this may run on a Perl as old as 5.10.0, although in practice I |
32
|
|
|
|
|
|
|
imagine that at least some of its dependencies have more recent requirements. |
33
|
|
|
|
|
|
|
In practice, I know that it works under 5.20.2. It should work on any |
34
|
|
|
|
|
|
|
reasonably modern Linux distribution, and may also be fine on MacOS or a BSD of |
35
|
|
|
|
|
|
|
your choosing. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
$ perl Build.PL |
38
|
|
|
|
|
|
|
$ ./Build installdeps |
39
|
|
|
|
|
|
|
$ ./Build test |
40
|
|
|
|
|
|
|
$ ./Build install |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 DESCRIPTION |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
This started life as C<display.pl>, a simple script to concatenate fragments of |
45
|
|
|
|
|
|
|
handwritten HTML by date. It has since haphazardly accumulated several of the |
46
|
|
|
|
|
|
|
usual weblog features (lightweight markup, feed generation, embedded Perl, |
47
|
|
|
|
|
|
|
poetry tools, image galleries, and ill-advised dependencies), but the basic |
48
|
|
|
|
|
|
|
idea hasn't changed that much. |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
The C<wrt> utility now generates static HTML files, instead of expecting to |
51
|
|
|
|
|
|
|
run as a CGI script. This is a better idea, for the most part. |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
The C<WRT> module will work with FastCGI, if called from the appropriate |
54
|
|
|
|
|
|
|
wrapper script, such as C<wrt-fcgi>. |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
By default, entries are stored in a simple directory tree under C<entry_dir>. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
Like: |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
archives/2001/1/1 |
61
|
|
|
|
|
|
|
archives/2001/1/1/sub_entry |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
It is possible (although not as flexible as it ought to be) to redefine the |
64
|
|
|
|
|
|
|
directory layout. More about this after a bit. |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
An entry may be either a plain text file, or a directory containing several |
67
|
|
|
|
|
|
|
files. If it's a directory, a file named "index" will be treated as the text |
68
|
|
|
|
|
|
|
of the entry, and all other lower-case filenames without extensions will be |
69
|
|
|
|
|
|
|
treated as sub-entries or documents within that entry, and displayed |
70
|
|
|
|
|
|
|
accordingly. Links to certain other filetypes will be displayed as well. |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
Directories may be nested to an arbitrary depth, although it's probably not a |
73
|
|
|
|
|
|
|
good idea to go very deep with the current display logic. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
A PNG or JPEG file with a name like |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
2001/1/1.icon.png |
78
|
|
|
|
|
|
|
2001/1/1/index.icon.png |
79
|
|
|
|
|
|
|
2001/1/1/whatever.icon.png |
80
|
|
|
|
|
|
|
2001/1/1/whatever/index.icon.png |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
will be treated as an icon for the appropriate entry file. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=head2 MARKUP |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
Entries may consist of hand-written HTML (to be passed along without further |
87
|
|
|
|
|
|
|
interpretation), a supported form of lightweight markup, or some combination |
88
|
|
|
|
|
|
|
thereof. Actually, an entry may consist of any darn thing you please, as long |
89
|
|
|
|
|
|
|
as Perl will agree that it is text, but presumably you're going to be feeding |
90
|
|
|
|
|
|
|
this to a browser. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
Special markup is indicated by a variety of HTML-like container tags. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
B<Embedded Perl> - evaluated and replaced by whatever value you return |
95
|
|
|
|
|
|
|
(evaluated in a scalar context): |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
<perl>my $dog = "Ralph."; return $dog;</perl> |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
This code is evaluated before any other processing is done, so you can return |
100
|
|
|
|
|
|
|
any other markup understood by the script and have it handled appropriately. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
B<Interpolated variables> - actually keys to the hash underlying the WRT |
103
|
|
|
|
|
|
|
object, for the moment: |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
<perl>$self->title("About Ralph, My Dog"); return '';</perl> |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
<p>The title is <em>${title}</em>.</p> |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
This will change. |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
Embedded code and variables are intended for use in the F<template> file, where |
112
|
|
|
|
|
|
|
it's handy to drop in titles or conditionalize aspects of a layout. You want to |
113
|
|
|
|
|
|
|
be careful with this sort of thing - it's useful in small doses, but it's also |
114
|
|
|
|
|
|
|
a maintainability nightmare waiting to happen. (WordPress, I am looking at |
115
|
|
|
|
|
|
|
you.) |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
B<Several forms of lightweight markup>: |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
<markdown>John Gruber's Markdown, by way of |
120
|
|
|
|
|
|
|
Text::Markdown</markdown> |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
<textile>Dean Allen's Textile, via Brad Choate's |
123
|
|
|
|
|
|
|
Text::Textile.</textile> |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
<freeverse>An easy way to |
126
|
|
|
|
|
|
|
get properly broken lines |
127
|
|
|
|
|
|
|
plus -- en and em dashes --- |
128
|
|
|
|
|
|
|
for poetry and such.</freeverse> |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
B<And a couple of shortcuts>: |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
<image>filename.ext |
133
|
|
|
|
|
|
|
alt text, if any</image> |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
<list> |
136
|
|
|
|
|
|
|
one list item |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
another list item |
139
|
|
|
|
|
|
|
</list> |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
As it stands, freeverse, image, and list are not particularly robust. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=cut |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
package WRT; |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
our ($VERSION) = '3.5.0'; |
148
|
|
|
|
|
|
|
|
149
|
3
|
|
|
3
|
|
181498
|
use strict; |
|
3
|
|
|
|
|
20
|
|
|
3
|
|
|
|
|
74
|
|
150
|
3
|
|
|
3
|
|
12
|
use warnings; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
67
|
|
151
|
3
|
|
|
3
|
|
12
|
no warnings 'uninitialized'; |
|
3
|
|
|
|
|
3
|
|
|
3
|
|
|
|
|
79
|
|
152
|
|
|
|
|
|
|
|
153
|
3
|
|
|
3
|
|
12
|
use base 'WRT::MethodSpit'; |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
662
|
|
154
|
|
|
|
|
|
|
|
155
|
3
|
|
|
3
|
|
15
|
use Cwd; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
140
|
|
156
|
3
|
|
|
3
|
|
919
|
use HTML::Entities; |
|
3
|
|
|
|
|
13675
|
|
|
3
|
|
|
|
|
188
|
|
157
|
3
|
|
|
3
|
|
1188
|
use JSON; |
|
3
|
|
|
|
|
26967
|
|
|
3
|
|
|
|
|
21
|
|
158
|
3
|
|
|
3
|
|
1562
|
use XML::Atom::SimpleFeed; |
|
3
|
|
|
|
|
35855
|
|
|
3
|
|
|
|
|
118
|
|
159
|
|
|
|
|
|
|
|
160
|
3
|
|
|
3
|
|
775
|
use WRT::Date; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
154
|
|
161
|
3
|
|
|
3
|
|
771
|
use WRT::HTML qw(:all); |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
612
|
|
162
|
3
|
|
|
3
|
|
753
|
use WRT::Image qw(image_size); |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
187
|
|
163
|
3
|
|
|
3
|
|
959
|
use WRT::Markup qw(line_parse image_markup eval_perl); |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
190
|
|
164
|
3
|
|
|
3
|
|
644
|
use WRT::Renderer qw(render); |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
153
|
|
165
|
3
|
|
|
3
|
|
698
|
use WRT::Util qw(dir_list get_date); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
9930
|
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=head1 CONFIGURATION |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=over |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=item options |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
See F<example/wrt.json> for a sample configuration. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=cut |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
my %default = ( |
178
|
|
|
|
|
|
|
root_dir => '.', # dir for wrt repository |
179
|
|
|
|
|
|
|
entry_dir => 'archives', # dir for entry files |
180
|
|
|
|
|
|
|
publish_dir => 'public', # dir to publish site to |
181
|
|
|
|
|
|
|
url_root => "$0?", # root URL for building links |
182
|
|
|
|
|
|
|
image_url_root => '', # same for images |
183
|
|
|
|
|
|
|
template_dir => 'templates', # dir for template files |
184
|
|
|
|
|
|
|
template => 'default', # template to use |
185
|
|
|
|
|
|
|
title => '', |
186
|
|
|
|
|
|
|
title_prefix => '', |
187
|
|
|
|
|
|
|
stylesheet_url => undef, |
188
|
|
|
|
|
|
|
favicon_url => undef, |
189
|
|
|
|
|
|
|
feed_alias => 'feed', |
190
|
|
|
|
|
|
|
author => undef, |
191
|
|
|
|
|
|
|
description => undef, |
192
|
|
|
|
|
|
|
content => undef, # place to stash content for templates |
193
|
|
|
|
|
|
|
embedded_perl => 1, # evaluate embedded <perl> tags? |
194
|
|
|
|
|
|
|
default_entry => 'new', |
195
|
|
|
|
|
|
|
license => 'public domain', |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# List of years for the menu: |
198
|
|
|
|
|
|
|
year_list => [ reverse(1997..(get_date('year') + 1900)) ], |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# What gets considered an entry _path_: |
201
|
|
|
|
|
|
|
entrypath_expr => qr/^ ([a-z0-9_\/-]+) $/x, |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# What gets considered a subentry file (slightly misleading terminology here): |
204
|
|
|
|
|
|
|
subentry_expr => qr/^[0-9a-z_-]+(\.(tgz|zip|tar[.]gz|gz|txt))?$/, |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# We'll show links for these, but not display them inline: |
207
|
|
|
|
|
|
|
binfile_expr => qr/[.](tgz|zip|tar[.]gz|gz|txt|pdf)$/, |
208
|
|
|
|
|
|
|
); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=item entry_map(\%map) |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
Takes a hashref which will dispatch entries matching various regexen to |
213
|
|
|
|
|
|
|
the appropriate output methods. The default looks something like this: |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
nnnn/[nn/nn/]doc_name - a document within a day. |
216
|
|
|
|
|
|
|
nnnn/nn/nn - a specific day. |
217
|
|
|
|
|
|
|
nnnn/nn - a month. |
218
|
|
|
|
|
|
|
nnnn - a year. |
219
|
|
|
|
|
|
|
doc_name - a document in the root directory. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
You can re-map things to an arbitrary archive layout. |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
Since the entry map is a hash, and handle() simply loops over its keys, there |
224
|
|
|
|
|
|
|
is no guaranteed precedence of patterns. Be extremely careful that no entry |
225
|
|
|
|
|
|
|
will match more than one pattern, or you will wind up with unexpected behavior. |
226
|
|
|
|
|
|
|
A good way to ensure that this does not happen is to use patterns like: |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
qr( |
229
|
|
|
|
|
|
|
^ # start of string |
230
|
|
|
|
|
|
|
[0-9/]{4}/ # year |
231
|
|
|
|
|
|
|
[0-9]{1,2}/ # month |
232
|
|
|
|
|
|
|
[0-9]{1,2] # day |
233
|
|
|
|
|
|
|
$ # end of string |
234
|
|
|
|
|
|
|
)x |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
...always marking the start and end of the string explicitly. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
This may eventually be rewritten to use an array so that the order can be |
239
|
|
|
|
|
|
|
explicitly specified. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=cut |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
$default{entry_map} = { |
244
|
|
|
|
|
|
|
qr'^[0-9/]{5,11}[a-z_/]+$' => sub { entry_stamped (@_, 'index') }, |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
qr'^[0-9]{4}/[0-9]{1,2}/ |
247
|
|
|
|
|
|
|
[0-9]{1,2}$'x => sub { entry_stamped (@_, 'all' ) }, |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
qr'^[0-9]{4}/[0-9]{1,2}$' => sub { month (@_ ) }, |
250
|
|
|
|
|
|
|
qr'^[0-9]{4}$' => sub { year (@_ ) }, |
251
|
|
|
|
|
|
|
qr'^[a-z_]' => sub { entry_stamped (@_, 'all' ) }, |
252
|
|
|
|
|
|
|
}; |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=item entry_descriptions(\%descriptions) |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
Takes a hashref which contains a map of entry titles to entry descriptions. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=cut |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# TODO: this has gotten more than a little silly. |
261
|
|
|
|
|
|
|
$default{entry_descriptions} = { |
262
|
|
|
|
|
|
|
new => 'newest entries', |
263
|
|
|
|
|
|
|
all => 'all entries', |
264
|
|
|
|
|
|
|
}; |
265
|
|
|
|
|
|
|
{ |
266
|
|
|
|
|
|
|
foreach my $yr ( @{ $default{year_list} } ) { |
267
|
|
|
|
|
|
|
$default{entry_descriptions}{$yr} = "entries for $yr"; |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Set up some accessor methods: |
272
|
|
|
|
|
|
|
__PACKAGE__->methodspit( keys %default ); |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=back |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=head1 METHODS |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
For no bigger than this thing is, it gets a little convoluted. |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=over |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=item new_from_file($config_file) |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Takes a filename to pull JSON config data out of, and |
285
|
|
|
|
|
|
|
returns a new WRT instance with the parameters set in |
286
|
|
|
|
|
|
|
that file. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=cut |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub new_from_file { |
291
|
2
|
|
|
2
|
1
|
236
|
my ($config_file) = @_; |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# Grab configuration from wrt.json: |
294
|
2
|
|
|
|
|
3
|
my $config_json; |
295
|
|
|
|
|
|
|
{ |
296
|
2
|
50
|
|
|
|
3
|
open my $fh, '<', $config_file |
|
2
|
|
|
|
|
44
|
|
297
|
|
|
|
|
|
|
or warn "Couldn't open configuration file: $config_file: $!\n"; |
298
|
|
|
|
|
|
|
# line separator: |
299
|
2
|
|
|
|
|
8
|
local $/ = undef; |
300
|
2
|
|
|
|
|
34
|
$config_json = <$fh>; |
301
|
2
|
|
|
|
|
15
|
close $fh; |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
2
|
|
|
|
|
39
|
my $JSON = JSON->new->utf8->pretty; |
305
|
2
|
|
|
|
|
11
|
$JSON->convert_blessed(1); |
306
|
|
|
|
|
|
|
|
307
|
2
|
|
|
|
|
24
|
my $config_hashref = $JSON->decode($config_json); |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# decode() returns (I think) a hashref; this needs to be dereferenced: |
310
|
2
|
|
|
|
|
4
|
return WRT->new(%{ $config_hashref }); |
|
2
|
|
|
|
|
14
|
|
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=item new(%params) |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Get a new WRT object with the specified parameters set. |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
=cut |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub new { |
320
|
3
|
|
|
3
|
1
|
106
|
my $class = shift; |
321
|
3
|
|
|
|
|
13
|
my %params = @_; |
322
|
|
|
|
|
|
|
|
323
|
3
|
|
|
|
|
7
|
my $self = \%default; |
324
|
3
|
|
|
|
|
6
|
bless $self, $class; |
325
|
|
|
|
|
|
|
|
326
|
3
|
|
|
|
|
28
|
$self->configure(%params); |
327
|
|
|
|
|
|
|
|
328
|
3
|
|
|
|
|
23
|
return $self; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=item display($entry1, $entry2, ...) |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
Return a string containing the given entries, which are in the form of |
334
|
|
|
|
|
|
|
date/entry strings. If no parameters are given, default to default_entry(). |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
display() expands aliases ("new" and "all", for example) as necessary, collects |
337
|
|
|
|
|
|
|
output from handle($entry), and wraps the whole thing in a template file. |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
=cut |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub display { |
342
|
15
|
|
|
15
|
1
|
257
|
my $self = shift; |
343
|
15
|
|
|
|
|
33
|
my (@options) = @_; |
344
|
|
|
|
|
|
|
|
345
|
15
|
|
33
|
|
|
27
|
$options[0] ||= $self->default_entry; |
346
|
15
|
|
|
|
|
24
|
$self->title(join ' ', map { encode_entities($_) } @options); # title for head/foot |
|
15
|
|
|
|
|
35
|
|
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
# Expand on any aliases: |
349
|
15
|
|
|
|
|
20
|
@options = map { $self->expand_option($_) } @options; |
|
15
|
|
|
|
|
34
|
|
350
|
|
|
|
|
|
|
|
351
|
15
|
|
|
|
|
38
|
$self->content(undef); |
352
|
15
|
|
|
|
|
11
|
my $output; |
353
|
15
|
|
|
|
|
21
|
for my $option (@options) { |
354
|
15
|
100
|
|
|
|
24
|
return $self->feed_print() if $option eq $self->feed_alias; |
355
|
14
|
|
|
|
|
22
|
$output .= $self->handle($option); |
356
|
|
|
|
|
|
|
} |
357
|
14
|
|
|
|
|
44
|
$self->content($output); # ${content} may now be used in the template below... |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# Wrap entries in template: |
360
|
14
|
|
|
|
|
32
|
my $rendered_page; |
361
|
14
|
50
|
|
|
|
25
|
if ($self->{overlay}) { |
362
|
0
|
|
|
|
|
0
|
$rendered_page .= $self->{overlay}; |
363
|
|
|
|
|
|
|
} else { |
364
|
14
|
|
|
|
|
32
|
$rendered_page .= $self->fragment_slurp($self->template_dir . '/' . $self->template); |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
14
|
|
|
|
|
51
|
return $rendered_page; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=item handle($entry) |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
Return the text of an individual entry. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=begin digression |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=item A digression about each() |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
I once spent a lot of time chasing down a bug caused by a while loop in this |
379
|
|
|
|
|
|
|
method. Specifically, I was using while to iterate over the entry_map hash. |
380
|
|
|
|
|
|
|
Since C<$self->entry_map> returns a reference to the same hash each time, every |
381
|
|
|
|
|
|
|
other request was finding C<each()> mid-way through iterating over this hash. |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
I initially solved this by copying the hash into a local one called C<%map> |
384
|
|
|
|
|
|
|
every time C<handle()> was called. I could also have called C<keys> or |
385
|
|
|
|
|
|
|
C<values> on the anonymous hash, as these reset C<each()>. |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
Presently I'm not using each() or an explicit loop, so this probably doesn't |
388
|
|
|
|
|
|
|
make a whole lot of sense in the context of the existing code. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=end digression |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=cut |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub handle { |
395
|
14
|
|
|
14
|
1
|
16
|
my $self = shift; |
396
|
14
|
|
|
|
|
17
|
my ($entry) = @_; |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
# Hashref: |
399
|
14
|
|
|
|
|
30
|
my $map = $self->entry_map; |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# Find the first pattern in entry_map that matches this entry... |
402
|
14
|
|
|
|
|
13
|
my ($pattern) = grep { $entry =~ $_ } keys %{ $map }; |
|
70
|
|
|
|
|
1016
|
|
|
14
|
|
|
|
|
38
|
|
403
|
|
|
|
|
|
|
|
404
|
14
|
50
|
|
|
|
34
|
return unless defined $pattern; |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
# ...and use the corresponding coderef to handle the entry: |
407
|
14
|
|
|
|
|
41
|
return $map->{$pattern}->($self, $entry); |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=item expand_option($option) |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
Expands/converts 'all' and 'new' to appropriate values. |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=cut |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
sub expand_option { |
417
|
15
|
|
|
15
|
1
|
23
|
my ($self, $option) = @_; |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
# Take care of trailing slashes: |
420
|
15
|
50
|
|
|
|
36
|
chop $option if $option =~ m{/$}; |
421
|
|
|
|
|
|
|
|
422
|
15
|
100
|
|
|
|
40
|
if ($option eq 'all') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
423
|
1
|
|
|
|
|
7
|
return dir_list($self->entry_dir, 'high_to_low', qr/^[0-9]{1,4}$/); |
424
|
|
|
|
|
|
|
} elsif ($option eq 'new') { |
425
|
1
|
|
|
|
|
3
|
return $self->recent_month(); |
426
|
|
|
|
|
|
|
} elsif ($option eq 'fulltext') { |
427
|
0
|
|
|
|
|
0
|
return $self->fulltext(); |
428
|
|
|
|
|
|
|
} else { |
429
|
13
|
|
|
|
|
31
|
return $option; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=item recent_month() |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
Tries to find the most recent month in the archive. |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
If a year file is text, returns that instead. |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=cut |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
sub recent_month { |
443
|
5
|
|
|
5
|
1
|
7
|
my $self = shift; |
444
|
5
|
|
|
|
|
11
|
my ($dir) = $self->entry_dir; |
445
|
|
|
|
|
|
|
|
446
|
5
|
|
|
|
|
15
|
my ($mon, $year) = get_date('mon', 'year'); |
447
|
|
|
|
|
|
|
|
448
|
5
|
|
|
|
|
8
|
$mon++; |
449
|
5
|
|
|
|
|
6
|
$year += 1900; |
450
|
|
|
|
|
|
|
|
451
|
5
|
50
|
|
|
|
50
|
if (-e "$dir/$year/$mon") { |
452
|
0
|
|
|
|
|
0
|
return "$year/$mon"; |
453
|
|
|
|
|
|
|
} else { |
454
|
5
|
|
|
|
|
29
|
my @year_files = dir_list($dir, 'high_to_low', qr/^[0-9]{1,4}$/); |
455
|
|
|
|
|
|
|
|
456
|
5
|
50
|
|
|
|
52
|
return $year_files[0] if -f "$dir/$year_files[0]"; |
457
|
|
|
|
|
|
|
|
458
|
5
|
|
|
|
|
29
|
my @month_files = dir_list( |
459
|
|
|
|
|
|
|
"$dir/$year_files[0]", 'high_to_low', qr/^[0-9]{1,2}$/ |
460
|
|
|
|
|
|
|
); |
461
|
|
|
|
|
|
|
|
462
|
5
|
|
|
|
|
32
|
return "$year_files[0]/$month_files[0]"; |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=item fulltext |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
The full text of all entries, in order. |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=cut |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
sub fulltext { |
473
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
474
|
|
|
|
|
|
|
|
475
|
0
|
|
|
|
|
0
|
my @individual_entries; |
476
|
|
|
|
|
|
|
|
477
|
0
|
|
|
|
|
0
|
my @years = dir_list($self->entry_dir, 'low_to_high', qr/^[0-9]{1,4}$/); |
478
|
0
|
|
|
|
|
0
|
foreach my $year (@years) { |
479
|
0
|
|
|
|
|
0
|
my @months = dir_list($self->entry_dir . '/' . $year, 'low_to_high', qr/^[0-9]+$/); |
480
|
0
|
|
|
|
|
0
|
foreach my $month (@months) { |
481
|
0
|
|
|
|
|
0
|
my @days = dir_list($self->entry_dir . '/' . $year . '/' . $month, 'low_to_high', qr/^[0-9]+$/); |
482
|
0
|
|
|
|
|
0
|
foreach my $day (@days) { |
483
|
0
|
|
|
|
|
0
|
push @individual_entries, "$year/$month/$day"; |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
0
|
|
|
|
|
0
|
return @individual_entries; |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=item link_bar(@extra_links) |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
Returns a little context-sensitive navigation bar. |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
=cut |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub link_bar { |
498
|
14
|
|
|
14
|
1
|
21
|
my $self = shift; |
499
|
14
|
|
|
|
|
16
|
my (@extra_links) = @_; |
500
|
|
|
|
|
|
|
|
501
|
14
|
|
|
|
|
31
|
my $title = $self->title; |
502
|
|
|
|
|
|
|
|
503
|
14
|
|
|
|
|
14
|
my $output; |
504
|
|
|
|
|
|
|
|
505
|
14
|
|
|
|
|
14
|
my (%description) = %{ $self->entry_descriptions() }; |
|
14
|
|
|
|
|
25
|
|
506
|
|
|
|
|
|
|
|
507
|
14
|
|
|
|
|
21
|
my @years = @{ $self->year_list }; |
|
14
|
|
|
|
|
22
|
|
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
# This makes the short list of years context sensitive: |
510
|
|
|
|
|
|
|
|
511
|
14
|
100
|
|
|
|
51
|
if ( my ($title_year) = $title =~ m/^([0-9]{4})/ ) { |
512
|
|
|
|
|
|
|
# We have a match. |
513
|
|
|
|
|
|
|
|
514
|
5
|
50
|
|
|
|
18
|
if ($title_year == $years[0] ) { $title_year--; } |
|
0
|
50
|
|
|
|
0
|
|
515
|
0
|
|
|
|
|
0
|
elsif ($title_year == $years[-1]) { $title_year++; } |
516
|
|
|
|
|
|
|
|
517
|
5
|
50
|
|
|
|
7
|
if (grep { $title_year eq $_ } @years) { |
|
105
|
|
|
|
|
104
|
|
518
|
5
|
|
|
|
|
7
|
my $prev = $title_year - 1; |
519
|
5
|
|
|
|
|
5
|
my $next = $title_year + 1; |
520
|
5
|
|
|
|
|
6
|
@years = grep { m/^($prev|$title_year|$next)$/ } @years; |
|
105
|
|
|
|
|
244
|
|
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
} else { |
523
|
9
|
|
|
|
|
46
|
@years = @years[0..2]; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
14
|
|
|
|
|
35
|
my @linklist = ( qw(new all), @years, @extra_links ); |
527
|
|
|
|
|
|
|
|
528
|
14
|
|
|
|
|
22
|
foreach my $link (@linklist) { |
529
|
70
|
|
|
|
|
62
|
my $link_title; |
530
|
70
|
100
|
|
|
|
90
|
if (exists $description{$link}) { |
531
|
28
|
|
|
|
|
50
|
$link_title = $description{$link}; |
532
|
|
|
|
|
|
|
} else { |
533
|
42
|
|
|
|
|
37
|
$link_title = 'entries for ' . $link; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
70
|
100
|
|
|
|
81
|
if ($title ne $link) { |
537
|
|
|
|
|
|
|
|
538
|
67
|
|
|
|
|
95
|
my $href = $self->url_root . $link . '/'; |
539
|
67
|
100
|
|
|
|
95
|
if ($link eq 'new') { |
540
|
13
|
|
|
|
|
17
|
$href = $self->url_root; |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
|
543
|
67
|
|
|
|
|
166
|
$output .= a({href => $href, title => $link_title}, $link) . "\n"; |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
} else { |
546
|
3
|
|
|
|
|
12
|
$output .= qq{<strong><span title="$link_title">$link</span></strong>\n}; |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
|
550
|
14
|
|
|
|
|
114
|
return $output; |
551
|
|
|
|
|
|
|
} |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
=item month_before($this_month) |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
Return the month before the given month in the archive. |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
Very naive; there has got to be a smarter way. |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=cut |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
{ my %cache; # cheap memoization |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
sub month_before { |
564
|
2
|
|
|
2
|
1
|
3
|
my $self = shift; |
565
|
2
|
|
|
|
|
4
|
my ($this_month) = @_; |
566
|
|
|
|
|
|
|
|
567
|
2
|
50
|
|
|
|
7
|
if (exists $cache{$this_month}) { |
568
|
0
|
|
|
|
|
0
|
return $cache{$this_month}; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
|
571
|
2
|
|
|
|
|
13
|
my ($year, $month) = $this_month =~ |
572
|
|
|
|
|
|
|
m/^ # start of string |
573
|
|
|
|
|
|
|
([0-9]{4}) # 4 digit year |
574
|
|
|
|
|
|
|
\/ # |
575
|
|
|
|
|
|
|
([0-9]{1,2}) # 2 digit month |
576
|
|
|
|
|
|
|
/x; |
577
|
|
|
|
|
|
|
|
578
|
2
|
50
|
|
|
|
6
|
if ($month == 1) { |
579
|
2
|
|
|
|
|
4
|
$month = 12; $year--; |
|
2
|
|
|
|
|
6
|
|
580
|
|
|
|
|
|
|
} else { |
581
|
0
|
|
|
|
|
0
|
$month--; |
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
|
584
|
2
|
|
|
|
|
7
|
until (-e $self->local_path("$year/$month")) { |
585
|
|
|
|
|
|
|
|
586
|
2
|
50
|
|
|
|
4
|
if (! -d $self->local_path($year) ) { |
587
|
|
|
|
|
|
|
# Give up easily, wrapping to newest month. |
588
|
2
|
|
|
|
|
6
|
return $self->recent_month; |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
# handle January: |
592
|
0
|
0
|
|
|
|
0
|
if ($month == 1) { |
593
|
0
|
|
|
|
|
0
|
$month = 12; $year--; |
|
0
|
|
|
|
|
0
|
|
594
|
0
|
|
|
|
|
0
|
next; |
595
|
|
|
|
|
|
|
} |
596
|
0
|
|
|
|
|
0
|
$month--; |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
|
599
|
0
|
|
|
|
|
0
|
return $cache{$this_month} = "$year/$month"; |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
=item year($year) |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
List out the updates for a year. |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
=cut |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
sub year { |
610
|
2
|
|
|
2
|
1
|
6
|
my $self = shift; |
611
|
2
|
|
|
|
|
4
|
my ($year) = @_; |
612
|
|
|
|
|
|
|
|
613
|
2
|
|
|
|
|
8
|
my ($year_file, $year_url) = $self->root_locations($year); |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# Year is a text file: |
616
|
2
|
50
|
|
|
|
26
|
return $self->entry_wrapped($year) if -f $year_file; |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
# If it's not a directory, we can't do anything. Bail out: |
619
|
2
|
50
|
|
|
|
30
|
return p('No such year.') if (! -d $year_file); |
620
|
|
|
|
|
|
|
|
621
|
2
|
|
|
|
|
2
|
my $result; |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
# Handle year directories with index files. |
624
|
2
|
50
|
|
|
|
22
|
$result .= $self->entry($year) |
625
|
|
|
|
|
|
|
if -f "$year_file/index"; |
626
|
|
|
|
|
|
|
|
627
|
2
|
|
|
|
|
8
|
my $header_text = $self->icon_markup($year, $year); |
628
|
2
|
|
50
|
|
|
14
|
$header_text ||= q{}; |
629
|
|
|
|
|
|
|
|
630
|
2
|
|
|
|
|
11
|
$result .= heading("${header_text}${year}", 3); |
631
|
|
|
|
|
|
|
|
632
|
2
|
|
|
|
|
26
|
my @months = dir_list($year_file, 'high_to_low', qr/^[0-9]{1,2}$/); |
633
|
|
|
|
|
|
|
|
634
|
2
|
|
|
|
|
3
|
my $year_text; |
635
|
2
|
|
|
|
|
5
|
my $count = 0; # explicitly defined for later printing. |
636
|
|
|
|
|
|
|
|
637
|
2
|
|
|
|
|
5
|
foreach my $month (@months) { |
638
|
2
|
|
|
|
|
13
|
my @entries = dir_list( |
639
|
|
|
|
|
|
|
"$year_file/$month", 'low_to_high', qr/^[0-9]{1,2}$/ |
640
|
|
|
|
|
|
|
); |
641
|
2
|
|
|
|
|
5
|
$count += @entries; |
642
|
|
|
|
|
|
|
|
643
|
2
|
|
|
|
|
3
|
my $month_text; |
644
|
2
|
|
|
|
|
4
|
foreach my $entry (@entries) { |
645
|
4
|
|
|
|
|
17
|
$month_text .= a({href => "$year_url/$month/$entry/"}, $entry) . "\n"; |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
|
648
|
2
|
|
|
|
|
9
|
$month_text = small("( $month_text )"); |
649
|
|
|
|
|
|
|
|
650
|
2
|
|
|
|
|
12
|
my $link = a({href => "$year_url/$month/"}, month_name($month)); |
651
|
|
|
|
|
|
|
|
652
|
2
|
|
|
|
|
13
|
$year_text .= table_row( |
653
|
|
|
|
|
|
|
table_cell({class => 'datelink'}, $link), |
654
|
|
|
|
|
|
|
table_cell({class => 'datelink'}, $month_text) |
655
|
|
|
|
|
|
|
) . "\n\n"; |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
2
|
50
|
|
|
|
5
|
if ($count > 1) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
659
|
2
|
|
|
|
|
6
|
$year_text .= table_row( |
660
|
|
|
|
|
|
|
table_cell(scalar(@months) . ' months'), |
661
|
|
|
|
|
|
|
table_cell("$count entries") |
662
|
|
|
|
|
|
|
); |
663
|
|
|
|
|
|
|
} |
664
|
0
|
|
|
|
|
0
|
elsif ($count == 0) { $year_text .= table_row(table_cell('No entries')); } |
665
|
0
|
|
|
|
|
0
|
elsif ($count == 1) { $year_text .= table_row(table_cell("$count entry")); } |
666
|
|
|
|
|
|
|
|
667
|
2
|
|
|
|
|
6
|
$result .= "\n\n" . table($year_text) . "\n"; |
668
|
|
|
|
|
|
|
|
669
|
2
|
|
|
|
|
5
|
return entry_markup($result); |
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
=item month($month) |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
Prints the entries in a given month (nnnn/nn). |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
=cut |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
sub month { |
679
|
2
|
|
|
2
|
1
|
4
|
my $self = shift; |
680
|
2
|
|
|
|
|
4
|
my ($month) = @_; |
681
|
|
|
|
|
|
|
|
682
|
2
|
|
|
|
|
6
|
my ($month_file, $month_url) = $self->root_locations($month); |
683
|
|
|
|
|
|
|
|
684
|
2
|
|
|
|
|
4
|
my $result; |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
# If a directory exists for $month, use dir_list to slurp |
687
|
|
|
|
|
|
|
# the entry files it contains into @entry_files, sorted |
688
|
|
|
|
|
|
|
# numerically. Then send each entry to entry_markup(). |
689
|
2
|
50
|
|
|
|
27
|
if (-d $month_file) { |
|
|
0
|
|
|
|
|
|
690
|
|
|
|
|
|
|
|
691
|
2
|
50
|
|
|
|
26
|
$result .= $self->entry($month) |
692
|
|
|
|
|
|
|
if -f "$month_file/index"; |
693
|
|
|
|
|
|
|
|
694
|
2
|
|
|
|
|
14
|
my @entry_files = dir_list($month_file, 'high_to_low', qr/^[0-9]{1,2}$/); |
695
|
|
|
|
|
|
|
|
696
|
2
|
|
|
|
|
8
|
foreach my $entry_file (@entry_files) { |
697
|
4
|
|
|
|
|
18
|
$result .= $self->entry_stamped("$month/$entry_file"); |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
} elsif (-f $month_file) { |
701
|
0
|
|
|
|
|
0
|
$result .= $self->entry($month); |
702
|
|
|
|
|
|
|
} |
703
|
|
|
|
|
|
|
|
704
|
2
|
|
|
|
|
6
|
my %link_params = ( |
705
|
|
|
|
|
|
|
href => $self->url_root . $self->month_before($month) . '/', |
706
|
|
|
|
|
|
|
title => 'previous month' |
707
|
|
|
|
|
|
|
); |
708
|
2
|
|
|
|
|
6
|
my $prev_link = a(\%link_params, '⇐'); |
709
|
|
|
|
|
|
|
|
710
|
2
|
|
|
|
|
8
|
$result .= div( |
711
|
|
|
|
|
|
|
{ class => 'entry' }, |
712
|
|
|
|
|
|
|
nav(p( {class => 'navigation'}, $prev_link )) . "\n\n" |
713
|
|
|
|
|
|
|
); |
714
|
|
|
|
|
|
|
|
715
|
2
|
|
|
|
|
14
|
return $result; |
716
|
|
|
|
|
|
|
} |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
=item entry_wrapped |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
Wraps entry() in entry_markup. |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
=cut |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
sub entry_wrapped { |
725
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
726
|
0
|
|
|
|
|
0
|
my ($entry, $level) = @_; |
727
|
|
|
|
|
|
|
|
728
|
0
|
|
|
|
|
0
|
return entry_markup($self->entry($entry, $level)); |
729
|
|
|
|
|
|
|
} |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=item entry_stamped |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
Wraps entry() + a datestamp in entry_markup() |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
=cut |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
sub entry_stamped { |
738
|
14
|
|
|
14
|
1
|
18
|
my $self = shift; |
739
|
14
|
|
|
|
|
27
|
my ($entry, $level) = @_; |
740
|
|
|
|
|
|
|
|
741
|
14
|
|
|
|
|
27
|
return entry_markup( |
742
|
|
|
|
|
|
|
$self->entry($entry, $level) |
743
|
|
|
|
|
|
|
. $self->datestamp($entry) |
744
|
|
|
|
|
|
|
); |
745
|
|
|
|
|
|
|
} |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
=item entry_topic_list |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
Get a list of topics (by tag-* files) for the entry. This hardcodes a |
750
|
|
|
|
|
|
|
p1k3-specific thing, and is dumb. |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
=cut |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
sub entry_topic_list { |
755
|
17
|
|
|
17
|
1
|
19
|
my $self = shift; |
756
|
17
|
|
|
|
|
26
|
my ($entry) = @_; |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
# Location of entry on local filesystem, and its URL: |
759
|
17
|
|
|
|
|
49
|
my ($entry_loc, $entry_url) = $self->root_locations($entry); |
760
|
|
|
|
|
|
|
|
761
|
17
|
|
|
|
|
27
|
my @tag_files; |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
# If it's a directory, look for some tag property files: |
764
|
17
|
100
|
|
|
|
237
|
if (-d $entry_loc) { |
765
|
13
|
|
|
|
|
44
|
@tag_files = dir_list($entry_loc, 'alpha', '^tag-.*[.]prop$'); |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
|
768
|
17
|
100
|
|
|
|
99
|
return '' unless @tag_files; |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
return join ', ', map { |
771
|
4
|
|
|
|
|
6
|
s/^tag-(.*)[.]prop$/$1/; |
|
4
|
|
|
|
|
21
|
|
772
|
4
|
|
|
|
|
17
|
a($_, { href => '/topics/' . $_ }) |
773
|
|
|
|
|
|
|
} @tag_files; |
774
|
|
|
|
|
|
|
} |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
=item entry($entry) |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
Returns the contents of a given entry. Calls dir_list |
779
|
|
|
|
|
|
|
and icon_markup. Recursively calls itself. |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
=cut |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
sub entry { |
784
|
22
|
|
|
22
|
1
|
24
|
my $self = shift; |
785
|
22
|
|
|
|
|
29
|
my ($entry, $level) = @_; |
786
|
22
|
|
100
|
|
|
59
|
$level ||= 'index'; |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
# Location of entry on local filesystem, and its URL: |
789
|
22
|
|
|
|
|
41
|
my ($entry_loc, $entry_url) = $self->root_locations($entry); |
790
|
|
|
|
|
|
|
|
791
|
22
|
|
|
|
|
26
|
my $result; |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
# Display an icon, if we have one: |
794
|
22
|
100
|
|
|
|
36
|
if ( my $ico_markup = $self->icon_markup($entry) ) { |
795
|
8
|
|
|
|
|
37
|
$result .= heading($ico_markup, 2) . "\n\n"; |
796
|
|
|
|
|
|
|
} |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
# For text files: |
799
|
22
|
100
|
|
|
|
149
|
if (-f $entry_loc) { |
800
|
7
|
|
|
|
|
28
|
return $result . $self->fragment_slurp($entry_loc); |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
|
803
|
15
|
50
|
|
|
|
73
|
return $result if ! -d $entry_loc; |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
# Print index as head, if extant and a normal file: |
806
|
15
|
100
|
|
|
|
121
|
if (-f "$entry_loc/index") { |
807
|
13
|
|
|
|
|
39
|
$result .= $self->fragment_slurp("$entry_loc/index"); |
808
|
|
|
|
|
|
|
} |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
# Followed by any sub-entries: |
811
|
15
|
|
|
|
|
43
|
my @sub_entries = $self->get_sub_entries($entry_loc); |
812
|
|
|
|
|
|
|
|
813
|
15
|
100
|
|
|
|
38
|
if (@sub_entries >= 1) { |
814
|
|
|
|
|
|
|
# If the wrt-noexpand property is present, then don't expand |
815
|
|
|
|
|
|
|
# sub-entries. A hack. |
816
|
9
|
100
|
66
|
|
|
71
|
if ($level eq 'index' || -f "$entry_loc/wrt-noexpand.prop") { |
|
|
50
|
|
|
|
|
|
817
|
|
|
|
|
|
|
# Icons or text links: |
818
|
5
|
|
|
|
|
18
|
$result .= $self->list_contents($entry, @sub_entries); |
819
|
|
|
|
|
|
|
} |
820
|
|
|
|
|
|
|
elsif ($level eq 'all') { |
821
|
|
|
|
|
|
|
# Everything in the directory: |
822
|
4
|
|
|
|
|
8
|
foreach my $se (@sub_entries) { |
823
|
6
|
50
|
|
|
|
17
|
next if ($se =~ $self->binfile_expr); |
824
|
6
|
|
|
|
|
22
|
$result .= p({class => 'centerpiece'}, '+') |
825
|
|
|
|
|
|
|
. $self->entry("$entry/$se"); |
826
|
|
|
|
|
|
|
} |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
} |
829
|
|
|
|
|
|
|
|
830
|
15
|
|
|
|
|
81
|
return $result; |
831
|
|
|
|
|
|
|
} |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
sub get_sub_entries { |
834
|
15
|
|
|
15
|
0
|
20
|
my $self = shift; |
835
|
15
|
|
|
|
|
23
|
my ($entry_loc) = @_; |
836
|
|
|
|
|
|
|
|
837
|
15
|
|
|
|
|
39
|
my %ignore = ('index' => 1); |
838
|
|
|
|
|
|
|
|
839
|
15
|
|
|
|
|
34
|
return grep { ! $ignore{$_} } |
|
24
|
|
|
|
|
71
|
|
840
|
|
|
|
|
|
|
dir_list($entry_loc, 'alpha', $self->subentry_expr); |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
sub list_contents { |
844
|
5
|
|
|
5
|
0
|
8
|
my $self = shift; |
845
|
5
|
|
|
|
|
8
|
my ($entry) = shift; |
846
|
5
|
|
|
|
|
9
|
my (@entries) = @_; |
847
|
|
|
|
|
|
|
|
848
|
5
|
|
|
|
|
5
|
my $contents; |
849
|
5
|
|
|
|
|
12
|
foreach my $se (@entries) { |
850
|
5
|
|
|
|
|
15
|
my $linktext = $self->icon_markup("$entry/$se", $se); |
851
|
5
|
|
66
|
|
|
24
|
$linktext ||= $se; |
852
|
|
|
|
|
|
|
|
853
|
5
|
|
|
|
|
77
|
$contents .= q{ } |
854
|
|
|
|
|
|
|
. a({ href => $self->url_root . "$entry/$se", |
855
|
|
|
|
|
|
|
title => $se }, |
856
|
|
|
|
|
|
|
$linktext); |
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
|
859
|
5
|
|
|
|
|
12
|
return p( em('more:') . " $contents" ) . "\n"; |
860
|
|
|
|
|
|
|
} |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
=item icon_markup |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
Check if an icon exists for a given entry if so, return markup to include it. |
865
|
|
|
|
|
|
|
Icons are PNG or JPEG image files following a specific naming convention: |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
index.icon.[png|jp(e)g] for directories |
868
|
|
|
|
|
|
|
[filename].icon.[png|jp(e)g] for flat text files |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
Calls image_size, uses filename to determine type. |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
=cut |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
{ my %cache; |
875
|
|
|
|
|
|
|
sub icon_markup { |
876
|
29
|
|
|
29
|
1
|
30
|
my $self = shift; |
877
|
29
|
|
|
|
|
35
|
my ($entry, $alt) = @_; |
878
|
|
|
|
|
|
|
|
879
|
29
|
100
|
|
|
|
70
|
if ($cache{$entry . $alt}) { |
880
|
6
|
|
|
|
|
17
|
return $cache{$entry.$alt}; |
881
|
|
|
|
|
|
|
} |
882
|
|
|
|
|
|
|
|
883
|
23
|
|
|
|
|
44
|
my ($entry_loc, $entry_url) = $self->root_locations($entry); |
884
|
|
|
|
|
|
|
|
885
|
23
|
|
|
|
|
27
|
my ($icon_loc, $icon_url); |
886
|
|
|
|
|
|
|
|
887
|
23
|
100
|
|
|
|
275
|
if (-f $entry_loc) { |
|
|
50
|
|
|
|
|
|
888
|
8
|
|
|
|
|
17
|
$icon_loc = "$entry_loc.icon"; |
889
|
8
|
|
|
|
|
11
|
$icon_url = "$entry_url.icon"; |
890
|
|
|
|
|
|
|
} |
891
|
|
|
|
|
|
|
elsif (-d $entry_loc) { |
892
|
15
|
|
|
|
|
26
|
$icon_loc = "$entry_loc/index.icon"; |
893
|
15
|
|
|
|
|
24
|
$icon_url = "$entry_url/index.icon"; |
894
|
|
|
|
|
|
|
} |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
# First suffix found will be used: |
897
|
23
|
|
|
|
|
40
|
my (@suffixes) = qw(png jpg gif jpeg); |
898
|
23
|
|
|
|
|
24
|
my $suffix; |
899
|
23
|
|
|
|
|
30
|
for (@suffixes) { |
900
|
80
|
100
|
|
|
|
396
|
if (-e "$icon_loc.$_") { |
901
|
4
|
|
|
|
|
6
|
$suffix = $_; |
902
|
4
|
|
|
|
|
6
|
last; |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
# fail unless there's a file with one of the above suffixes |
907
|
23
|
100
|
|
|
|
69
|
return 0 unless $suffix; |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
# call image_size to slurp width & height from the image file |
910
|
4
|
|
|
|
|
22
|
my ($width, $height) = image_size("$icon_loc.$suffix"); |
911
|
|
|
|
|
|
|
|
912
|
4
|
|
|
|
|
9752
|
return $cache{$entry . $alt} = |
913
|
|
|
|
|
|
|
qq{<img src="$icon_url.$suffix"\n width="$width" } |
914
|
|
|
|
|
|
|
. qq{height="$height"\n alt="$alt" />}; |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
=item datestamp |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
Returns a nice html datestamp / breadcrumbs for a given entry. |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
=cut |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
sub datestamp { |
925
|
17
|
|
|
17
|
1
|
26
|
my $self = shift; |
926
|
17
|
|
|
|
|
26
|
my ($entry) = @_; |
927
|
|
|
|
|
|
|
|
928
|
17
|
|
|
|
|
17
|
my ($stamp); |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
# Chop up by directory separator. |
931
|
17
|
|
|
|
|
54
|
my @pieces = split '/', $entry; |
932
|
|
|
|
|
|
|
|
933
|
17
|
|
|
|
|
18
|
my (@fragment_stack); |
934
|
17
|
|
|
|
|
44
|
my (@fragment_stamps) = ( |
935
|
|
|
|
|
|
|
a({ href => $self->url_root }, $self->title_prefix), |
936
|
|
|
|
|
|
|
); |
937
|
|
|
|
|
|
|
|
938
|
17
|
|
|
|
|
39
|
foreach my $fragment (@pieces) { |
939
|
43
|
|
|
|
|
66
|
push @fragment_stack, $fragment; |
940
|
43
|
|
|
|
|
91
|
push @fragment_stamps, |
941
|
|
|
|
|
|
|
a({ href => $self->url_root . (join '/', @fragment_stack) . '/', |
942
|
|
|
|
|
|
|
title => $fragment }, $fragment); |
943
|
|
|
|
|
|
|
} |
944
|
|
|
|
|
|
|
|
945
|
17
|
|
|
|
|
40
|
$stamp = "\n" |
946
|
|
|
|
|
|
|
. $self->entry_topic_list($entry) |
947
|
|
|
|
|
|
|
. " :: " |
948
|
|
|
|
|
|
|
. join(" /\n", @fragment_stamps) |
949
|
|
|
|
|
|
|
. "\n"; |
950
|
|
|
|
|
|
|
|
951
|
17
|
|
|
|
|
69
|
return p({class => 'datelink'}, $stamp); |
952
|
|
|
|
|
|
|
} |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
=item fragment_slurp |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
Read a text fragment, call line_parse() and eval_perl() to take care of funky |
958
|
|
|
|
|
|
|
markup and interpreting embedded code, and then return it as a string. Takes |
959
|
|
|
|
|
|
|
one parameter, the name of the file, and returns '' if it's not an extant text |
960
|
|
|
|
|
|
|
file. |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
This might be the place to implement an in-memory cache for FastCGI or mod_perl |
963
|
|
|
|
|
|
|
environments. The trick is that the results for certain files shouldn't be |
964
|
|
|
|
|
|
|
cached because they contain embedded code. |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
=cut |
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
sub fragment_slurp { |
969
|
34
|
|
|
34
|
1
|
41
|
my $self = shift; |
970
|
|
|
|
|
|
|
|
971
|
34
|
|
|
|
|
48
|
my ($file) = @_; |
972
|
|
|
|
|
|
|
|
973
|
34
|
|
|
|
|
33
|
my $everything; |
974
|
|
|
|
|
|
|
|
975
|
34
|
50
|
|
|
|
699
|
open my $fh, '<', $file |
976
|
|
|
|
|
|
|
or warn "Couldn't open $file: $!\n"; |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
{ |
979
|
|
|
|
|
|
|
# line separator: |
980
|
34
|
|
|
|
|
53
|
local $/ = undef; |
|
34
|
|
|
|
|
120
|
|
981
|
34
|
|
|
|
|
429
|
$everything = <$fh>; |
982
|
|
|
|
|
|
|
} |
983
|
|
|
|
|
|
|
|
984
|
34
|
50
|
|
|
|
176
|
close $fh or warn "Couldn't close: $!"; |
985
|
|
|
|
|
|
|
|
986
|
34
|
50
|
|
|
|
100
|
return $self->line_parse( |
987
|
|
|
|
|
|
|
# handle embedded perl first |
988
|
|
|
|
|
|
|
($self->embedded_perl ? $self->eval_perl($everything) : $everything), |
989
|
|
|
|
|
|
|
$file # some context to work with |
990
|
|
|
|
|
|
|
); |
991
|
|
|
|
|
|
|
} |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
=item month_name |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
Turn numeric dates into English. |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
=cut |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
sub month_name { |
1001
|
2
|
|
|
2
|
1
|
5
|
my ($number) = @_; |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
# "Null" is here so that $month_name[1] corresponds to January, etc. |
1004
|
2
|
|
|
|
|
17
|
my @months = qw(Null January February March April May June |
1005
|
|
|
|
|
|
|
July August September October November December); |
1006
|
|
|
|
|
|
|
|
1007
|
2
|
|
|
|
|
8
|
return $months[$number]; |
1008
|
|
|
|
|
|
|
} |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=item root_locations($file) |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
Given a file/entry, return the appropriate concatenations with |
1013
|
|
|
|
|
|
|
entry_dir and url_root. |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
=cut |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
sub root_locations { |
1018
|
|
|
|
|
|
|
return ( |
1019
|
67
|
|
|
67
|
1
|
118
|
$_[0]->local_path($_[1]), |
1020
|
|
|
|
|
|
|
$_[0]->url_root . $_[1] |
1021
|
|
|
|
|
|
|
); |
1022
|
|
|
|
|
|
|
} |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
=item local_path |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
Return an absolute path for a given file. Called by root_locations. |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
Arguably this is stupid and inefficient. |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=cut |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
sub local_path { |
1033
|
71
|
|
|
71
|
1
|
135
|
return $_[0]->entry_dir . '/' . $_[1]; |
1034
|
|
|
|
|
|
|
} |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
=item feed_print |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
Return an Atom feed of entries for a month. Defaults to the most |
1039
|
|
|
|
|
|
|
recent month in the archive. |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
Called from handle(), requires XML::Atom::SimpleFeed. |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
=cut |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
sub feed_print { |
1046
|
1
|
|
|
1
|
1
|
2
|
my $self = shift; |
1047
|
1
|
|
|
|
|
2
|
my ($month) = @_; |
1048
|
1
|
|
33
|
|
|
9
|
$month ||= $self->recent_month(); |
1049
|
|
|
|
|
|
|
|
1050
|
1
|
|
|
|
|
4
|
my $feed_url = $self->url_root . $self->feed_alias; |
1051
|
|
|
|
|
|
|
|
1052
|
1
|
|
|
|
|
4
|
my ($month_file, $month_url) = $self->root_locations($month); |
1053
|
|
|
|
|
|
|
|
1054
|
1
|
|
|
|
|
4
|
my $feed = XML::Atom::SimpleFeed->new( |
1055
|
|
|
|
|
|
|
title => $self->title_prefix . '::' . $self->title, |
1056
|
|
|
|
|
|
|
link => $self->url_root, |
1057
|
|
|
|
|
|
|
link => { rel => 'self', href => $feed_url, }, |
1058
|
|
|
|
|
|
|
icon => $self->favicon_url, |
1059
|
|
|
|
|
|
|
author => $self->author, |
1060
|
|
|
|
|
|
|
id => $self->url_root, |
1061
|
|
|
|
|
|
|
generator => 'WRT.pm / XML::Atom::SimpleFeed', |
1062
|
|
|
|
|
|
|
updated => WRT::Date::iso_date(WRT::Date::get_mtime($month_file)), |
1063
|
|
|
|
|
|
|
); |
1064
|
|
|
|
|
|
|
|
1065
|
1
|
|
|
|
|
1440
|
my @entry_files; |
1066
|
|
|
|
|
|
|
|
1067
|
1
|
50
|
|
|
|
14
|
if (-d $month_file) { |
1068
|
1
|
|
|
|
|
7
|
@entry_files = dir_list($month_file, 'high_to_low', qr/^[0-9]{1,2}$/); |
1069
|
|
|
|
|
|
|
} else { |
1070
|
0
|
|
|
|
|
0
|
return 0; |
1071
|
|
|
|
|
|
|
} |
1072
|
|
|
|
|
|
|
|
1073
|
1
|
|
|
|
|
3
|
foreach my $entry_file (@entry_files) { |
1074
|
2
|
|
|
|
|
531
|
my $entry = "$month/$entry_file"; |
1075
|
2
|
|
|
|
|
4
|
my $entry_url = $month_url . "/$entry_file"; |
1076
|
2
|
|
|
|
|
3
|
my $title = $entry; |
1077
|
2
|
|
|
|
|
5
|
my $content = $self->entry($entry) . "\n" . $self->datestamp($entry); |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
# try to pull out a header: |
1080
|
2
|
|
|
|
|
15
|
my ($extracted_title) = $content =~ m{<h1>(.*?)</h1>}s; |
1081
|
2
|
|
|
|
|
7
|
my (@subtitles) = $content =~ m{<h2>(.*?)</h2>}sg; |
1082
|
|
|
|
|
|
|
|
1083
|
2
|
50
|
|
|
|
6
|
if ($extracted_title) { |
1084
|
2
|
|
|
|
|
3
|
$title = $extracted_title; |
1085
|
2
|
50
|
|
|
|
3
|
if (@subtitles) { |
1086
|
0
|
|
|
|
|
0
|
$title .= ' - ' . join ' - ', @subtitles; |
1087
|
|
|
|
|
|
|
} |
1088
|
|
|
|
|
|
|
} |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
$feed->add_entry( |
1091
|
2
|
|
|
|
|
9
|
title => $title, |
1092
|
|
|
|
|
|
|
link => $entry_url, |
1093
|
|
|
|
|
|
|
id => $entry_url, |
1094
|
|
|
|
|
|
|
content => $content, |
1095
|
|
|
|
|
|
|
updated => WRT::Date::iso_date(WRT::Date::get_mtime("$month_file/$entry_file")), |
1096
|
|
|
|
|
|
|
); |
1097
|
|
|
|
|
|
|
} |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
# return "Content-type: application/atom+xml\n\n" . $feed->as_string; |
1100
|
1
|
|
|
|
|
516
|
return $feed->as_string; |
1101
|
|
|
|
|
|
|
} |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
=back |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
=head1 SEE ALSO |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
walawiki.org, Blosxom, rassmalog, Text::Textile, XML::Atom::SimpleFeed, |
1109
|
|
|
|
|
|
|
Image::Size, CGI::Fast, and about a gazillion static site generators. |
1110
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
=head1 AUTHOR |
1112
|
|
|
|
|
|
|
|
1113
|
|
|
|
|
|
|
Copyright 2001-2017 Brennen Bearnes |
1114
|
|
|
|
|
|
|
|
1115
|
|
|
|
|
|
|
=head1 LICENSE |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
wrt is free software; you can redistribute it and/or modify |
1118
|
|
|
|
|
|
|
it under the terms of the GNU General Public License as published by |
1119
|
|
|
|
|
|
|
the Free Software Foundation; either version 2 of the License, or |
1120
|
|
|
|
|
|
|
(at your option) any later version. |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, |
1123
|
|
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
1124
|
|
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1125
|
|
|
|
|
|
|
GNU General Public License for more details. |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License |
1128
|
|
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
=cut |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
1; |