summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin
diff options
context:
space:
mode:
Diffstat (limited to 'IkiWiki/Plugin')
-rw-r--r--IkiWiki/Plugin/aggregate.pm197
-rw-r--r--IkiWiki/Plugin/calendar.pm3
-rw-r--r--IkiWiki/Plugin/ddate.pm6
-rw-r--r--IkiWiki/Plugin/edittemplate.pm3
-rw-r--r--IkiWiki/Plugin/inline.pm33
-rw-r--r--IkiWiki/Plugin/meta.pm125
-rw-r--r--IkiWiki/Plugin/openid.pm27
-rw-r--r--IkiWiki/Plugin/poll.pm6
-rw-r--r--IkiWiki/Plugin/prettydate.pm5
-rw-r--r--IkiWiki/Plugin/recentchanges.pm169
-rw-r--r--IkiWiki/Plugin/teximg.pm4
-rw-r--r--IkiWiki/Plugin/version.pm3
12 files changed, 461 insertions, 120 deletions
diff --git a/IkiWiki/Plugin/aggregate.pm b/IkiWiki/Plugin/aggregate.pm
index 71368e254..ba40ee6bc 100644
--- a/IkiWiki/Plugin/aggregate.pm
+++ b/IkiWiki/Plugin/aggregate.pm
@@ -33,43 +33,62 @@ sub getopt () { #{{{
sub checkconfig () { #{{{
if ($config{aggregate} && ! ($config{post_commit} &&
IkiWiki::commit_hook_enabled())) {
- if (! IkiWiki::lockwiki(0)) {
- debug("wiki is locked by another process, not aggregating");
- exit 1;
+ # See if any feeds need aggregation.
+ loadstate();
+ my @feeds=needsaggregate();
+ return unless @feeds;
+ if (! lockaggregate()) {
+ debug("an aggregation process is already running");
+ return;
}
-
+ # force a later rebuild of source pages
+ $IkiWiki::forcerebuild{$_->{sourcepage}}=1
+ foreach @feeds;
+
# Fork a child process to handle the aggregation.
- # The parent process will then handle building the result.
- # This avoids messy code to clear state accumulated while
- # aggregating.
+ # The parent process will then handle building the
+ # result. This avoids messy code to clear state
+ # accumulated while aggregating.
defined(my $pid = fork) or error("Can't fork: $!");
if (! $pid) {
- loadstate();
IkiWiki::loadindex();
- aggregate();
+
+ # Aggregation happens without the main wiki lock
+ # being held. This allows editing pages etc while
+ # aggregation is running.
+ aggregate(@feeds);
+
+ IkiWiki::lockwiki;
+ # Merge changes, since aggregation state may have
+ # changed on disk while the aggregation was happening.
+ mergestate();
expire();
savestate();
+ IkiWiki::unlockwiki;
exit 0;
}
waitpid($pid,0);
if ($?) {
error "aggregation failed with code $?";
}
-
- IkiWiki::unlockwiki();
+
+ clearstate();
+ unlockaggregate();
}
} #}}}
sub needsbuild (@) { #{{{
my $needsbuild=shift;
- loadstate(); # if not already loaded
+ loadstate();
foreach my $feed (values %feeds) {
- if (grep { $_ eq $pagesources{$feed->{sourcepage}} } @$needsbuild) {
- # Mark all feeds originating on this page as removable;
- # preprocess will unmark those that still exist.
- remove_feeds($feed->{sourcepage});
+ if (exists $pagesources{$feed->{sourcepage}} &&
+ grep { $_ eq $pagesources{$feed->{sourcepage}} } @$needsbuild) {
+ # Mark all feeds originating on this page as
+ # not yet seen; preprocess will unmark those that
+ # still exist.
+ markunseen($feed->{sourcepage});
}
}
} # }}}
@@ -102,8 +121,7 @@ sub preprocess (@) { #{{{
$feed->{updateinterval}=defined $params{updateinterval} ? $params{updateinterval} * 60 : 15 * 60;
$feed->{expireage}=defined $params{expireage} ? $params{expireage} : 0;
$feed->{expirecount}=defined $params{expirecount} ? $params{expirecount} : 0;
- delete $feed->{remove};
- delete $feed->{expired};
+ delete $feed->{unseen};
$feed->{lastupdate}=0 unless defined $feed->{lastupdate};
$feed->{numposts}=0 unless defined $feed->{numposts};
$feed->{newposts}=0 unless defined $feed->{newposts};
@@ -133,11 +151,22 @@ sub delete (@) { #{{{
# Remove feed data for removed pages.
foreach my $file (@files) {
my $page=pagename($file);
- remove_feeds($page);
+ markunseen($page);
+ }
+} #}}}
+
+sub markunseen ($) { #{{{
+ my $page=shift;
+
+ foreach my $id (keys %feeds) {
+ if ($feeds{$id}->{sourcepage} eq $page) {
+ $feeds{$id}->{unseen}=1;
+ }
}
} #}}}
my $state_loaded=0;
+
sub loadstate () { #{{{
return if $state_loaded;
$state_loaded=1;
@@ -176,32 +205,13 @@ sub loadstate () { #{{{
sub savestate () { #{{{
return unless $state_loaded;
+ garbage_collect();
eval q{use HTML::Entities};
error($@) if $@;
my $newfile="$config{wikistatedir}/aggregate.new";
my $cleanup = sub { unlink($newfile) };
open (OUT, ">$newfile") || error("open $newfile: $!", $cleanup);
foreach my $data (values %feeds, values %guids) {
- if ($data->{remove}) {
- if ($data->{name}) {
- foreach my $guid (values %guids) {
- if ($guid->{feed} eq $data->{name}) {
- $guid->{remove}=1;
- }
- }
- }
- else {
- unlink pagefile($data->{page})
- if exists $data->{page};
- }
- next;
- }
- elsif ($data->{expired} && exists $data->{page}) {
- unlink pagefile($data->{page});
- delete $data->{page};
- delete $data->{md5};
- }
-
my @line;
foreach my $field (keys %$data) {
if ($field eq "name" || $field eq "feed" ||
@@ -222,6 +232,69 @@ sub savestate () { #{{{
error("rename $newfile: $!", $cleanup);
} #}}}
+sub garbage_collect () { #{{{
+ foreach my $name (keys %feeds) {
+ # remove any feeds that were not seen while building the pages
+ # that used to contain them
+ if ($feeds{$name}->{unseen}) {
+ delete $feeds{$name};
+ }
+ }
+
+ foreach my $guid (values %guids) {
+ # any guid whose feed is gone should be removed
+ if (! exists $feeds{$guid->{feed}}) {
+ unlink pagefile($guid->{page})
+ if exists $guid->{page};
+ delete $guids{$guid->{guid}};
+ }
+ # handle expired guids
+ elsif ($guid->{expired} && exists $guid->{page}) {
+ unlink pagefile($guid->{page});
+ delete $guid->{page};
+ delete $guid->{md5};
+ }
+ }
+} #}}}
+
+sub mergestate () { #{{{
+ # Load the current state in from disk, and merge into it
+ # values from the state in memory that might have changed
+ # during aggregation.
+ my %myfeeds=%feeds;
+ my %myguids=%guids;
+ clearstate();
+ loadstate();
+
+ # All that can change in feed state during aggregation is a few
+ # fields.
+ foreach my $name (keys %myfeeds) {
+ if (exists $feeds{$name}) {
+ foreach my $field (qw{message lastupdate numposts
+ newposts error}) {
+ $feeds{$name}->{$field}=$myfeeds{$name}->{$field};
+ }
+ }
+ }
+
+ # New guids can be created during aggregation.
+ # It's also possible that guids were removed from the on-disk state
+ # while the aggregation was in process. That would only happen if
+ # their feed was also removed, so any removed guids added back here
+ # will be garbage collected later.
+ foreach my $guid (keys %myguids) {
+ if (! exists $guids{$guid}) {
+ $guids{$guid}=$myguids{$guid};
+ }
+ }
+} #}}}
+
+sub clearstate () { #{{{
+ %feeds=();
+ %guids=();
+ $state_loaded=0;
+} #}}}
+
sub expire () { #{{{
foreach my $feed (values %feeds) {
next unless $feed->{expireage} || $feed->{expirecount};
@@ -253,7 +326,12 @@ sub expire () { #{{{
}
} #}}}
-sub aggregate () { #{{{
+sub needsaggregate () { #{{{
+ return values %feeds if $config{rebuild};
+ return grep { time - $_->{lastupdate} >= $_->{updateinterval} } values %feeds;
+} #}}}
+
+sub aggregate (@) { #{{{
eval q{use XML::Feed};
error($@) if $@;
eval q{use URI::Fetch};
@@ -261,15 +339,12 @@ sub aggregate () { #{{{
eval q{use HTML::Entities};
error($@) if $@;
- foreach my $feed (values %feeds) {
- next unless $config{rebuild} ||
- time - $feed->{lastupdate} >= $feed->{updateinterval};
+ foreach my $feed (@_) {
$feed->{lastupdate}=time;
$feed->{newposts}=0;
$feed->{message}=sprintf(gettext("processed ok at %s"),
displaytime($feed->{lastupdate}));
$feed->{error}=0;
- $IkiWiki::forcerebuild{$feed->{sourcepage}}=1;
debug(sprintf(gettext("checking feed %s ..."), $feed->{name}));
@@ -477,18 +552,6 @@ sub htmlabs ($$) { #{{{
return $ret;
} #}}}
-sub remove_feeds () { #{{{
- my $page=shift;
-
- my %removed;
- foreach my $id (keys %feeds) {
- if ($feeds{$id}->{sourcepage} eq $page) {
- $feeds{$id}->{remove}=1;
- $removed{$id}=1;
- }
- }
-} #}}}
-
sub pagefile ($) { #{{{
my $page=shift;
@@ -499,4 +562,26 @@ sub htmlfn ($) { #{{{
return shift().".".$config{htmlext};
} #}}}
+my $aggregatelock;
+
+sub lockaggregate () { #{{{
+ # Take an exclusive lock to prevent multiple concurrent aggregators.
+ # Returns true if the lock was aquired.
+ if (! -d $config{wikistatedir}) {
+ mkdir($config{wikistatedir});
+ }
+ open($aggregatelock, '>', "$config{wikistatedir}/aggregatelock") ||
+ error ("cannot open to $config{wikistatedir}/aggregatelock: $!");
+ if (! flock($aggregatelock, 2 | 4)) { # LOCK_EX | LOCK_NB
+ close($aggregatelock) || error("failed closing aggregatelock: $!");
+ return 0;
+ }
+ return 1;
+} #}}}
+
+sub unlockaggregate () { #{{{
+ return close($aggregatelock) if $aggregatelock;
+ return;
+} #}}}
+
1
diff --git a/IkiWiki/Plugin/calendar.pm b/IkiWiki/Plugin/calendar.pm
index 4bb4c2c21..aed087eed 100644
--- a/IkiWiki/Plugin/calendar.pm
+++ b/IkiWiki/Plugin/calendar.pm
@@ -390,7 +390,8 @@ sub needsbuild (@) { #{{{
# the current day
push @$needsbuild, $pagesources{$page};
}
- if (grep { $_ eq $pagesources{$page} } @$needsbuild) {
+ if (exists $pagesources{$page} &&
+ grep { $_ eq $pagesources{$page} } @$needsbuild) {
# remove state, will be re-added if
# the calendar is still there during the
# rebuild
diff --git a/IkiWiki/Plugin/ddate.pm b/IkiWiki/Plugin/ddate.pm
index 6b67f4202..d081cb509 100644
--- a/IkiWiki/Plugin/ddate.pm
+++ b/IkiWiki/Plugin/ddate.pm
@@ -18,6 +18,10 @@ sub checkconfig () { #{{{
sub IkiWiki::displaytime ($;$) { #{{{
my $time=shift;
+ my $format=shift;
+ if (! defined $format) {
+ $format=$config{timeformat};
+ }
eval q{
use DateTime;
use DateTime::Calendar::Discordian;
@@ -27,7 +31,7 @@ sub IkiWiki::displaytime ($;$) { #{{{
}
my $dt = DateTime->from_epoch(epoch => $time);
my $dd = DateTime::Calendar::Discordian->from_object(object => $dt);
- return $dd->strftime($IkiWiki::config{timeformat});
+ return $dd->strftime($format);
} #}}}
5
diff --git a/IkiWiki/Plugin/edittemplate.pm b/IkiWiki/Plugin/edittemplate.pm
index aa72b0845..b7651ce02 100644
--- a/IkiWiki/Plugin/edittemplate.pm
+++ b/IkiWiki/Plugin/edittemplate.pm
@@ -21,7 +21,8 @@ sub needsbuild (@) { #{{{
foreach my $page (keys %pagestate) {
if (exists $pagestate{$page}{edittemplate}) {
- if (grep { $_ eq $pagesources{$page} } @$needsbuild) {
+ if (exists $pagesources{$page} &&
+ grep { $_ eq $pagesources{$page} } @$needsbuild) {
# remove state, it will be re-added
# if the preprocessor directive is still
# there during the rebuild
diff --git a/IkiWiki/Plugin/inline.pm b/IkiWiki/Plugin/inline.pm
index 59eabb606..b40303078 100644
--- a/IkiWiki/Plugin/inline.pm
+++ b/IkiWiki/Plugin/inline.pm
@@ -34,6 +34,8 @@ sub getopt () { #{{{
GetOptions(
"rss!" => \$config{rss},
"atom!" => \$config{atom},
+ "allowrss!" => \$config{allowrss},
+ "allowatom!" => \$config{allowatom},
);
}
@@ -91,11 +93,10 @@ sub preprocess_inline (@) { #{{{
}
my $raw=yesno($params{raw});
my $archive=yesno($params{archive});
- my $rss=($config{rss} && exists $params{rss}) ? yesno($params{rss}) : $config{rss};
- my $atom=($config{atom} && exists $params{atom}) ? yesno($params{atom}) : $config{atom};
+ my $rss=(($config{rss} || $config{allowrss}) && exists $params{rss}) ? yesno($params{rss}) : $config{rss};
+ my $atom=(($config{atom} || $config{allowatom}) && exists $params{atom}) ? yesno($params{atom}) : $config{atom};
my $quick=exists $params{quick} ? yesno($params{quick}) : 0;
my $feeds=exists $params{feeds} ? yesno($params{feeds}) : !$quick;
- $feeds=0 if $params{preview};
my $feedonly=yesno($params{feedonly});
if (! exists $params{show} && ! $archive) {
$params{show}=10;
@@ -182,7 +183,7 @@ sub preprocess_inline (@) { #{{{
my $atomurl=basename(atompage($params{destpage}).$feednum) if $feeds && $atom;
my $ret="";
- if ($config{cgiurl} && (exists $params{rootpage} ||
+ if ($config{cgiurl} && ! $params{preview} && (exists $params{rootpage} ||
(exists $params{postform} && yesno($params{postform})))) {
# Add a blog post form, with feed buttons.
my $formtemplate=template("blogpost.tmpl", blind_cache => 1);
@@ -201,7 +202,7 @@ sub preprocess_inline (@) { #{{{
}
$ret.=$formtemplate->output;
}
- elsif ($feeds) {
+ elsif ($feeds && !$params{preview}) {
# Add feed buttons.
my $linktemplate=template("feedlink.tmpl", blind_cache => 1);
$linktemplate->param(rssurl => $rssurl) if $rss;
@@ -231,6 +232,8 @@ sub preprocess_inline (@) { #{{{
$template->param(pageurl => urlto(bestlink($params{page}, $page), $params{destpage}));
$template->param(title => pagetitle(basename($page)));
$template->param(ctime => displaytime($pagectime{$page}, $params{timeformat}));
+ $template->param(first => 1) if $page eq $list[0];
+ $template->param(last => 1) if $page eq $list[$#list];
if ($actions) {
my $file = $pagesources{$page};
@@ -286,18 +289,22 @@ sub preprocess_inline (@) { #{{{
if ($rss) {
my $rssp=rsspage($params{destpage}).$feednum;
will_render($params{destpage}, $rssp);
- writefile($rssp, $config{destdir},
- genfeed("rss", $rssurl, $desc, $params{destpage}, @list));
- $toping{$params{destpage}}=1 unless $config{rebuild};
- $feedlinks{$params{destpage}}=qq{<link rel="alternate" type="application/rss+xml" title="RSS" href="$rssurl" />};
+ if (! $params{preview}) {
+ writefile($rssp, $config{destdir},
+ genfeed("rss", $rssurl, $desc, $params{destpage}, @list));
+ $toping{$params{destpage}}=1 unless $config{rebuild};
+ $feedlinks{$params{destpage}}=qq{<link rel="alternate" type="application/rss+xml" title="RSS" href="$rssurl" />};
+ }
}
if ($atom) {
my $atomp=atompage($params{destpage}).$feednum;
will_render($params{destpage}, $atomp);
- writefile($atomp, $config{destdir},
- genfeed("atom", $atomurl, $desc, $params{destpage}, @list));
- $toping{$params{destpage}}=1 unless $config{rebuild};
- $feedlinks{$params{destpage}}=qq{<link rel="alternate" type="application/atom+xml" title="Atom" href="$atomurl" />};
+ if (! $params{preview}) {
+ writefile($atomp, $config{destdir},
+ genfeed("atom", $atomurl, $desc, $params{destpage}, @list));
+ $toping{$params{destpage}}=1 unless $config{rebuild};
+ $feedlinks{$params{destpage}}=qq{<link rel="alternate" type="application/atom+xml" title="Atom" href="$atomurl" />};
+ }
}
}
diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm
index d2c6e7f8b..621e87674 100644
--- a/IkiWiki/Plugin/meta.pm
+++ b/IkiWiki/Plugin/meta.pm
@@ -6,13 +6,7 @@ use warnings;
use strict;
use IkiWiki 2.00;
-my %meta;
-my %title;
-my %permalink;
-my %author;
-my %authorurl;
-my %license;
-my %copyright;
+my %metaheaders;
sub import { #{{{
hook(type => "needsbuild", id => "meta", call => \&needsbuild);
@@ -24,7 +18,8 @@ sub needsbuild (@) { #{{{
my $needsbuild=shift;
foreach my $page (keys %pagestate) {
if (exists $pagestate{$page}{meta}) {
- if (grep { $_ eq $pagesources{$page} } @$needsbuild) {
+ if (exists $pagesources{$page} &&
+ grep { $_ eq $pagesources{$page} } @$needsbuild) {
# remove state, it will be re-added
# if the preprocessor directive is still
# there during the rebuild
@@ -71,16 +66,16 @@ sub preprocess (@) { #{{{
# Metadata collection that needs to happen during the scan pass.
if ($key eq 'title') {
- $title{$page}=HTML::Entities::encode_numeric($value);
+ $pagestate{$page}{meta}{title}=HTML::Entities::encode_numeric($value);
}
elsif ($key eq 'license') {
- push @{$meta{$page}}, '<link rel="license" href="#page_license" />';
- $license{$page}=$value;
+ push @{$metaheaders{$page}}, '<link rel="license" href="#page_license" />';
+ $pagestate{$page}{meta}{license}=$value;
return "";
}
elsif ($key eq 'copyright') {
- push @{$meta{$page}}, '<link rel="copyright" href="#page_copyright" />';
- $copyright{$page}=$value;
+ push @{$metaheaders{$page}}, '<link rel="copyright" href="#page_copyright" />';
+ $pagestate{$page}{meta}{copyright}=$value;
return "";
}
elsif ($key eq 'link' && ! %params) {
@@ -89,11 +84,11 @@ sub preprocess (@) { #{{{
return "";
}
elsif ($key eq 'author') {
- $author{$page}=$value;
+ $pagestate{$page}{meta}{author}=$value;
# fallthorough
}
elsif ($key eq 'authorurl') {
- $authorurl{$page}=$value;
+ $pagestate{$page}{meta}{authorurl}=$value;
# fallthrough
}
@@ -111,8 +106,8 @@ sub preprocess (@) { #{{{
}
}
elsif ($key eq 'permalink') {
- $permalink{$page}=$value;
- push @{$meta{$page}}, scrub('<link rel="bookmark" href="'.encode_entities($value).'" />');
+ $pagestate{$page}{meta}{permalink}=$value;
+ push @{$metaheaders{$page}}, scrub('<link rel="bookmark" href="'.encode_entities($value).'" />');
}
elsif ($key eq 'stylesheet') {
my $rel=exists $params{rel} ? $params{rel} : "alternate stylesheet";
@@ -123,17 +118,17 @@ sub preprocess (@) { #{{{
if (! length $stylesheet) {
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).
'" title="'.encode_entities($title).
"\" type=\"text/css\" />";
}
elsif ($key eq 'openid') {
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" />';
}
- push @{$meta{$page}}, '<link href="'.encode_entities($value).
+ push @{$metaheaders{$page}}, '<link href="'.encode_entities($value).
'" rel="openid.delegate" />';
}
elsif ($key eq 'redir') {
@@ -172,11 +167,11 @@ sub preprocess (@) { #{{{
if (! $safe) {
$redir=scrub($redir);
}
- push @{$meta{$page}}, $redir;
+ push @{$metaheaders{$page}}, $redir;
}
elsif ($key eq 'link') {
if (%params) {
- push @{$meta{$page}}, scrub("<link href=\"".encode_entities($value)."\" ".
+ push @{$metaheaders{$page}}, scrub("<link href=\"".encode_entities($value)."\" ".
join(" ", map {
encode_entities($_)."=\"".encode_entities(decode_entities($params{$_}))."\""
} keys %params).
@@ -184,7 +179,7 @@ sub preprocess (@) { #{{{
}
}
else {
- push @{$meta{$page}}, scrub('<meta name="'.encode_entities($key).
+ push @{$metaheaders{$page}}, scrub('<meta name="'.encode_entities($key).
'" content="'.encode_entities($value).'" />');
}
@@ -197,32 +192,80 @@ sub pagetemplate (@) { #{{{
my $destpage=$params{destpage};
my $template=$params{template};
- if (exists $meta{$page} && $template->query(name => "meta")) {
+ if (exists $metaheaders{$page} && $template->query(name => "meta")) {
# avoid duplicate meta lines
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")) {
- $template->param(title => $title{$page});
+ if (exists $pagestate{$page}{meta}{title} && $template->query(name => "title")) {
+ $template->param(title => $pagestate{$page}{meta}{title});
$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") &&
- ($page eq $destpage || ! exists $license{$destpage} ||
- $license{$page} ne $license{$destpage})) {
- $template->param(license => htmlize($page, $destpage, $license{$page}));
+ foreach my $field (qw{author authorurl permalink}) {
+ $template->param($field => $pagestate{$page}{meta}{$field})
+ if exists $pagestate{$page}{meta}{$field} && $template->query(name => $field);
}
- if (exists $copyright{$page} && $template->query(name => "copyright") &&
- ($page eq $destpage || ! exists $copyright{$destpage} ||
- $copyright{$page} ne $copyright{$destpage})) {
- $template->param(copyright => htmlize($page, $destpage, $copyright{$page}));
+
+ foreach my $field (qw{license copyright}) {
+ if (exists $pagestate{$page}{meta}{$field} && $template->query(name => $field) &&
+ ($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
diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm
index e8dbe964f..de31f38ee 100644
--- a/IkiWiki/Plugin/openid.pm
+++ b/IkiWiki/Plugin/openid.pm
@@ -164,4 +164,31 @@ sub getobj ($$) { #{{{
);
} #}}}
+package IkiWiki;
+
+# This is not used by this plugin, but this seems the best place to put it.
+# Used elsewhere to pretty-display the name of an openid user.
+sub openiduser ($) { #{{{
+ my $user=shift;
+
+ if ($user =~ m!^https?://! &&
+ eval q{use Net::OpenID::VerifiedIdentity; 1} && !$@) {
+ my $oid=Net::OpenID::VerifiedIdentity->new(identity => $user);
+ my $display=$oid->display;
+ # Convert "user.somehost.com" to "user [somehost.com]".
+ if ($display !~ /\[/) {
+ $display=~s/^(.*?)\.([^.]+\.[a-z]+)$/$1 [$2]/;
+ }
+ # Convert "http://somehost.com/user" to "user [somehost.com]".
+ if ($display !~ /\[/) {
+ $display=~s/^https?:\/\/(.+)\/([^\/]+)$/$2 [$1]/;
+ }
+ $display=~s!^https?://!!; # make sure this is removed
+ eval q{use CGI 'escapeHTML'};
+ error($@) if $@;
+ return escapeHTML($display);
+ }
+ return;
+}
+
1
diff --git a/IkiWiki/Plugin/poll.pm b/IkiWiki/Plugin/poll.pm
index 63c93c62d..41ebd74a0 100644
--- a/IkiWiki/Plugin/poll.pm
+++ b/IkiWiki/Plugin/poll.pm
@@ -7,7 +7,7 @@ use IkiWiki 2.00;
sub import { #{{{
hook(type => "preprocess", id => "poll", call => \&preprocess);
- hook(type => "cgi", id => "poll", call => \&cgi);
+ hook(type => "sessioncgi", id => "poll", call => \&sessioncgi);
} # }}}
sub yesno ($) { #{{{
@@ -74,8 +74,9 @@ sub preprocess (@) { #{{{
return "<div class=poll>$ret</div>";
} # }}}
-sub cgi ($) { #{{{
+sub sessioncgi ($$) { #{{{
my $cgi=shift;
+ my $session=shift;
if (defined $cgi->param('do') && $cgi->param('do') eq "poll") {
my $choice=$cgi->param('choice');
if (! defined $choice) {
@@ -92,7 +93,6 @@ sub cgi ($) { #{{{
# Did they vote before? If so, let them change their vote,
# and check for dups.
- my $session=IkiWiki::cgi_getsession();
my $choice_param="poll_choice_${page}_$num";
my $oldchoice=$session->param($choice_param);
if (defined $oldchoice && $oldchoice eq $choice) {
diff --git a/IkiWiki/Plugin/prettydate.pm b/IkiWiki/Plugin/prettydate.pm
index b6110e427..745e6a1de 100644
--- a/IkiWiki/Plugin/prettydate.pm
+++ b/IkiWiki/Plugin/prettydate.pm
@@ -63,6 +63,10 @@ sub checkconfig () { #{{{
sub IkiWiki::displaytime ($;$) { #{{{
my $time=shift;
+ my $format=shift;
+ if (! defined $format) {
+ $format=$config{prettydateformat};
+ }
eval q{use Date::Format};
error($@) if $@;
@@ -93,7 +97,6 @@ sub IkiWiki::displaytime ($;$) { #{{{
$t=~s{\%A-}{my @yest=@t; $yest[6]--; strftime("%A", \@yest)}eg;
- my $format=$config{prettydateformat};
$format=~s/\%X/$t/g;
return strftime($format, \@t);
} #}}}
diff --git a/IkiWiki/Plugin/recentchanges.pm b/IkiWiki/Plugin/recentchanges.pm
new file mode 100644
index 000000000..22f934f2e
--- /dev/null
+++ b/IkiWiki/Plugin/recentchanges.pm
@@ -0,0 +1,169 @@
+#!/usr/bin/perl
+package IkiWiki::Plugin::recentchanges;
+
+use warnings;
+use strict;
+use IkiWiki 2.00;
+
+sub import { #{{{
+ hook(type => "checkconfig", id => "recentchanges", call => \&checkconfig);
+ hook(type => "refresh", id => "recentchanges", call => \&refresh);
+ hook(type => "pagetemplate", id => "recentchanges", call => \&pagetemplate);
+ hook(type => "htmlize", id => "_change", call => \&htmlize);
+ hook(type => "cgi", id => "recentchanges", call => \&cgi);
+} #}}}
+
+sub checkconfig () { #{{{
+ $config{recentchangespage}='recentchanges' unless defined $config{recentchangespage};
+ $config{recentchangesnum}=100 unless defined $config{recentchangesnum};
+} #}}}
+
+sub refresh ($) { #{{{
+ my %seen;
+
+ # add new changes
+ foreach my $change (IkiWiki::rcs_recentchanges($config{recentchangesnum})) {
+ $seen{store($change, $config{recentchangespage})}=1;
+ }
+
+ # delete old and excess changes
+ foreach my $page (keys %pagesources) {
+ if ($page =~ /\._change$/ && ! $seen{$page}) {
+ unlink($config{srcdir}.'/'.$pagesources{$page});
+ }
+ }
+} #}}}
+
+# Enable the recentchanges link on wiki pages.
+sub pagetemplate (@) { #{{{
+ my %params=@_;
+ my $template=$params{template};
+ my $page=$params{page};
+ if ($config{rcs} && $page ne $config{recentchangespage} &&
+ $template->query(name => "recentchangesurl")) {
+ $template->param(recentchangesurl => urlto($config{recentchangespage}, $page));
+ $template->param(have_actions => 1);
+ }
+} #}}}
+
+# Pages with extension _change have plain html markup, pass through.
+sub htmlize (@) { #{{{
+ my %params=@_;
+ return $params{content};
+} #}}}
+
+sub cgi ($) { #{{{
+ my $cgi=shift;
+ if (defined $cgi->param('do') && $cgi->param('do') eq "recentchanges_link") {
+ # This is a link from a change page to some
+ # other page. Since the change pages are only generated
+ # once, statically, links on them won't be updated if the
+ # page they link to is deleted, or newly created, or
+ # changes for whatever reason. So this CGI handles that
+ # dynamic linking stuff.
+ my $page=$cgi->param("page");
+ if (!defined $page) {
+ error("missing page parameter");
+ }
+
+ IkiWiki::loadindex();
+
+ my $link=bestlink("", $page);
+ if (! length $link) {
+ print "Content-type: text/html\n\n";
+ print IkiWiki::misctemplate(gettext(gettext("missing page")),
+ "<p>".
+ sprintf(gettext("The page %s does not exist."),
+ htmllink("", "", $page)).
+ "</p>");
+ }
+ else {
+ IkiWiki::redirect($cgi, $config{url}."/".htmlpage($link));
+ }
+
+ exit;
+ }
+}
+
+sub store ($$$) { #{{{
+ my $change=shift;
+
+ my $page="$config{recentchangespage}/change_".IkiWiki::titlepage($change->{rev});
+
+ # Optimisation to avoid re-writing pages. Assumes commits never
+ # change (or that any changes are not important).
+ return $page if exists $pagesources{$page} && ! $config{rebuild};
+
+ # Limit pages to first 10, and add links to the changed pages.
+ my $is_excess = exists $change->{pages}[10];
+ delete @{$change->{pages}}[10 .. @{$change->{pages}}] if $is_excess;
+ $change->{pages} = [
+ map {
+ if (length $config{cgiurl}) {
+ $_->{link} = "<a href=\"".
+ IkiWiki::cgiurl(
+ do => "recentchanges_link",
+ page => $_->{page}
+ ).
+ "\">".
+ IkiWiki::pagetitle($_->{page}).
+ "</a>"
+ }
+ else {
+ $_->{link} = IkiWiki::pagetitle($_->{page});
+ }
+ $_->{baseurl}="$config{url}/" if length $config{url};
+
+ $_;
+ } @{$change->{pages}}
+ ];
+ push @{$change->{pages}}, { link => '...' } if $is_excess;
+
+ # See if the committer is an openid.
+ $change->{author}=$change->{user};
+ my $oiduser=eval { IkiWiki::openiduser($change->{user}) };
+ if (defined $oiduser) {
+ $change->{authorurl}=$change->{user};
+ $change->{user}=$oiduser;
+ }
+ elsif (length $config{cgiurl}) {
+ $change->{authorurl} = IkiWiki::cgiurl(
+ do => "recentchanges_link",
+ page => (length $config{userdir} ? "$config{userdir}/" : "").$change->{author},
+ );
+ }
+
+ # escape wikilinks and preprocessor stuff in commit messages
+ if (ref $change->{message}) {
+ foreach my $field (@{$change->{message}}) {
+ if (exists $field->{line}) {
+ $field->{line} =~ s/(?<!\\)\[\[/\\\[\[/g;
+ }
+ }
+ }
+
+ # Fill out a template with the change info.
+ my $template=template("change.tmpl", blind_cache => 1);
+ $template->param(
+ %$change,
+ commitdate => displaytime($change->{when}, "%X %x"),
+ wikiname => $config{wikiname},
+ );
+ IkiWiki::run_hooks(pagetemplate => sub {
+ shift->(page => $page, destpage => $page, template => $template);
+ });
+
+ my $file=$page."._change";
+ writefile($file, $config{srcdir}, $template->output);
+ utime $change->{when}, $change->{when}, "$config{srcdir}/$file";
+
+ return $page;
+} #}}}
+
+sub updatechanges ($$) { #{{{
+ my $subdir=shift;
+ my @changes=@{shift()};
+
+} #}}}
+
+1
diff --git a/IkiWiki/Plugin/teximg.pm b/IkiWiki/Plugin/teximg.pm
index 5f9589fdd..5dff5feef 100644
--- a/IkiWiki/Plugin/teximg.pm
+++ b/IkiWiki/Plugin/teximg.pm
@@ -82,8 +82,8 @@ sub create ($$$) { #{{{
$logurl = urlto($imglog, $params->{destpage});
}
else {
- $imgurl="$params->{page}/$digest.png";
- $logurl="$params->{page}/$digest.log";
+ $imgurl=$params->{page}."/$digest.png";
+ $logurl=$params->{page}."/$digest.log";
}
if (-e "$config{destdir}/$imglink" ||
diff --git a/IkiWiki/Plugin/version.pm b/IkiWiki/Plugin/version.pm
index d39fd9419..f96d2df06 100644
--- a/IkiWiki/Plugin/version.pm
+++ b/IkiWiki/Plugin/version.pm
@@ -18,7 +18,8 @@ sub needsbuild (@) { #{{{
if ($pagestate{$page}{version}{shown} ne $IkiWiki::version) {
push @$needsbuild, $pagesources{$page};
}
- if (grep { $_ eq $pagesources{$page} } @$needsbuild) {
+ if (exists $pagesources{$page} &&
+ grep { $_ eq $pagesources{$page} } @$needsbuild) {
# remove state, will be re-added if
# the version is still shown during the
# rebuild