-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathblogdir.py
More file actions
413 lines (383 loc) · 13.7 KB
/
blogdir.py
File metadata and controls
413 lines (383 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
#
# Support a 'blog' style view of a directory.
import time
import derrors, htmlrends, template, views
import pageranges
import httputil
def blogtime(context):
"""Generate a YYYY-MM-DD HH:MM:SS timestamp of the current page."""
ts = context.page.timestamp
if ts:
dstr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts))
return dstr
else:
return ''
htmlrends.register("blog::time", blogtime)
# Just year-month-day
def blogdate(context):
"""Generates a YYYY-MM-DD timestamp of the current page."""
ts = context.page.timestamp
if ts:
dstr = time.strftime("%Y-%m-%d", time.localtime(ts))
return dstr
else:
return ''
htmlrends.register("blog::date", blogdate)
def blognamedate(context):
"""Generate a Month DD, YYYY timestamp of the current page."""
ts = context.page.timestamp
if ts:
return time.strftime("%B %e, %Y", time.localtime(ts))
else:
return ''
htmlrends.register("blog::namedate", blognamedate)
def blogtod(context):
"""Generates a HH:MM:SS timestamp of the current page."""
ts = context.page.timestamp
if ts:
dstr = time.strftime("%H:%M:%S", time.localtime(ts))
return dstr
else:
return ''
htmlrends.register("blog::timeofday", blogtod)
# blogdate, but generated on a rolling basis; we don't generate it
# if we've already generated this string and it's in the context.
# (Except we store it in a parent object so it will persist across
# cloned contexts. Maybe we should introduce the idea of a parent
# context.)
# FIXME: really, we should.
rollvar = ":blog:entrystore"
def rollingdate(context):
"""Inside a blog::blog or blog::blogdir rendering, generate a
YYYY-MM-DD date stamp for the current page *if* this has changed
from the last page; otherwise, generates nothing."""
res = blogdate(context)
if not res:
return ''
if rollvar in context and \
'date' in context[rollvar] and \
context[rollvar]['date'] == res:
return ''
else:
if rollvar in context:
context[rollvar]['date'] = res
return res
htmlrends.register("blog::datemarker", rollingdate)
# This may have to grow more complicated in the future, but for now
# I laugh madly and sadly and say 'no, I think not'; we use Unix
# ownership.
def pageowner(context):
"""Display the owner of the current page."""
owner = context.page.owner()
if owner:
return owner
else:
return ''
htmlrends.register("blog::owner", pageowner)
#
# This is the complicated renderer. It renders all of the pages in
# a directory into a single (potentially horking big) blob, using
# a 'blog/blogdirpage.tmpl' template for each of them. Entries are sorted
# by modtime, newest first.
def blogdir(context):
"""Generate a BlogDir rendering of the current directory:
display all real pages in the current directory from most
recent to oldest, rendering each with the template
_blog/blogdirpage.tmpl_. Supports VirtualDirectory restrictions."""
if context.page.type != "dir":
return ''
if not context.page.displayable():
raise derrors.IntErr("undisplayable directory page")
dl = context.page.children("file")
if not dl:
return ''
# Boil it down to just files, in modtime order.
# (Technically this is real files: displayable, non-redirect.)
dl = [z for z in dl if z.realpage() and not z.is_util()]
# This directory might have nothing.
if not dl:
return ''
# Apply restrictions, backwardly.
# Because restrictions are designed to be hugely scalable, they
# want the output we'd get from page.descendants().
if pageranges.is_restriction(context):
tl = pageranges.filter_files(context, [(z.timestamp, z.path)
for z in dl])
if not tl:
return ''
dl = [context.model.get_page(z[1]) for z in tl]
else:
dl.sort(key=lambda x: x.timestamp, reverse=True)
# For each file, clone the context, set the current page to
# it (!), and render it with the blogentry template.
to = context.model.get_template("blog/blogdirpage.tmpl")
context.setvar(rollvar, {})
res = []
for page in dl:
# Note: we do not reset the view type, because we do
# not render through the interface that cares; we go
# straight to template.
nc = context.clone_to_page(page)
res.append(template.Template(to).render(nc))
context.newtime(nc.modtime)
return ''.join(res)
htmlrends.register("blog::blogdir", blogdir)
# Regular directory listing goes here for obscure reasons.
# Render a directory, with help from the model.
def directory(ctx):
"""List the contents of the current directory, with links to each
page and subdirectory. Supports VirtualDirectory restrictions, but
always shows subdirectories."""
res = []
if ctx.page.type != "dir" or not ctx.page.displayable():
return ''
# Just in case:
ctx.newtime(ctx.page.timestamp)
dl = ctx.page.children()
if not dl:
return ''
# Restrict the results if we've been asked to. This is complicated
# by our need to preserve directories *always*, because we don't
# know if they have any files within the restriction inside them.
if pageranges.is_restriction(ctx):
dirl = [z for z in dl if z.type == 'dir']
tl = pageranges.filter_files(ctx, [(z.timestamp, z.path)
for z in dl
if z.type != 'dir'])
if not tl and not dirl:
return ''
dl = [ctx.model.get_page(z[1]) for z in tl]
dl.extend(dirl)
dl.sort(key=lambda x: x.name)
res.append("<ul>")
for de in dl:
res.append("\n<li> ")
res.append(htmlrends.makelink(de.name, ctx.url(de)))
res.append("\n</ul>\n")
return ''.join(res)
htmlrends.register("listdir", directory)
#
# ---------------
# If we are not using a specific limit restriction or a day
# restriction and there is 'too much', clip us down and note
# this fact. We clip only at day boundaries, because when we
# drill down to the days we will show that many entries on
# a page anyways.
def clipDown(context, dl):
if 'blog-display-howmany' in context:
cutpoint = context['blog-display-howmany']
else:
cutpoint = TOPN_NUMBER
if len(dl) > cutpoint and \
(not pageranges.is_restriction(context) or \
pageranges.restriction(context) in ('year', 'month')):
t1 = time.localtime(dl[cutpoint-1][0])
i = cutpoint
while i < len(dl):
t2 = time.localtime(dl[i][0])
if t1.tm_mday != t2.tm_mday:
break
i += 1
if i < len(dl):
context.setvar(":blog:clippedrange", dl[i])
context.setvar(":blog:clipsize", i)
dl = dl[:i]
return dl
# 'blog' support.
# This is like blogdir but with two differences:
# First, we look at all descendants, not just direct children.
# And second, unless we are restricted to days, ranges, or latest, we
# only show the top 'n' of the result.
TOPN_NUMBER = 10
def blogview(context):
"""Generate a Blog rendering of the current directory: all
descendant real pages, from most recent to oldest, possibly
truncated at a day boundary if there's 'too many', and sets
up information for blog navigation renderers. Each
displayed page is rendered with the _blog/blogentry.tmpl_
template. Supports VirtualDirectory restrictions."""
if context.page.type != "dir":
return ''
if not context.page.displayable():
raise derrors.IntErr("undisplayable directory page")
# This automatically applies restrictions.
dl = context.cache_page_children(context.page)
if not dl:
return ''
dl = clipDown(context, dl)
# For each file, clone the context, set the current page to
# it (!), and render it with the blogentry template.
to = context.model.get_template("blog/blogentry.tmpl")
# Set up our rolling storage. We can't hook directly onto
# the context, because we drop the context after every
# entry. So we write our own dictionary into the context.
context.setvar(rollvar, {})
res = []
rootpath = context.page.me().path
dupDict = {}
for ts, path in dl:
# Skip redirects and other magic files.
# We go whole hog and drop anything that is not a real page,
# which subsumes both redirect pages and non-displayable
# pages. Since attempting to render a non-displayable page
# is a fatal internal error, we must drop them before we
# go to the template.
np = context.model.get_page(path)
if not np.realpage() or np.is_util():
continue
# Suppress multiple occurrences of the same page as
# may happen with, eg, hardlinks. Note that this is
# slightly questionable; macros mean that a file's
# rendering output may depend on not just its contents
# but its position in the file hierarchy. We don't
# care.
pageid = np.identity()
if pageid in dupDict:
continue
else:
dupDict[pageid] = True
# Note: we do not reset the view type, because we do
# not render through the interface that cares; we go
# straight to template.
nc = context.clone_to_page(np)
nc.setvar('relname', path[len(rootpath)+1:])
res.append(template.Template(to).render(nc))
context.newtime(nc.modtime)
return ''.join(res)
htmlrends.register("blog::blog", blogview)
def link_to_tm(context, tm, plain = None):
suf = "%d/%02d/%02d" % (tm.tm_year, tm.tm_mon, tm.tm_mday)
if not plain:
plain = suf
page = context.model.get_virtual_page(context.page.me(), suf)
return htmlrends.makelink(plain, context.url(page))
def cutoff(context):
"""With blog::blog, generates a 'see more' link to the date of
the next entry if the display of pages has been truncated; the
text of the link is the target date. This renderer is somewhat
misnamed."""
if ":blog:clippedrange" not in context:
return ''
rv = context[":blog:clippedrange"]
t = time.localtime(rv[0])
# We could really use anchors here.
return link_to_tm(context, t)
htmlrends.register("blog::seemore", cutoff)
def month_cutoff(context):
"""With blog::blog, generate a 'see more' set of links for the
month and the year of the next entry if the display of pages has
been truncated."""
if ":blog:clippedrange" not in context:
return ''
rv = context[":blog:clippedrange"]
t = time.localtime(rv[0])
l1 = pageranges.gen_monthlink(context, t.tm_year, t.tm_mon)
pg = context.model.get_virtual_page(context.page.me(), "%d" % t.tm_year)
l2 = htmlrends.makelink("%d" % t.tm_year, context.url(pg))
return "%s %s" % (l1, l2)
htmlrends.register("blog::seemonthyear", month_cutoff)
# This plays around with the innards of here, and as such is not
# in conditions.py.
def isclipped(context):
"""Succeeds (by generating a space) if we are in a blog
view that is clipped. Fails otherwise."""
if context.view != "blog":
return ''
dl = context.cache_page_children(context.page)
if not dl:
return ''
# We clone the context so that any clip variables that
# clipDown() will set do not contaminate the current
# context. Various renderers consider their presence
# a sign, and we may not want that.
d2 = clipDown(context.clone(), dl)
if len(d2) != len(dl):
return ' '
else:
return ''
htmlrends.register("cond::blogclipped", isclipped)
#
# Generate a blog-style index of page titles, by (reverse) date. This
# is hard to format in separate renderers, so it actually hardcodes
# more than usual. We would like to do this in a <dl> instead of a
# table, except that Firefox does not support 'display: compact'
# and I consider that essential.
def titleindex(context):
"""Like _blog::blog_, except that instead of rendering entries
through a template, it just displays a table of dates and entry
titles (or relative paths for entries without titles), linking
to entries and to the day pages. Respects VirtualDirectory
restrictions. Unlike _blog::blog_, it always displays information
for all applicable entries."""
if context.page.type != "dir":
return ''
if not context.page.displayable():
raise derrors.IntErr("undisplayable directory page")
# This automatically applies restrictions.
dl = context.cache_page_children(context.page)
if not dl:
return ''
# Building a table is unfortunately much more complicated
# than a <dl> would be, because we have to use <br> to separate
# multiple entries for the same day instead of <td>, which means
# that we have to keep track of when we need to generate one and
# so on.
rl = ['<table class="blogtitles">\n',]
lday = None
dupDict = {}
rootpath = context.page.me().path
# Rather than directly use a wikirend routine by importing
# it, we indirect through the renderer registration. Since
# either way we know a wikirend name, I figure this is no
# worse.
rfunc = htmlrends.get_renderer("wikitext:title:nolinks")
for ts, path in dl:
# FIXME: this duplication is code smell.
np = context.model.get_page(path)
if not np.realpage() or np.is_util() or \
not np.access_ok(context):
continue
pageid = np.identity()
if pageid in dupDict:
continue
else:
dupDict[pageid] = None
# Do we need to generate a new row for a new day?
# Our basic running state is that we are always in
# a <td> for page links (except right at the start),
# so we must close it off et cetera and then reopen
# it.
t = time.localtime(ts)
plain = "%d-%02d-%02d" % (t.tm_year, t.tm_mon, t.tm_mday)
if plain != lday:
if lday:
# Not first entry ever, so close off
# the last day table row.
rl.append("\n</td> </tr>\n")
rl.append("<tr> <td> %s: </td> <td>\n" % \
link_to_tm(context, t, plain))
lday = plain
else:
# If we are the second or later entry for a
# given day, we must put a <br> between ourselves
# and the previous entry.
rl.append("<br>\n")
# As usual, we must work in a new context.
nc = context.clone_to_page(np)
ltitle = rfunc(nc)
if not ltitle:
ltitle = httputil.quotehtml(path[len(rootpath)+1:])
# We can't use htmlrends.makelink() because that would
# quote the live HTML in real titles.
rl.append(' <a href="%s">%s</a>' % \
(context.nurl(np), ltitle))
context.newtime(nc.modtime)
# Done all; close off the <table>
rl.append('\n</td></tr></table>\n')
return ''.join(rl)
htmlrends.register("blog::titles", titleindex)
# View registration.
views.register('blog', views.TemplateView, onDir = True, onFile = False,
pubDir = True)
views.register('blogdir', views.TemplateView, onDir = True, onFile = False,
pubDir = True)