]> sipb.mit.edu Git - ikiwiki.git/commitdiff
Merge branch 'master' into dependency-types
authorJoey Hess <joey@gnu.kitenet.net>
Sun, 11 Oct 2009 23:56:50 +0000 (19:56 -0400)
committerJoey Hess <joey@gnu.kitenet.net>
Sun, 11 Oct 2009 23:56:50 +0000 (19:56 -0400)
43 files changed:
IkiWiki.pm
IkiWiki/Plugin/brokenlinks.pm
IkiWiki/Plugin/calendar.pm
IkiWiki/Plugin/conditional.pm
IkiWiki/Plugin/edittemplate.pm
IkiWiki/Plugin/img.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/Inline_doesn__39__t_wikilink_to_pages.mdwn
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/ikiwiki/pagespec/sorting.mdwn [new file with mode: 0644]
doc/plugins.mdwn
doc/plugins/contrib.mdwn
doc/plugins/orphans.mdwn
doc/plugins/sidebar.mdwn
doc/plugins/write.mdwn
doc/todo/dependency_types.mdwn
doc/translation.mdwn
ikiwiki-transition
t/add_depends.t [new file with mode: 0755]
t/pagespec_match.t
t/pagespec_match_list.t [new file with mode: 0755]
t/pagespec_match_result.t [new file with mode: 0755]
t/yesno.t
underlays/basewiki/ikiwiki/pagespec/sorting.mdwn [new file with mode: 0644]

index d667e7e10fe43c9162b9dd54a0105c41c80c3afe..cd93fe969de8a38b4356ad041ba7c1cf6de3b801 100644 (file)
@@ -17,17 +17,23 @@ use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase
            %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
+                 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");
@@ -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};
@@ -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}) {
@@ -1752,23 +1768,50 @@ 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);
+       # Add explicit dependencies for influences.
+       my $sub=pagespec_translate($pagespec);
+       return if $@;
+       foreach my $p (keys %pagesources) {
+               my $r=$sub->($p, location => $page);
+               my $i=$r->influences;
+               foreach my $k (keys %$i) {
+                       $depends_simple{$page}{lc $k} |= $i->{$k};
+               }
+               last if $r->influences_static;
+       }
 
-       $depends{$page}{$pagespec} = 1;
+       $depends{$page}{$pagespec} |= $deptype;
        return 1;
 }
 
+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 (@_) {
@@ -1876,10 +1919,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;
@@ -1925,27 +1968,87 @@ sub pagespec_match ($$;@) {
 }
 
 sub pagespec_match_list ($$;@) {
-       my $pages=shift;
-       my $spec=shift;
-       my @params=@_;
+       my $page=shift;
+       my $pagespec=shift;
+       my %params=@_;
 
-       my $sub=pagespec_translate($spec);
-       error "syntax error in pagespec \"$spec\""
-               if $@ || ! defined $sub;
-       
-       my @ret;
-       my $r;
-       foreach my $page (@$pages) {
-               $r=$sub->($page, @params);
-               push @ret, $page if $r;
+       # Backwards compatability with old calling convention.
+       if (ref $page) {
+               print STDERR "warning: a plugin (".caller().") is using pagespec_match_list in an obsolete way, and needs to be updated\n";
+               $params{list}=$page;
+               $page=$params{location}; # ugh!
        }
 
-       if (! @ret && defined $r && $r->isa("IkiWiki::ErrorReason")) {
-               error(sprintf(gettext("cannot match pages: %s"), $r));
+       my $sub=pagespec_translate($pagespec);
+       error "syntax error in pagespec \"$pagespec\""
+               if $@ || ! defined $sub;
+
+       my @candidates;
+       if (exists $params{list}) {
+               @candidates=exists $params{filter}
+                       ? grep { ! $params{filter}->($_) } @{$params{list}}
+                       : @{$params{list}};
        }
        else {
-               return @ret;
+               @candidates=exists $params{filter}
+                       ? grep { ! $params{filter}->($_) } keys %pagesources
+                       : 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};
+       
+       $depends{$page}{$pagespec} |= ($params{deptype} || $DEPEND_CONTENT);
+       
+       # clear params, remainder is passed to pagespec
+       my $num=$params{num};
+       delete @params{qw{num deptype reverse sort filter list}};
+       
+       my @matches;
+       my $firstfail;
+       my $count=0;
+       my $accum=IkiWiki::SuccessReason->new();
+       foreach my $p (@candidates) {
+               my $r=$sub->($p, %params, location => $page);
+               error(sprintf(gettext("cannot match pages: %s"), $r))
+                       if $r->isa("IkiWiki::ErrorReason");
+               $accum |= $r;
+               if ($r) {
+                       push @matches, $p;
+                       last if defined $num && ++$count == $num;
+               }
+       }
+
+       # Add simple dependencies for accumulated influences.
+       my $i=$accum->influences;
+       foreach my $k (keys %$i) {
+               $depends_simple{$page}{lc $k} |= $i->{$k};
        }
+
+       return @matches;
 }
 
 sub pagespec_valid ($) {
@@ -1965,36 +2068,56 @@ 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;
+       $this->[1]={@_} if @_;
+       my %i=%{$this->[1]};
+       delete $i{""};
+       return \%i;
+}
+
+sub influences_static {
+       return ! $_[0][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;
 
@@ -2045,27 +2168,30 @@ 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, "" => 1)
                                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, "" => 1)
                                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, "" => 1)
                                if match_glob($p_rel, $link, %params);
                }
        }
-       return IkiWiki::FailReason->new("$page does not link to $link");
+       return IkiWiki::FailReason->new("$page does not link to $link", "" => 1);
 }
 
 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 ($$;@) {
@@ -2077,14 +2203,14 @@ 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);
        }
 }
 
@@ -2097,14 +2223,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);
        }
 }
 
