]> sipb.mit.edu Git - ikiwiki.git/commitdiff
Merge branch 'master' into dependency-types
authorJoey Hess <joey@gnu.kitenet.net>
Wed, 7 Oct 2009 17:00:54 +0000 (13:00 -0400)
committerJoey Hess <joey@gnu.kitenet.net>
Wed, 7 Oct 2009 17:00:54 +0000 (13:00 -0400)
33 files changed:
IkiWiki.pm
IkiWiki/Plugin/brokenlinks.pm
IkiWiki/Plugin/calendar.pm
IkiWiki/Plugin/edittemplate.pm
IkiWiki/Plugin/inline.pm
IkiWiki/Plugin/linkmap.pm
IkiWiki/Plugin/listdirectives.pm
IkiWiki/Plugin/map.pm
IkiWiki/Plugin/meta.pm
IkiWiki/Plugin/orphans.pm
IkiWiki/Plugin/pagecount.pm
IkiWiki/Plugin/pagestats.pm
IkiWiki/Plugin/postsparkline.pm
IkiWiki/Plugin/progress.pm
IkiWiki/Render.pm
debian/NEWS
debian/changelog
debian/postinst
doc/bugs/bestlink_change_update_issue.mdwn
doc/bugs/transitive_dependencies.mdwn
doc/examples/blog/index.mdwn
doc/examples/blog/tags.mdwn
doc/ikiwiki/directive/inline.mdwn
doc/ikiwiki/directive/linkmap.mdwn
doc/ikiwiki/directive/pagestats.mdwn
doc/plugins.mdwn
doc/plugins/contrib.mdwn
doc/plugins/orphans.mdwn
doc/plugins/sidebar.mdwn
doc/plugins/write.mdwn
doc/todo/dependency_types.mdwn
ikiwiki-transition
t/add_depends.t [new file with mode: 0755]

index 97d84c9deb175b694bd303b10ff75fba6b95bb08..7b1d24c6a2214d14543aadcbbadde9767a15af52 100644 (file)
@@ -28,6 +28,11 @@ 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");
@@ -1524,18 +1529,28 @@ sub loadindex () {
                                $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};
@@ -1581,11 +1596,11 @@ sub saveindex () {
                };
 
                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}) {
@@ -1753,20 +1768,58 @@ sub rcs_receive () {
        $hooks{rcs}{rcs_receive}{call}->();
 }
 
-sub add_depends ($$) {
+sub add_depends ($$;@) {
        my $page=shift;
        my $pagespec=shift;
 
-       if ($pagespec =~ /$config{wiki_file_regexp}/ &&
-               $pagespec !~ /[\s*?()!]/) {
-               # a simple dependency, which can be matched by string eq
-               $depends_simple{$page}{lc $pagespec} = 1;
+       # Is the pagespec a simple page name?
+       my $simple=$pagespec =~ /$config{wiki_file_regexp}/ &&
+               $pagespec !~ /[\s*?()!]/;
+
+       my $deptype=0;
+       if (@_) {
+               my %params=@_;
+               
+               if ($params{presence}) {
+                       # Is the pagespec limited to terms that will continue
+                       # to match pages as long as those pages exist?
+                       my $presence_limited=1;
+                       while ($presence_limited && $pagespec=~m/(\w+)\([^\)]*\)/g) {
+                               $presence_limited = $1 =~ /^(glob|internal|creation_month|creation_day|creation_year|created_before|created_after)$/;
+                       }
+                       if ($presence_limited) {
+                               $deptype=$deptype | $DEPEND_PRESENCE;
+                       }
+                       else {
+                               $deptype=$deptype | $DEPEND_CONTENT;
+                       }
+               }
+               if ($params{links}) {
+                       # Is the pagespec limited to terms that will continue
+                       # to match pages as long as those pages exist and
+                       # link to the same places?
+                       my $links_limited=1;
+                       while ($links_limited && $pagespec=~m/(\w+)\([^\)]*\)/g) {
+                               $links_limited = $1 =~ /^(glob|internal|creation_month|creation_day|creation_year|created_before|created_after|backlink)$/;
+                       }
+                       if ($links_limited) {
+                               $deptype=$deptype | $DEPEND_LINKS;
+                       }
+                       else {
+                               $deptype=$deptype | $DEPEND_CONTENT;
+                       }
+               }
+       }
+       $deptype=$DEPEND_CONTENT unless $deptype;
+
+       if ($simple) {
+               $depends_simple{$page}{lc $pagespec} |= $deptype;
                return 1;
        }
 
        return unless pagespec_valid($pagespec);
 
-       $depends{$page}{$pagespec} = 1;
+       $depends{$page}{$pagespec} |= $deptype;
        return 1;
 }
 
index eb698b0bef9f7b2c9df90b342dcfac56c79a1795..9e65f52c656f55dd201c6b2abba6fd5f9419e3a8 100644 (file)
@@ -23,9 +23,8 @@ sub preprocess (@) {
        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}, links => 1);
        
        my @broken;
        foreach my $link (keys %IkiWiki::brokenlinks) {
index 5d16dff75ba7a98a68c3fc8c94d9d9378cb11732..a1117992a865ed3a5cd5a7a8ded409d3b8ace33e 100644 (file)
@@ -104,19 +104,22 @@ sub format_month (@) {
                        "$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),
+               presence => 1);
        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),
