%forcerebuild %loaded_plugins};
use Exporter q{import};
-our @EXPORT = qw(hook debug error template htmlpage add_depends pagespec_match
- pagespec_match_list bestlink htmllink readfile writefile
- pagetype srcfile pagename displaytime will_render gettext urlto
- targetpage add_underlay pagetitle titlepage linkpage
- newpagefile inject add_link
+our @EXPORT = qw(hook debug error template htmlpage deptype use_pagespec
+ add_depends pagespec_match pagespec_match_list bestlink
+ htmllink readfile writefile pagetype srcfile pagename
+ displaytime will_render gettext urlto targetpage
+ add_underlay pagetitle titlepage linkpage newpagefile
+ inject add_link
%config %links %pagestate %wikistate %renderedfiles
%pagesources %destsources);
our $VERSION = 3.00; # plugin interface version, next is ikiwiki version
our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE
our $installdir='/usr'; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE
+# Page dependency types.
+our $DEPEND_CONTENT=1;
+our $DEPEND_PRESENCE=2;
+our $DEPEND_LINKS=4;
+
# Optimisation.
use Memoize;
memoize("abs2rel");
$links{$page}=$d->{links};
$oldlinks{$page}=[@{$d->{links}}];
}
- if (exists $d->{depends_simple}) {
+ if (ref $d->{depends_simple} eq 'ARRAY') {
+ # old format
$depends_simple{$page}={
map { $_ => 1 } @{$d->{depends_simple}}
};
}
+ elsif (exists $d->{depends_simple}) {
+ $depends_simple{$page}=$d->{depends_simple};
+ }
if (exists $d->{dependslist}) {
+ # old format
$depends{$page}={
- map { $_ => 1 } @{$d->{dependslist}}
+ map { $_ => $DEPEND_CONTENT }
+ @{$d->{dependslist}}
};
}
+ elsif (exists $d->{depends} && ! ref $d->{depends}) {
+ # old format
+ $depends{$page}={$d->{depends} => $DEPEND_CONTENT };
+ }
elsif (exists $d->{depends}) {
- $depends{$page}={$d->{depends} => 1};
+ $depends{$page}=$d->{depends};
}
if (exists $d->{state}) {
$pagestate{$page}=$d->{state};
};
if (exists $depends{$page}) {
- $index{page}{$src}{dependslist} = [ keys %{$depends{$page}} ];
+ $index{page}{$src}{depends} = $depends{$page};
}
if (exists $depends_simple{$page}) {
- $index{page}{$src}{depends_simple} = [ keys %{$depends_simple{$page}} ];
+ $index{page}{$src}{depends_simple} = $depends_simple{$page};
}
if (exists $pagestate{$page}) {
$hooks{rcs}{rcs_receive}{call}->();
}
-sub add_depends ($$) {
+sub add_depends ($$;$) {
my $page=shift;
my $pagespec=shift;
+ my $deptype=shift || $DEPEND_CONTENT;
+ # Is the pagespec a simple page name?
if ($pagespec =~ /$config{wiki_file_regexp}/ &&
- $pagespec !~ /[\s*?()!]/) {
- # a simple dependency, which can be matched by string eq
- $depends_simple{$page}{lc $pagespec} = 1;
+ $pagespec !~ /[\s*?()!]/) {
+ $depends_simple{$page}{lc $pagespec} |= $deptype;
return 1;
}
- return unless pagespec_valid($pagespec);
-
- $depends{$page}{$pagespec} = 1;
+ # Analyse the pagespec, and match it against all pages
+ # to get a list of influences, and add explicit dependencies
+ # for those.
+ #my $sub=pagespec_translate($pagespec);
+ #return if $@;
+ #foreach my $p (keys %pagesources) {
+ # my $r=$sub->($p, location => $page );
+ # my %i=$r->influences;
+ # foreach my $i (keys %i) {
+ # $depends_simple{$page}{lc $i} |= $i{$i};
+ # }
+ #}
+ print STDERR "warning: use of add_depends; influences not tracked\n";
+
+ $depends{$page}{$pagespec} |= $deptype;
return 1;
}
+sub use_pagespec ($$;@) {
+ my $page=shift;
+ my $pagespec=shift;
+ my %params=@_;
+
+ my $sub=pagespec_translate($pagespec);
+ error "syntax error in pagespec \"$pagespec\""
+ if $@ || ! defined $sub;
+
+ my @candidates;
+ if (exists $params{limit}) {
+ @candidates=grep { $params{limit}->($_) } keys %pagesources;
+ }
+ else {
+ @candidates=keys %pagesources;
+ }
+
+ if (defined $params{sort}) {
+ my $f;
+ if ($params{sort} eq 'title') {
+ $f=sub { pagetitle(basename($a)) cmp pagetitle(basename($b)) };
+ }
+ elsif ($params{sort} eq 'title_natural') {
+ eval q{use Sort::Naturally};
+ if ($@) {
+ error(gettext("Sort::Naturally needed for title_natural sort"));
+ }
+ $f=sub { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) };
+ }
+ elsif ($params{sort} eq 'mtime') {
+ $f=sub { $pagemtime{$b} <=> $pagemtime{$a} };
+ }
+ elsif ($params{sort} eq 'age') {
+ $f=sub { $pagectime{$b} <=> $pagectime{$a} };
+ }
+ else {
+ error sprintf(gettext("unknown sort type %s"), $params{sort});
+ }
+ @candidates = sort { &$f } @candidates;
+ }
+
+ @candidates=reverse(@candidates) if $params{reverse};
+
+ my @matches;
+ my $firstfail;
+ my $count=0;
+ foreach my $p (@candidates) {
+ my $r=$sub->($p, location => $page);
+ if ($r) {
+ push @matches, [$p, $r];
+ last if defined $params{num} && ++$count == $params{num};
+ }
+ elsif (! defined $firstfail) {
+ $firstfail=$r;
+ }
+ }
+
+ $depends{$page}{$pagespec} |= ($params{deptype} || $DEPEND_CONTENT);
+
+ my @ret;
+ if (@matches) {
+ # Add all influences from successful matches.
+ foreach my $m (@matches) {
+ push @ret, $m->[0];
+ my %i=$m->[1]->influences;
+ foreach my $i (keys %i) {
+ $depends_simple{$page}{lc $i} |= $i{$i};
+ }
+ }
+ }
+ elsif (defined $firstfail) {
+ # Add influences from one failure. (Which one should not
+ # matter; all should have the same influences.)
+ my %i=$firstfail->influences;
+ foreach my $i (keys %i) {
+ $depends_simple{$page}{lc $i} |= $i{$i};
+ }
+
+ error(sprintf(gettext("cannot match pages: %s"), $firstfail))
+ if $firstfail->isa("IkiWiki::ErrorReason");
+ }
+
+ return @ret;
+}
+
+sub deptype (@) {
+ my $deptype=0;
+ foreach my $type (@_) {
+ if ($type eq 'presence') {
+ $deptype |= $DEPEND_PRESENCE;
+ }
+ elsif ($type eq 'links') {
+ $deptype |= $DEPEND_LINKS;
+ }
+ elsif ($type eq 'content') {
+ $deptype |= $DEPEND_CONTENT;
+ }
+ }
+ return $deptype;
+}
+
sub file_pruned ($;$) {
my $file=shift;
if (@_) {
}gx) {
my $word=$1;
if (lc $word eq 'and') {
- $code.=' &&';
+ $code.=' &';
}
elsif (lc $word eq 'or') {
- $code.=' ||';
+ $code.=' |';
}
elsif ($word eq "(" || $word eq ")" || $word eq "!") {
$code.=' '.$word;
package IkiWiki::FailReason;
use overload (
- '""' => sub { ${$_[0]} },
+ '""' => sub { $_[0][0] },
'0+' => sub { 0 },
'!' => sub { bless $_[0], 'IkiWiki::SuccessReason'},
+ '&' => sub { $_[0]->merge_influences($_[1]); $_[0] },
+ '|' => sub { $_[1]->merge_influences($_[0]); $_[1] },
fallback => 1,
);
-sub new {
- my $class = shift;
- my $value = shift;
- return bless \$value, $class;
-}
-
-package IkiWiki::ErrorReason;
-
-our @ISA = 'IkiWiki::FailReason';
+our @ISA = 'IkiWiki::SuccessReason';
package IkiWiki::SuccessReason;
use overload (
- '""' => sub { ${$_[0]} },
+ '""' => sub { $_[0][0] },
'0+' => sub { 1 },
'!' => sub { bless $_[0], 'IkiWiki::FailReason'},
+ '&' => sub { $_[1]->merge_influences($_[0]); $_[1] },
+ '|' => sub { $_[0]->merge_influences($_[1]); $_[0] },
fallback => 1,
);
sub new {
my $class = shift;
my $value = shift;
- return bless \$value, $class;
-};
+ return bless [$value, {@_}], $class;
+}
+
+sub influences {
+ my $this=shift;
+ if (! @_) {
+ return %{$this->[1]};
+ }
+ else {
+ $this->[1]={@_};
+ }
+}
+
+sub merge_influences {
+ my $this=shift;
+ my $other=shift;
+ foreach my $influence (keys %{$other->[1]}) {
+ $this->[1]{$influence} |= $other->[1]{$influence};
+ }
+}
+
+package IkiWiki::ErrorReason;
+
+our @ISA = 'IkiWiki::FailReason';
package IkiWiki::PageSpec;
my $from=exists $params{location} ? $params{location} : '';
my $links = $IkiWiki::links{$page};
- return IkiWiki::FailReason->new("$page has no links") unless $links && @{$links};
+ return IkiWiki::FailReason->new("$page has no links")
+ unless $links && @{$links};
my $bestlink = IkiWiki::bestlink($from, $link);
foreach my $p (@{$links}) {
if (length $bestlink) {
- return IkiWiki::SuccessReason->new("$page links to $link")
+ return IkiWiki::SuccessReason->new("$page links to $link", $page => $IkiWiki::DEPEND_LINKS)
if $bestlink eq IkiWiki::bestlink($page, $p);
}
else {
- return IkiWiki::SuccessReason->new("$page links to page $p matching $link")
+ return IkiWiki::SuccessReason->new("$page links to page $p matching $link", $page => $IkiWiki::DEPEND_LINKS)
if match_glob($p, $link, %params);
my ($p_rel)=$p=~/^\/?(.*)/;
$link=~s/^\///;
- return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link")
+ return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link", $page => $IkiWiki::DEPEND_LINKS)
if match_glob($p_rel, $link, %params);
}
}
}
sub match_backlink ($$;@) {
- return match_link($_[1], $_[0], @_);
+ my $ret=match_link($_[1], $_[0], @_);
+ $ret->influences($_[1] => $IkiWiki::DEPEND_LINKS);
+ return $ret;
}
sub match_created_before ($$;@) {
if (exists $IkiWiki::pagectime{$testpage}) {
if ($IkiWiki::pagectime{$page} < $IkiWiki::pagectime{$testpage}) {
- return IkiWiki::SuccessReason->new("$page created before $testpage");
+ return IkiWiki::SuccessReason->new("$page created before $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE);
}
else {
- return IkiWiki::FailReason->new("$page not created before $testpage");
+ return IkiWiki::FailReason->new("$page not created before $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE);
}
}
else {
- return IkiWiki::ErrorReason->new("$testpage does not exist");
+ return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage => $IkiWiki::DEPEND_PRESENCE);
}
}
if (exists $IkiWiki::pagectime{$testpage}) {
if ($IkiWiki::pagectime{$page} > $IkiWiki::pagectime{$testpage}) {
- return IkiWiki::SuccessReason->new("$page created after $testpage");
+ return IkiWiki::SuccessReason->new("$page created after $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE);
}
else {
- return IkiWiki::FailReason->new("$page not created after $testpage");
+ return IkiWiki::FailReason->new("$page not created after $testpage", $testpage => $IkiWiki::DEPEND_PRESENCE);
}
}
else {
- return IkiWiki::ErrorReason->new("$testpage does not exist");
+ return IkiWiki::ErrorReason->new("$testpage does not exist", $testpage => $IkiWiki::DEPEND_PRESENCE);
}
}
my %params=@_;
$params{pages}="*" unless defined $params{pages};
- # Needs to update whenever a page is added or removed, so
- # register a dependency.
- add_depends($params{page}, $params{pages});
+ # Needs to update whenever the links on a page change.
+ add_depends($params{page}, $params{pages}, deptype("links"));
my @broken;
foreach my $link (keys %IkiWiki::brokenlinks) {
"$archivebase/$year/".sprintf("%02d", $month),
linktext => " $monthname ");
}
- add_depends($params{page}, "$archivebase/$year/".sprintf("%02d", $month));
+ add_depends($params{page}, "$archivebase/$year/".sprintf("%02d", $month),
+ deptype("presence"));
if (exists $cache{$pagespec}{"$pyear/$pmonth"}) {
$purl = htmllink($params{page}, $params{destpage},
"$archivebase/$pyear/" . sprintf("%02d", $pmonth),
linktext => " $pmonthname ");
}
- add_depends($params{page}, "$archivebase/$pyear/".sprintf("%02d", $pmonth));
+ add_depends($params{page}, "$archivebase/$pyear/".sprintf("%02d", $pmonth),
+ deptype("presence"));
if (exists $cache{$pagespec}{"$nyear/$nmonth"}) {
$nurl = htmllink($params{page}, $params{destpage},
"$archivebase/$nyear/" . sprintf("%02d", $nmonth),
linktext => " $nmonthname ");
}
- add_depends($params{page}, "$archivebase/$nyear/".sprintf("%02d", $nmonth));
+ add_depends($params{page}, "$archivebase/$nyear/".sprintf("%02d", $nmonth),
+ deptype("presence"));
# Start producing the month calendar
$calendar=<<EOF;
# Add dependencies to update the calendar whenever pages
# matching the pagespec are added or removed.
- add_depends($params{page}, $params{pages});
- # Explicitly add all currently linked pages as dependencies, so
- # that if they are removed, the calendar will be sure to be updated.
- foreach my $p (@list) {
- add_depends($params{page}, $p);
- }
+ add_depends($params{page}, $params{pages}, deptype("presence"));
return $calendar;
}
"$archivebase/$year",
linktext => "$year");
}
- add_depends($params{page}, "$archivebase/$year");
+ add_depends($params{page}, "$archivebase/$year", deptype("presence");
if (exists $cache{$pagespec}{"$pyear"}) {
$purl = htmllink($params{page}, $params{destpage},
"$archivebase/$pyear",
linktext => "\←");
}
- add_depends($params{page}, "$archivebase/$pyear");
+ add_depends($params{page}, "$archivebase/$pyear", deptype("presence"));
if (exists $cache{$pagespec}{"$nyear"}) {
$nurl = htmllink($params{page}, $params{destpage},
"$archivebase/$nyear",
linktext => "\→");
}
- add_depends($params{page}, "$archivebase/$nyear");
+ add_depends($params{page}, "$archivebase/$nyear", deptype("presence"));
# Start producing the year calendar
$calendar=<<EOF;
else {
$calendar.=qq{\t<td class="$tag">$monthabbr</td>\n};
}
- add_depends($params{page}, "$archivebase/$year/$mtag");
+ add_depends($params{page}, "$archivebase/$year/$mtag",
+ deptype("presence"));
$calendar.=qq{\t</tr>\n} if ($month % $params{months_per_row} == 0);
}
$pagestate{$params{page}}{edittemplate}{$params{match}}=$link;
return "" if ($params{silent} && IkiWiki::yesno($params{silent}));
- add_depends($params{page}, $link);
+ add_depends($params{page}, $link, deptype("presence"));
return sprintf(gettext("edittemplate %s registered for %s"),
htmllink($params{page}, $params{destpage}, $link),
$params{match});
@list = map { bestlink($params{page}, $_) }
split ' ', $params{pagenames};
- }
- else {
- add_depends($params{page}, $params{pages});
- @list = pagespec_match_list(
- [ grep { $_ ne $params{page} } keys %pagesources ],
- $params{pages}, location => $params{page});
-
- if (exists $params{sort} && $params{sort} eq 'title') {
- @list=sort { pagetitle(basename($a)) cmp pagetitle(basename($b)) } @list;
+ if (yesno($params{reverse})) {
+ @list=reverse(@list);
}
- elsif (exists $params{sort} && $params{sort} eq 'title_natural') {
- eval q{use Sort::Naturally};
- if ($@) {
- error(gettext("Sort::Naturally needed for title_natural sort"));
- }
- @list=sort { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) } @list;
+
+ foreach my $p (@list) {
+ add_depends($params{page}, $p, deptype($quick ? "presence" : "content"));
}
- elsif (exists $params{sort} && $params{sort} eq 'mtime') {
- @list=sort { $pagemtime{$b} <=> $pagemtime{$a} } @list;
+ }
+ else {
+ my $num=0;
+ if ($params{show}) {
+ $num=$params{show};
}
- elsif (! exists $params{sort} || $params{sort} eq 'age') {
- @list=sort { $pagectime{$b} <=> $pagectime{$a} } @list;
+ if ($params{feedshow} && $num < $params{feedshow}) {
+ $num=$params{feedshow};
}
- else {
- error sprintf(gettext("unknown sort type %s"), $params{sort});
+ if ($params{skip}) {
+ $num+=$params{skip};
}
- }
- if (yesno($params{reverse})) {
- @list=reverse(@list);
+ @list = use_pagespec($params{page}, $params{pages},
+ deptype => deptype($quick ? "presence" : "content"),
+ limit => sub { $_[0] ne $params{page} },
+ sort => exists $params{sort} ? $params{sort} : "age",
+ reverse => yesno($params{reverse}),
+ num => $num,
+ );
}
if (exists $params{skip}) {
- @list=@list[$params{skip} .. scalar @list - 1];
+ @list=@list[$params{skip} .. $#list];
}
my @feedlist;
@list=@list[0..$params{show} - 1];
}
- # Explicitly add all currently displayed pages as dependencies, so
- # that if they are removed or otherwise changed, the inline will be
- # sure to be updated.
- foreach my $p ($#list >= $#feedlist ? @list : @feedlist) {
- add_depends($params{page}, $p);
- }
-
if ($feeds && exists $params{feedpages}) {
- @feedlist=pagespec_match_list(\@feedlist, $params{feedpages}, location => $params{page});
+ @feedlist = use_pagespec($params{page}, $params{feedpages},
+ list => \@feedlist);
}
my ($feedbase, $feednum);
$params{pages}="*" unless defined $params{pages};
- # Needs to update whenever a page is added or removed, so
- # register a dependency.
- add_depends($params{page}, $params{pages});
+ # Needs to update whenever a relevant page is added, or removed, or
+ # its links change.
+ add_depends($params{page}, $params{pages}, deptype("presence", "links"));
# Can't just return the linkmap here, since the htmlscrubber
# scrubs out all <object> tags (with good reason!)
# Get all the items to map.
my %mapitems = ();
- foreach my $item (keys %links) {
+ foreach my $item (keys %pagesources) {
if (pagespec_match($item, $params{pages}, location => $params{page})) {
$mapitems{$item}=urlto($item, $params{destpage});
}
foreach my $plugin (@pluginlist) {
$result .= '<li class="listdirectives">';
my $link=linkpage($config{directive_description_dir}."/".$plugin);
- add_depends($params{page}, $link);
+ add_depends($params{page}, $link, deptype("presence"));
$result .= htmllink($params{page}, $params{destpage}, $link);
$result .= '</li>';
}
my %params=@_;
$params{pages}="*" unless defined $params{pages};
+ # Needs to update whenever a page is added or removed (or in some
+ # cases, when its content changes, if show= is specified).
+ my $deptype=deptype(exists $params{show} ? "content" : "presence");
+
my $common_prefix;
# Get all the items to map.
my %mapitems;
- foreach my $page (pagespec_match_list([keys %pagesources],
- $params{pages}, location => $params{page})) {
+ foreach my $page (use_pagespec($params{page}, $params{pages}, deptype => $deptype)) {
if (exists $params{show} &&
exists $pagestate{$page} &&
exists $pagestate{$page}{meta}{$params{show}}) {
$common_prefix=IkiWiki::dirname($common_prefix);
}
- # Needs to update whenever a page is added or removed (or in some
- # cases, when its content changes, if show=title), so register a
- # dependency.
- add_depends($params{page}, $params{pages});
- # Explicitly add all currently shown pages, to detect when pages
- # are removed.
- foreach my $item (keys %mapitems) {
- add_depends($params{page}, $item);
- }
-
# Create the map.
my $parent="";
my $indent=0;
my $addparent="";
my $map = "<div class='map'>\n";
- # Return empty div if %mapitems is empty
- if (!scalar(keys %mapitems)) {
+ if (! keys %mapitems) {
+ # return empty div for empty map
$map .= "</div>\n";
return $map;
}
- else { # continue populating $map
+ else {
$map .= "<ul>\n";
}
if (! length $link) {
error gettext("redir page not found")
}
- add_depends($page, $link);
+ add_depends($page, $link, deptype("presence"));
$value=urlto($link, $page);
$value.='#'.$redir_anchor if defined $redir_anchor;
if (defined $val) {
if ($val=~/^$re$/i) {
- return IkiWiki::SuccessReason->new("$re matches $field of $page");
+ return IkiWiki::SuccessReason->new("$re matches $field of $page", $page => $IkiWiki::DEPEND_CONTENT);
}
else {
return IkiWiki::FailReason->new("$re does not match $field of $page");
package IkiWiki::PageSpec;
sub match_title ($$;@) {
- IkiWiki::Plugin::meta::match("title", @_);
+ IkiWiki::Plugin::meta::match("title", @_);
}
sub match_author ($$;@) {
my %params=@_;
$params{pages}="*" unless defined $params{pages};
- # Needs to update whenever a page is added or removed, so
- # register a dependency.
- add_depends($params{page}, $params{pages});
+ # Needs to update whenever a link changes, on any page
+ # since any page could link to one of the pages we're
+ # considering as orphans.
+ add_depends($params{page}, "*", deptype("links"));
+ # Also needs to update whenever potential orphans are added or
+ # removed.
+ add_depends($params{page}, $params{pages}, deptype("presence"));
my @orphans;
foreach my $page (pagespec_match_list(
sub preprocess (@) {
my %params=@_;
- $params{pages}="*" unless defined $params{pages};
+ my $pages=defined $params{pages} ? $params{pages} : "*";
- # Needs to update count whenever a page is added or removed, so
- # register a dependency.
- add_depends($params{page}, $params{pages});
-
- my @pages;
- if ($params{pages} eq "*") {
- @pages=keys %pagesources;
- }
- else {
- @pages=pagespec_match_list([keys %pagesources], $params{pages}, location => $params{page});
+ # Just get a list of all the pages, and count the items in it.
+ # Use a presence dependency to only update when pages are added
+ # or removed.
+
+ if ($pages eq '*') {
+ # optimisation to avoid needing to try matching every page
+ add_depends($params{page}, $pages, deptype("presence"));
+ return scalar keys %pagesources;
}
- return $#pages+1;
+
+ return scalar use_pagespec($params{page}, $pages,
+ deptype => deptype("presence"));
}
1
$params{pages}="*" unless defined $params{pages};
my $style = ($params{style} or 'cloud');
- # Needs to update whenever a page is added or removed, so
- # register a dependency.
- add_depends($params{page}, $params{pages});
- add_depends($params{page}, $params{among}) if exists $params{among};
-
my %counts;
my $max = 0;
- foreach my $page (pagespec_match_list([keys %links],
- $params{pages}, location => $params{page})) {
+ foreach my $page (use_pagespec($params{page}, $params{pages},
+ # update when a displayed page is added or removed
+ deptype => deptype("presence"))) {
use IkiWiki::Render;
my @backlinks = IkiWiki::backlink_pages($page);
if (exists $params{among}) {
- @backlinks = pagespec_match_list(\@backlinks,
- $params{among}, location => $params{page});
+ # only consider backlinks from the amoung pages
+ @backlinks = use_pagespec($params{page}, $params{among},
+ # update whenever links on those pages change
+ deptype => deptype("links"),
+ list => \@backlinks
+ );
+ }
+ else {
+ # update when any page with links changes,
+ # in case the links point to our displayed pages
+ add_depends($params{page}, "*", deptype("links"));
}
$counts{$page} = scalar(@backlinks);
error gettext("unknown formula");
}
- add_depends($params{page}, $params{pages});
+ add_depends($params{page}, $params{pages}, deptype("presence"));
my @list=sort { $params{timehash}->{$b} <=> $params{timehash}->{$a} }
pagespec_match_list(
$fill.="%";
}
elsif (defined $params{totalpages} and defined $params{donepages}) {
- add_depends($params{page}, $params{totalpages});
- add_depends($params{page}, $params{donepages});
+ add_depends($params{page}, $params{totalpages}, deptype("presence"));
+ add_depends($params{page}, $params{donepages}, deptype("presence"));
my @pages=keys %pagesources;
my $totalcount=0;
use IkiWiki;
use Encode;
-my %backlinks;
+my (%backlinks, %rendered);
our %brokenlinks;
my $links_calculated=0;
sub scan ($) {
my $file=shift;
+ debug(sprintf(gettext("scanning %s"), $file));
+
my $type=pagetype($file);
if (defined $type) {
my $srcfile=srcfile($file);
}
}
-sub render ($) {
+sub render ($$) {
my $file=shift;
+ return if $rendered{$file};
+ debug(shift);
+ $rendered{$file}=1;
my $type=pagetype($file);
my $srcfile=srcfile($file);
}
sub find_src_files () {
- my (@files, %pages);
+ my @files;
+ my %pages;
eval q{use File::Find};
error($@) if $@;
find({
},
}, $dir);
};
-
- # Returns a list of all source files found, and a hash of
- # the corresponding page names.
return \@files, \%pages;
}
-sub refresh () {
- srcdir_check();
- run_hooks(refresh => sub { shift->() });
- my ($files, $exists)=find_src_files();
+sub find_new_files ($) {
+ my $files=shift;
+ my @new;
+ my @internal_new;
- my (%rendered, @add, @del, @internal);
- # check for added or removed pages
foreach my $file (@$files) {
my $page=pagename($file);
if (exists $pagesources{$page} && $pagesources{$page} ne $file) {
$pagesources{$page}=$file;
if (! $pagemtime{$page}) {
if (isinternal($page)) {
- push @internal, $file;
+ push @internal_new, $file;
}
else {
- push @add, $file;
+ push @new, $file;
if ($config{getctime} && -e "$config{srcdir}/$file") {
eval {
my $time=rcs_getctime("$config{srcdir}/$file");
}
}
}
+
+ return \@new, \@internal_new;
+}
+
+sub find_del_files ($) {
+ my $pages=shift;
+ my @del;
+ my @internal_del;
+
foreach my $page (keys %pagemtime) {
- if (! $exists->{$page}) {
+ if (! $pages->{$page}) {
if (isinternal($page)) {
- push @internal, $pagesources{$page};
+ push @internal_del, $pagesources{$page};
}
else {
debug(sprintf(gettext("removing old page %s"), $page));
}
}
- # find changed and new files
- my @needsbuild;
+ return \@del, \@internal_del;
+}
+
+sub find_changed ($) {
+ my $files=shift;
+ my @changed;
+ my @internal_changed;
foreach my $file (@$files) {
my $page=pagename($file);
my ($srcfile, @stat)=srcfile_stat($file);
$stat[9] > $pagemtime{$page} ||
$forcerebuild{$page}) {
$pagemtime{$page}=$stat[9];
+
if (isinternal($page)) {
- push @internal, $file;
# Preprocess internal page in scan-only mode.
preprocess($page, $page, readfile($srcfile), 1);
+ push @internal_changed, $file;
}
else {
- push @needsbuild, $file;
+ push @changed, $file;
}
}
}
- run_hooks(needsbuild => sub { shift->(\@needsbuild) });
+ return \@changed, \@internal_changed;
+}
- # scan and render files
- foreach my $file (@needsbuild) {
- debug(sprintf(gettext("scanning %s"), $file));
- scan($file);
- }
- calculate_links();
- foreach my $file (@needsbuild) {
- debug(sprintf(gettext("building %s"), $file));
- render($file);
- $rendered{$file}=1;
- }
- foreach my $file (@internal) {
- # internal pages are not rendered
+sub calculate_old_links ($$) {
+ my ($changed, $del)=@_;
+ my %oldlink_targets;
+ foreach my $file (@$changed, @$del) {
my $page=pagename($file);
- delete $depends{$page};
- delete $depends_simple{$page};
- foreach my $old (@{$renderedfiles{$page}}) {
- delete $destsources{$old};
+ if (exists $oldlinks{$page}) {
+ foreach my $l (@{$oldlinks{$page}}) {
+ $oldlink_targets{$page}{$l}=bestlink($page, $l);
+ }
}
- $renderedfiles{$page}=[];
}
-
- # rebuild pages that link to added or removed pages
- if (@add || @del) {
- foreach my $f (@add, @del) {
- my $p=pagename($f);
- foreach my $page (keys %{$backlinks{$p}}) {
- my $file=$pagesources{$page};
- next if $rendered{$file};
- debug(sprintf(gettext("building %s, which links to %s"), $file, $p));
- render($file);
- $rendered{$file}=1;
+ return \%oldlink_targets;
+}
+
+sub derender_internal ($) {
+ my $file=shift;
+ my $page=pagename($file);
+ delete $depends{$page};
+ delete $depends_simple{$page};
+ foreach my $old (@{$renderedfiles{$page}}) {
+ delete $destsources{$old};
+ }
+ $renderedfiles{$page}=[];
+}
+
+sub render_linkers ($) {
+ my $f=shift;
+ my $p=pagename($f);
+ foreach my $page (keys %{$backlinks{$p}}) {
+ my $file=$pagesources{$page};
+ render($file, sprintf(gettext("building %s, which links to %s"), $file, $p));
+ }
+}
+
+sub remove_unrendered () {
+ foreach my $src (keys %rendered) {
+ my $page=pagename($src);
+ foreach my $file (@{$oldrenderedfiles{$page}}) {
+ if (! grep { $_ eq $file } @{$renderedfiles{$page}}) {
+ debug(sprintf(gettext("removing %s, no longer built by %s"), $file, $page));
+ prune($config{destdir}."/".$file);
}
}
}
+}
- if (%rendered || @del || @internal) {
- my @changed=(keys %rendered, @del);
+sub calculate_changed_links ($$$) {
+ my ($changed, $del, $oldlink_targets)=@_;
- my %lcchanged = map { lc(pagename($_)) => 1 } @changed;
-
- # rebuild dependant pages
- foreach my $f (@$files) {
- next if $rendered{$f};
- my $p=pagename($f);
- my $reason = undef;
+ my (%backlinkchanged, %linkchangers);
- if (exists $depends_simple{$p}) {
- foreach my $d (keys %{$depends_simple{$p}}) {
- if (exists $lcchanged{$d}) {
- $reason = $d;
- last;
- }
+ foreach my $file (@$changed, @$del) {
+ my $page=pagename($file);
+
+ if (exists $links{$page}) {
+ foreach my $l (@{$links{$page}}) {
+ my $target=bestlink($page, $l);
+ if (! exists $oldlink_targets->{$page}{$l} ||
+ $target ne $oldlink_targets->{$page}{$l}) {
+ $backlinkchanged{$target}=1;
+ $linkchangers{lc($page)}=1;
}
+ delete $oldlink_targets->{$page}{$l};
}
+ }
+ if (exists $oldlink_targets->{$page} &&
+ %{$oldlink_targets->{$page}}) {
+ foreach my $target (values %{$oldlink_targets->{$page}}) {
+ $backlinkchanged{$target}=1;
+ }
+ $linkchangers{lc($page)}=1;
+ }
+ }
- if (exists $depends{$p} && ! defined $reason) {
- D: foreach my $d (keys %{$depends{$p}}) {
- my $sub=pagespec_translate($d);
- next if $@ || ! defined $sub;
+ return \%backlinkchanged, \%linkchangers;
+}
- # only consider internal files
- # if the page explicitly depends
- # on such files
- foreach my $file (@changed, $d =~ /internal\(/ ? @internal : ()) {
+sub render_dependent ($$$$$$$) {
+ my ($files, $new, $internal_new, $del, $internal_del,
+ $internal_changed, $linkchangers)=@_;
+
+ my @changed=(keys %rendered, @$del);
+ my @exists_changed=(@$new, @$del);
+
+ my %lc_changed = map { lc(pagename($_)) => 1 } @changed;
+ my %lc_exists_changed = map { lc(pagename($_)) => 1 } @exists_changed;
+
+ foreach my $f (@$files) {
+ next if $rendered{$f};
+ my $p=pagename($f);
+ my $reason = undef;
+
+ if (exists $depends_simple{$p}) {
+ foreach my $d (keys %{$depends_simple{$p}}) {
+ if (($depends_simple{$p}{$d} & $IkiWiki::DEPEND_CONTENT &&
+ $lc_changed{$d})
+ ||
+ ($depends_simple{$p}{$d} & $IkiWiki::DEPEND_PRESENCE &&
+ $lc_exists_changed{$d})
+ ||
+ ($depends_simple{$p}{$d} & $IkiWiki::DEPEND_LINKS &&
+ $linkchangers->{$d})
+ ) {
+ $reason = $d;
+ last;
+ }
+ }
+ }
+
+ if (exists $depends{$p} && ! defined $reason) {
+ foreach my $dep (keys %{$depends{$p}}) {
+ my $sub=pagespec_translate($dep);
+ next if $@ || ! defined $sub;
+
+ # only consider internal files
+ # if the page explicitly depends
+ # on such files
+ my $internal_dep=$dep =~ /internal\(/;
+
+ my $in=sub {
+ my $list=shift;
+ my $type=shift;
+ foreach my $file (@$list) {
next if $file eq $f;
my $page=pagename($file);
if ($sub->($page, location => $p)) {
- $reason = $page;
- last D;
+ if ($type == $IkiWiki::DEPEND_LINKS) {
+ next unless $linkchangers->{lc($page)};
+ }
+ return $page;
}
}
+ return undef;
+ };
+
+ if ($depends{$p}{$dep} & $IkiWiki::DEPEND_CONTENT) {
+ last if $reason =
+ $in->(\@changed, $IkiWiki::DEPEND_CONTENT);
+ last if $internal_dep && ($reason =
+ $in->($internal_new, $IkiWiki::DEPEND_CONTENT) ||
+ $in->($internal_del, $IkiWiki::DEPEND_CONTENT) ||
+ $in->($internal_changed, $IkiWiki::DEPEND_CONTENT));
}
- }
-
- if (defined $reason) {
- debug(sprintf(gettext("building %s, which depends on %s"), $f, $reason));
- render($f);
- $rendered{$f}=1;
- }
- }
-
- # handle backlinks; if a page has added/removed links,
- # update the pages it links to
- my %linkchanged;
- foreach my $file (@changed) {
- my $page=pagename($file);
-
- if (exists $links{$page}) {
- foreach my $link (map { bestlink($page, $_) } @{$links{$page}}) {
- if (length $link &&
- (! exists $oldlinks{$page} ||
- ! grep { bestlink($page, $_) eq $link } @{$oldlinks{$page}})) {
- $linkchanged{$link}=1;
- }
+ if ($depends{$p}{$dep} & $IkiWiki::DEPEND_PRESENCE) {
+ last if $reason =
+ $in->(\@exists_changed, $IkiWiki::DEPEND_PRESENCE);
+ last if $internal_dep && ($reason =
+ $in->($internal_new, $IkiWiki::DEPEND_PRESENCE) ||
+ $in->($internal_del, $IkiWiki::DEPEND_PRESENCE));
}
- }
- if (exists $oldlinks{$page}) {
- foreach my $link (map { bestlink($page, $_) } @{$oldlinks{$page}}) {
- if (length $link &&
- (! exists $links{$page} ||
- ! grep { bestlink($page, $_) eq $link } @{$links{$page}})) {
- $linkchanged{$link}=1;
- }
+ if ($depends{$p}{$dep} & $IkiWiki::DEPEND_LINKS) {
+ last if $reason =
+ $in->(\@changed, $IkiWiki::DEPEND_LINKS);
+ last if $internal_dep && ($reason =
+ $in->($internal_new, $IkiWiki::DEPEND_LINKS) ||
+ $in->($internal_del, $IkiWiki::DEPEND_LINKS) ||
+ $in->($internal_changed, $IkiWiki::DEPEND_LINKS));
}
}
}
-
- foreach my $link (keys %linkchanged) {
- my $linkfile=$pagesources{$link};
- if (defined $linkfile) {
- next if $rendered{$linkfile};
- debug(sprintf(gettext("building %s, to update its backlinks"), $linkfile));
- render($linkfile);
- $rendered{$linkfile}=1;
- }
+
+ if (defined $reason) {
+ render($f, sprintf(gettext("building %s, which depends on %s"), $f, $reason));
+ return 1;
}
}
- # remove no longer rendered files
- foreach my $src (keys %rendered) {
- my $page=pagename($src);
- foreach my $file (@{$oldrenderedfiles{$page}}) {
- if (! grep { $_ eq $file } @{$renderedfiles{$page}}) {
- debug(sprintf(gettext("removing %s, no longer built by %s"), $file, $page));
- prune($config{destdir}."/".$file);
- }
+ return 0;
+}
+
+sub render_backlinks ($) {
+ my $backlinkchanged=shift;
+ foreach my $link (keys %$backlinkchanged) {
+ my $linkfile=$pagesources{$link};
+ if (defined $linkfile) {
+ render($linkfile, sprintf(gettext("building %s, to update its backlinks"), $linkfile));
}
}
+}
+
+sub refresh () {
+ srcdir_check();
+ run_hooks(refresh => sub { shift->() });
+ my ($files, $pages)=find_src_files();
+ my ($new, $internal_new)=find_new_files($files);
+ my ($del, $internal_del)=find_del_files($pages);
+ my ($changed, $internal_changed)=find_changed($files);
+ run_hooks(needsbuild => sub { shift->($changed) });
+ my $oldlink_targets=calculate_old_links($changed, $del);
+
+ foreach my $file (@$changed) {
+ scan($file);
+ }
+
+ calculate_links();
+
+ foreach my $file (@$changed) {
+ render($file, sprintf(gettext("building %s"), $file));
+ }
+ foreach my $file (@$internal_new, @$internal_del, @$internal_changed) {
+ derender_internal($file);
+ }
+
+ my ($backlinkchanged, $linkchangers)=calculate_changed_links($changed,
+ $del, $oldlink_targets);
+
+ foreach my $file (@$new, @$del) {
+ render_linkers($file);
+ }
+
+ if (@$changed || @$internal_changed ||
+ @$del || @$internal_del || @$internal_new) {
+ 1 while render_dependent($files, $new, $internal_new,
+ $del, $internal_del, $internal_changed,
+ $linkchangers);
+ }
+
+ render_backlinks($backlinkchanged);
+ remove_unrendered();
- if (@del) {
- run_hooks(delete => sub { shift->(@del) });
+ if (@$del) {
+ run_hooks(delete => sub { shift->(@$del) });
}
if (%rendered) {
run_hooks(change => sub { shift->(keys %rendered) });
+ikiwiki (3.14159266) UNRELEASED; urgency=low
+
+ To take advantage of significant performance improvements, all
+ wikis need to be rebuilt on upgrade to this version. If you
+ listed your wiki in /etc/ikiwiki/wikilist this will be done
+ automatically when the Debian package is upgraded. Or use
+ ikiwiki-mass-rebuild to force a rebuild.
+
+ -- Joey Hess <joeyh@debian.org> Mon, 05 Oct 2009 16:48:59 -0400
+
ikiwiki (3.1415926) unstable; urgency=low
In order to fix a performance bug, all wikis need to be rebuilt on
* mirrorlist: Display nothing if list is empty.
* Fix a bug that could lead to duplicate links being recorded
for tags.
+ * Added support framework for multiple types of dependencies.
+ * Allow declaring that a dependency is only affected by page presence
+ or changes to its links.
+ * pagecount, calendar, postsparkline, progress: Use a presence dependency,
+ which makes these directives much less expensive to use, since page
+ edits will no longer trigger an unnecessary update.
+ * map: Use a presence dependency unless show= is specified.
+ This makes maps efficient enough that they can be used on sidebars!
+ * inline: Use a presence dependency in quick mode.
+ * brokenlinks: Use a link dependency.
+ This makes it much more efficient, only updating when really necessary.
+ * orphans, pagestats: Use a combination of presence and link dependencies.
+ This makes them more efficient. It also fixes a longstanding bug,
+ where if only a small set of pages were considered by orphans/pagestats,
+ changes to links on other pages failed to cause an update.
+ * linkmap: Use a combination of presence and link dependencies.
+ This makes the map be regenerated much less frequently in many cases,
+ so larger maps are more practical to use now.
+ * Transitive dependencies are now correctly supported.
+ * Rebuild wikis on upgrade to this version to get improved dependency
+ info.
+ * Plugins providing PageSpec `match_*` functions should pass additional
+ influence information when creating result objects.
+ * Added `use_pagespec` function, that plugins can use to find a list
+ of matching pages and add dependencies and influences, all at once,
+ and efficiently.
* Optimize away most expensive file prune calls, when refreshing,
by only checking new files.
# Change this when some incompatible change is made that requires
# rebuilding all wikis.
-firstcompat=3.1415926
+firstcompat=3.14159266
if [ "$1" = configure ] && \
dpkg --compare-versions "$2" lt "$firstcompat"; then
\[[!inline pages="bugs/* and !*/discussion and backlink(bugs)" feeds=no postform=no archive=yes show="10"]]
-But here it is:
-
-[[!inline pages="bugs/* and !*/discussion and backlink(bugs)" feeds=no postform=no archive=yes show="10"]]
-
and note that it only included the 'normal' wikilinks (and also note that this page is not marked done even though the done page is inlined).
One might also wonder if inline would make this page link to any internal links on those inlined pages too, but I think
that would be overkill.
* Has bugs updating things if the bestlink of a page changes due to
adding/removing a page. For example, if Foo/Bar links to "Baz", which is
Foo/Baz, and Foo/Bar/Baz gets added, it will update the links in Foo/Bar
- to point to it, but will forget to update the linkbacks in Foo/Baz.
+ to point to it, but will forget to update the backlinks in Foo/Baz.
-* And if Foo/Bar/Baz is then removed, it forgets to update Foo/Bar to link
- back to Foo/Baz.
+ The buggy code is in `refresh()`, when it determines what
+ links, on what pages, have changed. It only looks at
+ changed/added/deleted pages when doing this. But when Foo/Bar/Baz
+ is added, Foo/Bar is not changed -- so the change it its
+ backlinks is not noticed.
-As of 1.33, this is still true. The buggy code is the %linkchanged
-calculation in refresh(), which doesn't detect that the link has changed in
-this case.
+ To fix this, it needs to consider, when rebuilding Foo/Bar for the changed
+ links, what oldlinks Foo/Bar had. If one of the oldlinks linked to
+ Foo/Baz, and not links to Foo/Bar/Baz, it could then rebuild Foo/Baz.
-Still true in 1.43 although the code is much different now..
+ Problem is that in order to do that, it needs to be able to tell that
+ the oldlinks linked to Foo/Baz. Which would mean either calculating
+ all links before the scan phase, or keeping a copy of the backlinks
+ from the last build, and using that. The first option would be a lot
+ of work for this minor issue.. it might be less expensive to just rebuild
+ *all* pages that Foo/Bar links to.
+
+ Keeping a copy of the backlinks has some merit. It could also be
+ incrementally updated.
+
+* And if Foo/Bar/Baz is then removed, Foo/Bar gets a broken link,
+ instead of changing back to linking to Foo/Baz.
+
+This old bug still exists as of 031d1bf5046ab77c796477a19967e7c0c512c417.
modification to plugins/brokenlinks causes an unnecessary update of
plugins, and could be solved by adding more dependency types.)
---[[Joey]]
+[[done]] --[[Joey]]
-[[!pagestats pages="./tags/*"]]
+[[!pagestats pages="./tags/*" among="./posts/*"]]
Welcome to my blog.
-[[!pagestats pages="./tags/*"]]
+[[!pagestats pages="./tags/*" among="./posts/*"]]
On the right you can see the tag cloud for this blog.
if raw is set to "yes", the page will be included raw, without additional
markup around it, as if it were a literal part of the source of the
inlining page.
-* `sort` - Controls how inlined pages are sorted. The default, "age" is to
- sort newest created pages first. Setting it to "title" will sort pages by
- title, and "mtime" sorts most recently modified pages first. If
- [[!cpan Sort::Naturally]] is installed, `sort` can be set to "title_natural"
- to sort by title with numbers treated as such ("1 2 9 10 20" instead of
- "1 10 2 20 9").
+* `sort` - Controls how inlined pages are [[sorted|pagespec/sorting]].
+ The default is to sort the newest created pages first.
* `reverse` - If set to "yes", causes the sort order to be reversed.
* `feedshow` - Specify the maximum number of matching pages to include in
the rss/atom feeds. The default is the same as the `show` value above.
* `feedonly` - Only generate the feed, do not display the pages inline on
the page.
* `quick` - Build archives in quick mode, without reading page contents for
- metadata. By default, this also turns off generation of any feeds.
+ metadata. This also turns off generation of any feeds.
* `timeformat` - Use this to specify how to display the time or date for pages
in the blog. The format string is passed to the strftime(3) function.
* `feedpages` - A [[PageSpec]] of inlined pages to include in the rss/atom
unmapped pages will be omitted. If the pages to include are not specified,
the links between all pages (and other files) in the wiki are mapped. For
best results, only a small set of pages should be mapped, since otherwise
-the map can become very large, unwieldy, and complicated. Also, the map is
-rebuilt whenever one of the mapped pages is changed, which can make the
-wiki a bit slow.
+the map can become very large, unwieldy, and complicated.
Here are descriptions of all the supported parameters to the `linkmap`
directive:
\[[!pagestats style="table"]]
-The optional `among` parameter limits display to pages that match a
-[[ikiwiki/PageSpec]]. For instance, to display a cloud of tags used on blog
-entries, you could use:
+The optional `among` parameter limits the pages whose outgoing links are
+considered. For instance, to display a cloud of tags used on blog
+entries, while ignoring other pages that use those tags, you could use:
\[[!pagestats pages="tags/*" among="blog/posts/*"]]
-or to display a cloud of tags related to Linux, you could use:
+Or to display a cloud of tags related to Linux, you could use:
\[[!pagestats pages="tags/* and not tags/linux" among="tagged(linux)"]]
--- /dev/null
+Some [[directives|ikiwiki/directive]] that use
+[[PageSpecs|ikiwiki/pagespec]], such as [[ikiwiki/directive/inline]], allow
+specifying the order that matching pages are shown in. The following sort
+orders can be specified.
+
+* `age` - List pages from the most recently created to the oldest.
+* `mtime` - List pages with the most recently modified first.
+* `title` - Order by title.
+* `title_natural` - Only available if [[!cpan Sort::Naturally]] is
+ installed. Orders by title, but numbers in the title are treated
+ as such, ("1 2 9 10 20" instead of "1 10 2 20 9")
Most of ikiwiki's [[features]] are implemented as plugins. Many of these
plugins are included with ikiwiki.
-[[!pagestats pages="plugins/type/* and !plugins/type/slow"]]
+[[!pagestats pages="plugins/type/* and !plugins/type/slow" among="plugins/*"]]
There's documentation if you want to [[write]] your own plugins, or you can
[[install]] plugins [[contributed|contrib]] by others.
## Plugin directory
-[[!inline pages="plugins/* and !plugins/type/* and !plugins/write and
-!plugins/write/* and !plugins/contrib and !plugins/install and !*/Discussion"
-feedpages="created_after(plugins/graphviz)" archive="yes" sort=title
-rootpage="plugins/contrib" postformtext="Add a new plugin named:" show=0]]
+[[!map pages="plugins/* and !plugins/type/* and !plugins/write and
+!plugins/write/* and !plugins/contrib and !plugins/install and !*/Discussion"]]
These plugins are provided by third parties and are not currently
included in ikiwiki. See [[install]] for installation help.
-[[!inline pages="plugins/contrib/* and !*/Discussion"
-feedpages="created_after(plugins/contrib/navbar)" archive="yes"
-rootpage="plugins/contrib" postformtext="Add a new plugin named:" show=0]]
+[[!map pages="plugins/contrib/* and !*/Discussion"]]
[[!orphans pages="* and !news/* and !todo/* and !bugs/* and !users/* and
!recentchanges and !examples/* and !tips/* and !sandbox/* and !templates/* and
+!forum/* and !*.js and
!wikiicons/* and !plugins/*"]]
"""]]
Warning: Any change to the sidebar will cause a rebuild of the whole wiki,
since every page includes a copy that has to be updated. This can
-especially be a problem if the sidebar includes [[inline]] or [[map]]
-directives, since any changes to pages inlined or mapped onto the sidebar
+especially be a problem if the sidebar includes an [[ikiwiki/directive/inline]]
+directive, since any changes to pages inlined into the sidebar
will change the sidebar and cause a full wiki rebuild.
+
+Instead, if you include a [[ikiwiki/directive/map]] directive on the sidebar,
+and it does not use the `show` parameter, only adding or removing pages
+included in the map will cause a full rebuild. Modifying pages will not.
Use this when constructing the filename of a html file. Use `urlto` when
generating a link to a page.
-#### `add_depends($$;@)`
+### `deptype(@)`
+
+Use this function to generate ikiwiki's internal representation of a
+dependency type from one or more of these keywords:
+
+* `content` is the default. Any change to the content
+ of a page triggers the dependency.
+* `presence` is only triggered by a change to the presence
+ of a page.
+* `links` is only triggered by a change to the links of a page.
+ This includes when a link is added, removed, or changes what
+ it points to due to other changes. It does not include the
+ addition or removal of a duplicate link.
+
+If multiple types are specified, they are combined.
+
+#### `use_pagespec($$;@)`
+
+Passed a page name, and [[ikiwiki/PageSpec]], returns a list of pages
+in the wiki that match the [[ikiwiki/PageSpec]].
+
+The page will automatically be made to depend on the specified
+[[ikiwiki/PageSpec]], so `add_depends` does not need to be called. This
+is significantly more efficient than calling `add_depends`
+followed by `pagespec_match_list`. You should use this anytime a plugin
+needs to match a set of pages and generate something based on that list.
+
+Additional named parameters can be specified:
+
+* `deptype` optionally specifies the type of dependency to add. Use the
+ `deptype` function to generate a dependency type.
+* `limit` is a reference to a function, that is called and passed a page,
+ and must return true for the page to be included.
+* `sort` specifies a sort order for the list. See
+ [[ikiwiki/PageSpec/sorting]] for the avilable sort methods.
+* `reverse` if true, sorts in reverse.
+* `num` if nonzero, specifies the maximum number of matching pages that
+ will be returned.
+* `list` makes it only match amoung the specified list of pages.
+ Default is to match amoung all pages in the wiki.
+
+#### `add_depends($$;$)`
Makes the specified page depend on the specified [[ikiwiki/PageSpec]].
By default, dependencies are full content dependencies, meaning that the
page will be updated whenever anything matching the PageSpec is modified.
-This default can be overridden by additional named parameters, which can be
-used to indicate weaker types of dependencies:
-
-* `presence` if set to true, only the presence of a matching page triggers
- the dependency.
-* `links` if set to true, any change to links on a matching page
- triggers the dependency. This includes when a link is added, removed,
- or changes what it points to due to other changes. It does not include
- the addition or removal of a duplicate link.
+This can be overridden by passing a `deptype` value as the third parameter.
#### `pagespec_match($$;@)`
attempted at all, for any page, it can instead return an
IkiWiki::ErrorReason object explaining why.
+When constructing these objects, you should also include information about
+of any pages whose contents or other metadata influenced the result of the
+match. Do this by passing a list of pages, followed by `deptype` values.
+
+For example, "backlink(foo)" is influenced by the contents of page foo;
+"link(foo)" and "title(bar)" are influenced by the contents of any page
+they match; "created_before(foo)" is influenced by the metadata of foo;
+while "glob(*)" is not influenced by the contents of any page.
+
### Setup plugins
The ikiwiki setup file is loaded using a pluggable mechanism. If you look
that the page links to, which is just what link dependencies are
triggered on.
+[[done]]
----
### the removal problem
userdir => "users",
usedirs => 0,
prefix_directives => 1,
- add_plugins => [qw{goodstuff version haiku polygen fortune}],
+ add_plugins => [qw{linkmap goodstuff version haiku polygen fortune}],
}
$pagemtime{$page}=$items{mtime}[0];
$oldlinks{$page}=[@{$items{link}}];
$links{$page}=[@{$items{link}}];
- $depends{$page}={ $items{depends}[0] => 1 } if exists $items{depends};
+ $depends{$page}={ $items{depends}[0] => $IkiWiki::DEPEND_CONTENT } if exists $items{depends};
$destsources{$_}=$page foreach @{$items{dest}};
$renderedfiles{$page}=[@{$items{dest}}];
$pagecase{lc $page}=$page;
--- /dev/null
+#!/usr/bin/perl
+use warnings;
+use strict;
+use Test::More tests => 88;
+
+BEGIN { use_ok("IkiWiki"); }
+%config=IkiWiki::defaultconfig();
+$config{srcdir}=$config{destdir}="/dev/null";
+IkiWiki::checkconfig();
+
+$pagesources{"foo$_"}="foo$_.mdwn" for 0..9;
+
+# avoids adding an unparseable pagespec
+ok(! add_depends("foo0", "foo and (bar"));
+ok(! add_depends("foo0", "foo another"));
+
+# simple and not-so-simple dependencies split
+ok(add_depends("foo0", "*"));
+ok(add_depends("foo0", "bar"));
+ok(add_depends("foo0", "BAZ"));
+ok(exists $IkiWiki::depends_simple{foo0}{"bar"});
+ok(exists $IkiWiki::depends_simple{foo0}{"baz"}); # lowercase
+ok(! exists $IkiWiki::depends_simple{foo0}{"*"});
+ok(! exists $IkiWiki::depends{foo0}{"bar"});
+ok(! exists $IkiWiki::depends{foo0}{"baz"});
+
+# default dependencies are content dependencies
+ok($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+ok($IkiWiki::depends_simple{foo0}{"bar"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends_simple{foo0}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+
+# adding other dep types standalone
+ok(add_depends("foo2", "*", presence => 1));
+ok(add_depends("foo2", "bar", links => 1));
+ok($IkiWiki::depends{foo2}{"*"} & $IkiWiki::DEPEND_PRESENCE);
+ok(! ($IkiWiki::depends{foo2}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)));
+ok($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_LINKS);
+ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT)));
+
+# adding combined dep types
+ok(add_depends("foo2", "baz", links => 1, presence => 1));
+ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS);
+ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE);
+ok(! ($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT));
+
+# adding dep types to existing dependencies should merge the flags
+ok(add_depends("foo2", "baz"));
+ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_LINKS);
+ok($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_PRESENCE);
+ok(($IkiWiki::depends_simple{foo2}{"baz"} & $IkiWiki::DEPEND_CONTENT));
+ok(add_depends("foo2", "bar", presence => 1)); # had only links before
+ok($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_LINKS | $IkiWiki::DEPEND_PRESENCE));
+ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_CONTENT));
+ok(add_depends("foo0", "bar", links => 1)); # had only content before
+ok($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS));
+ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE));
+
+# Adding a pagespec that requires page metadata should add the influence
+# as an explicit dependency. In the case of a link, a links dependency.
+$links{foo0}=$links{foo9}=[qw{bar baz}];
+foreach my $spec ("* and ! link(bar)", "* or link(bar)") {
+ ok(add_depends("foo3", $spec, presence => 1));
+ ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_PRESENCE);
+ ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)));
+ ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS);
+ ok(add_depends("foo4", $spec, links => 1));
+ ok($IkiWiki::depends{foo4}{$spec} & $IkiWiki::DEPEND_LINKS);
+ ok(! ($IkiWiki::depends{foo4}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE)));
+ ok($IkiWiki::depends_simple{foo4}{foo4} == $IkiWiki::DEPEND_LINKS);
+}
+
+# a pagespec with backlinks() will add as an influence the page with the links
+$links{foo0}=[qw{foo5 foo7}];
+foreach my $spec ("bugs or (backlink(foo0) and !*.png)", "backlink(foo)") {
+ ok(add_depends("foo5", $spec, presence => 1));
+ ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_PRESENCE);
+ ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)));
+ ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS);
+ ok(add_depends("foo6", $spec, links => 1));
+ ok($IkiWiki::depends{foo6}{$spec} & $IkiWiki::DEPEND_LINKS);
+ ok(! ($IkiWiki::depends{foo6}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT)));
+ ok($IkiWiki::depends_simple{foo5}{foo0} == $IkiWiki::DEPEND_LINKS);
+ ok(add_depends("foo7", $spec, presence => 1, links => 1));
+ ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE);
+ ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS);
+ ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT));
+ ok($IkiWiki::depends_simple{foo7}{foo0} == $IkiWiki::DEPEND_LINKS);
+ ok(add_depends("foo8", $spec));
+ ok($IkiWiki::depends{foo8}{$spec} & $IkiWiki::DEPEND_CONTENT);
+ ok(! ($IkiWiki::depends{foo8}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+ ok($IkiWiki::depends_simple{foo8}{foo0} == $IkiWiki::DEPEND_LINKS);
+}
+
+# content is the default if unknown types are entered
+ok(add_depends("foo9", "*", presenCe => 1));
+ok($IkiWiki::depends{foo9}{"*"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends{foo9}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
#!/usr/bin/perl
use warnings;
use strict;
-use Test::More tests => 54;
+use Test::More tests => 64;
BEGIN { use_ok("IkiWiki"); }
my $ret=pagespec_match("foo", "(invalid");
ok(! $ret, "syntax error");
ok($ret =~ /syntax error/, "error message");
+
+$ret=pagespec_match("foo", "bar or foo");
+ok($ret, "simple match");
+is($ret, "foo matches foo", "stringified return");
+
+my %i=pagespec_match("foo", "link(bar)")->influences;
+is(join(",", keys %i), 'foo', "link is influenced by the page with the link");
+%i=pagespec_match("bar", "backlink(foo)")->influences;
+is(join(",", keys %i), 'foo', "backlink is influenced by the page with the link");
+%i=pagespec_match("bar", "backlink(foo)")->influences;
+is(join(",", keys %i), 'foo', "backlink is influenced by the page with the link");
+%i=pagespec_match("bar", "created_before(foo)")->influences;
+is(join(",", keys %i), 'foo', "created_before is influenced by the comparison page");
+%i=pagespec_match("bar", "created_after(foo)")->influences;
+is(join(",", keys %i), 'foo', "created_after is influenced by the comparison page");
+%i=pagespec_match("bar", "link(quux) and created_after(foo)")->influences;
+is(join(",", sort keys %i), 'bar,foo', "influences add up over AND");
+%i=pagespec_match("bar", "link(quux) and created_after(foo)")->influences;
+is(join(",", sort keys %i), 'bar,foo', "influences add up over OR");
+%i=pagespec_match("bar", "!link(quux) and !created_after(foo)")->influences;
+is(join(",", sort keys %i), 'bar,foo', "influences unaffected by negation");
--- /dev/null
+#!/usr/bin/perl
+use warnings;
+use strict;
+use Test::More tests => 10;
+
+BEGIN { use_ok("IkiWiki"); }
+
+%pagesources=(
+ foo => "foo.mdwn",
+ bar => "bar.mdwn",
+ "post/1" => "post/1.mdwn",
+ "post/2" => "post/2.mdwn",
+ "post/3" => "post/3.mdwn",
+);
+
+is_deeply([use_pagespec("foo", "bar")], ["bar"]);
+is_deeply([sort(use_pagespec("foo", "post/*"))], ["post/1", "post/2", "post/3"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title", reverse => 1)],
+ ["post/3", "post/2", "post/1"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 2)],
+ ["post/1", "post/2"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 50)],
+ ["post/1", "post/2", "post/3"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title",
+ limit => sub { $_[0] !~ /3/}) ],
+ ["post/1", "post/2"]);
+my $r=eval { use_pagespec("foo", "beep") };
+ok(eval { use_pagespec("foo", "beep") } == 0);
+ok(! $@, "does not fail with error when unable to match anything");
+eval { use_pagespec("foo", "this is not a legal pagespec!") };
+ok($@, "fails with error when pagespec bad");