]> sipb.mit.edu Git - ikiwiki.git/commitdiff
Merge branch 'master' into dependency-types
authorJoey Hess <joey@gnu.kitenet.net>
Fri, 9 Oct 2009 00:33:58 +0000 (20:33 -0400)
committerJoey Hess <joey@gnu.kitenet.net>
Fri, 9 Oct 2009 00:33:58 +0000 (20:33 -0400)
Conflicts:
IkiWiki.pm
IkiWiki/Render.pm
debian/changelog

1  2 
IkiWiki.pm
IkiWiki/Render.pm
debian/changelog
doc/todo/dependency_types.mdwn

diff --combined IkiWiki.pm
index c735b26c80e2800955e79730260d4e4cd1ce0b13,d667e7e10fe43c9162b9dd54a0105c41c80c3afe..daa71059b093c5bdc95327459acf508c8ff72bc4
@@@ -17,28 -17,21 +17,27 @@@ use vars qw{%config %links %oldlinks %p
            %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");
  memoize("pagespec_translate");
- memoize("file_pruned");
  memoize("template_file");
  
  sub getsetup () {
@@@ -1530,28 -1523,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};
@@@ -1597,11 -1580,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}) {
@@@ -1769,145 -1752,35 +1768,149 @@@ sub rcs_receive () 
        $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 file_pruned ($$) {
-       require File::Spec;
-       my $file=File::Spec->canonpath(shift);
-       my $base=File::Spec->canonpath(shift);
-       $file =~ s#^\Q$base\E/+##;
 +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 (@_) {
+               require File::Spec;
+               $file=File::Spec->canonpath($file);
+               my $base=File::Spec->canonpath(shift);
+               return if $file eq $base;
+               $file =~ s#^\Q$base\E/+##;
+       }
  
        my $regexp='('.join('|', @{$config{wiki_file_prune_regexps}}).')';
-       return $file =~ m/$regexp/ && $file ne $base;
+       return $file =~ m/$regexp/;
  }
  
  sub define_gettext () {
@@@ -2003,10 -1876,10 +2006,10 @@@ sub pagespec_translate ($) 
        }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;
@@@ -2092,54 -1965,36 +2095,54 @@@ sub glob2re ($) 
  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;
  
@@@ -2190,20 -2045,19 +2193,20 @@@ sub match_link ($$;@) 
        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);
        }
  }
  
@@@ -2245,14 -2097,14 +2248,14 @@@ sub match_created_after ($$;@) 
  
        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);
        }
  }
  
diff --combined IkiWiki/Render.pm
index 79935f32393f9dddb225f989784597dacb3c47f8,a8236b954fbbc334696542e1e61f17ff6f617bc6..0fe20c64f3a0939d7dde93107e7b0285e883e2a2
@@@ -7,7 -7,7 +7,7 @@@ use strict
  use IkiWiki;
  use Encode;
  
 -my %backlinks;
 +my (%backlinks, %rendered);
  our %brokenlinks;
  my $links_calculated=0;
  
