X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/blobdiff_plain/582e52d3ca79d0d5d0335486718fe2167d6304cd..da40e3512ba63a467853eb9f04eccba7363b09d8:/IkiWiki.pm diff --git a/IkiWiki.pm b/IkiWiki.pm index 75c957932..d5d11ee85 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -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}; + %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"); @@ -134,6 +139,13 @@ sub getsetup () { 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 => '', @@ -145,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, @@ -515,12 +528,19 @@ sub getsetup () { }, cookiejar => { type => "string", - default => "$ENV{HOME}/.ikiwiki/cookies", + default => { file => "$ENV{HOME}/.ikiwiki/cookies" }, description => "cookie control", - example => { file => "$ENV{HOME}/.ikiwiki/cookies" }, 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 () { @@ -729,6 +749,7 @@ sub debug ($) { } my $log_open=0; +my $log_failed=0; sub log_message ($$) { my $type=shift; @@ -739,9 +760,18 @@ sub log_message ($$) { 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"; @@ -1484,7 +1514,7 @@ sub preprocess ($$$;$$) { 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 ". @@ -1782,7 +1812,8 @@ sub enable_commit_hook () { 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=(); @@ -1794,7 +1825,8 @@ sub loadindex () { open ($in, "<", "$config{wikistatedir}/indexdb") || return; } else { - $config{gettime}=1; # first build + # gettime on first build + $config{gettime}=1 unless defined $config{gettime}; return; } } @@ -1822,10 +1854,16 @@ sub loadindex () { 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}) { @@ -1875,6 +1913,8 @@ sub loadindex () { foreach my $page (keys %renderedfiles) { $destsources{$_}=$page foreach @{$renderedfiles{$page}}; } + $lastrev=$index->{lastrev}; + @underlayfiles=@{$index->{underlayfiles}} if ref $index->{underlayfiles}; return close($in); } @@ -1896,6 +1936,7 @@ sub saveindex () { my $src=$pagesources{$page}; $index{page}{$src}={ + page => $page, ctime => $pagectime{$page}, mtime => $pagemtime{$page}, dest => $renderedfiles{$page}, @@ -1915,11 +1956,7 @@ sub saveindex () { } 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}; } } @@ -1931,6 +1968,9 @@ sub saveindex () { } } + $index{lastrev}=$lastrev; + $index{underlayfiles}=\@underlayfiles; + $index{version}="3"; my $ret=Storable::nstore_fd(\%index, $out); return if ! defined $ret || ! $ret; @@ -1988,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, @@ -2286,6 +2334,7 @@ sub useragent () { return LWP::UserAgent->new( cookie_jar => $config{cookiejar}, env_proxy => 1, # respect proxy env vars + agent => $config{useragent}, ); } @@ -2425,6 +2474,19 @@ sub pagespec_match ($$;@) { 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; @@ -2530,21 +2592,48 @@ our @ISA = 'IkiWiki::SuccessReason'; 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 @_; @@ -2553,15 +2642,46 @@ sub influences { 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]}) { @@ -2574,6 +2694,8 @@ sub merge_influences { } } +# Change $this so it is not considered to be influenced by $torm. + sub remove_influence { my $this=shift; my $torm=shift;