Merge branch 'ready/templatebody'
authorSimon McVittie <smcv@debian.org>
Mon, 15 Sep 2014 20:52:03 +0000 (21:52 +0100)
committerSimon McVittie <smcv@debian.org>
Mon, 15 Sep 2014 20:52:03 +0000 (21:52 +0100)
16 files changed:
IkiWiki.pm
IkiWiki/Plugin/templatebody.pm [new file with mode: 0644]
IkiWiki/Render.pm
doc/ikiwiki/directive/edittemplate.mdwn
doc/ikiwiki/directive/template.mdwn
doc/ikiwiki/directive/templatebody.mdwn [new file with mode: 0644]
doc/plugins/templatebody.mdwn [new file with mode: 0644]
doc/plugins/write.mdwn
doc/templates.mdwn
doc/templates/gitbranch.mdwn
doc/templates/links.mdwn
doc/templates/note.mdwn
doc/templates/plugin.mdwn
doc/templates/popup.mdwn
po/po2wiki
t/templatebody.t [new file with mode: 0755]

index 4ad365c1d90991e9098ea2439c231b46d4c43ee1..d5d11ee857c02b1520fc6a971032cc592df78d23 100644 (file)
@@ -14,7 +14,7 @@ use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase
        %pagestate %wikistate %renderedfiles %oldrenderedfiles
        %pagesources %delpagesources %destsources %depends %depends_simple
        @mass_depends %hooks %forcerebuild %loaded_plugins %typedlinks
-       %oldtypedlinks %autofiles @underlayfiles $lastrev};
+       %oldtypedlinks %autofiles @underlayfiles $lastrev $phase};
 
 use Exporter q{import};
 our @EXPORT = qw(hook debug error htmlpage template template_depends
@@ -34,6 +34,11 @@ our $DEPEND_CONTENT=1;
 our $DEPEND_PRESENCE=2;
 our $DEPEND_LINKS=4;
 
+# Phases of processing.
+sub PHASE_SCAN () { 0 }
+sub PHASE_RENDER () { 1 }
+$phase = PHASE_SCAN;
+
 # Optimisation.
 use Memoize;
 memoize("abs2rel");
@@ -152,7 +157,8 @@ sub getsetup () {
                type => "internal",
                default => [qw{mdwn link inline meta htmlscrubber passwordauth
                                openid signinedit lockedit conditional
-                               recentchanges parentlinks editpage}],
+                               recentchanges parentlinks editpage
+                               templatebody}],
                description => "plugins to enable by default",
                safe => 0,
                rebuild => 1,
