%pagestate %wikistate %renderedfiles %oldrenderedfiles
%pagesources %delpagesources %destsources %depends %depends_simple
@mass_depends %hooks %forcerebuild %loaded_plugins %typedlinks
- %oldtypedlinks %autofiles};
+ %oldtypedlinks %autofiles @underlayfiles $lastrev $phase};
use Exporter q{import};
our @EXPORT = qw(hook debug error htmlpage template template_depends
htmllink readfile writefile pagetype srcfile pagename
displaytime strftime_utf8 will_render gettext ngettext urlto targetpage
add_underlay pagetitle titlepage linkpage newpagefile
- inject add_link add_autofile
+ inject add_link add_autofile useragent
%config %links %pagestate %wikistate %renderedfiles
%pagesources %destsources %typedlinks);
our $VERSION = 3.00; # plugin interface version, next is ikiwiki version
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");
safe => 0,
rebuild => 0,
},
+ cgi_overload_delay => {
+ type => "string",
+ default => '',
+ example => "10",
+ description => "number of seconds to delay CGI requests when overloaded",
+ safe => 1,
+ rebuild => 0,
+ },
+ cgi_overload_message => {
+ type => "string",
+ default => '',
+ example => "Please wait",
+ description => "message to display when overloaded (may contain html)",
+ safe => 1,
+ rebuild => 0,
+ },
+ only_committed_changes => {
+ type => "boolean",
+ default => 0,
+ description => "enable optimization of only refreshing committed changes?",
+ safe => 1,
+ rebuild => 0,
+ },
rcs => {
type => "string",
default => '',
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,
safe => 0,
rebuild => 0,
},
+ cookiejar => {
+ type => "string",
+ default => { file => "$ENV{HOME}/.ikiwiki/cookies" },
+ description => "cookie control",
+ safe => 0, # hooks into perl module internals
+ rebuild => 0,
+ },
+ useragent => {
+ type => "string",
+ default => undef,
+ example => "Wget/1.13.4 (linux-gnu)",
+ description => "set custom user agent string for outbound HTTP requests e.g. when fetching aggregated RSS feeds",
+ safe => 0,
+ rebuild => 0,
+ },
}
sub defaultconfig () {
}
my $log_open=0;
+my $log_failed=0;
sub log_message ($$) {
my $type=shift;
Sys::Syslog::openlog('ikiwiki', '', 'user');
$log_open=1;
}
- return eval {
- Sys::Syslog::syslog($type, "[$config{wikiname}] %s", join(" ", @_));
+ eval {
+ # keep a copy to avoid editing the original config repeatedly
+ my $wikiname = $config{wikiname};
+ utf8::encode($wikiname);
+ Sys::Syslog::syslog($type, "[$wikiname] %s", join(" ", @_));
};
+ if ($@) {
+ print STDERR "failed to syslog: $@" unless $log_failed;
+ $log_failed=1;
+ print STDERR "@_\n";
+ }
+ return $@;
}
elsif (! $config{cgi}) {
return print "@_\n";
join("&", map $_."=".uri_escape_utf8($params{$_}), keys %params);
}
+sub cgiurl_abs (@) {
+ eval q{use URI};
+ URI->new_abs(cgiurl(@_), $config{cgiurl});
+}
+
sub baseurl (;$) {
my $page=shift;
# consider it significant.
my @params;
while ($params =~ m{
- (?:([-\w]+)=)? # 1: named parameter key?
+ (?:([-.\w]+)=)? # 1: named parameter key?
(?:
"""(.*?)""" # 2: triple-quoted value
|
push @params, $val, '';
}
}
- if ($preprocessing{$page}++ > 3) {
+ if ($preprocessing{$page}++ > 8) {
# Avoid loops of preprocessed pages preprocessing
# other pages that preprocess them, etc.
return "[[!$command <span class=\"error\">".
( # 4: the parameters..
\s+ # Must have space if parameters present
(?:
- (?:[-\w]+=)? # named parameter key?
+ (?:[-.\w]+=)? # named parameter key?
(?:
""".*?""" # triple-quoted value
|
\s+
( # 4: the parameters..
(?:
- (?:[-\w]+=)? # named parameter key?
+ (?:[-.\w]+=)? # named parameter key?
(?:
""".*?""" # triple-quoted value
|
sub loadindex () {
%oldrenderedfiles=%pagectime=();
- if (! $config{rebuild}) {
+ my $rebuild=$config{rebuild};
+ if (! $rebuild) {
%pagesources=%pagemtime=%oldlinks=%links=%depends=
%destsources=%renderedfiles=%pagecase=%pagestate=
%depends_simple=%typedlinks=%oldtypedlinks=();
open ($in, "<", "$config{wikistatedir}/indexdb") || return;
}
else {
- $config{gettime}=1; # first build
+ # gettime on first build
+ $config{gettime}=1 unless defined $config{gettime};
return;
}
}
foreach my $src (keys %$pages) {
my $d=$pages->{$src};
- my $page=pagename($src);
+ my $page;
+ if (exists $d->{page} && ! $rebuild) {
+ $page=$d->{page};
+ }
+ else {
+ $page=pagename($src);
+ }
$pagectime{$page}=$d->{ctime};
$pagesources{$page}=$src;
- if (! $config{rebuild}) {
+ if (! $rebuild) {
$pagemtime{$page}=$d->{mtime};
$renderedfiles{$page}=$d->{dest};
if (exists $d->{links} && ref $d->{links}) {
foreach my $page (keys %renderedfiles) {
$destsources{$_}=$page foreach @{$renderedfiles{$page}};
}
+ $lastrev=$index->{lastrev};
+ @underlayfiles=@{$index->{underlayfiles}} if ref $index->{underlayfiles};
return close($in);
}
my $src=$pagesources{$page};
$index{page}{$src}={
+ page => $page,
ctime => $pagectime{$page},
mtime => $pagemtime{$page},
dest => $renderedfiles{$page},
}
if (exists $pagestate{$page}) {
- foreach my $id (@plugins) {
- foreach my $key (keys %{$pagestate{$page}{$id}}) {
- $index{page}{$src}{state}{$id}{$key}=$pagestate{$page}{$id}{$key};
- }
- }
+ $index{page}{$src}{state}=$pagestate{$page};
}
}
}
}
+ $index{lastrev}=$lastrev;
+ $index{underlayfiles}=\@underlayfiles;
+
$index{version}="3";
my $ret=Storable::nstore_fd(\%index, $out);
return if ! defined $ret || ! $ret;
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,
$autofiles{$file}{generator}=$generator;
}
+sub useragent () {
+ return LWP::UserAgent->new(
+ cookie_jar => $config{cookiejar},
+ env_proxy => 1, # respect proxy env vars
+ agent => $config{useragent},
+ );
+}
+
sub sortspec_translate ($$) {
my $spec = shift;
my $reverse = shift;
return $sub->($page, @params);
}
+# e.g. @pages = sort_pages("title", \@pages, reverse => "yes")
+#
+# Not exported yet, but could be in future if it is generally useful.
+# Note that this signature is not the same as IkiWiki::SortSpec::sort_pages,
+# which is "more internal".
+sub sort_pages ($$;@) {
+ my $sort = shift;
+ my $list = shift;
+ my %params = @_;
+ $sort = sortspec_translate($sort, $params{reverse});
+ return IkiWiki::SortSpec::sort_pages($sort, @$list);
+}
+
sub pagespec_match_list ($$;@) {
my $page=shift;
my $pagespec=shift;
package IkiWiki::SuccessReason;
+# A blessed array-ref:
+#
+# [0]: human-readable reason for success (or, in FailReason subclass, failure)
+# [1]{""}:
+# - if absent or false, the influences of this evaluation are "static",
+# see the influences_static method
+# - if true, they are dynamic (not static)
+# [1]{any other key}:
+# the dependency types of influences, as returned by the influences method
+
use overload (
+ # in string context, it's the human-readable reason
'""' => sub { $_[0][0] },
+ # in boolean context, SuccessReason is 1 and FailReason is 0
'0+' => sub { 1 },
+ # negating a result gives the opposite result with the same influences
'!' => sub { bless $_[0], 'IkiWiki::FailReason'},
+ # A & B = (A ? B : A) with the influences of both
'&' => sub { $_[1]->merge_influences($_[0], 1); $_[1] },
+ # A | B = (A ? A : B) with the influences of both
'|' => sub { $_[0]->merge_influences($_[1]); $_[0] },
fallback => 1,
);
+# SuccessReason->new("human-readable reason", page => deptype, ...)
+
sub new {
my $class = shift;
my $value = shift;
return bless [$value, {@_}], $class;
}
+# influences(): return a reference to a copy of the hash
+# { page => dependency type } describing the pages that indirectly influenced
+# this result, but would not cause a dependency through ikiwiki's core
+# dependency logic.
+#
+# See [[todo/dependency_types]] for extensive discussion of what this means.
+#
+# influences(page => deptype, ...): remove all influences, replace them
+# with the arguments, and return a reference to a copy of the new influences.
+
sub influences {
my $this=shift;
$this->[1]={@_} if @_;
return \%i;
}
+# True if this result has the same influences whichever page it matches,
+# For instance, whether bar matches backlink(foo) is influenced only by
+# the set of links in foo, so its only influence is { foo => DEPEND_LINKS },
+# which does not mention bar anywhere.
+#
+# False if this result would have different influences when matching
+# different pages. For instance, when testing whether link(foo) matches bar,
+# { bar => DEPEND_LINKS } is an influence on that result, because changing
+# bar's links could change the outcome; so its influences are not the same
+# as when testing whether link(foo) matches baz.
+#
+# Static influences are one of the things that make pagespec_match_list
+# more efficient than repeated calls to pagespec_match.
+
sub influences_static {
return ! $_[0][1]->{""};
}
+# Change the influences of $this to be the influences of "$this & $other"
+# or "$this | $other".
+#
+# If both $this and $other are either successful or have influences,
+# or this is an "or" operation, the result has all the influences from
+# either of the arguments. It has dynamic influences if either argument
+# has dynamic influences.
+#
+# If this is an "and" operation, and at least one argument is a
+# FailReason with no influences, the result has no influences, and they
+# are not dynamic. For instance, link(foo) matching bar is influenced
+# by bar, but enabled(ddate) has no influences. Suppose ddate is disabled;
+# then (link(foo) and enabled(ddate)) not matching bar is not influenced by
+# bar, because it would be false however often you edit bar.
+
sub merge_influences {
my $this=shift;
my $other=shift;
my $anded=shift;
+ # This "if" is odd because it needs to avoid negating $this
+ # or $other, which would alter the objects in-place. Be careful.
if (! $anded || (($this || %{$this->[1]}) &&
($other || %{$other->[1]}))) {
foreach my $influence (keys %{$other->[1]}) {
}
}
+# Change $this so it is not considered to be influenced by $torm.
+
sub remove_influence {
my $this=shift;
my $torm=shift;
my $user=shift;
my %params=@_;
- my $regexp=IkiWiki::glob2re($user);
-
if (! exists $params{user}) {
return IkiWiki::ErrorReason->new("no user specified");
}
+ my $regexp=IkiWiki::glob2re($user);
+
if (defined $params{user} && $params{user}=~$regexp) {
return IkiWiki::SuccessReason->new("user is $user");
}
if (! exists $params{ip}) {
return IkiWiki::ErrorReason->new("no IP specified");
}
+
+ my $regexp=IkiWiki::glob2re(lc $ip);
- if (defined $params{ip} && lc $params{ip} eq lc $ip) {
+ if (defined $params{ip} && lc $params{ip}=~$regexp) {
return IkiWiki::SuccessReason->new("IP is $ip");
}
else {