@@@ -147,8 -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);
@@@ -204,11 -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);
@@@ -278,31 -273,32 +278,33 @@@ sub srcdir_check () 
  }
  
  sub find_src_files () {
 -      my (@files, %pages);
 +      my @files;
 +      my %pages;
        eval q{use File::Find};
        error($@) if $@;
        find({
                no_chdir => 1,
                wanted => sub {
-                       $_=decode_utf8($_);
-                       if (file_pruned($_, $config{srcdir})) {
+                       my $file=decode_utf8($_);
+                       $file=~s/^\Q$config{srcdir}\E\/?//;
+                       my $page = pagename($file);
+                       if (! exists $pagesources{$page} &&
+                           file_pruned($file)) {
                                $File::Find::prune=1;
+                               return;
                        }
-                       elsif (! -l $_ && ! -d _) {
-                               my ($f)=/$config{wiki_file_regexp}/; # untaint
-                               if (! defined $f) {
-                                       warn(sprintf(gettext("skipping bad filename %s"), $_)."\n");
-                               }
-                               else {
-                                       $f=~s/^\Q$config{srcdir}\E\/?//;
-                                       push @files, $f;
-                                       my $page = pagename($f);
-                                       if ($pages{$page}) {
-                                               debug(sprintf(gettext("%s has multiple possible source pages"), $page));
-                                       }
-                                       $pages{$page}=1;
+                       return if -l $_ || -d _ || ! length $file;
+                       my ($f) = $file =~ /$config{wiki_file_regexp}/; # untaint
+                       if (! defined $f) {
+                               warn(sprintf(gettext("skipping bad filename %s"), $file)."\n");
+                       }
+                       else {
+                               push @files, $f;
+                               if ($pages{$page}) {
+                                       debug(sprintf(gettext("%s has multiple possible source pages"), $page));
                                }
+                               $pages{$page}=1;
                        }
                },
        }, $config{srcdir});
                find({
                        no_chdir => 1,
                        wanted => sub {
-                               $_=decode_utf8($_);
-                               if (file_pruned($_, $dir)) {
+                               my $file=decode_utf8($_);
+                               $file=~s/^\Q$dir\E\/?//;
+                               my $page=pagename($file);
+                               if (! exists $pagesources{$page} &&
+                                   file_pruned($file)) {
                                        $File::Find::prune=1;
+                                       return;
                                }
-                               elsif (! -l $_ && ! -d _) {
-                                       my ($f)=/$config{wiki_file_regexp}/; # untaint
-                                       if (! defined $f) {
-                                               warn(sprintf(gettext("skipping bad filename %s"), $_)."\n");
-                                       }
-                                       else {
-                                               $f=~s/^\Q$dir\E\/?//;
-                                               # avoid underlaydir
-                                               # override attacks; see
-                                               # security.mdwn
-                                               if (! -l "$config{srcdir}/$f" && 
-                                                   ! -e _) {
-                                                       my $page=pagename($f);
-                                                       if (! $pages{$page}) {
-                                                               push @files, $f;
-                                                               $pages{$page}=1;
-                                                       }
+                               return if -l $_ || -d _ || ! length $file;
+                               my ($f) = $file =~ /$config{wiki_file_regexp}/; # untaint
+                               if (! defined $f) {
+                                       warn(sprintf(gettext("skipping bad filename %s"), $file)."\n");
+                               }
+                               else {
+                                       # avoid underlaydir override
+                                       # attacks; see security.mdwn
+                                       if (! -l "$config{srcdir}/$f" && 
+                                           ! -e _) {
+                                               if (! $pages{$page}) {
+                                                       push @files, $f;
+                                                       $pages{$page}=1;
                                                }
                                        }
                                }
                        },
                }, $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) });
diff --combined debian/changelog
index 12ddebac9fc46d4d4ca153ae2ae431ce9cd02f02,6c435306537eb82a7d55c3a28b7b8022abba3e07..3a6fdf77d22495ca5d60e8479022195c7a298e3a
@@@ -10,32 -10,8 +10,34 @@@ ikiwiki (3.14159266) UNRELEASED; urgenc
    * 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.
  
   -- Joey Hess <joeyh@debian.org>  Sun, 27 Sep 2009 17:40:03 -0400
  
index 479cc95ccd404cb64c1d4b5dbb63917b16293872,f06603874ab5331bd98188db2a44149db359b330..d9e68841d119bdba305da6d25f9304a2022ddf7f
@@@ -222,7 -222,7 +222,7 @@@ ShavedByBob.mdwn
  
  Does ShavedByBob.mdwn include itself?
  
- (Yeah - in IkiWiki currently links are included by include, but the idea holds.  I had a good example a while back, but I can't think of it right now.)
+ (Yeah - in IkiWiki currently links are *not* included by include, but the idea holds.  I had a good example a while back, but I can't think of it right now.)
  
  sigh.
  
  > to determine what metadata, pages, etc they depend on. It is indeed
  > tricky to do. More thoughts on influence lists a bit below. --[[Joey]] 
  
+ >> The big part of what makes this tricky is that there may be cycles in the
+ >> dependency graph.  This can lead to situations where the result is just not
+ >> well defined.  This is what I was trying to get at above. -- [[Will]]
+ >>> Hmm, I'm not seeing cycles be a problem, at least with the current
+ >>> pagespec terms. --[[Joey]] 
+ >>>> Oh, they're not with current pagespec terms.  But this is really close to extending to handle
+ >>>> functional pagespecs, etc.  And I think I'd like to think about that now.
+ >>>>
+ >>>> Having said that, I don't want to hold you up - you seem to be making progress.  The best is
+ >>>> the enemy of the good, etc. etc.
+ >>>>
+ >>>> For my part, I'm imagining we have two more constructs in IkiWiki:
+ >>>>
+ >>>>  * A map directive that actually wikilinks to the pages it links to, and
+ >>>>  * A `match_sharedLink(pageX)` matching function that matches pageY if both pageX and pageY each have links to any same third page, pageZ.
+ >>>>
+ >>>> With those two constructs, one page changing might change the set of pages included in a map somewhere, which might then change the set of pages matched by some other pagespec, which might then...
+ >>>>
+ >>>> --[[Will]]
+ >>>>> I think that should be supported by [[bugs/transitive_dependencies]].
+ >>>>> At least in the current implementation, which considers each page
+ >>>>> that is rendered to be changed, and rebuilds pages that are dependent
+ >>>>> on it, in a loop. An alternate implementation, which could be faster,
+ >>>>> is to construct a directed graph and traverse it just once. Sounds
+ >>>>> like that would probably not support what you want to do.
+ >>>>> --[[Joey]]
  ---- 
  
  ### Link dependencies
    that the page links to, which is just what link dependencies are
    triggered on.
  
 +[[done]]
  ----
  
  ### the removal problem
@@@ -273,7 -302,7 +303,7 @@@ One way to fix this is to include with 
  that currently match it. If the list changes, the dependency is triggered.
  
  Should be doable, but may involve more work than
- currently. Consider that a dependency on "bugs/*" currently
+ currently. Consider that a dependency on `bugs/*` currently
  is triggered by just checking until *one* page is found to match it.
  But to store the list, *every* page would have to be tried against it.
  Unless the list can somehow be intelligently updated, looking at only the
@@@ -305,10 -334,53 +335,53 @@@ changes, is needed
  I'm using this term for the concept of a list of pages whose modification
  can indirectly influence what pages a pagespec matches.
  
+ > Trying to make a formal definition of this: (Note, I'm using the term sets rather than lists, but they're roughly equivalent)
+ >
+ >  * Let the *matching set* for a pagespec be the set of existing pages that the pagespec matches.
+ >  * Let a *influence set* for a pagespec be the set of all pages, *p*, whose alteration might:
+ >    * cause the pagespec to include or exclude a page other than *p*, or
+ >    * cause the pagespec to exclude *p*.
+ >
+ >> \[Will snipped some stuff and edited the formal definition]
+ >
+ > --[[Will]]
+ >> I appreciate the formalism! 
+ >>
+ >> Only existing pages need to be in these sets, because if a page is added
+ >> in the future, the existing dependency code will always test to see
+ >> if it matches. So it will be in the maching set (or not) at that point.
+ >>
+ >>> Hrm, I agree with you in general, but I think I can come up with nasty counter-examples.  What about a pagespec
+ >>> of "!backlink(bogus)" where the page bogus doesn't exist?  In this case, the page 'bogus' needs to be in the influence
+ >>> set even though it doesn't exist.
+ >>>
+ >>>> I think you're right, this is a case that the current code is not
+ >>>> handling. Actually, I made all the pagespecs return influences
+ >>>> even if the influence was not present or did not match. But, it
+ >>>> currently only records influences as dependencies when a pagespec
+ >>>> successfully matches. Now I'm sure that is wrong, and I've removed
+ >>>> that false optimisation. I've updated some of the below. --[[Joey]]
+ >>>
+ >>> Also, I would really like the formalism to include the whole dependency system, not just any additions to it.  That will make
+ >>> the whole thing much easier to reason about.
+ >>
+ >> The problem with your definition of direct influence set seems to be
+ >> that it doesn't allow `link()` and `title()` to have as an indirect
+ >> influence, the page that matches. But I'm quite sure we need those.
+ >>  --[[Joey]] 
+ >>> I see what you mean.  Does the revised definition capture this effectively?
+ >>> The problem with this revised definition is that it still doesn't match your examples below.
+ >>> My revised definition will include pretty much all currently matching pages to be in the influence list
+ >>> because deletion of any of them would cause a change in which pages are matched - the removal problem.
+ >>> -- [[Will]]
  #### Examples
  
  * The pagespec "created_before(foo)" has an influence list that contains foo.
-   The removal or (re)creation of foo changes what pages match it.
+   The removal or (re)creation of foo changes what pages match it. Note that
+   this is true even if the pagespec currently fails to match.
  
  * The pagespec "foo" has an empty influence list. This is because a
    modification/creation/removal of foo directly changes what the pagespec
    Avoiding including every page in the wiki into its influence list is
    very important!
  
+ >>> So, why don't the above influence lists contain the currently matched pages?
+ >>> Don't you need this to handle the removal problem? -- [[Will]]
+ >>>> The removal problem is slightly confusingly named, since it does not
+ >>>> affect pages that were matched by a glob and have been removed. Such
+ >>>> pages can be handled without being influences, because ikiwiki knows
+ >>>> they have been removed, and so can still match them against the
+ >>>> pagespec, and see they used to match; and thus knows that the
+ >>>> dependency has triggered.
+ >>>>
+ >>>> Maybe the thing to do is consider this an optimisation, where such
+ >>>> pages are influences, but ikiwiki is able to implicitly find them,
+ >>>> so they do not need to be explicitly stored. --[[Joey]]
  * The pagespec "title(foo)" has an influence list that contains every page
    that currently matches it. A change to any matching page can change its
-   title. Why is that considered an indirect influence? Well, the pagespec
-   might be used in a presence dependency, and so its title changing
-   would not directly affect the dependency.
+   title, making it not match any more, and so the list is needed due to the
+   removal problem. A page that does not have a matching title is not an
+   influence, because modifying the page to change its title directly
+   changes what the pagespec matches.
  
  * The pagespec "backlink(index)" has an influence list
    that contains index (because a change to index changes the backlinks).
+   Note that this is true even if the backlink currently fails.
  
  * The pagespec "link(done)" has an influence list that
    contains every page that it matches. A change to any matching page can
    remove a link and make it not match any more, and so the list is needed
    due to the removal problem.
  
+ >> Why doesn't this include every page?  If I change a page that doesn't have a link to
+ >> 'done' to include a link to 'done', then it will now match...  or is that considered a
+ >> 'direct match'? -- [[Will]]
+ >>> The regular dependency calculation code will check if every changed
+ >>> page matches every dependency. So it will notice the link was added.
+ >>> --[[Joey]] 
  #### Low-level Calculation
  
  One way to calculate a pagespec's influence would be to
@@@ -379,17 -475,89 +476,89 @@@ Given that, the `backlink` will always 
  onto the influence list. If we combine the influences from each
  successful match, we get the right result.
  
- > This is implemented, seems to work ok. --[[Joey]] 
+ > This is implemented, seems to work ok. --[[Joey]]
  
- #### High-level Calculation and Storage
+ > `or` short-circuits too, but the implementation correctly uses `|`,
+ > which I assume is what you meant. --[[smcv]]
  
- Calculating the full influence list for a pagespec requires trying to match
- it against every page in the wiki. 
+ >> Er, yeah. --[[Joey]] 
  
- I'd like to avoid doing such expensive matching redundantly. So add a
- `pagespec_match_all`, which returns a list of all pages in the whole
- wiki that match the pagespec, and also adds the pagespec as a dependency,
- and while it's at it, calculates and stores the influence list.
+ ----
+ What about: "!link(done)"
+ Specifically, I want to make sure it works now that I've changed
+ `match_link` to only return a page as an influence if it *does*
+ link to done.
+ So, when matching against page P, that does not link to done,
+ there are no influences, and the pagespec matches. If P is later
+ changed to add a link to done, then the dependency resolver will directly
+ notice that.
+ When matching against page P, that does link to done, P
+ is an influence, and the pagespec does not match. If P is later changed
+ to not link to done, the influence will do its job.
+ Looks good!
+ ----
+ Here is a case where this approach has some false positives.
+ "bugs/* and link(patch)"
+ This finds as influences all pages that link to patch, even
+ if they are not under bugs/, and so can never match.
+ To fix this, the influence calculation would need to consider boolean
+ operators. Currently, this turns into roughly:
+ `FailReason() & SuccessReason(patch)`
+ Let's say that the glob instead returns a HardFailReason, which when
+ ANDed with another object, drops their influences. (But when ORed, combines
+ them.) Fixes the above, but does it always work?
+ "(bugs/* or link(patch)) and backlink(index)" =>
+ `( HardFailReason() | SuccessReason(page) ) & SuccessReason(index)`` =>
+ `SuccessReason(page & SuccessReason(index)` =>
+ SuccessReason(page, index) => right
+ "(bugs/* and link(patch)) or backlink(index)" =>
+ `( HardFailReason() & SuccessReason(page) ) | SuccessReason(index)`` =>
+ `HardFailReason() | SuccessReason(index)` =>
+ `SuccessReason(index)` => right
+ "!bugs/* and link(patch)" =>
+ `HardFailReason() | SuccessReason(bugs/foo)` =>  
+ `HardFailReason()` => right
+ #### High-level Calculation and Storage
+ Naively calculating the full influence list for a pagespec requires trying
+ to match it against every page in the wiki. I'd like to avoid doing such
+ expensive matching redundantly.
+ It may be possible, for some types of pagespecs, to just try matching a
+ single, arbitrary page against it, and know the full influence list has
+ been obtained. It seems to be that case that if a pagespec has any
+ influences, matching any page will return at least one. So if none are
+ returned, we can skip trying other pages.
+ If the influence list does not include the page that was tried, we know
+ that the pagespec does not things like `link()` and `title()`, that are
+ influenced by the page's own content. So it *might* be safe to not try
+ matching any more pages in this case too. I think it would work for all
+ current pagespec terms. There might be a hypothetical term where this
+ optimisation doesn't work. We could add a special case to ensure it can
+ work: If a term declares it is unfluenced by "", then it means it is
+ always influenced by the matching page.
+ Anyway, this seems worth doing: Add a `pagespec_match_all`, which returns a
+ list of all pages in the whole wiki that match the pagespec, and also adds
+ the pagespec as a dependency, and while it's at it, calculates and stores
+ the influence list.
  
  It could have an optional sort parameter, and limit parameter, to control
  how many items to return and the sort order. So when inline wants to
@@@ -414,7 -582,7 +583,7 @@@ it's calculated more smartly, and is ad
  
  > I've implemented influence calculation in `add_depends`. As expected,
  > it means rather a lot more work, and makes some things much slower.
- > Optimisation via `pagespec_match_depends` next.. --[[Joey]] 
+ > Optimisations next.. --[[Joey]] 
  
  #### Influence types
  
@@@ -422,3 -590,10 +591,10 @@@ Note that influences can also have type
  For example, "backlink(foo)" has an influence of foo, of type links.
  "created_before(foo)" also is influenced by foo, but it's a presence
  type. Etc.
+ > This is an interesting concept that I hadn't considered.  It might
+ > allow significant computational savings, but I suspect will be tricky
+ > to implement. -- [[Will]]
+ >> It was actually really easy to implement it, assuming I picked the right
+ >> dependency types of course. --[[Joey]]