+               presence => 1);
        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),
+               presence => 1);
 
        # Start producing the month calendar
        $calendar=<<EOF;
@@ -209,11 +212,11 @@ EOF
 
        # Add dependencies to update the calendar whenever pages
        # matching the pagespec are added or removed.
-       add_depends($params{page}, $params{pages});
+       add_depends($params{page}, $params{pages}, presence => 1);
        # 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}, $p, presence => 1);
        }
 
        return $calendar;
@@ -246,19 +249,19 @@ sub format_year (@) {
                        "$archivebase/$year",
                        linktext => "$year");
        }
-       add_depends($params{page}, "$archivebase/$year");
+       add_depends($params{page}, "$archivebase/$year", presence => 1);
        if (exists $cache{$pagespec}{"$pyear"}) {
                $purl = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$pyear",
                        linktext => "\&larr;");
        }
-       add_depends($params{page}, "$archivebase/$pyear");
+       add_depends($params{page}, "$archivebase/$pyear", presence => 1);
        if (exists $cache{$pagespec}{"$nyear"}) {
                $nurl = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$nyear",
                        linktext => "\&rarr;");
        }
-       add_depends($params{page}, "$archivebase/$nyear");
+       add_depends($params{page}, "$archivebase/$nyear", presence => 1);
 
        # Start producing the year calendar
        $calendar=<<EOF;
@@ -310,7 +313,7 @@ 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", presence => 1);
 
                $calendar.=qq{\t</tr>\n} if ($month % $params{months_per_row} == 0);
        }
index 0bafc95d06d854b860e566a8c14134119d51db17..2dd1dbe682c2567e4bf879e0708e0ea9e2fcb8c0 100644 (file)
@@ -58,7 +58,7 @@ sub preprocess (@) {
        $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, presence => 1);
        return sprintf(gettext("edittemplate %s registered for %s"),
                htmllink($params{page}, $params{destpage}, $link),
                $params{match});
