#
# HTML renderers register themselves here.
# (This is not in the HTML view stuff because of import recursion issues)
# Who is registered is a global setting.
# All renderers are called the same way: renderer(context)
# They return the result or raise derrors.RendErr if they fail
# (sufficiently badly).
# Note that wiki rendering has to do additional work to get the data
# it needs (off the page).
# Note that template handling is not considered a renderer, because
# it requires an additional argument (the template name) so it is handled
# out of line.
import time, urllib
import derrors, httputil
reg_renderers = {}
def register(name, callable):
reg_renderers[name] = callable
def get_renderer(name):
if name in reg_renderers:
return reg_renderers[name]
else:
raise derrors.RendErr("renderer '%s' not available" % name)
def all_renderers():
return reg_renderers.keys()
#
# ---
# Some HTML renderers too small to call for their own file(s).
#
# So many people use this that I should make it a utility routine.
def makelink(text, dest, nofollow = False, ctype = None):
if not text:
text = '/'
nfstr = ''; ctstr = ''
if nofollow:
nfstr = ' rel="nofollow"'
if ctype:
ctstr = ' type="%s"' % ctype
return '%s' % (ctstr, dest, nfstr,
httputil.quotehtml(text))
# Create a little set of 'breadcrumbs' links, presumably at the top.
# The wiki's root is always a link, even if we are at the Wiki's root
# page (whatever that is), because I think that's more consistent.
def breadcrumbs(context):
"""Display a 'breadcrumbs' hierarchy of links from the DWiki root
to the current page."""
result = []
result.append('')
result.append(makelink(context.cfg['wikiname'],
context.web.url_from_path("")))
# We don't generate breadcrumbs at the root.
# We need to check context.page.path too because we may be
# rendering for the root directory in some contexts.
if context.page.path != context.wiki_root() and context.page.path:
tl = []
curpage = context.page
while curpage.path != '':
tl.append(curpage)
curpage = curpage.parent()
tl.reverse()
last = tl.pop()
skippingPages = False
for page in tl:
if not page.exists() and skippingPages:
continue
result.append(" »\n ")
if not page.exists():
result.append("....")
skippingPages = True
continue
# Virtual pages breadcrumb in the current view,
# because this is what you really want.
if page.virtual():
result.append(makelink(page.name,
context.url(page)))
else:
result.append(makelink(page.name,
context.nurl(page)))
# Last entry is not a link; it's where we *are*.
# Making it a link is a) redundant and b) slightly confusing
# and c) not how I've done breadcrumbs by hand in the past.
# I like my old way, so we do it this way automatically.
result.append(" »\n ")
result.append(httputil.quotehtml(last.name))
result.append("")
return ''.join(result)
register('breadcrumbs', breadcrumbs)
# This is sort of the inverse of breadcrumbs: everywhere we can go down.
# This is more or less like wikirend's macro_ListDir.
def listofdirs(context):
"""Display a list of the subdirectories in the current directory."""
curdir = context.page.curdir()
# Fancy how this ... just works.
fc = curdir.children("dir")
if not fc:
return ''
fc = [makelink(z.name, context.url(z)) for z in fc]
return ', '.join(fc)
register('listofdirs', listofdirs)
# Render a link to this page, using either the full path or the
# page name as the link text.
# ISSUE: should we use .url or .nurl? Not sure.
def linktoself(context):
"""A link to this page, titled with the full page path."""
return makelink(context.page.me().path, context.url(context.page.me()))
register("linktoself", linktoself)
def shortlink(context):
"""A link to this page, titled with the page's name."""
return makelink(context.page.me().name, context.url(context.page.me()))
register("linkshort", shortlink)
# Shortlink, but forced to the normal view.
def nshortlink(context):
"""A link to this page in the normal view, titled with the page's
name."""
return makelink(context.page.me().name,
context.nurl(context.page.me()))
register("linkshortnormal", nshortlink)
def linktonormalself(context):
"""A link to this page in the normal view, titled with the full
page path."""
return makelink(context.page.me().path,
context.nurl(context.page.me()))
register("linktonormal", linktonormalself)
def commentlink(context):
"""Create a link to this page that will show comments (if any).
Otherwise the same as _linktonormal_."""
url = context.url(context.page, context.comment_view())
return makelink(context.page.me().path, url)
register("linktocomments", commentlink)
# Uses the 'relname' context variable, if defined, and otherwise
# punts to linktoself.
def relnamelink(context):
"""Inside blog::blog, generate a link to this page titled with
the page's path relative to the blog::blog page. Outside that
context, the same as linktoself."""
if 'relname' not in context:
return linktoself(context)
return makelink(context['relname'], context.url(context.page.me()))
register("linkrelname", relnamelink)
def rooturl(context):
"""Generate the URL to the root of this DWiki."""
return context.url(context.model.get_page(""))
register("rooturl", rooturl)
# Creates an anchor for the current page's name.
def anchorself(context):
"""Generates an anchor *start* where the name is the full path
to the current page. You must close the anchor by hand."""
return '' % urllib.quote(context.page.path)
register("anchor::self", anchorself)
def anchorshort(context):
"""Generates an anchor *start* where the name is the name of
the current page. You must close the anchor by hand."""
return '' % urllib.quote(context.page.name)
register("anchor::short", anchorshort)
# ---
# Link tools: links to various other formats (source, history, etc).
def linktosource(context):
"""Generate a link to this page's source called 'View Source', if it
has any and you can see it."""
if not context.page.has_source() or \
not context.page.displayable() or \
not context.page.access_ok(context):
return ''
return makelink("View Source", context.url(context.page, "source"),
True)
register("linksource", linktosource)
def linktohistory(context):
"""Generate a link to this page's history called 'View History', if
it has any."""
if not context.page.hashistory():
return ''
if context['view-format'] == "history":
return ''
return makelink("View History", context.url(context.page, "history"))
register("linkhistory", linktohistory)
def linktonormal(context):
"""Generate a link to this page's normal view called 'View Normal'
if it is a file and we are not displaying it in normal view."""
if context.page.type != "file" or \
context['view-format'] == "normal":
return ''
return makelink("View Normal", context.nurl(context.page))
register("linknormal", linktonormal)
# Link to all the alternative views of directories than the current one.
def linktoaltdirviews(context):
"""Generate a list of links to acceptable alternate ways to view
the page if it is a directory."""
if context.page.type != "dir":
return ''
curview = context.view
pv = context.pref_view(context.page)
altlist = context.web.all_dir_views()
# If the current view is not in the directory views, we are
# up to something funny in the land of GET and POST.
# (The specific case that triggered this is search, which uses
# a synthetic view hooked on the root.)
if curview not in altlist:
return ''
altlist.remove(curview)
altlist.sort()
res = []
for view in altlist:
if not context.web.page_view_ok(context, context.page, view):
continue
# We mark directory links to non-default formats as
# nofollow, to keep Google and friends from indexing
# redundant data repeatedly (especially since it is
# the non-preferred format).
if view == pv or \
view == "normal" and not pv:
nurl = context.nurl(context.page)
nf = False
else:
nurl = context.url(context.page, view)
nf = True
view = view.capitalize()
res.append(makelink("See As %s" % view, nurl, nf))
return ', '.join(res)
register("dir::altviews", linktoaltdirviews)
# Write Comment link.
def writecomment(context):
"""Generate a link to start writing comments on the current page,
if the current user can comment on the page."""
if not context.page.comment_ok(context):
return ''
curl = context.url(context.page, "writecomment")
return makelink("Add Comment", curl, True)
register("comment::write", writecomment)
ptools = (linktosource, linktohistory, linktonormal,
linktoaltdirviews, writecomment, )
def pagetools(context):
"""Generate a comma-separated list of all 'page tools' links,
such as 'View Source' and alternate directory views, that are
applicable to the current page."""
res = []
for pt in ptools:
res.append(pt(context))
res = [x for x in res if x]
return ', '.join (res)
register("pagetools", pagetools)
# The 'last modified' time that we display here is the last modified time
# of the *page file*, not the last modified time that will be splurted out
# as part of the HTTP response, because this is a lot more useful (if less
# show-offy).
# We use the ctime instead of the mtime for obscure reasons having to do
# with editing blog entries without changing their modtime.
def lastmod(context):
"""Display the page's last modification time, if it has one. (This
is not the same as the last-modified time that the HTTP response
will have, which is taken from *all* of the pieces that contribute
to displaying the page, including all templates.)"""
# Not displayed for directories except in the normal view.
if context.page.type == "dir" and context.view != "normal":
return ''
ts = context.page.timestamp
if ts is not None and ts > 0:
return time.asctime(time.localtime(ts))
else:
return ''
register("lastmodified", lastmod)
def lastctime(context):
"""Display the page's last change time, if it has one. The change
time is taken from the inode ctime."""
ts = context.page.modstamp
if ts > 0:
return time.asctime(time.localtime(ts))
else:
return ''
register("lastchangetime", lastctime)
def readmore(context):
"""Generate a 'Read more' link to this page."""
return 'Read more »' % \
context.url(context.page.me())
register("readmore", readmore)