index eb698b0bef9f7b2c9df90b342dcfac56c79a1795..8ee734bf947b6137a2925bac3eeb69ebc965a9bd 100644 (file)
@@ -23,19 +23,15 @@ 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});
-       
        my @broken;
        foreach my $link (keys %IkiWiki::brokenlinks) {
                next if $link =~ /.*\/\Q$config{discussionpage}\E/i && $config{discussion};
 
-               my @pages;
-               foreach my $page (@{$IkiWiki::brokenlinks{$link}}) {
-                       push @pages, $page
-                               if pagespec_match($page, $params{pages}, location => $params{page});
-               }
+               my @pages=pagespec_match_list($params{page}, $params{pages},
+                       list => $IkiWiki::brokenlinks{$link},
+                       # needs to update when links on a page change
+                       deptype => deptype("links")
+               );
                next unless @pages;
 
                my $page=$IkiWiki::brokenlinks{$link}->[0];
index 5d16dff75ba7a98a68c3fc8c94d9d9378cb11732..c50d038df4f80314bf4a3f3070aa65fa474e264d 100644 (file)
@@ -24,8 +24,6 @@ use IkiWiki 3.00;
 use Time::Local;
 use POSIX;
 
-my %cache;
-my %linkcache;
 my $time=time;
 my @now=localtime($time);
 
@@ -75,6 +73,23 @@ sub format_month (@) {
        my $pyear    = $params{pyear};
        my $nyear    = $params{nyear};
 
+       my %linkcache;
+       foreach my $p (pagespec_match_list($params{page}, $params{pagespec},
+                               # add presence dependencies to update
+                               # month calendar when pages are added/removed
+                               deptype => deptype("presence"))) {
+               my $mtime = $IkiWiki::pagectime{$p};
+               my $src   = $pagesources{$p};
+               my @date  = localtime($mtime);
+               my $mday  = $date[3];
+               my $month = $date[4] + 1;
+               my $year  = $date[5] + 1900;
+               my $mtag  = sprintf("%02d", $month);
+
+               # Only one posting per day is being linked to.
+               $linkcache{"$year/$mtag/$mday"} = "$src";
+       }
+
        my @list;
        my $calendar="\n";
 
@@ -99,24 +114,27 @@ sub format_month (@) {
   
        # Calculate URL's for monthly archives.
        my ($url, $purl, $nurl)=("$monthname",'','');
-       if (exists $cache{$pagespec}{"$year/$month"}) {
+       if (exists $pagesources{"$archivebase/$year/$month"}) {
                $url = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$year/".sprintf("%02d", $month),
                        linktext => " $monthname ");
        }
-       add_depends($params{page}, "$archivebase/$year/".sprintf("%02d", $month));
-       if (exists $cache{$pagespec}{"$pyear/$pmonth"}) {
+       add_depends($params{page}, "$archivebase/$year/".sprintf("%02d", $month),
+               deptype("presence"));
+       if (exists $pagesources{"$archivebase/$pyear/$pmonth"}) {
                $purl = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$pyear/" . sprintf("%02d", $pmonth),
                        linktext => " $pmonthname ");
        }
-       add_depends($params{page}, "$archivebase/$pyear/".sprintf("%02d", $pmonth));
-       if (exists $cache{$pagespec}{"$nyear/$nmonth"}) {
+       add_depends($params{page}, "$archivebase/$pyear/".sprintf("%02d", $pmonth),
+               deptype("presence"));
+       if (exists $pagesources{"$archivebase/$nyear/$nmonth"}) {
                $nurl = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$nyear/" . sprintf("%02d", $nmonth),
                        linktext => " $nmonthname ");
        }
-       add_depends($params{page}, "$archivebase/$nyear/".sprintf("%02d", $nmonth));
+       add_depends($params{page}, "$archivebase/$nyear/".sprintf("%02d", $nmonth),
+               deptype("presence"));
 
        # Start producing the month calendar
        $calendar=<<EOF;
@@ -170,7 +188,7 @@ EOF
                
                my $tag;
                my $mtag = sprintf("%02d", $month);
-               if (defined $cache{$pagespec}{"$year/$mtag/$day"}) {
+               if (defined $pagesources{"$archivebase/$year/$mtag/$day"}) {
                        if ($day == $today) {
                                $tag='month-calendar-day-this-day';
                        }
@@ -207,15 +225,6 @@ EOF
 </table>
 EOF
 
-       # Add dependencies to update the calendar whenever pages
-       # matching the pagespec are added or removed.
-       add_depends($params{page}, $params{pages});
-       # Explicitly add all currently linked pages as dependencies, so
-       # that if they are removed, the calendar will be sure to be updated.
-       foreach my $p (@list) {
-               add_depends($params{page}, $p);
-       }
-
        return $calendar;
 }
 
@@ -241,24 +250,24 @@ sub format_year (@) {
 
        # calculate URL's for previous and next years
        my ($url, $purl, $nurl)=("$year",'','');
-       if (exists $cache{$pagespec}{"$year"}) {
+       if (exists $pagesources{"$archivebase/$year"}) {
                $url = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$year",
                        linktext => "$year");
        }
-       add_depends($params{page}, "$archivebase/$year");
-       if (exists $cache{$pagespec}{"$pyear"}) {
+       add_depends($params{page}, "$archivebase/$year", deptype("presence"));
+       if (exists $pagesources{"$archivebase/$pyear"}) {
                $purl = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$pyear",
                        linktext => "\&larr;");
        }
-       add_depends($params{page}, "$archivebase/$pyear");
-       if (exists $cache{$pagespec}{"$nyear"}) {
+       add_depends($params{page}, "$archivebase/$pyear", deptype("presence"));
+       if (exists $pagesources{"$archivebase/$nyear"}) {
                $nurl = htmllink($params{page}, $params{destpage}, 
                        "$archivebase/$nyear",
                        linktext => "\&rarr;");
        }
-       add_depends($params{page}, "$archivebase/$nyear");
+       add_depends($params{page}, "$archivebase/$nyear", deptype("presence"));
 
        # Start producing the year calendar
        $calendar=<<EOF;
@@ -282,14 +291,14 @@ EOF
                my $tag;
                my $mtag=sprintf("%02d", $month);
                if ($month == $params{month}) {
-                       if ($cache{$pagespec}{"$year/$mtag"}) {
+                       if ($pagesources{"$archivebase/$year/$mtag"}) {
                                $tag = 'this_month_link';
                        }
                        else {
                                $tag = 'this_month_nolink';
                        }
                }
-               elsif ($cache{$pagespec}{"$year/$mtag"}) {
+               elsif ($pagesources{"$archivebase/$year/$mtag"}) {
                        $tag = 'month_link';
                } 
                elsif ($future_month && $month >= $future_month) {
@@ -299,7 +308,7 @@ EOF
                        $tag = 'month_nolink';
                }
 
-               if ($cache{$pagespec}{"$year/$mtag"}) {
+               if ($pagesources{"$archivebase/$year/$mtag"}) {
                        $murl = htmllink($params{page}, $params{destpage}, 
                                "$archivebase/$year/$mtag",
                                linktext => "$monthabbr");
@@ -310,7 +319,8 @@ EOF
                else {
                        $calendar.=qq{\t<td class="$tag">$monthabbr</td>\n};
                }
-               add_depends($params{page}, "$archivebase/$year/$mtag");
+               add_depends($params{page}, "$archivebase/$year/$mtag",
+                       deptype("presence"));
 
                $calendar.=qq{\t</tr>\n} if ($month % $params{months_per_row} == 0);
        }
@@ -367,26 +377,6 @@ sub preprocess (@) {
        $params{nyear} =$nyear;
 
        my $calendar="\n";
-       my $pagespec=$params{pages};
-       my $page =$params{page};
-
-       if (! defined $cache{$pagespec}) {
-               foreach my $p (pagespec_match_list([keys %pagesources], $pagespec)) {
-                       my $mtime = $IkiWiki::pagectime{$p};
-                       my $src   = $pagesources{$p};
-                       my @date  = localtime($mtime);
-                       my $mday  = $date[3];
-                       my $month = $date[4] + 1;
-                       my $year  = $date[5] + 1900;
-                       my $mtag  = sprintf("%02d", $month);
-
-                       # Only one posting per day is being linked to.
-                       $linkcache{"$year/$mtag/$mday"} = "$src";
-                       $cache{$pagespec}{"$year"}++;
-                       $cache{$pagespec}{"$year/$mtag"}++;
-                       $cache{$pagespec}{"$year/$mtag/$mday"}++;
-               }
-       }
 
        if ($params{type} =~ /month/i) {
                $calendar=format_month(%params);
index 7445dbdad71420bb4228f573543ac673732b197c..aad617812fc69229aa9ffa6979570d078f090023 100644 (file)
@@ -29,11 +29,10 @@ sub preprocess_if (@) {
        }
 
        my $result=0;
-       if ((exists $params{all} && lc $params{all} eq "no") ||
-               # An optimisation to avoid needless looping over every page
-               # and adding of dependencies for simple uses of some of the
-               # tests.
-               $params{test} =~ /^([\s\!()]*((enabled|sourcepage|destpage|included)\([^)]*\)|(and|or))[\s\!()]*)+$/) {
+       if (! IkiWiki::yesno($params{all}) ||
+           # An optimisation to avoid needless looping over every page
+           # for simple uses of some of the tests.
+           $params{test} =~ /^([\s\!()]*((enabled|sourcepage|destpage|included)\([^)]*\)|(and|or))[\s\!()]*)+$/) {
                add_depends($params{page}, "($params{test}) and $params{page}");
                $result=pagespec_match($params{page}, $params{test},
                                location => $params{page},
@@ -41,17 +40,12 @@ sub preprocess_if (@) {
                                destpage => $params{destpage});
        }
        else {
-               add_depends($params{page}, $params{test});
-
-               foreach my $page (keys %pagesources) {
-                       if (pagespec_match($page, $params{test}, 
-                                       location => $params{page},
-                                       sourcepage => $params{page},
-                                       destpage => $params{destpage})) {
-                               $result=1;
-                               last;
-                       }
-               }
+               $result=pagespec_match_list($params{page}, $params{test},
+                       # stop after first match
+                       num => 1,
+                       sourcepage => $params{page},
+                       destpage => $params{destpage},
+               );
        }
 
        my $ret;
index 0bafc95d06d854b860e566a8c14134119d51db17..7d2eba194537857cb1088a90e2a4afa82009b507 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, deptype("presence"));
        return sprintf(gettext("edittemplate %s registered for %s"),
                htmllink($params{page}, $params{destpage}, $link),
                $params{match});
index e2f541506c309cd4dcef8ad0290c6ffdd03703d1..32023fa97af8ba8e63192cacaff10a4677d20654 100644 (file)
@@ -170,7 +170,7 @@ sub preprocess (@) {
                my $b = bestlink($params{page}, $params{link});
        
                if (length $b) {
-                       add_depends($params{page}, $b);
+                       add_depends($params{page}, $b, deptype("presence"));
                        $imgtag=htmllink($params{page}, $params{destpage},
                                $params{link}, linktext => $imgtag,
                                noimageinline => 1);
index ccfadfd699929a8be1764546118d89ee9f958f56..0fe0bd2e108a444b6b85c2ded93840f10e4fd2fc 100644 (file)
@@ -195,41 +195,38 @@ sub preprocess_inline (@) {
 
                @list = map { bestlink($params{page}, $_) }
                        split ' ', $params{pagenames};
-       }
-       else {
-               add_depends($params{page}, $params{pages});
 
-               @list = pagespec_match_list(
-                       [ grep { $_ ne $params{page} } keys %pagesources ],
-                       $params{pages}, location => $params{page});
-
-               if (exists $params{sort} && $params{sort} eq 'title') {
-                       @list=sort { pagetitle(basename($a)) cmp pagetitle(basename($b)) } @list;
+               if (yesno($params{reverse})) {
+                       @list=reverse(@list);
                }
-               elsif (exists $params{sort} && $params{sort} eq 'title_natural') {
-                       eval q{use Sort::Naturally};
-                       if ($@) {
-                               error(gettext("Sort::Naturally needed for title_natural sort"));
-                       }
-                       @list=sort { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) } @list;
+
+               foreach my $p (@list) {
+                       add_depends($params{page}, $p, deptype($quick ? "presence" : "content"));
                }
-               elsif (exists $params{sort} && $params{sort} eq 'mtime') {
-                       @list=sort { $pagemtime{$b} <=> $pagemtime{$a} } @list;
+       }
+       else {
+               my $num=0;
+               if ($params{show}) {
+                       $num=$params{show};
                }
-               elsif (! exists $params{sort} || $params{sort} eq 'age') {
-                       @list=sort { $pagectime{$b} <=> $pagectime{$a} } @list;
+               if ($params{feedshow} && $num < $params{feedshow}) {
+                       $num=$params{feedshow};
                }
-               else {
-                       error sprintf(gettext("unknown sort type %s"), $params{sort});
+               if ($params{skip}) {
+                       $num+=$params{skip};
                }
-       }
 
-       if (yesno($params{reverse})) {
-               @list=reverse(@list);
+               @list = pagespec_match_list($params{page}, $params{pages},
+                       deptype => deptype($quick ? "presence" : "content"),
+                       filter => sub { $_[0] eq $params{page} },
+                       sort => exists $params{sort} ? $params{sort} : "age",
+                       reverse => yesno($params{reverse}),
+                       num => $num,
+               );
        }
 
        if (exists $params{skip}) {
-               @list=@list[$params{skip} .. scalar @list - 1];
+               @list=@list[$params{skip} .. $#list];
        }
        
        my @feedlist;
@@ -247,15 +244,12 @@ sub preprocess_inline (@) {
                @list=@list[0..$params{show} - 1];
        }
 
-       # Explicitly add all currently displayed pages as dependencies, so
-       # that if they are removed or otherwise changed, the inline will be
-       # sure to be updated.
-       foreach my $p ($#list >= $#feedlist ? @list : @feedlist) {
-               add_depends($params{page}, $p);
-       }
-       
        if ($feeds && exists $params{feedpages}) {
-               @feedlist=pagespec_match_list(\@feedlist, $params{feedpages}, location => $params{page});
+               @feedlist = pagespec_match_list(
+                       $params{page}, "($params{pages}) and ($params{feedpages})",
+                       deptype => deptype($quick ? "presence" : "content"),
+                       list => \@feedlist,
+               );
        }
 
        my ($feedbase, $feednum);
index 941ed5f3672145bb6111c5a6650fb14431641297..9540bd112f397cd5cd98e89eb54572737aefa839 100644 (file)
@@ -28,10 +28,6 @@ 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});
-       
        # Can't just return the linkmap here, since the htmlscrubber
        # scrubs out all <object> tags (with good reason!)
        # Instead, insert a placeholder tag, which will be expanded during
@@ -55,12 +51,11 @@ sub genmap ($) {
        my %params=%{$maps{$mapnum}};
 
        # Get all the items to map.
-       my %mapitems = ();
-       foreach my $item (keys %links) {
-               if (pagespec_match($item, $params{pages}, location => $params{page})) {
-                       $mapitems{$item}=urlto($item, $params{destpage});
-               }
-       }
+       my %mapitems = map { $_ => urlto($_, $params{destpage}) }
+               pagespec_match_list($params{page}, $params{pages},
+                       # update when a page is added or removed, or its
+                       # links change
+                       deptype => deptype("presence", "links"));
 
        my $dest=$params{page}."/linkmap.png";
 
index bd73f1a04e728638c19994c0d64a4f288a120150..09f08c5671ad82a7562a558342723669e5614641 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, deptype("presence"));
                $result .= htmllink($params{page}, $params{destpage}, $link);
                $result .= '</li>';
        }
index 54146dc467bdcb513e426257a6fd0879005cd35a..788b9682706c9e3a303b0920267e8ea20061305e 100644 (file)
@@ -28,12 +28,16 @@ sub preprocess (@) {
        my %params=@_;
        $params{pages}="*" unless defined $params{pages};
        
+       # Needs to update whenever a page is added or removed (or in some
+       # cases, when its content changes, if show= is specified).
+       my $deptype=deptype(exists $params{show} ? "content" : "presence");
+       
        my $common_prefix;
 
        # Get all the items to map.
        my %mapitems;
-       foreach my $page (pagespec_match_list([keys %pagesources],
-                               $params{pages}, location => $params{page})) {
+       foreach my $page (pagespec_match_list($params{page}, $params{pages},
+                                       deptype => $deptype)) {
                if (exists $params{show} && 
                    exists $pagestate{$page} &&
                    exists $pagestate{$page}{meta}{$params{show}}) {
@@ -67,16 +71,6 @@ sub preprocess (@) {
                $common_prefix=IkiWiki::dirname($common_prefix);
        }
 
-       # Needs to update whenever a page is added or removed (or in some
-       # cases, when its content changes, if show=title), so register a
-       # dependency.
-       add_depends($params{page}, $params{pages});
-       # Explicitly add all currently shown pages, to detect when pages
-       # are removed.
-       foreach my $item (keys %mapitems) {
-               add_depends($params{page}, $item);
-       }
-
        # Create the map.
        my $parent="";
        my $indent=0;
@@ -84,12 +78,12 @@ sub preprocess (@) {
        my $addparent="";
        my $map = "<div class='map'>\n";
 
-       # Return empty div if %mapitems is empty
-       if (!scalar(keys %mapitems)) {
+       if (! keys %mapitems) {
+               # return empty div for empty map
                $map .= "</div>\n";
                return $map; 
        } 
-       else { # continue populating $map
+       else {
                $map .= "<ul>\n";
        }
 
index 514b0936907ce58edabcbafcb44d5559a154eec9..8dcd73a1a847683747c247068259255b6d905078 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, deptype("presence"));
 
                        $value=urlto($link, $page);
                        $value.='#'.$redir_anchor if defined $redir_anchor;
@@ -291,21 +291,21 @@ sub match {
 
        if (defined $val) {
                if ($val=~/^$re$/i) {
-                       return IkiWiki::SuccessReason->new("$re matches $field of $page");
+                       return IkiWiki::SuccessReason->new("$re matches $field of $page", $page => $IkiWiki::DEPEND_CONTENT, "" => 1);
                }
                else {
-                       return IkiWiki::FailReason->new("$re does not match $field of $page");
+                       return IkiWiki::FailReason->new("$re does not match $field of $page", "" => 1);
                }
        }
        else {
-               return IkiWiki::FailReason->new("$page does not have a $field");
+               return IkiWiki::FailReason->new("$page does not have a $field", "" => 1);
        }
 }
 
 package IkiWiki::PageSpec;
 
 sub match_title ($$;@) {
-       IkiWiki::Plugin::meta::match("title", @_);      
+       IkiWiki::Plugin::meta::match("title", @_);
 }
 
 sub match_author ($$;@) {
index 71122677273275d3e2f160bde3c8b44a8f333e49..702943f87d1cb96927270a2b609de192a7488aa7 100644 (file)
@@ -23,24 +23,34 @@ 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}, "*", deptype("links"));
        
-       my @orphans;
-       foreach my $page (pagespec_match_list(
-                       [ grep { ! IkiWiki::backlink_pages($_) && $_ ne 'index' }
-                               keys %pagesources ],
-                       $params{pages}, location => $params{page})) {
-               # If the page has a link to some other page, it's
-               # indirectly linked to a page via that page's backlinks.
-               next if grep { 
-                       length $_ &&
-                       ($_ !~ /\/\Q$config{discussionpage}\E$/i || ! $config{discussion}) &&
-                       bestlink($page, $_) !~ /^(\Q$page\E|)$/ 
-               } @{$links{$page}};
-               push @orphans, $page;
-       }
+       my @orphans=pagespec_match_list($params{page}, $params{pages},
+               # update when orphans are added/removed
+               deptype => deptype("presence"),
+               filter => sub {
+                       my $page=shift;
+
+                       # Filter out pages that other pages link to.
+                       return 1 if IkiWiki::backlink_pages($page);
+
+                       # Toplevel index is assumed to never be orphaned.
+                       return 1 if $page eq 'index';
+
+                       # If the page has a link to some other page, it's
+                       # indirectly linked via that page's backlinks.
+                       return 1 if grep {
+                               length $_ &&
+                               ($_ !~ /\/\Q$config{discussionpage}\E$/i || ! $config{discussion}) &&
+                               bestlink($page, $_) !~ /^(\Q$page\E|)$/ 
+                       } @{$links{$page}};
+                       
+                       return 0;
+               },
+       );
        
        return gettext("All pages have other pages linking to them.") unless @orphans;
        return "<ul>\n".
index 5a2301af49232943d4f0889c0f3b9ba28f05396f..8d36f057eb0820a62c9704988d66fde75ccf6c4b 100644 (file)
@@ -20,20 +20,20 @@ sub getsetup () {
 
 sub preprocess (@) {
        my %params=@_;
-       $params{pages}="*" unless defined $params{pages};
+       my $pages=defined $params{pages} ? $params{pages} : "*";
        
-       # Needs to update count whenever a page is added or removed, so
-       # register a dependency.
-       add_depends($params{page}, $params{pages});
-       
-       my @pages;
-       if ($params{pages} eq "*") {
-               @pages=keys %pagesources;
-       }
-       else {
-               @pages=pagespec_match_list([keys %pagesources], $params{pages}, location => $params{page});
+       # Just get a list of all the pages, and count the items in it.
+       # Use a presence dependency to only update when pages are added
+       # or removed.
+
+       if ($pages eq '*') {
+               # optimisation to avoid needing to try matching every page
+               add_depends($params{page}, $pages, deptype("presence"));
+               return scalar keys %pagesources;
        }
-       return $#pages+1;
+
+       return scalar pagespec_match_list($params{page}, $pages,
+               deptype => deptype("presence"));
 }
 
 1
index 874ead7e6de669c1d33c66b74984ef90504f9b8e..47638210aeddeff49866ac1e5a6459828556ddd2 100644 (file)
@@ -35,22 +35,28 @@ 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};
-       
        my %counts;
        my $max = 0;
-       foreach my $page (pagespec_match_list([keys %links],
-                       $params{pages}, location => $params{page})) {
+       foreach my $page (pagespec_match_list($params{page}, $params{pages},
+                                 # update when a displayed page is added/removed
+                                 deptype => deptype("presence"))) {
                use IkiWiki::Render;
 
                my @backlinks = IkiWiki::backlink_pages($page);
 
                if (exists $params{among}) {
-                       @backlinks = pagespec_match_list(\@backlinks,
-                               $params{among}, location => $params{page});
+                       # only consider backlinks from the amoung pages
+                       @backlinks = pagespec_match_list(
+                               $params{page}, $params{among},
+                               # update whenever links on those pages change
+                               deptype => deptype("links"),
+                               list => \@backlinks
+                       );
+               }
+               else {
+                       # update when any page with links changes,
+                       # in case the links point to our displayed pages
+                       add_depends($params{page}, "*", deptype("links"));
                }
 
                $counts{$page} = scalar(@backlinks);
index d2e5c23788678efb7819e10c59964bd852663e06..0d5a12e33c5c2f918181784b2dd8784f54353ff7 100644 (file)
@@ -30,11 +30,16 @@ sub preprocess (@) {
                return "";
        }
 
+       my $deptype;
        if (! exists $params{time} || $params{time} ne 'mtime') {
                $params{timehash} = \%IkiWiki::pagectime;
+               # need to update when pages are added or removed
+               $deptype = deptype("presence");
        }
        else {
                $params{timehash} = \%IkiWiki::pagemtime;
+               # need to update when pages are changed
+               $deptype = deptype("content");
        }
 
        if (! exists $params{formula}) {
@@ -48,12 +53,11 @@ sub preprocess (@) {
                error gettext("unknown formula");
        }
 
-       add_depends($params{page}, $params{pages});
-
        my @list=sort { $params{timehash}->{$b} <=> $params{timehash}->{$a} } 
-               pagespec_match_list(
-                       [ grep { $_ ne $params{page} } keys %pagesources],
-                       $params{pages}, location => $params{page});
+               pagespec_match_list($params{page}, $params{pages},
+                       deptype => $deptype,
+                       filter => sub { $_[0] eq $params{page} },
+               );
 
        my @data=eval qq{IkiWiki::Plugin::postsparkline::formula::$formula(\\\%params, \@list)};
        if ($@) {
index 76d994acc7ad0f9d49c63c32d3d5d8dd54db5554..fe64b40b1da13fe9b21936e2f5098c7bcc225c79 100644 (file)
@@ -36,16 +36,12 @@ sub preprocess (@) {
                $fill.="%";
        }
        elsif (defined $params{totalpages} and defined $params{donepages}) {
-               add_depends($params{page}, $params{totalpages});
-               add_depends($params{page}, $params{donepages});
-
-               my @pages=keys %pagesources;
-               my $totalcount=0;
-               my $donecount=0;
-               foreach my $page (@pages) {
-                       $totalcount++ if pagespec_match($page, $params{totalpages}, location => $params{page});
-                       $donecount++ if pagespec_match($page, $params{donepages}, location => $params{page});
-               }
+               my $totalcount=pagespec_match_list(
+                       $params{page}, $params{totalpages},
+                       deptype => deptype("presence"));
+               my $donecount=pagespec_match_list(
+                       $params{page}, $params{donepages},
+                       deptype => deptype("presence"));
                
                if ($totalcount == 0) {
                        $fill = "100%";
index a8236b954fbbc334696542e1e61f17ff6f617bc6..0fe20c64f3a0939d7dde93107e7b0285e883e2a2 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({
@@ -334,19 +340,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) {
@@ -356,10 +357,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");
@@ -376,10 +377,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));
@@ -400,8 +410,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);
@@ -409,150 +424,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{$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) });
index 808105fd5b1e7cbc49306bd81bb5b15be2d79490..7c88ca7335d1a30b82abc4d3599b29cb4ccd3ba7 100644 (file)
@@ -1,3 +1,13 @@
+ikiwiki (3.2009XXXX) 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 22935955aaf7fe81049e73eb07b7115d6c0d1248..0e288dd08a2aaba2b7846dff4e430aa7c9e34abe 100644 (file)
@@ -1,9 +1,35 @@
-ikiwiki (3.20091010) UNRELEASED; urgency=low
-
+ikiwiki (3.2009XXXX) UNRELEASED; urgency=low
+
+  * Added support framework for multiple types of dependencies, including
+    dependncies that are only affected by page precence or link changes.
+  * Rebuild wikis on upgrade to this version to get improved dependency
+    info.
+  * 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.
+  * Plugins providing PageSpec `match_*` functions should pass additional
+    influence information when creating result objects. This allows correctly
+    handling many more complicated dependencies.
+  * API change: `pagespec_match_list` has completly changed its interface.
+    The old interface will be removed soon, and a warning will be printed
+    if any plugins try to use it.
+  * Transitive dependencies are now correctly supported.
   * ikiwiki-calendar: New command automates creation of archive pages
     using the calendar plugin.
 
- -- Joey Hess <joeyh@debian.org>  Sun, 11 Oct 2009 15:54:45 -0400
+ -- Joey Hess <joeyh@debian.org>  Fri, 09 Oct 2009 19:53:50 -0400
 
 ikiwiki (3.20091009) unstable; urgency=low
 
@@ -17,7 +43,7 @@ ikiwiki (3.20091009) unstable; urgency=low
   * mirrorlist: Display nothing if list is empty.
   * Fix a bug that could lead to duplicate links being recorded
     for tags.
-  * Optimize away most expensive file prune calls, when refreshing,
+  * Optimize away most expensive file prune checks, when refreshing,
     by only checking new files.
 
  -- Joey Hess <joeyh@debian.org>  Fri, 09 Oct 2009 19:53:50 -0400
index 2ba26e5b65010cee1649390240228a3d939b7afb..bf1825ab7b1ca5239fe1e831d44e2dd08dcafbf5 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.20091010
 
 if [ "$1" = configure ] && \
    dpkg --compare-versions "$2" lt "$firstcompat"; then
index 32f9f12454a70a72cc9cdb847da8d8922f3b69e3..13b80b436503480b8795609cc2a95339f67c52b4 100644 (file)
@@ -2,10 +2,6 @@ It seems that the [[ikiwiki/directive/inline]] directive doesn't generate wikili
 
 \[[!inline pages="bugs/* and !*/discussion and backlink(bugs)" feeds=no postform=no archive=yes show="10"]]
 
-But here it is:
-
-[[!inline pages="bugs/* and !*/discussion and backlink(bugs)" feeds=no postform=no archive=yes show="10"]]
-
 and note that it only included the 'normal' wikilinks (and also note that this page is not marked done even though the done page is inlined).
 One might also wonder if inline would make this page link to any internal links on those inlined pages too, but I think
 that would be overkill.
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 70b5fb4d4426edf12443b5ccfcd112a42a8671a6..c44fe7962ba4e90e864d955f7d378c61c56cadb9 100644 (file)
@@ -65,7 +65,7 @@ Downsides here:
   modification to plugins/brokenlinks causes an unnecessary update of
   plugins, and could be solved by adding more dependency types.)
 
---[[Joey]] 
+[[done]] --[[Joey]] 
 
 > Some questions/comments...  I've thought about this a lot for [[todo/tracking_bugs_with_dependencies]].
 > 
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..c6a23ce3c6e7c5c5f263b623bf8c440103ecf3ab 100644 (file)
@@ -86,19 +86,15 @@ Here are some less often needed parameters:
   if raw is set to "yes", the page will be included raw, without additional
   markup around it, as if it were a literal part of the source of the 
   inlining page.
-* `sort` - Controls how inlined pages are sorted. The default, "age" is to
-  sort newest created pages first. Setting it to "title" will sort pages by
-  title, and "mtime" sorts most recently modified pages first. If
-  [[!cpan Sort::Naturally]] is installed, `sort` can be set to "title_natural"
-  to sort by title with numbers treated as such ("1 2 9 10 20" instead of
-  "1 10 2 20 9").
+* `sort` - Controls how inlined pages are [[sorted|pagespec/sorting]].
+  The default is to sort the newest created pages first.
 * `reverse` - If set to "yes", causes the sort order to be reversed.
 * `feedshow` - Specify the maximum number of matching pages to include in
   the rss/atom feeds. The default is the same as the `show` value above.
 * `feedonly` - Only generate the feed, do not display the pages inline on
   the page.
 * `quick` - Build archives in quick mode, without reading page contents for
-  metadata. By default, this also turns off generation of any feeds.
+  metadata. This also turns off generation of any feeds.
 * `timeformat` - Use this to specify how to display the time or date for pages
   in the blog. The format string is passed to the strftime(3) function.
 * `feedpages` - A [[PageSpec]] of inlined pages to include in the rss/atom
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)"]]
 
diff --git a/doc/ikiwiki/pagespec/sorting.mdwn b/doc/ikiwiki/pagespec/sorting.mdwn
new file mode 100644 (file)
index 0000000..41aa581
--- /dev/null
@@ -0,0 +1,11 @@
+Some [[directives|ikiwiki/directive]] that use
+[[PageSpecs|ikiwiki/pagespec]] allow
+specifying the order that matching pages are shown in. The following sort
+orders can be specified.
+
+* `age` - List pages from the most recently created to the oldest.
+* `mtime` - List pages with the most recently modified first.
+* `title` - Order by title.
+* `title_natural` - Only available if [[!cpan Sort::Naturally]] is
+  installed. Orders by title, but numbers in the title are treated
+  as such, ("1 2 9 10 20" instead of "1 10 2 20 9")
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 8e8c3311e4a1a23e8e9d1f36f5689e7adba55269..c72418c3c24eefe06662497a1197c33b5c0d4fab 100644 (file)
@@ -609,21 +609,60 @@ 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($$;@)`
+### `deptype(@)`
+
+Use this function to generate ikiwiki's internal representation of a
+dependency type from one or more of these keywords:
+
+* `content` is the default. Any change to the content
+  of a page triggers the dependency.
+* `presence` is only triggered by a change to the presence
+  of a page.
+* `links` is only triggered by a change to the links of a page.
+  This includes when a link is added, removed, or changes what
+  it points to due to other changes. It does not include the
+  addition or removal of a duplicate link.
+
+If multiple types are specified, they are combined.
+
+#### `pagespec_match_list($$;@)`
+
+Passed a page name, and [[ikiwiki/PageSpec]], returns a list of pages
+in the wiki that match the [[ikiwiki/PageSpec]]. 
+
+The page will automatically be made to depend on the specified
+[[ikiwiki/PageSpec]], so `add_depends` does not need to be called. This
+is often significantly more efficient than calling `add_depends` and
+`pagespec_match` in a loop. You should use this anytime a plugin
+needs to match a set of pages and do something based on that list.
+
+Unlike pagespec_match, this may throw an error if there is an error in
+the pagespec.
+
+Additional named parameters can be specified:
+
+* `deptype` optionally specifies the type of dependency to add. Use the
+  `deptype` function to generate a dependency type.
+* `filter` is a reference to a function, that is called and passed a page,
+  and returns true if the page should be filtered out of the list.
+* `sort` specifies a sort order for the list. See
+  [[ikiwiki/PageSpec/sorting]] for the avilable sort methods.
+* `reverse` if true, sorts in reverse.
+* `num` if nonzero, specifies the maximum number of matching pages that
+  will be returned.
+* `list` makes it only match amoung the specified list of pages.
+  Default is to match amoung all pages in the wiki.
+
+Any other named parameters are passed on to `pagespec_match`, to further
+limit the match.
+
+#### `add_depends($$;$)`
 
 Makes the specified page depend on the specified [[ikiwiki/PageSpec]].
 
 By default, dependencies are full content dependencies, meaning that the
 page will be updated whenever anything matching the PageSpec is modified.
-This default can be overridden by additional named parameters, which can be
-used to indicate weaker types of dependencies:
-
-* `presence` if set to true, only the presence of a matching page triggers
-  the dependency.
-* `links` if set to true, any change to links on a matching page
-  triggers the dependency. This includes when a link is added, removed,
-  or changes what it points to due to other changes. It does not include
-  the addition or removal of a duplicate link.
+This can be overridden by passing a `deptype` value as the third parameter.
 
 #### `pagespec_match($$;@)`
 
@@ -639,19 +678,6 @@ The most often used is "location", which specifies the location the
 PageSpec should match against. If not passed, relative PageSpecs will match
 relative to the top of the wiki.
 
-#### `pagespec_match_list($$;@)`
-
-Passed a reference to a list of page names, and [[ikiwiki/PageSpec]],
-returns the set of pages that match the [[ikiwiki/PageSpec]].
-
-Additional named parameters can be passed, to further limit the match.
-The most often used is "location", which specifies the location the
-PageSpec should match against. If not passed, relative PageSpecs will match
-relative to the top of the wiki.
-
-Unlike pagespec_match, this may throw an error if there is an error in
-the pagespec.
-
 #### `bestlink($$)`
 
 Given a page and the text of a link on the page, determine which
@@ -982,6 +1008,15 @@ an IkiWiki::FailReason object if the match fails. If the match cannot be
 attempted at all, for any page, it can instead return an
 IkiWiki::ErrorReason object explaining why.
 
+When constructing these objects, you should also include information about
+of any pages whose contents or other metadata influenced the result of the
+match. Do this by passing a list of pages, followed by `deptype` values.
+
+For example, "backlink(foo)" is influenced by the contents of page foo;
+"link(foo)" and "title(bar)" are influenced by the contents of any page
+they match; "created_before(foo)" is influenced by the metadata of foo;
+while "glob(*)" is not influenced by the contents of any page.
+
 ### Setup plugins
 
 The ikiwiki setup file is loaded using a pluggable mechanism. If you look
index 1c2f579b359eca24e7bfb64c9b5ec94321fba57c..da9b5e6cf1859ff7848183fce3e2af22c9f6b428 100644 (file)
@@ -280,6 +280,7 @@ sigh.
   that the page links to, which is just what link dependencies are
   triggered on.
 
+[[done]]
 ----
 
 ### the removal problem
@@ -565,9 +566,16 @@ SuccessReason(page, index) => right
 `HardFailReason() | SuccessReason(index)` =>
 `SuccessReason(index)` => right
 
+Ok so far, but:
+
 "!bugs/* and link(patch)" =>
-`HardFailReason() | SuccessReason(bugs/foo)` =>  
-`HardFailReason()` => right
+`!SuccessReason() | SuccessReason(bugs/foo)` =>  
+'FailReason() | SuccessReason(bugs/foo)
+`FailReason(bugs/foo)` => wrong!
+
+This could be fixed by adding a HardSuccessReason that glob also returns.
+Maybe just a field of the object that is set if it is "hard" is a better
+approach though.
 
 #### Influence types
 
index d869a682172eb560a950d10656d614d6af583bc3..9d874d98e8b537a1cbeac21d7e4d95938a961189 100644 (file)
@@ -23,7 +23,7 @@ essentially three pieces needed for a complete translation:
      file, and in preprocessor directives.
 
 1. The [[basewiki]] needs to be translated. The
-   [[plugins/contrib/po]] ikiwiki plugin will allow translating
+   [[plugins/po]] ikiwiki plugin will allow translating
    wikis using po files and can be used for this.
 
    There is now a website, [l10n.ikiwiki.info](http://l10n.ikiwiki.info)
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..9b07481
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use Test::More tests => 38;
+
+BEGIN { use_ok("IkiWiki"); }
+%config=IkiWiki::defaultconfig();
+$config{srcdir}=$config{destdir}="/dev/null";
+IkiWiki::checkconfig();
+
+$pagesources{"foo$_"}="foo$_.mdwn" for 0..9;
+
+# avoids adding an unparseable pagespec
+ok(! add_depends("foo0", "foo and (bar"));
+ok(! add_depends("foo0", "foo another"));
+
+# simple and not-so-simple dependencies split
+ok(add_depends("foo0", "*"));
+ok(add_depends("foo0", "bar"));
+ok(add_depends("foo0", "BAZ"));
+ok(exists $IkiWiki::depends_simple{foo0}{"bar"});
+ok(exists $IkiWiki::depends_simple{foo0}{"baz"}); # lowercase
+ok(! exists $IkiWiki::depends_simple{foo0}{"*"});
+ok(! exists $IkiWiki::depends{foo0}{"bar"});
+ok(! exists $IkiWiki::depends{foo0}{"baz"});
+
+# default dependencies are content dependencies
+ok($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+ok($IkiWiki::depends_simple{foo0}{"bar"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends_simple{foo0}{"bar"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+
+# adding other dep types standalone
+ok(add_depends("foo2", "*", deptype("presence")));
+ok(add_depends("foo2", "bar", deptype("links")));
+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", deptype("links", "presence")));
+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", deptype("presence"))); # had only links before
+ok($IkiWiki::depends_simple{foo2}{"bar"} & ($IkiWiki::DEPEND_LINKS | $IkiWiki::DEPEND_PRESENCE));
+ok(! ($IkiWiki::depends_simple{foo2}{"bar"} & $IkiWiki::DEPEND_CONTENT));
+ok(add_depends("foo0", "bar", deptype("links"))); # had only content before
+ok($IkiWiki::depends{foo0}{"*"} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS));
+ok(! ($IkiWiki::depends{foo0}{"*"} & $IkiWiki::DEPEND_PRESENCE));
+
+# content is the default if unknown types are entered
+ok(add_depends("foo9", "*", deptype("monkey")));
+ok($IkiWiki::depends{foo9}{"*"} & $IkiWiki::DEPEND_CONTENT);
+ok(! ($IkiWiki::depends{foo9}{"*"} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
index 64408f4898cb8b0671ba24fb7f2f8433e87dacae..d529106f751ab90a60a63074aab0e418fe36af7d 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More tests => 54;
+use Test::More tests => 64;
 
 BEGIN { use_ok("IkiWiki"); }
 
@@ -88,3 +88,24 @@ ok(! pagespec_match("foo", "no_such_function(foo)"), "foo");
 my $ret=pagespec_match("foo", "(invalid");
 ok(! $ret, "syntax error");
 ok($ret =~ /syntax error/, "error message");
+
+$ret=pagespec_match("foo", "bar or foo");
+ok($ret, "simple match");
+is($ret, "foo matches foo", "stringified return");
+
+my $i=pagespec_match("foo", "link(bar)")->influences;
+is(join(",", keys %$i), 'foo', "link is influenced by the page with the link");
+$i=pagespec_match("bar", "backlink(foo)")->influences;
+is(join(",", keys %$i), 'foo', "backlink is influenced by the page with the link");
+$i=pagespec_match("bar", "backlink(foo)")->influences;
+is(join(",", keys %$i), 'foo', "backlink is influenced by the page with the link");
+$i=pagespec_match("bar", "created_before(foo)")->influences;
+is(join(",", keys %$i), 'foo', "created_before is influenced by the comparison page");
+$i=pagespec_match("bar", "created_after(foo)")->influences;
+is(join(",", keys %$i), 'foo', "created_after is influenced by the comparison page");
+$i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences;
+is(join(",", sort keys %$i), 'bar,foo', "influences add up over AND");
+$i=pagespec_match("foo", "link(baz) and created_after(bar)")->influences;
+is(join(",", sort keys %$i), 'bar,foo', "influences add up over OR");
+$i=pagespec_match("foo", "!link(baz) and !created_after(bar)")->influences;
+is(join(",", sort keys %$i), 'bar,foo', "influences unaffected by negation");
diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t
new file mode 100755 (executable)
index 0000000..301197c
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use Test::More tests => 49;
+
+BEGIN { use_ok("IkiWiki"); }
+
+%config=IkiWiki::defaultconfig();
+$config{srcdir}=$config{destdir}="/dev/null";
+IkiWiki::checkconfig();
+
+%pagesources=(
+       foo => "foo.mdwn",
+       foo2 => "foo2.mdwn",
+       foo3 => "foo3.mdwn",
+       bar => "bar.mdwn",
+       "post/1" => "post/1.mdwn",
+       "post/2" => "post/2.mdwn",
+       "post/3" => "post/3.mdwn",
+);
+$links{foo}=[qw{post/1 post/2}];
+$links{foo2}=[qw{bar}];
+$links{foo3}=[qw{bar}];
+
+is_deeply([pagespec_match_list("foo", "bar")], ["bar"]);
+is_deeply([sort(pagespec_match_list("foo", "* and !post/*"))], ["bar", "foo", "foo2", "foo3"]);
+is_deeply([sort(pagespec_match_list("foo", "post/*"))], ["post/1", "post/2", "post/3"]);
+is_deeply([pagespec_match_list("foo", "post/*", sort => "title", reverse => 1)],
+       ["post/3", "post/2", "post/1"]);
+is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 2)],
+       ["post/1", "post/2"]);
+is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 50)],
+       ["post/1", "post/2", "post/3"]);
+is_deeply([pagespec_match_list("foo", "post/*", sort => "title",
+                         filter => sub { $_[0] =~ /3/}) ],
+       ["post/1", "post/2"]);
+my $r=eval { pagespec_match_list("foo", "beep") };
+ok(eval { pagespec_match_list("foo", "beep") } == 0);
+ok(! $@, "does not fail with error when unable to match anything");
+eval { pagespec_match_list("foo", "this is not a legal pagespec!") };
+ok($@, "fails with error when pagespec bad");
+
+# A pagespec that requires page metadata should add influences
+# as an explicit dependency. In the case of a link, a links dependency.
+foreach my $spec ("* and link(bar)", "* or link(bar)") {
+       pagespec_match_list("foo2", $spec, deptype => deptype("presence"));
+       ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE);
+       ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)));
+       ok($IkiWiki::depends_simple{foo2}{foo2} == $IkiWiki::DEPEND_LINKS);
+       %IkiWiki::depends_simple=();
+       %IkiWiki::depends=();
+       pagespec_match_list("foo3", $spec, deptype => deptype("links"));
+       ok($IkiWiki::depends{foo3}{$spec} & $IkiWiki::DEPEND_LINKS);
+       ok(! ($IkiWiki::depends{foo3}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_PRESENCE)));
+       ok($IkiWiki::depends_simple{foo3}{foo3} == $IkiWiki::DEPEND_LINKS);
+       %IkiWiki::depends_simple=();
+       %IkiWiki::depends=();
+}
+
+# a pagespec with backlinks() will add as an influence the page with the links
+foreach my $spec ("bar or (backlink(foo) and !*.png)", "backlink(foo)") {
+       pagespec_match_list("foo2", $spec, deptype => deptype("presence"));
+       ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE);
+       ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_CONTENT | $IkiWiki::DEPEND_LINKS)));
+       ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS);
+       %IkiWiki::depends_simple=();
+       %IkiWiki::depends=();
+       pagespec_match_list("foo2", $spec, deptype => deptype("links"));
+       ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_LINKS);
+       ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_CONTENT)));
+       ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS);
+       %IkiWiki::depends_simple=();
+       %IkiWiki::depends=();
+       pagespec_match_list("foo2", $spec, deptype => deptype("presence", "links"));
+       ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_PRESENCE);
+       ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_LINKS);
+       ok(! ($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_CONTENT));
+       ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS);
+       %IkiWiki::depends_simple=();
+       %IkiWiki::depends=();
+       pagespec_match_list("foo2", $spec);
+       ok($IkiWiki::depends{foo2}{$spec} & $IkiWiki::DEPEND_CONTENT);
+       ok(! ($IkiWiki::depends{foo2}{$spec} & ($IkiWiki::DEPEND_PRESENCE | $IkiWiki::DEPEND_LINKS)));
+       ok($IkiWiki::depends_simple{foo2}{foo} == $IkiWiki::DEPEND_LINKS);
+}
diff --git a/t/pagespec_match_result.t b/t/pagespec_match_result.t
new file mode 100755 (executable)
index 0000000..c2112bf
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use Test::More tests => 71;
+
+BEGIN { use_ok("IkiWiki"); }
+
+# Note that new objects have to be constructed freshly for each test, since
+# object states are mutated as they are combined.
+sub S { IkiWiki::SuccessReason->new("match", @_) }
+sub F { IkiWiki::FailReason->new("no match", @_) }
+sub E { IkiWiki::ErrorReason->new("error in matching", @_) }
+
+ok(S() eq "match");
+ok(F() eq "no match");
+ok(E() eq "error in matching");
+
+ok(S());
+ok(! F());
+ok(! E());
+
+ok(!(! S()));
+ok(!(!(! F)));
+ok(!(!(! E)));
+
+ok(S() | F());
+ok(F() | S());
+ok(!(F() | E()));
+ok(!(!S() | F() | E()));
+
+ok(S() & S() & S());
+ok(!(S() & E()));
+ok(!(S() & F()));
+ok(!(S() & F() & E()));
+ok(S() & (F() | F() | S()));
+
+# influences are always merged, no matter the operation performed,
+# as long as the two items are always both present
+foreach my $op ('$s | $f', '$s & $f', '$s & $f & E()', '$s | E() | $f',
+                '! $s | ! $f', '!(!(!$s)) | $f') {
+       my $s=S(foo => 1, bar => 1);
+       is($s->influences->{foo}, 1);
+       is($s->influences->{bar}, 1);
+       my $f=F(bar => 2, baz => 1);
+       is($f->influences->{bar}, 2);
+       is($f->influences->{baz}, 1);
+       my $c = eval $op;
+       ok(ref $c);
+       is($c->influences->{foo}, 1, "foo ($op)");
+       is($c->influences->{bar}, (1 | 2), "bar ($op)");
+       is($c->influences->{baz}, 1, "baz ($op)");
+}
+
+my $s=S(foo => 0, bar => 1);
+$s->influences(baz => 1);
+ok(! $s->influences->{foo}, "removed 0 influence");
+ok(! $s->influences->{bar}, "removed 1 influence");
+ok($s->influences->{baz}, "set influence");
+ok($s->influences_static);
+
+# influence blocking
+my $r=F()->block & S(foo => 1);
+ok(! $r->influences->{foo}, "failed blocker & influence -> does not pass");
+$r=F()->block | S(foo => 1);
+ok($r->influences->{foo}, "failed blocker | influence -> does pass");
+$r=S(foo => 1) & F()->block;
+ok(! $r->influences->{foo}, "influence & failed blocker -> does not pass");
+$r=S(foo => 1) | F()->block;
+ok($r->influences->{foo}, "influence | failed blocker -> does pass");
+$r=S(foo => 1) & F()->block & S(foo => 2);
+ok(! $r->influences->{foo}, "influence & failed blocker & influence -> does not pass");
+$r=S(foo => 1) | F()->block | S(foo => 2);
+ok($r->influences->{foo}, "influence | failed blocker | influence -> does pass");
+$r=S()->block & S(foo => 1);
+ok($r->influences->{foo}, "successful blocker -> does pass");
+$r=(! S()->block) & S(foo => 1);
+ok(! $r->influences->{foo}, "! successful blocker -> failed blocker");
index 60a8c071d6153739639cb6c50322fd2aaea082c0..8770390a1075651caf9c6c4aeb46db3df338f0f8 100755 (executable)
--- a/t/yesno.t
+++ b/t/yesno.t
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More tests => 10;
+use Test::More tests => 11;
 
 BEGIN { use_ok("IkiWiki"); }
 
@@ -19,3 +19,5 @@ ok(IkiWiki::yesno("NO") == 0);
 ok(IkiWiki::yesno("1") == 1);
 ok(IkiWiki::yesno("0") == 0);
 ok(IkiWiki::yesno("mooooooooooo") == 0);
+
+ok(IkiWiki::yesno(undef) == 0);
diff --git a/underlays/basewiki/ikiwiki/pagespec/sorting.mdwn b/underlays/basewiki/ikiwiki/pagespec/sorting.mdwn
new file mode 100644 (file)
index 0000000..41aa581
--- /dev/null
@@ -0,0 +1,11 @@
+Some [[directives|ikiwiki/directive]] that use
+[[PageSpecs|ikiwiki/pagespec]] allow
+specifying the order that matching pages are shown in. The following sort
+orders can be specified.
+
+* `age` - List pages from the most recently created to the oldest.
+* `mtime` - List pages with the most recently modified first.
+* `title` - Order by title.
+* `title_natural` - Only available if [[!cpan Sort::Naturally]] is
+  installed. Orders by title, but numbers in the title are treated
+  as such, ("1 2 9 10 20" instead of "1 10 2 20 9")