@@ -2022,11 +2028,19 @@ sub template_depends ($$;@) {
        if (defined $page && defined $tpage) {
                add_depends($page, $tpage);
        }
-       
+
        my @opts=(
                filter => sub {
                        my $text_ref = shift;
                        ${$text_ref} = decode_utf8(${$text_ref});
+                       run_hooks(readtemplate => sub {
+                               ${$text_ref} = shift->(
+                                       id => $name,
+                                       page => $tpage,
+                                       content => ${$text_ref},
+                                       untrusted => $untrusted,
+                               );
+                       });
                },
                loop_context_vars => 1,
                die_on_bad_params => 0,
diff --git a/IkiWiki/Plugin/templatebody.pm b/IkiWiki/Plugin/templatebody.pm
new file mode 100644 (file)
index 0000000..3848b75
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+# Define self-documenting templates as wiki pages without HTML::Template
+# markup leaking into IkiWiki's output.
+# Copyright © 2013-2014 Simon McVittie. GPL-2+, see debian/copyright
+package IkiWiki::Plugin::templatebody;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+use Encode;
+
+sub import {
+       hook(type => "getsetup", id => "templatebody", call => \&getsetup);
+       hook(type => "preprocess", id => "templatebody", call => \&preprocess,
+               scan => 1);
+       hook(type => "readtemplate", id => "templatebody",
+               call => \&readtemplate);
+}
+
+sub getsetup () {
+       return
+               plugin => {
+                       safe => 1,
+                       rebuild => undef,
+                       section => "core",
+               },
+}
+
+# This doesn't persist between runs: we're going to read and scan the
+# template file regardless, so there's no point in saving it to the index.
+# Example contents:
+# ("templates/note" => "<div class=\"notebox\">\n<TMPL_VAR text>\n</div>")
+my %templates;
+
+sub preprocess (@) {
+       my %params=@_;
+
+       # [[!templatebody "<div>hello</div>"]] results in
+       # preprocess("<div>hello</div>" => undef, page => ...)
+       my $content = $_[0];
+       if (length $_[1]) {
+               error(gettext("first parameter must be the content"));
+       }
+
+       $templates{$params{page}} = $content;
+
+       return "";
+}
+
+sub readtemplate {
+       my %params = @_;
+       my $tpage = $params{page};
+       my $content = $params{content};
+       my $filename = $params{filename};
+
+       # pass-through if it's a .tmpl attachment or otherwise unsuitable
+       return $content unless defined $tpage;
+       return $content if $tpage =~ /\.tmpl$/;
+       my $src = $pagesources{$tpage};
+       return $content unless defined $src;
+       return $content unless defined pagetype($src);
+
+       # We might be using the template for [[!template]], which has to run
+       # during the scan stage so that templates can include scannable
+       # directives which are expanded in the resulting page. Calls to
+       # IkiWiki::scan are in arbitrary order, so the template might
+       # not have been scanned yet. Make sure.
+       require IkiWiki::Render;
+       IkiWiki::scan($src);
+
+       # Having scanned it, we know whether it had a [[!templatebody]].
+       if (exists $templates{$tpage}) {
+               return $templates{$tpage};
+       }
+
+       # If not, return the whole thing. (Eventually, after implementing
+       # a transition, this can become an error.)
+       return $content;
+}
+
+1
index fa2940b01ce23e5f988f9c9176c4a775c2171e53..825c077daa7416483f731cc0e31ed4add4444fab 100644 (file)
@@ -6,7 +6,7 @@ use warnings;
 use strict;
 use IkiWiki;
 
-my (%backlinks, %rendered);
+my (%backlinks, %rendered, %scanned);
 our %brokenlinks;
 my $links_calculated=0;
 
@@ -154,6 +154,8 @@ sub genpage ($$) {
 
 sub scan ($) {
        my $file=shift;
+       return if $phase > PHASE_SCAN || $scanned{$file};
+       $scanned{$file}=1;
 
        debug(sprintf(gettext("scanning %s"), $file));
 
@@ -825,6 +827,8 @@ sub gen_autofile ($$$) {
 }
 
 sub refresh () {
+       $phase = PHASE_SCAN;
+
        srcdir_check();
        run_hooks(refresh => sub { shift->() });
        my ($files, $pages, $new, $internal_new, $del, $internal_del, $changed, $internal_changed);
@@ -876,7 +880,13 @@ sub refresh () {
        }
 
        calculate_links();
-       
+
+       # At this point it becomes OK to start matching pagespecs.
+       $phase = PHASE_RENDER;
+       # Save some memory: we no longer need to keep track of which pages
+       # we've scanned
+       %scanned = ();
+
        remove_del(@$del, @$internal_del);
 
        foreach my $file (@$changed) {
@@ -940,6 +950,10 @@ sub commandline_render () {
        loadindex();
        unlockwiki();
 
+       # This function behaves as though it's in the render phase;
+       # all other files are assumed to have been scanned last time.
+       $phase = PHASE_RENDER;
+
        my $srcfile=possibly_foolish_untaint($config{render});
        my $file=$srcfile;
        $file=~s/\Q$config{srcdir}\E\/?//;
index 6269f5dd8a23935f1970fe62b998ecc15f569885..70bd2ad258c78c2a2655544323e127fa9a09d459 100644 (file)
@@ -18,13 +18,15 @@ the directive displaying a note about the template being registered, add
 "silent=yes".
 
 Often the template page contains a simple skeleton for a particular type of
-page. For the bug report pages in the above example, it might look
-something like:
+page, wrapped in a [[templatebody]] directive. For the bug report pages in
+the above example, it might look something like:
 
+       \[[!templatebody <<ENDBODY
        Package: 
        Version: 
        Reproducible: y/n
        Details:
+       ENDBODY]]
 
 The template page can also contain [[!cpan HTML::Template]] directives,
 like other ikiwiki [[templates]].
@@ -43,4 +45,10 @@ These variables might be set:
   suitable for use in `\[[!meta date="<TMPL_VAR time>"]]`
   (see [[meta]]) or `\[[!date "<TMPL_VAR time>"]]` (see [[date]]).
 
+Text outside the [[templatebody]] directive is not part of the template,
+and can be used to document it.
+
+If the template does not contain a [[templatebody]] directive, the entire
+source of the page is used for the template. This is deprecated.
+
 [[!meta robots="noindex, follow"]]
index 9e3ae54dfd6209c92528ace49ad5ae2b3279d486..dd1ca3d5250d7b75dd29dc64b1385dfca1a516d8 100644 (file)
@@ -31,16 +31,25 @@ large chunks of marked up text to be embedded into a template:
 
 ## Creating a template
 
-The template is a regular wiki page, located in the `templates/`
+The template is in a regular wiki page, located in the `templates/`
 subdirectory inside the source directory of the wiki.
+The contents of the [[templatebody]] directive are used as the
+template. Anything outside that directive is not included in the template,
+and is usually used as documentation describing the template.
+
+If the template does not contain a [[templatebody]] directive, the entire
+source of the page is used for the template. This is deprecated, because
+it leads to the template markup being interpreted as ordinary
+page source when the page is built, as well as being used as the template.
 
 Alternatively, templates can be stored in a directory outside the wiki,
 as files with the extension ".tmpl".
 By default, these are searched for in `/usr/share/ikiwiki/templates`,
 the `templatedir` setting can be used to make another directory be searched
 first.  When referring to templates outside the wiki source directory, the "id"
-parameter is not interpreted as a pagespec, and you must include the full filename
-of the template page, including the ".tmpl" extension. E.g.:
+parameter is not interpreted as a pagespec, you must include the full filename
+of the template page including the ".tmpl" extension,
+and the templatebody directive is not used. E.g.:
 
     \[[!template id=blogpost.tmpl]]
 
@@ -63,6 +72,7 @@ few things:
 
 Here's a sample template:
 
+        \[[!templatebody <<ENDBODY
         <span class="infobox">
         Name: \[[<TMPL_VAR raw_name>]]<br />
         Age: <TMPL_VAR age><br />
@@ -76,6 +86,10 @@ Here's a sample template:
         <TMPL_VAR notes>
         </TMPL_IF>
         </span>
+        ENDBODY]]
+
+       This template describes a person. Parameters: name, age,
+       color (favorite color, optional), notes (optional).
 
 The filled out template will be formatted the same as the rest of the page
 that contains it, so you can include WikiLinks and all other forms of wiki
diff --git a/doc/ikiwiki/directive/templatebody.mdwn b/doc/ikiwiki/directive/templatebody.mdwn
new file mode 100644 (file)
index 0000000..dfadb2c
--- /dev/null
@@ -0,0 +1,28 @@
+The `templatebody` directive is supplied by the
+[[!iki plugins/templatebody desc=templatebody]] plugin.
+
+This directive allows wiki pages to be used as templates
+for the [[template]] or [[edittemplate]] directive, without having
+[[!cpan HTML::Template]] markup interpreted as wiki markup when that
+page is built.
+
+This directive does not produce any output in the wiki page that
+defines the template; the rest of that page can be used to to document
+how to use the template.
+
+The first, un-named parameter is the content of the template.
+Because templates often contain [[directives|ikiwiki/directive]], it's
+convenient to use the "here-document" syntax for it:
+
+       \[[!templatebody <<ENDBODY
+       [[!meta title="<TMPL_VAR name>"]]
+       [[!tag person]]
+       <dl>
+       <dt>Name:</dt><dd><TMPL_VAR name></dd>
+       <dt>Age:</dt><dd><TMPL_VAR age></dd>
+       </dl>
+
+       <TMPL_VAR description>
+       ENDBODY]]
+
+[[!meta robots="noindex, follow"]]
diff --git a/doc/plugins/templatebody.mdwn b/doc/plugins/templatebody.mdwn
new file mode 100644 (file)
index 0000000..eee5cde
--- /dev/null
@@ -0,0 +1,7 @@
+[[!template id=plugin name=templatebody author="[[smcv]]" core=1]]
+[[!tag type/special-purpose]]
+
+This plugin provides the [[ikiwiki/directive/templatebody]]
+[[ikiwiki/directive]]. With this plugin, you can set up templates
+stored in the wiki for [[template]] or [[edittemplate]] without the
+[[!cpan HTML::Template]] markup being interpreted as wiki markup.
index d2d1a6329c4c9c0cf0429d14f35b820ce5329e06..15f054c097462292d06ef5a26077c972758c9e6a 100644 (file)
@@ -211,6 +211,29 @@ them to `%links`. Present in IkiWiki 2.40 and later.
 The function is passed named parameters "page" and "content". Its return
 value is ignored.
 
+### <a name="readtemplate">readtemplate</a>
+
+       hook(type => "readtemplate", id => "foo", call => \&readtemplate);
+
+Runs on the raw source of a page or `*.tmpl` file that is being
+used as a template, before it is parsed by [[!cpan HTML::Template]].
+For instance, the [[plugins/templatebody]] plugin uses this to return
+the content of the [[ikiwiki/directive/templatebody]] directive (if there
+is one) instead of the page's full content.
+
+The function is passed named parameters:
+
+* `id`: the name under which the template was looked up,
+  such as `page.tmpl` or `note`
+* `page`: the name of the template as a page or attachment in the wiki,
+  such as `templates/note`, or `undef` if it's outside the wiki (e.g. in
+  `/usr/share/ikiwiki/templates`)
+* `content`: the content of the corresponding file
+* `untrusted`: true if the template was loaded from the wiki or an underlay,
+  false if it was loaded from a trusted location
+
+It should return the replacement content.
+
 ### <a name="filter">filter</a>
 
        hook(type => "filter", id => "foo", call => \&filter);
index d0f891c21320243125f2b5b7d26fc278c6292dde..80372fcb7742ff17d87ce969d40bb31e7c0f8d36 100644 (file)
@@ -14,8 +14,10 @@ easy to learn. All you really need to know to modify templates is this:
 [[!if test="enabled(template) or enabled(edittemplate)" then="""
 ## template pages
 
-Template pages are regular wiki pages that are used as templates for other
-pages.
+Template pages are regular wiki pages containing a
+[[!iki ikiwiki/directive/templatebody desc="templatebody directive"]],
+used as templates for other pages. The parts of the template
+page outside the directive can be used to document it.
 """]]
 
 [[!if test="enabled(template)" then="""
@@ -38,6 +40,9 @@ feeds=no archive=yes sort=title template=titlepage
 rootpage=templates postformtext="Add a new template page named:"]]
 """]]
 
+If the template does not contain a `templatebody` directive, the entire
+source of the page is used for the template. This is deprecated.
+
 ## template files
 
 Template files are unlike template pages in that they have the extension
index 853da92801ffc8c039c29f9133238cd4342aa4d6..4ea73c91b24c6d6f6b545caf7d9334a22c1c62b4 100644 (file)
@@ -1,9 +1,11 @@
+[[!templatebody <<ENDBODY
 <div class="infobox">
 Available in a [[!taglink /git]] repository [[!taglink branch|/branches]].<br />
 Branch: <TMPL_IF browse><a href="<TMPL_VAR browse>"></TMPL_IF><TMPL_VAR branch><TMPL_IF browse></a></TMPL_IF><br />
 <TMPL_IF author>Author: <TMPL_VAR author><br /></TMPL_IF>
 </div>
-<TMPL_UNLESS branch>
+ENDBODY]]
+
 This template is used to create an infobox for a git branch. It uses
 these parameters:
 
@@ -13,4 +15,3 @@ these parameters:
   (e.g. github/master)</li>
 <li>author - the author of the branch</li>
 </ul>
-</TMPL_UNLESS>
index 4bd1a85bf9559c1d6f38c378f9a696b2f21298b3..3239a59c239ff49bdba5a444a755ea6d3a1c1d65 100644 (file)
@@ -1,3 +1,4 @@
+[[!templatebody <<ENDBODY
 <div class="infobox">
 [[ikiwiki_logo|logo/ikiwiki.png]]  
 <ul>
@@ -14,3 +15,6 @@
 <img src="https://api.flattr.com/button/flattr-badge-large.png"
 alt="Flattr this" title="Flattr this" /></a>
 </div>
+ENDBODY]]
+
+This template contains the navigation links used on the front page.
index 9ef5ad9421abcdb730996dc51a621ea5e74340fa..8de7374bc0c1a096cbe10fb26606547d99a0bae5 100644 (file)
@@ -1,11 +1,12 @@
+[[!templatebody <<ENDBODY
 <div class="notebox">
 <TMPL_VAR text>
 </div>
-<TMPL_UNLESS text>
+ENDBODY]]
+
 Use this template to insert a note into a page. The note will be styled to
 float to the right of other text on the page. This template has one
 parameter:
 <ul>
 <li>`text` - the text to display in the note
 </ul>
-</TMPL_UNLESS>
index 322c494457af3768e54dbeaa56b6884902a42d3b..d36dd5f85172c4db42eae534ae4919d355d45915 100644 (file)
@@ -1,3 +1,4 @@
+[[!templatebody <<ENDBODY
 <span class="infobox">
 Plugin: <TMPL_VAR name><br />
 Author: <TMPL_VAR author><br />
@@ -8,7 +9,8 @@ Currently enabled: [[!if test="enabled(<TMPL_VAR name>)" then="yes" else="no"]]<
 </span>
 [[!if test="sourcepage(plugins/contrib/*)" then="""[[!meta title="<TMPL_VAR name> (third party plugin)"]]"""]]
 <TMPL_IF core>[[!tag plugins/type/core]]</TMPL_IF>
-<TMPL_UNLESS name>
+ENDBODY]]
+
 This template is used to create an infobox for an ikiwiki plugin. It uses
 these parameters:
 <ul>
@@ -16,4 +18,3 @@ these parameters:
 <li>author - the author of the plugin
 <li>core - set to a true value if the plugin is enabled by default
 </ul>
-</TMPL_UNLESS>
index 92455eb216acf1df2250dd8d14026bb743179b64..b721a95f9febee926f2ba220ece3cf837c25a16d 100644 (file)
@@ -1,4 +1,3 @@
-<TMPL_UNLESS mouseover>
 Use this template to create a popup window that is displayed when the mouse
 is over part of the page. This template has two parameters:
 <ul>
@@ -10,7 +9,9 @@ large for good usability.
 </ul>
 Note that browsers that do not support the CSS will display the popup
 inline in the page, inside square brackets.
-</TMPL_UNLESS>
+
+[[templatebody <<ENDBODY
 <span class="popup"><TMPL_VAR mouseover>
 <span class="paren">[</span><span class="balloon"><TMPL_VAR popup></span><span class="paren">]</span>
 </span>
+ENDBODY]]
index 126aa8e17d0949d0c9a55477268464936092ff33..862aa9d97b7b3a80abe7e0bdbf4b5d7acf371ef0 100755 (executable)
@@ -22,6 +22,8 @@ foreach my $file (@$files) {
        $pagesources{$page}=$file; # used by po plugin functions
 }
 
+$IkiWiki::phase = IkiWiki::PHASE_RENDER;
+
 foreach my $lang (@{$config{po_slave_languages}}) {
        my ($ll, $name)=IkiWiki::Plugin::po::splitlangpair($lang);
        $config{destdir}="../underlays/locale/$ll";
diff --git a/t/templatebody.t b/t/templatebody.t
new file mode 100755 (executable)
index 0000000..b0bc82c
--- /dev/null
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+package IkiWiki;
+
+use warnings;
+use strict;
+use Test::More tests => 18;
+
+BEGIN { use_ok("IkiWiki"); }
+BEGIN { use_ok("IkiWiki::Render"); }
+BEGIN { use_ok("IkiWiki::Plugin::templatebody"); }
+BEGIN { use_ok("IkiWiki::Plugin::mdwn"); }
+BEGIN { use_ok("IkiWiki::Plugin::tag"); }
+BEGIN { use_ok("IkiWiki::Plugin::template"); }
+
+sub assert_pagespec_matches {
+       my $page = shift;
+       my $spec = shift;
+       my @params = @_;
+       @params = (location => 'index') unless @params;
+
+       my $res = pagespec_match($page, $spec, @params);
+
+       if ($res) {
+               pass($res);
+       }
+       else {
+               fail($res);
+       }
+}
+
+sub assert_pagespec_doesnt_match {
+       my $page = shift;
+       my $spec = shift;
+       my @params = @_;
+       @params = (location => 'index') unless @params;
+
+       my $res = pagespec_match($page, $spec, @params);
+
+       if (ref $res && $res->isa("IkiWiki::ErrorReason")) {
+               fail($res);
+       }
+       elsif ($res) {
+               fail($res);
+       }
+       else {
+               pass($res);
+       }
+}
+
+ok(! system("rm -rf t/tmp; mkdir t/tmp t/tmp/src t/tmp/dst"));
+
+$config{verbose} = 1;
+$config{srcdir} = 't/tmp/src';
+$config{underlaydir} = 't/tmp/src';
+$config{destdir} = 't/tmp/dst';
+$config{underlaydirbase} = '.';
+$config{templatedir} = 'templates';
+$config{usedirs} = 1;
+$config{htmlext} = 'html';
+$config{wiki_file_chars} = "-[:alnum:]+/.:_";
+$config{default_pageext} = "mdwn";
+$config{wiki_file_prune_regexps} = [qr/^\./];
+
+is(checkconfig(), 1);
+
+%oldrenderedfiles=%pagectime=();
+%pagesources=%pagemtime=%oldlinks=%links=%depends=%typedlinks=%oldtypedlinks=
+%destsources=%renderedfiles=%pagecase=%pagestate=();
+
+$pagesources{index} = "index.mdwn";
+$pagemtime{index} = $pagectime{index} = 1000000;
+writefile("index.mdwn", "t/tmp/src", <<EOF
+[[!template id="deftmpl" greeting="hello" them="world"]]
+[[!template id="oldtmpl" greeting="greetings" them="earthlings"]]
+EOF
+);
+
+$pagesources{"templates/deftmpl"} = "templates/deftmpl.mdwn";
+$pagemtime{index} = $pagectime{index} = 1000000;
+writefile("templates/deftmpl.mdwn", "t/tmp/src", <<EOF
+[[!templatebody <<ENDBODY
+<p><b><TMPL_VAR GREETING>, <TMPL_VAR THEM></b></p>
+[[!tag greeting]]
+ENDBODY]]
+
+This template says hello to someone.
+[[!tag documentation]]
+EOF
+);
+
+$pagesources{"templates/oldtmpl"} = "templates/oldtmpl.mdwn";
+$pagemtime{index} = $pagectime{index} = 1000000;
+writefile("templates/oldtmpl.mdwn", "t/tmp/src", <<EOF
+<p><i><TMPL_VAR GREETING>, <TMPL_VAR THEM></i></p>
+EOF
+);
+
+my %content;
+
+foreach my $page (keys %pagesources) {
+       my $content = readfile("t/tmp/src/$pagesources{$page}");
+       $content = IkiWiki::filter($page, $page, $content);
+       $content = IkiWiki::preprocess($page, $page, $content);
+       $content{$page} = $content;
+}
+
+# Templates are expanded
+like($content{index}, qr{<p><b>hello, world</b></p>});
+like($content{index}, qr{<p><i>greetings, earthlings</i></p>});
+assert_pagespec_matches('index', 'tagged(greeting)');
+# The documentation from the templatebody-using page is not expanded
+unlike($content{index}, qr{This template says hello to someone});
+assert_pagespec_doesnt_match('index', 'tagged(documentation)');
+
+# In the templatebody-using page, the documentation is expanded
+like($content{'templates/deftmpl'}, qr{This template says hello to someone});
+assert_pagespec_matches('templates/deftmpl', 'tagged(documentation)');
+# In the templatebody-using page, the template is *not* expanded
+unlike($content{'templates/deftmpl'}, qr{<p><b>hello, world</b></p>});
+unlike($content{'templates/deftmpl'}, qr{<p><i>greetings, earthlings</i></p>});
+assert_pagespec_doesnt_match('templates/deftmpl', 'tagged(greeting)');
+
+1;