Subdirectories: NewFeatures.
2005-09-27
What Templates Can Contain
Templates are literal text except for four magic template expansions (call them substitutions or macros if you want):
${...},#{...},@{...}, and%{...}.Generally, it is a fatal error for any of the expansions not to work: undefined variables, missing templates, no renderer by the name you listed in the template, etc.
${...}inserts the value of the named global variable. There are three modifiers to variable expansion:
${|var1|var2|...}is alternatives: it inserts the value of the first of var1, var2, etc that are defined.${?...}is error-free expansion: it makes it not an error for the rest of the expansion to use undefined variables; instead an empty result is inserted.${?|...|..}works.${!...}is cancelling expansion: if the variable or variable sequence isn't defined, the whole template produces nothing.A
?or!modifier must come before a|modifier.Variable expansion always produces valid HTML-quoted results.
@{...}invokes the named renderer and inserts its output. That's it; renderers take no arguments (or guff).
%{...}invokes the named renderer and inserts its output, except that if the renderer produces no output the entire template will produce no output. Thus a template consisting ofLast modified: %{lastmodified} <br/>is entirely empty if the
lastmodifiedrenderer produces nothing, instead of being 'Last modified:' and a line break (which would look ugly).
#{...}is template inclusion: the named template is recursively expanded. Template names are just file names for files under the template root directory (set in DWiki's configuration file). There are three variations:
#{|t1|t2|...}is alternative expansion: it inserts the first of t1, t2, etc that expanded to something non-blank.#{?t1|t2|...}is conditional expansion: it only expands the additional templates if t1 expanded to something.#{<t1[|t2|...]}and#{!t1[|t2|...]}are first found expansion, and requires a longer explanation.First Found Expansion
First found expansion is a way of testing a number of possibly existing templates and using the first one that actually exists. With the
#{!...}form it is a fatal error for no template to be found; with the#{<...}form it is not, and the whole expansion is just empty.Each of the t1, t2, etc alternatives are paths, augmented with expansion operators. There are two:
$(<varname>)expands a global variable, like${...}at the template level. (Unlike${...}, the variable expansion is not HTML-quoted.)...<rest>first tries the full path<rest>, and then tries backing up to each of<rest>'s parent directories until they run out. (That's a literal three dots at the start.)An example may help. With a
$(pagename)ofdwiki/TemplateSyntaxand a$(view-format)ofnormal, the template inclusion#{<Overrides/...$(pagename)/magic.tmpl|default/$(view-format).tmpl}would first try Overrides/dwiki/TemplateSyntax/magic.tmpl, then Overrides/dwiki/magic.tmpl, then Overrides/magic.tmpl, and finally default/normal.tmpl.
An example:
This DWiki uses %{..} and #{|t1|t2} expansion to produce a nice message about a directory being entirely empty of pages if it is, instead of 'The following pages are available in this directory:' followed by nothing at all. (You can see this at Tests/SubTestDir.)
A simplified version of the template for directories is:
#{structure/header.tmpl} <h1> Directory ${pagename} </h1> #{|dir/dirconts.tmpl|dir/dirempty.tmpl} #{structure/footer.tmpl}The
dir/dirconts.tmpltemplate is:<p>The following pages are available in this directory: %{listdir}</p>while the the
dir/dirempty.tmpltemplate is:<p> This directory is empty. </p>The
%{listdir}in dirconts.tmpl makes the entire template empty if thelistdirrenderer returns nothing (ie, the directory is empty). Then the#{|..|..}sees that the first template is empty and goes on to use diremtpy.tmpl. If there are files in the directory, the dirconts.tmpl template has content and dirempty.tmpl does not get used.Available renders
For convenience (mostly ChrisSiebenmann's), here is the canonical list of all available renderers. This is generated by the code itself, so is is guaranteed to be 100% accurate (at least for names; your mileage may vary for documentation):
anchor::comment: Generate an anchor start for the current comment. You must close the anchor by hand.anchor::self: Generates an anchor start where the name is the full path to the current page. You must close the anchor by hand.anchor::short: Generates an anchor start where the name is the name of the current page. You must close the anchor by hand.atom::autodisc: Generate a suitable Atom feed autodiscovery<link>string, suitable for inclusion in the<head>section. Generates nothing if there is no Atom recent changes feed.atom::comment: Display the current comment in a way suitable for inclusion in an Atom feed.atom::commentfeed: Generate a link to the Atom comments feed for the current page, if comments are turned on.atom::commentid: Generate a hopefully unique ID for the current comment.atom::comments: Generate an Atom feed of recent comments on or below the current page. Each comment is rendered throughsyndication/atomcomment.tmpl. Supports VirtualDirectory restrictions, which limit which pages the feed will include comments for.atom::commentstamp: Generate an Atom feed format timestamp for the current comment.atom::commenturl: Generate the URL for the current comment.atom::dirfeed: Generate a link to the Atom feed for the current page if the current page is a directory or the wiki root.atom::feeds: Generate a comma-separated list of all Atom feed links, that are applicable for the current page.atom::feedtitle: Generate an Atom feed title for the current page.atom::feedurl: Generate the URL of this page for the current feed.atom::modstamp: Generate an Atom timestamp for the current page based on its change time.atom::now: Generate an Atom timestamp for right now.atom::pages: Generate an Atom feed of the current directory and all its descendants (showing only the most recent so many entries, newest first). Each page is rendered throughsyndication/atomentry.tmpl, which should result in a valid Atom feed entry. Supports VirtualDirectory restrictions.atom::pagetag: Generate an Atom entry ID. If theatomfeed-tagconfiguration option is not defined, this is the same as atom::pageurl. Ifatomfeed-tagis defined, the entry ID is <tag value>:/<page path>. Ifatomfeed-tag-timeis defined, only pages from after this time are given tag-based IDs; for pages before then, this is the same as atom::pageurl.atom::pageterse: Generate wikitext:terse run through a HTML entity quoter, thus suitable for use in Atom feeds.atom::pageterse:notitle: Generate wikitext:terse:notitle run through a HTML entity quoter, thus suitable for use in Atom feeds.atom::pageurl: Generate the URL of this page in its normal view.atom::recentcomment: Generate an Atom format timestamp for the most recent comment that will be displayed in a comment syndication feed.atom::recentpage: Generate an Atom format timestamp for an Atom page feed for the current directory (and all its descendants).atom::timestamp: Generate an Atom timestamp for the current page.auth::loginbox: Generate the form for a login or logout box. Generates nothing if DWiki authentication is disabled. As a side effect, kills page modification time if it generates anything.blog::blog: 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 theblog/blogentry.tmpltemplate. Supports VirtualDirectory restrictions.blog::blogdir: 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 templateblog/blogdirpage.tmpl. Supports VirtualDirectory restrictions.blog::date: Generates a YYYY-MM-DD timestamp of the current page.blog::datecrumbs: Create date breadcrumbs for the blog directory if the current page is in a blog directory but is not being displayed inside a virtual directory. The 'blog directory' is the directory that made the blog view the default view.blog::datemarker: 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.blog::namedate: Generate a Month DD, YYYY timestamp of the current page.blog::next:title: Create a link to the next page (if one exists) for the current page if the current page is in a blog directory but is not being displayed inside a virtual directory; the title of the link is the page's title if available. The 'blog directory' is the directory that made the blog view the default view.blog::owner: Display the owner of the current page.blog::prev:title: Create a link to the previous page (if one exists) for the current page if the current page is in a blog directory but is not being displayed inside a virtual directory; the title of the link is the page's title if available. The 'blog directory' is the directory that made the blog view the default view.blog::prevnext: Create Previous and Next links for the current page if the current page is in a blog directory but is not being displayed inside a virtual directory. The 'blog directory' is the directory that made the blog view the default view.blog::seemonthyear: 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.blog::seemore: 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.blog::time: Generate a YYYY-MM-DD HH:MM:SS timestamp of the current page.blog::timeofday: Generates a HH:MM:SS timestamp of the current page.blog::titles: Likeblog::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. Unlikeblog::blog, it always displays information for all applicable entries.breadcrumbs: Display a 'breadcrumbs' hierarchy of links from the DWiki root to the current page.comment::atomlink: Just likecomment::countlink, except that the URL is absolute and the HTML is escaped so that it can be used in an Atom syndication feed.comment::author: Display the author information for a comment, drawing on the given name, website URL, DWiki login, and comment IP address as necessary and available. Only works inside comment::showall. This potentially generates HTML, not just plain text.comment::comment: Display a particular comment. Only works inside comment::showall.comment::count: Display a count of comments for the current page.comment::countlink: Display the count of comments as a link to show them for the current page.comment::date: Display the date of a comment. Only works inside comment::showall.comment::form: Create the form for writing a new comment in, if the page is commentable by the current user.comment::pre: In a comment-writing context, generate a <pre> block of the comment being written.comment::preview: In a comment-writing context, show a preview of the comment being written.comment::showall: Display all of the comments for the current page (if any), using the templatecomment/comment.tmplfor each in succession.comment::user: Display the user who wrote a comment if it isn't the default DWiki user. Only works inside comment::showall.comment::write: Generate a link to start writing comments on the current page, if the current user can comment on the page.cond::anonymous: Suceeds (by generating a space) if this is an anonymous request, one with no logged in real user. Fails otherwise.cond::blogclipped: Succeeds (by generating a space) if we are in a blog view that is clipped. Fails otherwise.cond::blogyearmonth: Suceeds (by generating a space) if we are a directory, in a blog view, and we are in a month or year VirtualDirectory. Fails otherwise.cond::invirtual: Succeed (by generating a space) if we are in a VirtualDirectory (either directly or during rendering of a subpage). Fails otherwise.cond::notblogroot: Succeds (by generating a space) if we are a directory that is in a default blog view but is not the directory that made it the default view. Fails otherwise.cond::pageinblog: Succeeds (by rendering a space) if the current page is in a blog directory but is not being displayed inside a virtual directory (ie the page itself is being displayed). This also excludes 'utility' pages.dir::altviews: Generate a list of links to acceptable alternate ways to view the page if it is a directory.error::body: Generates the body for an error page from a template inerrors/, if the template exists; otherwise uses a default. Only usable during generation of an error page.error::title: Generate the title for an error from a template inerrors/, if the template exists; otherwise uses a default. Only usable during generation of an error page.hist::dirty: If the current page has been RCS-locked, display whether or not it has been modified from the version in RCS.hist::lockedby: If the current page is under RCS and is locked, display who has locked it.hist::revtable: If the current page is under RCS, display a version history table.inject::blogreadme: Likeinject::readme, except it looks for__readmeonly in the 'blog directory', the directory that made the blog view the default view. If there is no such directory between the current directory and the DWiki root directory, this does nothing.inject::index: Insert the wikitext file__indexin HTML form, if such a file exists in the current directory.inject::readme: Insert the wikitext file__readme, in HTML form, if such a file exists in the current directory.inject::upreadme: Likeinject::readme, except it searches for__readmeall the way back to the DWiki root directory, not just in the current directory.lastchangetime: Display the page's last change time, if it has one. The change time is taken from the inode ctime.lastmodified: 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.)linkhistory: Generate a link to this page's history called 'View History', if it has any.linknormal: 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.linkrelname: 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.linkshort: A link to this page, titled with the page's name.linkshortnormal: A link to this page in the normal view, titled with the page's name.linksource: Generate a link to this page's source called 'View Source', if it has any and you can see it.linktocomments: Create a link to this page that will show comments (if any). Otherwise the same aslinktonormal.linktonormal: A link to this page in the normal view, titled with the full page path.linktoself: A link to this page, titled with the full page path.listdir: List the contents of the current directory, with links to each page and subdirectory. Supports VirtualDirectory restrictions, but always shows subdirectories.listofdirs: Display a list of the subdirectories in the current directory.pagetools: 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.post::oldpage: Generate a link to the origin page for a POST request in a POST form context.range::bar: Display a simple range navigation bar inside a VirtualDirectory.range::blogrange: With blog::blog, generates a day navigation bar if the display of pages has been truncated.range::calbar: With blog::blog, generates a calendar-based navigation bar.range::moreclip: With blog::blog, generate a 'or back N more' link if the display of pages has been truncated outside of a VirtualDirectory context.readmore: Generate a 'Read more' link to this page.rooturl: Generate the URL to the root of this DWiki.rss2::pages: Generate a RSS 2.0 feed of the current directory and all its descendants (showing only the most recent so many entries, newest first). Each page is rendered throughsyndication/rss2entry.tmpl, which should result in a valid RSS 2.0 feed entry. Supports VirtualDirectory restrictions.rss2::recentpage: Generate an RSS 2.0 format timestamp for an RSS 2.0 page feed for the current directory (and all its descendants).rss2::timestamp: Generate a RSS 2.0 timestamp for the current page.search::display: Display the results of a search.search::enter: Create the search form, if searching is enabled.seterror:permissions: If we are rendering the top level page of a request (ie, not rendering a subpage for blog, blogdir, atom feed, etc context), mark this page as having a permission error. This causes the page to be reported as a HTTP 403 error.sitemap::minurlset: Generate a Google Sitemap set of <url> entities for the directory hierarchy starting at the current directory. Supports VirtualDirectory restrictions.wikitext: Convert wikitext into HTML.wikitext:cache: Convert wikitext into HTML but do not display the result; instead it is just cached for later (re)use. This has three effects. First, it makes variables like ${:wikitext:title} available (as do all other wikitext renderers). Second, it's somewhat more efficient if you intend to use a sequence of wikitext renderers, such as a title one followed by a text one. Third, it can be used as a conditional renderer to check permissions; this renderer succeeds (by generating a space) if permissions allow the wikitext to be displayed, and fails (generating nothing) if they don't.wikitext:firstpara: Convert wikitext into HTML, showing only the first paragraph (and the title) if this is possible. This renderer fails if there is no findable first paragraph. It honors the {{CutShort}} macro.wikitext:notitle: Convert wikitext into HTML but without the title.wikitext:short: Convert wikitext into HTML, honoring the {{CutShort}} macro.wikitext:terse: Convert wikitext into terse 'absolute' HTML, with all links fully qualified and no macros having any effect except CutShort, CanComment, IMG, and Restricted.wikitext:terse:notitle: Convert wikitext into terse 'absolute' HTML with all links fully qualified et al (as withwikitext:terse) but omit the title of the page, as withwikitext:notitle.wikitext:title: Generate and return the title of a wikitext page.wikitext:title:html: Generate and return the title of a wikitext page complete with its surrounding '<hN>' and '</hN>' tags.wikitext:title:nohtml: Generate and return the title of a wikitext page without HTML markup.wikitext:title:nolinks: Generate and return the title of a wikitext page without links.For quite a lot of these, the best real documentation is to see how they are used in the default DWiki template set. (Which is unfortunately a bit of a dark twisted maze at the moment.)
Renderers normally produce things about or from the current page, although some of them (for use in peculiar context) operate on other things. Unless otherwise specified, all renderers are silent if they can't produce something appropriate, which is handy for use in
%{....}or just in general.
2005-09-24
As part of fixing Atom feeds to not break embedded https:// urls, I decided that we should support plaintext https:// URLs, like say https://bugzilla.redhat.com/.
DWiki should now support non-HTTP URLs much better in general (before, there were a number of problems and issued). You can even include mailto: links if you really want to.
2005-09-15
There's a standard for autodiscovery of Atom feeds, involving
<link rel="alternate" type="application/atom+xml" href="...">
element in your <head>. Now DWiki has a atom::autodisc renderer to
create them.
The current code only generates 'recently changed pages' Atom feed
links, and so disappears entirely when there isn't one. In theory one
can have multiple autodiscoverable feeds (the first is the default,
and they get title="..." elements), but I don't quite feel like
being that daring just yet.
(I am also not confidant that clients have the UI issues involved sorted out. I'm not sure I have the issues sorted out; for example, should file pages have only the comment feed in the autodiscovery, or should they also have the recent changes for their directory feed in? Which better matches practical user expectations? Can I expect users to be aware of the difference between directory pages and file pages?)
2005-09-14
This is a little new renderer that creates a link to a page in the view necessary to show comments. In turn, this has caused the 'your comment has been posted' template page to be tarted up so that it uses it, thereby letting people who have posted comments see them in the page they go to.
(I have decided not to have it link to the comment section of the page, just on general principle. I may change my mind about this.)
2005-09-12
feed-max-size and feed-max-size-ipsThis is all because LiveJournal has undocumented size limits on incoming syndication feeds, limits that DWiki can easily blow past. Since I actually wanted LiveJournal to be able to get syndication feeds from me, DWiki has grown two new configuration settings.
feed-max-size is an integer kilobytes. It is a rough limit on how
large any feed can be; once DWiki generates a feed that is this many
kilobytes or larger it stopps adding more entries, regardless of the
setting for atomfeed-display-howmany. If unset, there is no size
limit.
feed-max-size-ips restricts feed-max-size to the whitespace
separated list of IP addresses or tcpwrappers style IP address
prefixes (eg '66.150.15.' to get all of 66.150.15.*).
Syndication fetches from other addresses will behave as if there was
no feed-max-size.
Strictly speaking, feed-max-size limits only the size of the
atom::pages or atom::comments output to that size. Whatever else
is tacked on to make up a feed (hopefully not very big) will add some
extra size.
Moral: undersize feed-max-size a bit. For LiveJournal, the limit is
apparently 150 kilobytes (currently), so setting it to '120' or so
should provide a comfortable safety margin.
Although I'm not entirely fond of this (to put it one way), the documentation has been updated appropriately, making this feature more or less official.
2005-09-03
/oldest/ virtual directory restrictionDWiki has long been able to give people the latest N things in a
virtual directory context (as latest/<N>). Now it can give them the
oldest N things, using the obvious syntax: oldest/<howmany>.
Just to show off, ranges properly convert themselves into 'oldest/<N>' at the end of their run, just as they convert themselves into 'latest/<N>' at the start.
Documentation has been updated appropriately.
Virtual Directories in DWiki
A virtual directory is a way of restricting what pages get shown out of a real directory. It works by tacking on 'virtual' directories after the real directory (ie, as subdirectories) to tell DWiki what you want to see.
Virtual directories restrict pages based on their most recent modification time. There are three versions available:
- calendar: with the format
<year>/[<month>/[<day>]], all as digits. Only pages most recently changed in the time period get selected.- latest: with the format
latest/<howmany>. They show just the most recently changed<howmany>pages.- oldest: with the format
oldest/<howmany>. They show just the least recently changed<howmany>pages.- range: with the format
range/<start>-<end>. They show the start'th to the end'th most recently changed page.All pieces of a virtual directory must really be virtual. If you have a directory
Foo/with aFoo/2005/subdirectory (or file), you cannot use the virtual directoryFoo/2005/05/to see things from May of 2005 inFoo/. Moral: let DWiki organize things based on time for you, don't do it yourself.Virtual directories are paid attention to by some renderers, which are generally used in some views. You can get the full list in TemplateSyntax.
Over the past while it has become increasingly obvious that it's
useful for as many responses as possible to carry a Last-Modified:
header. (The last straw was wanting Google's index to show
modification dates for DWiki pages.)
My reason for killing Last-Modified: was so that things like logging
in and logging out, which can't be reflected in the timestamp, would
still have conditional GETs be served new pages. But since the
conditional GET logic is in DWiki itself, I can have DWiki be
smarter about it.
DWiki now separates the page timestamp from the idea of whether the
page timestamp is reliable or simply vaguely useful information. The
page timestamp will always be served if it exists at all, but
conditional GETs only look at the page timestamp if it's reliable
(which means that if authentication is on, the answer is generally
'not').
This should work much better.