* meta: Add pagespec functions to match against title, author, authorurl,

license, and copyright. This can be used to create custom RecentChanges.
* meta: To support the pagespec functions, metadata about pages has to be
  retained as pagestate.
* Fix encoding bug when pagestate values contained spaces.
master
Joey Hess 2008-01-29 17:16:51 -05:00
parent fbfbda614d
commit 64a8c828b8
10 changed files with 136 additions and 71 deletions

View File

@ -973,7 +973,7 @@ sub saveindex () { #{{{
if (exists $pagestate{$page}) { if (exists $pagestate{$page}) {
foreach my $id (@hookids) { foreach my $id (@hookids) {
foreach my $key (keys %{$pagestate{$page}{$id}}) { foreach my $key (keys %{$pagestate{$page}{$id}}) {
$line.=' '.$id.'_'.encode_entities($key)."=".encode_entities($pagestate{$page}{$id}{$key}); $line.=' '.$id.'_'.encode_entities($key)."=".encode_entities($pagestate{$page}{$id}{$key}, " \t\n");
} }
} }
} }

View File

@ -6,13 +6,7 @@ use warnings;
use strict; use strict;
use IkiWiki 2.00; use IkiWiki 2.00;
my %meta; my %metaheaders;
my %title;
my %permalink;
my %author;
my %authorurl;
my %license;
my %copyright;
sub import { #{{{ sub import { #{{{
hook(type => "needsbuild", id => "meta", call => \&needsbuild); hook(type => "needsbuild", id => "meta", call => \&needsbuild);
@ -71,16 +65,16 @@ sub preprocess (@) { #{{{
# Metadata collection that needs to happen during the scan pass. # Metadata collection that needs to happen during the scan pass.
if ($key eq 'title') { if ($key eq 'title') {
$title{$page}=HTML::Entities::encode_numeric($value); $pagestate{$page}{meta}{title}=HTML::Entities::encode_numeric($value);
} }
elsif ($key eq 'license') { elsif ($key eq 'license') {
push @{$meta{$page}}, '<link rel="license" href="#page_license" />'; push @{$metaheaders{$page}}, '<link rel="license" href="#page_license" />';
$license{$page}=$value; $pagestate{$page}{meta}{license}=$value;
return ""; return "";
} }
elsif ($key eq 'copyright') { elsif ($key eq 'copyright') {
push @{$meta{$page}}, '<link rel="copyright" href="#page_copyright" />'; push @{$metaheaders{$page}}, '<link rel="copyright" href="#page_copyright" />';
$copyright{$page}=$value; $pagestate{$page}{meta}{copyright}=$value;
return ""; return "";
} }
elsif ($key eq 'link' && ! %params) { elsif ($key eq 'link' && ! %params) {
@ -89,11 +83,11 @@ sub preprocess (@) { #{{{
return ""; return "";
} }
elsif ($key eq 'author') { elsif ($key eq 'author') {
$author{$page}=$value; $pagestate{$page}{meta}{author}=$value;
# fallthorough # fallthorough
} }
elsif ($key eq 'authorurl') { elsif ($key eq 'authorurl') {
$authorurl{$page}=$value; $pagestate{$page}{meta}{authorurl}=$value;
# fallthrough # fallthrough
} }
@ -111,8 +105,8 @@ sub preprocess (@) { #{{{
} }
} }
elsif ($key eq 'permalink') { elsif ($key eq 'permalink') {
$permalink{$page}=$value; $pagestate{$page}{meta}{permalink}=$value;
push @{$meta{$page}}, scrub('<link rel="bookmark" href="'.encode_entities($value).'" />'); push @{$metaheaders{$page}}, scrub('<link rel="bookmark" href="'.encode_entities($value).'" />');
} }
elsif ($key eq 'stylesheet') { elsif ($key eq 'stylesheet') {
my $rel=exists $params{rel} ? $params{rel} : "alternate stylesheet"; my $rel=exists $params{rel} ? $params{rel} : "alternate stylesheet";
@ -123,17 +117,17 @@ sub preprocess (@) { #{{{
if (! length $stylesheet) { if (! length $stylesheet) {
return "[[meta ".gettext("stylesheet not found")."]]"; return "[[meta ".gettext("stylesheet not found")."]]";
} }
push @{$meta{$page}}, '<link href="'.urlto($stylesheet, $page). push @{$metaheaders{$page}}, '<link href="'.urlto($stylesheet, $page).
'" rel="'.encode_entities($rel). '" rel="'.encode_entities($rel).
'" title="'.encode_entities($title). '" title="'.encode_entities($title).
"\" type=\"text/css\" />"; "\" type=\"text/css\" />";
} }
elsif ($key eq 'openid') { elsif ($key eq 'openid') {
if (exists $params{server}) { if (exists $params{server}) {
push @{$meta{$page}}, '<link href="'.encode_entities($params{server}). push @{$metaheaders{$page}}, '<link href="'.encode_entities($params{server}).
'" rel="openid.server" />'; '" rel="openid.server" />';
} }
push @{$meta{$page}}, '<link href="'.encode_entities($value). push @{$metaheaders{$page}}, '<link href="'.encode_entities($value).
'" rel="openid.delegate" />'; '" rel="openid.delegate" />';
} }
elsif ($key eq 'redir') { elsif ($key eq 'redir') {
@ -172,11 +166,11 @@ sub preprocess (@) { #{{{
if (! $safe) { if (! $safe) {
$redir=scrub($redir); $redir=scrub($redir);
} }
push @{$meta{$page}}, $redir; push @{$metaheaders{$page}}, $redir;
} }
elsif ($key eq 'link') { elsif ($key eq 'link') {
if (%params) { if (%params) {
push @{$meta{$page}}, scrub("<link href=\"".encode_entities($value)."\" ". push @{$metaheaders{$page}}, scrub("<link href=\"".encode_entities($value)."\" ".
join(" ", map { join(" ", map {
encode_entities($_)."=\"".encode_entities(decode_entities($params{$_}))."\"" encode_entities($_)."=\"".encode_entities(decode_entities($params{$_}))."\""
} keys %params). } keys %params).
@ -184,7 +178,7 @@ sub preprocess (@) { #{{{
} }
} }
else { else {
push @{$meta{$page}}, scrub('<meta name="'.encode_entities($key). push @{$metaheaders{$page}}, scrub('<meta name="'.encode_entities($key).
'" content="'.encode_entities($value).'" />'); '" content="'.encode_entities($value).'" />');
} }
@ -197,32 +191,80 @@ sub pagetemplate (@) { #{{{
my $destpage=$params{destpage}; my $destpage=$params{destpage};
my $template=$params{template}; my $template=$params{template};
if (exists $meta{$page} && $template->query(name => "meta")) { if (exists $metaheaders{$page} && $template->query(name => "meta")) {
# avoid duplicate meta lines # avoid duplicate meta lines
my %seen; my %seen;
$template->param(meta => join("\n", grep { (! $seen{$_}) && ($seen{$_}=1) } @{$meta{$page}})); $template->param(meta => join("\n", grep { (! $seen{$_}) && ($seen{$_}=1) } @{$metaheaders{$page}}));
} }
if (exists $title{$page} && $template->query(name => "title")) { if (exists $pagestate{$page}{meta}{title} && $template->query(name => "title")) {
$template->param(title => $title{$page}); $template->param(title => $pagestate{$page}{meta}{title});
$template->param(title_overridden => 1); $template->param(title_overridden => 1);
} }
$template->param(permalink => $permalink{$page})
if exists $permalink{$page} && $template->query(name => "permalink");
$template->param(author => $author{$page})
if exists $author{$page} && $template->query(name => "author");
$template->param(authorurl => $authorurl{$page})
if exists $authorurl{$page} && $template->query(name => "authorurl");
if (exists $license{$page} && $template->query(name => "license") && foreach my $field (qw{author authorurl permalink}) {
($page eq $destpage || ! exists $license{$destpage} || $template->param($field => $pagestate{$page}{meta}{$field})
$license{$page} ne $license{$destpage})) { if exists $pagestate{$page}{meta}{$field} && $template->query(name => $field);
$template->param(license => htmlize($page, $destpage, $license{$page}));
} }
if (exists $copyright{$page} && $template->query(name => "copyright") &&
($page eq $destpage || ! exists $copyright{$destpage} || foreach my $field (qw{license copyright}) {
$copyright{$page} ne $copyright{$destpage})) { if (exists $pagestate{$page}{meta}{$field} && $template->query(name => $field) &&
$template->param(copyright => htmlize($page, $destpage, $copyright{$page})); ($page eq $destpage || ! exists $pagestate{$destpage}{meta}{$field} ||
$pagestate{$page}{meta}{$field} ne $pagestate{$destpage}{meta}{$field})) {
$template->param($field => htmlize($page, $destpage, $pagestate{$page}{meta}{$field}));
}
} }
} # }}} } # }}}
sub match { #{{{
my $field=shift;
my $page=shift;
# turn glob into a safe regexp
my $re=quotemeta(shift);
$re=~s/\\\*/.*/g;
$re=~s/\\\?/./g;
my $val;
if (exists $pagestate{$page}{meta}{$field}) {
$val=$pagestate{$page}{meta}{$field};
}
elsif ($field eq 'title') {
$val=pagetitle($page);
}
if (defined $val) {
if ($val=~/^$re$/i) {
return IkiWiki::SuccessReason->new("$re matches $field of $page");
}
else {
return IkiWiki::FailReason->new("$re does not match $field of $page");
}
}
else {
return IkiWiki::FailReason->new("$page does not have a $field");
}
} #}}}
package IkiWiki::PageSpec;
sub match_title ($$;@) { #{{{
IkiWiki::Plugin::meta::match("title", @_);
} #}}}
sub match_author ($$;@) { #{{{
IkiWiki::Plugin::meta::match("author", @_);
} #}}}
sub match_authorurl ($$;@) { #{{{
IkiWiki::Plugin::meta::match("authorurl", @_);
} #}}}
sub match_license ($$;@) { #{{{
IkiWiki::Plugin::meta::match("license", @_);
} #}}}
sub match_copyright ($$;@) { #{{{
IkiWiki::Plugin::meta::match("copyright", @_);
} #}}}
1 1

View File

@ -377,6 +377,7 @@ sub refresh () { #{{{
$pagemtime{$page}=$mtime; $pagemtime{$page}=$mtime;
if (isinternal($page)) { if (isinternal($page)) {
push @internal, $file; push @internal, $file;
scan($file);
} }
else { else {
push @needsbuild, $file; push @needsbuild, $file;

5
debian/changelog vendored
View File

@ -31,6 +31,11 @@ ikiwiki (2.21) UNRELEASED; urgency=low
the svnrepo and notify settings, though both will be ignored if left in the svnrepo and notify settings, though both will be ignored if left in
setup files. Also gone with it is the "user()" pagespec. setup files. Also gone with it is the "user()" pagespec.
* Add refresh hook. * Add refresh hook.
* meta: Add pagespec functions to match against title, author, authorurl,
license, and copyright. This can be used to create custom RecentChanges.
* meta: To support the pagespec functions, metadata about pages has to be
retained as pagestate.
* Fix encoding bug when pagestate values contained spaces.
-- Joey Hess <joeyh@debian.org> Fri, 11 Jan 2008 15:09:37 -0500 -- Joey Hess <joeyh@debian.org> Fri, 11 Jan 2008 15:09:37 -0500

View File

@ -33,10 +33,13 @@ functions:
was created was created
* "`created_before(page)`" - match only pages created before the given page * "`created_before(page)`" - match only pages created before the given page
was created was created
* "`glob(foo)`" - match pages that match the given glob `foo`. Just writing * "`glob(someglob)`" - match pages that match the given glob. Just writing
the glob by itself is actually a shorthand for this function. the glob by itself is actually a shorthand for this function.
* "`internal(foo)`" - like `glob()`, but matches even internal-use * "`internal(glob)`" - like `glob()`, but matches even internal-use
pages that globs do not usually match. pages that globs do not usually match.
* "`title(glob)`", "`author(glob)`", "`authorurl(glob)`",
"`license(glob)`", "`copyright(glob)`" - match pages that have the given
metadata, matching the specified glob.
For example, to match all pages in a blog that link to the page about music For example, to match all pages in a blog that link to the page about music
and were written in 2005: and were written in 2005:

View File

@ -10,3 +10,17 @@ plugin, but you can use it elsewhere too if you like. It's used like this:
\[[inline pages="internal(recentchanges/change_*)" \[[inline pages="internal(recentchanges/change_*)"
template=recentchanges show=0]] template=recentchanges show=0]]
Here's an example of how to show only changes to "bugs/*".
This matches against the title of the change, which includes a list of
modified pages.
\[[inline pages="internal(recentchanges/change_*) and title(*bugs/*)"
template=recentchanges show=0]]
Here's an example of how to show only changes that Joey didn't make.
(Joey commits sometimes as user `joey`, and sometimes via openid.)
\[[inline pages="internal(recentchanges/change_*) and
!author(joey) and !author(http://joey.kitenet.net*)"
template=recentchanges show=0]]

View File

@ -533,18 +533,16 @@ rendered to.
## Internal use pages ## Internal use pages
Sometimes it's useful to put pages in the wiki without having them be Sometimes it's useful to put pages in the wiki without the overhead of
rendered to individual html files. Such internal use pages are collected having them be rendered to individual html files. Such internal use pages
together to form the RecentChanges page, for example. are collected together to form the RecentChanges page, for example.
To make an internal use page, register a filename extension that starts To make an internal use page, register a filename extension that starts
with "_". Internal use pages cannot be edited with the web interface, are with "_". Internal use pages cannot be edited with the web interface,
not scanned for wikilinks (though wikilinks and preprocessor directives can generally shouldn't contain wikilinks or preprocessor directives (use
appear on them, this is rarely a good idea and should be done with caution), either on them with extreme caution), and are not matched by regular
and are not matched by regular PageSpecs glob patterns, but instead only by a PageSpecs glob patterns, but instead only by a special `internal()`
special `internal()` [[ikiwiki/PageSpec]]. [[ikiwiki/PageSpec]].
Ikiwiki is optimised to handle lots of internal use pages, very quickly.
## RCS plugins ## RCS plugins

View File

@ -15,6 +15,8 @@ use IkiWiki::Setup::Standard {
syslog => 0, syslog => 0,
userdir => "users", userdir => "users",
usedirs => 0, usedirs => 0,
rcs => "git",
rss => 1,
url => "http://ikiwiki.info/",
add_plugins => [qw{goodstuff version haiku polygen fortune}], add_plugins => [qw{goodstuff version haiku polygen fortune}],
disable_plugins => [qw{recentchanges}],
} }

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-01-29 15:46-0500\n" "POT-Creation-Date: 2008-01-29 17:15-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -47,8 +47,8 @@ msgstr ""
#: ../IkiWiki/CGI.pm:382 ../IkiWiki/Plugin/brokenlinks.pm:24 #: ../IkiWiki/CGI.pm:382 ../IkiWiki/Plugin/brokenlinks.pm:24
#: ../IkiWiki/Plugin/inline.pm:241 ../IkiWiki/Plugin/opendiscussion.pm:17 #: ../IkiWiki/Plugin/inline.pm:241 ../IkiWiki/Plugin/opendiscussion.pm:17
#: ../IkiWiki/Plugin/orphans.pm:28 ../IkiWiki/Render.pm:100 #: ../IkiWiki/Plugin/orphans.pm:28 ../IkiWiki/Render.pm:101
#: ../IkiWiki/Render.pm:180 #: ../IkiWiki/Render.pm:181
msgid "discussion" msgid "discussion"
msgstr "" msgstr ""
@ -218,7 +218,7 @@ msgstr ""
msgid "nonexistant template %s" msgid "nonexistant template %s"
msgstr "" msgstr ""
#: ../IkiWiki/Plugin/inline.pm:249 ../IkiWiki/Render.pm:104 #: ../IkiWiki/Plugin/inline.pm:249 ../IkiWiki/Render.pm:105
msgid "Discussion" msgid "Discussion"
msgstr "" msgstr ""
@ -240,15 +240,15 @@ msgstr ""
msgid "failed to load Markdown.pm perl module (%s) or /usr/bin/markdown (%s)" msgid "failed to load Markdown.pm perl module (%s) or /usr/bin/markdown (%s)"
msgstr "" msgstr ""
#: ../IkiWiki/Plugin/meta.pm:124 #: ../IkiWiki/Plugin/meta.pm:118
msgid "stylesheet not found" msgid "stylesheet not found"
msgstr "" msgstr ""
#: ../IkiWiki/Plugin/meta.pm:148 #: ../IkiWiki/Plugin/meta.pm:142
msgid "redir page not found" msgid "redir page not found"
msgstr "" msgstr ""
#: ../IkiWiki/Plugin/meta.pm:161 #: ../IkiWiki/Plugin/meta.pm:155
msgid "redir cycle is not allowed" msgid "redir cycle is not allowed"
msgstr "" msgstr ""
@ -503,47 +503,47 @@ msgstr ""
msgid "getctime not implemented" msgid "getctime not implemented"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:280 ../IkiWiki/Render.pm:301 #: ../IkiWiki/Render.pm:281 ../IkiWiki/Render.pm:302
#, perl-format #, perl-format
msgid "skipping bad filename %s" msgid "skipping bad filename %s"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:350 #: ../IkiWiki/Render.pm:351
#, perl-format #, perl-format
msgid "removing old page %s" msgid "removing old page %s"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:389 #: ../IkiWiki/Render.pm:391
#, perl-format #, perl-format
msgid "scanning %s" msgid "scanning %s"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:394 #: ../IkiWiki/Render.pm:396
#, perl-format #, perl-format
msgid "rendering %s" msgid "rendering %s"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:415 #: ../IkiWiki/Render.pm:417
#, perl-format #, perl-format
msgid "rendering %s, which links to %s" msgid "rendering %s, which links to %s"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:436 #: ../IkiWiki/Render.pm:438
#, perl-format #, perl-format
msgid "rendering %s, which depends on %s" msgid "rendering %s, which depends on %s"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:475 #: ../IkiWiki/Render.pm:477
#, perl-format #, perl-format
msgid "rendering %s, to update its backlinks" msgid "rendering %s, to update its backlinks"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:487 #: ../IkiWiki/Render.pm:489
#, perl-format #, perl-format
msgid "removing %s, no longer rendered by %s" msgid "removing %s, no longer rendered by %s"
msgstr "" msgstr ""
#: ../IkiWiki/Render.pm:513 #: ../IkiWiki/Render.pm:515
#, perl-format #, perl-format
msgid "ikiwiki: cannot render %s" msgid "ikiwiki: cannot render %s"
msgstr "" msgstr ""

View File

@ -2,7 +2,7 @@
<TMPL_IF AUTHORURL> <TMPL_IF AUTHORURL>
[[meta authorurl="""<TMPL_VAR AUTHORURL>"""]] [[meta authorurl="""<TMPL_VAR AUTHORURL>"""]]
</TMPL_IF> </TMPL_IF>
[[meta title="""update of <TMPL_VAR WIKINAME>'s <TMPL_LOOP NAME="PAGES"> <TMPL_VAR PAGE></TMPL_LOOP>"""]] [[meta title="""update of <TMPL_VAR WIKINAME>'s<TMPL_LOOP NAME="PAGES"> <TMPL_VAR PAGE></TMPL_LOOP>"""]]
<div class="metadata"> <div class="metadata">
<span class="desc"><br />Changed pages:</span> <span class="desc"><br />Changed pages:</span>
<span class="pagelinks"> <span class="pagelinks">