index ccfadfd699929a8be1764546118d89ee9f958f56..cebd9037cc65c6095bca009c99663d2894223dd4 100644 (file)
@@ -197,7 +197,7 @@ sub preprocess_inline (@) {
                        split ' ', $params{pagenames};
        }
        else {
-               add_depends($params{page}, $params{pages});
+               add_depends($params{page}, $params{pages}, presence => $quick);
 
                @list = pagespec_match_list(
                        [ grep { $_ ne $params{page} } keys %pagesources ],
@@ -248,10 +248,9 @@ sub preprocess_inline (@) {
        }
 
        # 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.
+       # that if they are removed, the inline will be sure to be updated.
        foreach my $p ($#list >= $#feedlist ? @list : @feedlist) {
-               add_depends($params{page}, $p);
+               add_depends($params{page}, $p, presence => $quick);
        }
        
        if ($feeds && exists $params{feedpages}) {
index 941ed5f3672145bb6111c5a6650fb14431641297..3d20a65212ac7904f5b19aea0d2eb642ddaf1781 100644 (file)
@@ -28,9 +28,9 @@ sub preprocess (@) {
 
        $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}, presence => 1, links => 1);
        
        # Can't just return the linkmap here, since the htmlscrubber
        # scrubs out all <object> tags (with good reason!)
@@ -56,7 +56,7 @@ sub genmap ($) {
 
        # 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});
                }
index bd73f1a04e728638c19994c0d64a4f288a120150..4023ed7d75fd4b2582b7fe00265a5ac6a449619c 100644 (file)
@@ -84,7 +84,7 @@ sub preprocess (@) {
        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, presence => 1);
                $result .= htmllink($params{page}, $params{destpage}, $link);
                $result .= '</li>';
        }
index 54146dc467bdcb513e426257a6fd0879005cd35a..625cfdfcaaa723a80f7c15e155d70d0583d2b9b7 100644 (file)
@@ -68,13 +68,13 @@ sub preprocess (@) {
        }
 
        # 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});
+       # cases, when its content changes, if show= is specified), so
+       # register a dependency.
+       add_depends($params{page}, $params{pages}, presence => ! exists $params{show});
        # Explicitly add all currently shown pages, to detect when pages
        # are removed.
        foreach my $item (keys %mapitems) {
-               add_depends($params{page}, $item);
+               add_depends($params{page}, $item, presence => ! exists $params{show});
        }
 
        # Create the map.
index 514b0936907ce58edabcbafcb44d5559a154eec9..9b041a748c35590820f89f17a65b6e183dbcf59c 100644 (file)
@@ -195,7 +195,7 @@ sub preprocess (@) {
                        if (! length $link) {
                                error gettext("redir page not found")
                        }
-                       add_depends($page, $link);
+                       add_depends($page, $link, presence => 1);
 
                        $value=urlto($link, $page);
                        $value.='#'.$redir_anchor if defined $redir_anchor;
index 71122677273275d3e2f160bde3c8b44a8f333e49..ae330b23b81e78abf227cbcb0a5cb757088e485a 100644 (file)
@@ -23,9 +23,13 @@ sub preprocess (@) {
        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}, "*", links => 1);
+       # Also needs to update whenever potential orphans are added or
+       # removed.
+       add_depends($params{page}, $params{pages}, presence => 1);
        
        my @orphans;
        foreach my $page (pagespec_match_list(
index 5a2301af49232943d4f0889c0f3b9ba28f05396f..80561350b5fe44d7d282fb5fac6b5c9ac8e295ea 100644 (file)
@@ -23,8 +23,8 @@ sub preprocess (@) {
        $params{pages}="*" unless defined $params{pages};
        
        # Needs to update count whenever a page is added or removed, so
-       # register a dependency.
-       add_depends($params{page}, $params{pages});
+       # register a presence dependency.
+       add_depends($params{page}, $params{pages}, presence => 1);
        
        my @pages;
        if ($params{pages} eq "*") {
index 874ead7e6de669c1d33c66b74984ef90504f9b8e..afe4eeaf2e4b69855600ac5972e219292d1d2af4 100644 (file)
@@ -35,14 +35,17 @@ sub preprocess (@) {
        $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};
+       # Needs to update whenever a page is added or removed.
+       add_depends($params{page}, $params{pages}, exists => 1);
+       # Also needs to update when any page with links changes, 
+       # in case the links point to our displayed pages.
+       # (Among limits this further.)
+       add_depends($params{page}, exists $params{among} ? $params{among} : "*",
+               links => 1); 
        
        my %counts;
        my $max = 0;
-       foreach my $page (pagespec_match_list([keys %links],
+       foreach my $page (pagespec_match_list([keys %pagesources],
                        $params{pages}, location => $params{page})) {
                use IkiWiki::Render;
 
index d2e5c23788678efb7819e10c59964bd852663e06..3205958d40dcb5f8b0a510b58d14fba95423c1ff 100644 (file)
@@ -48,7 +48,7 @@ sub preprocess (@) {
                error gettext("unknown formula");
        }
 
-       add_depends($params{page}, $params{pages});
+       add_depends($params{page}, $params{pages}, presence => 1);
 
        my @list=sort { $params{timehash}->{$b} <=> $params{timehash}->{$a} } 
                pagespec_match_list(
index 76d994acc7ad0f9d49c63c32d3d5d8dd54db5554..26c537a84022ae51001beb9be86db90b1da25e0a 100644 (file)
@@ -36,8 +36,8 @@ sub preprocess (@) {
                $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}, presence => 1);
+               add_depends($params{page}, $params{donepages}, presence => 1);
 
                my @pages=keys %pagesources;
                my $totalcount=0;
index 246c2260d7748e9fe2b3522d73035564e0fb6754..e28381c3332b848d442ffe943ad0fd86f5b141f6 100644 (file)
@@ -7,7 +7,7 @@ use strict;
 use IkiWiki;
 use Encode;
 
-my %backlinks;
+my (%backlinks, %rendered);
 our %brokenlinks;
 my $links_calculated=0;
 
@@ -147,6 +147,8 @@ sub genpage ($$) {
 sub scan ($) {
        my $file=shift;
 
+       debug(sprintf(gettext("scanning %s"), $file));
+
        my $type=pagetype($file);
        if (defined $type) {
                my $srcfile=srcfile($file);
@@ -202,8 +204,11 @@ sub fast_file_copy (@) {
        }
 }
 
-sub render ($) {
+sub render ($$) {
        my $file=shift;
+       return if $rendered{$file};
+       debug(shift);
+       $rendered{$file}=1;
        
        my $type=pagetype($file);
        my $srcfile=srcfile($file);
@@ -273,7 +278,8 @@ sub srcdir_check () {
 }
 
 sub find_src_files () {
-       my (@files, %pages);
+       my @files;
+       my %pages;
        eval q{use File::Find};
        error($@) if $@;
        find({
@@ -291,11 +297,11 @@ sub find_src_files () {
                                else {
                                        $f=~s/^\Q$config{srcdir}\E\/?//;
                                        push @files, $f;
-                                       my $pagename = pagename($f);
-                                       if ($pages{$pagename}) {
-                                               debug(sprintf(gettext("%s has multiple possible source pages"), $pagename));
+                                       my $page = pagename($f);
+                                       if ($pages{$page}) {
+                                               debug(sprintf(gettext("%s has multiple possible source pages"), $page));
                                        }
-                                       $pages{$pagename}=1;
+                                       $pages{$page}=1;
                                }
                        }
                },
@@ -331,19 +337,14 @@ sub find_src_files () {
                        },
                }, $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) {
@@ -353,10 +354,10 @@ sub refresh () {
                $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");
@@ -373,10 +374,19 @@ sub refresh () {
                        }
                }
        }
+
+       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));
@@ -397,8 +407,13 @@ sub refresh () {
                }
        }
 
-       # 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);
@@ -406,150 +421,242 @@ sub refresh () {
                    $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{$l}=1;
+                                       $linkchangers{lc($page)}=1;
                                }
+                               delete $oldlink_targets->{$page}{$l};
                        }
+               }
+               if (exists $oldlink_targets->{$page} &&
+                   %{$oldlink_targets->{$page}}) {
+                       foreach my $target (keys %{$oldlink_targets->{$page}}) {
+                               $backlinkchanged{$target}=1;
+                       }
+                       $linkchangers{lc($page)}=1;
+               }
+       }
+
+       return \%backlinkchanged, \%linkchangers;
+}
 
-                       if (exists $depends{$p} && ! defined $reason) {
-                               D: foreach my $d (keys %{$depends{$p}}) {
-                                       my $sub=pagespec_translate($d);
-                                       next if $@ || ! defined $sub;
+sub render_dependent ($$$$$$$) {
+       my ($files, $new, $internal_new, $del, $internal_del,
+               $internal_changed, $linkchangers)=@_;
 
-                                       # only consider internal files
-                                       # if the page explicitly depends
-                                       # on such files
-                                       foreach my $file (@changed, $d =~ /internal\(/ ? @internal : ()) {
+       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) });
index 808105fd5b1e7cbc49306bd81bb5b15be2d79490..520f3881535efb4afc1a5619ac7c86ab200bad0c 100644 (file)
@@ -1,3 +1,13 @@
+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
index ca5409af7949f7a7e6eb900ab697892c57134e25..dc6ee0a81c2e6da02f5f47014e4a25b57d0c3af8 100644 (file)
@@ -10,6 +10,28 @@ ikiwiki (3.14159266) UNRELEASED; urgency=low
   * 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.
+    (By passing presence => 1 or links => 1 to add_depends.)
+  * 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.
 
  -- Joey Hess <joeyh@debian.org>  Sun, 27 Sep 2009 17:40:03 -0400
 
index 2ba26e5b65010cee1649390240228a3d939b7afb..dd4be6e0e7948d408551a38ca0e886828189b102 100755 (executable)
@@ -4,7 +4,7 @@ set -e
 
 # 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
index 5ce4a93d22707e69a0f546c9e850bf54dba444e1..8a526e8211b4402ced79a64853660d9b45319faa 100644 (file)
@@ -1,13 +1,29 @@
 * 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.
index 9586bc9b02767a29ceaaa5050a034a6b0f0fff45..0a2e9ec2896176658a2a94f7c44b5921d54b03b8 100644 (file)
@@ -65,4 +65,4 @@ Downsides here:
   modification to plugins/brokenlinks causes an unnecessary update of
   plugins, and could be solved by adding more dependency types.)
 
---[[Joey]] 
+[[done]] --[[Joey]] 
index aef46eb685070ae9792d15e55ae64ccf1b34abc9..01b714fcdfbcd6744a962bbe9f5bdf24f8dd021e 100644 (file)
@@ -1,4 +1,4 @@
-[[!pagestats pages="./tags/*"]]
+[[!pagestats pages="./tags/*" among="./posts/*"]]
 
 Welcome to my blog.
 
index 53cc8d368b599795bc1eacea1ab47df73b994fc0..b5eca5b710bf2bd70cea22c4ef97679456d0dc30 100644 (file)
@@ -1,3 +1,3 @@
-[[!pagestats pages="./tags/*"]]
+[[!pagestats pages="./tags/*" among="./posts/*"]]
 
 On the right you can see the tag cloud for this blog.
index 99f795972dcf8f4d8010a7807763834be2d01880..4e087ab6c5e1272b69b416aa0ccbcffb3cdcaa44 100644 (file)
@@ -98,7 +98,7 @@ Here are some less often needed parameters:
 * `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
index db79a1491c9a9d85b445a236b718fe9f67fa76c0..38cf0fd119a8892a4fb1deddf1047a196a5a6f1a 100644 (file)
@@ -9,9 +9,7 @@ Only links between mapped pages will be shown; links pointing to or from
 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:
index 66f851dbd9204250389b9fdb888d9fb12a485658..f14c80b0798d85b6e6279622b1fe6d774b5fdad0 100644 (file)
@@ -12,13 +12,13 @@ And here's how to create a table of all the pages on the wiki:
 
        \[[!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)"]]
 
index 527568208913e1c379c1ae31b79e2202173dab58..697b4a21964c884703bf616c6626fb82ba4b1f1d 100644 (file)
@@ -1,7 +1,7 @@
 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.
@@ -13,7 +13,5 @@ will fit most uses of ikiwiki.
 
 ## 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"]]
index a03e6a95d72993abf28aeb5b68a40ed0cbbf4dc2..ac6c1b75134d65c035c22642a82e19d72c0ff3eb 100644 (file)
@@ -1,6 +1,4 @@
 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"]]
index ea7c4df13f42aba0a83a3b957cd3fcc621d89e69..e403c2d189249f8bd6069ab06d594cab3a177253 100644 (file)
@@ -10,5 +10,6 @@ Here's a list of orphaned pages on this wiki:
 
 [[!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/*"]]
 """]]
index 36982eff386a31e91eaa3fade012795bb9fd61c1..4e356d65a8984885a256788f82f1f2173ffeaa67 100644 (file)
@@ -16,6 +16,10 @@ will turn off the sidebar altogether.
 
 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.
index 668f8d8b69dff65acc0727fa5fa3233e516b132a..133030f08fc5bef55dc7dcf2fbca03add189a2db 100644 (file)
@@ -609,10 +609,22 @@ page created from it. (Ie, it appends ".html".)
 Use this when constructing the filename of a html file. Use `urlto` when
 generating a link to a page.
 
-#### `add_depends($$)`
+#### `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.
+
 #### `pagespec_match($$;@)`
 
 Passed a page name, and [[ikiwiki/PageSpec]], returns true if the
index 0503b47afd652a45543185c8f2a212e9a18f7c86..4bc38e9c0a7b63c28964870a2a5086ea91cb639d 100644 (file)
@@ -223,10 +223,7 @@ sigh.
 
 * `add_depends($page, $spec, links => 1, presence => 1)`
   adds a links + presence dependency.
-* `refresh` only rebuilds a page with a links dependency if
-  pages matched by the pagespec gain or lose links. (What the link
-  actually points to may change independent of this, due to changes
-  elsewhere, without it firing.)
+* Use backlinks change code to detect changes to link dependencies too.
 * So, brokenlinks can fire whenever any links in any of the
   pages it's tracking change, or when pages are added or
   removed.
@@ -236,6 +233,7 @@ sigh.
   that the page links to, which is just what link dependencies are
   triggered on.
 
+[[done]]
 ----
 
 ### the removal problem
index 8a20cf655f021b328eef5afe509a75908f202d2a..1bebb1176d99569341a6735e83c4cb51c14e1ffc 100755 (executable)
@@ -299,7 +299,7 @@ sub oldloadindex {
                        $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;
diff --git a/t/add_depends.t b/t/add_depends.t
new file mode 100755 (executable)
index 0000000..2d686a1
--- /dev/null
@@ -0,0 +1,92 @@
+#!/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();
+
+# avoids adding an unparseable pagespec
+ok(! add_depends("foo", "foo and (bar"));
+ok(! add_depends("foo", "foo another"));
+
+# simple and not-so-simple dependencies split
+ok(add_depends("foo", "*"));
+ok(add_depends("foo", "bar"));
+ok(add_depends("foo", "BAZ"));
+ok(exists $IkiWiki::depends_simple{foo}{"bar"});
+ok(exists $IkiWiki::depends_simple{foo}{"baz"}); # lowercase
+ok(! exists $IkiWiki::depends_simple{foo}{"*"});
+ok(! exists $IkiWiki::depends{foo}{"bar"});
+ok(! exists $IkiWiki::depends{foo}{"baz"});
+
+# default dependencies are content dependencies
+ok($IkiWiki::depends{foo}{"*"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends{foo}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+ok($IkiWiki::depends_simple{foo}{"bar"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends_simple{foo}{"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("foo", "bar", links => 1)); # had only content before
+ok($IkiWiki::depends{foo}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS));
+ok(! ($IkiWiki::depends{foo}{"*"} & $IkiWiki::DEPEND_PRESENCE));
+
+# adding a pagespec that requires page metadata should cause a fallback to
+# a content dependency
+foreach my $spec ("* and ! link(bar)", "* or link(bar)", "unknownspec()",
+       "title(hi)",
+       "* or unknown(yo)", # this one could actually be acceptably be
+                           # detected to not need a content dep .. in
+                           # theory!
+       ) {
+       ok(add_depends("foo3", $spec, presence => 1));
+       ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT);
+       ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+       ok(add_depends("foo4", $spec, links => 1));
+       ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_CONTENT);
+       ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+}
+
+# a pagespec with backlinks() in it is acceptable for a links dependency,
+# but not a presence dependency
+foreach my $spec ("index or (backlink(index) and !*.png)", "backlink(foo)") {
+       ok(add_depends("foo5", $spec, presence => 1));
+       ok($IkiWiki::depends{foo5}{$spec} & $IkiWiki::DEPEND_CONTENT);
+       ok(! ($IkiWiki::depends{foo5}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $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)));
+       # combining both ends up with a content+links dependency
+       ok(add_depends("foo7", $spec, presence => 1, links => 1));
+       ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_CONTENT);
+       ok($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_LINKS);
+       ok(! ($IkiWiki::depends{foo7}{$spec} & $IkiWiki::DEPEND_PRESENCE));
+}
+
+# content is the default if unknown types are entered
+ok(add_depends("foo8", "*", presenCe => 1));
+ok($IkiWiki::depends{foo8}{"*"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends{foo8}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));