add use_pagespec and deptype functions
authorJoey Hess <joey@gnu.kitenet.net>
Thu, 8 Oct 2009 20:49:53 +0000 (16:49 -0400)
committerJoey Hess <joey@gnu.kitenet.net>
Thu, 8 Oct 2009 21:53:20 +0000 (17:53 -0400)
IkiWiki.pm
debian/changelog
doc/plugins/write.mdwn
t/use_pagespec.t [new file with mode: 0755]

index 2064c881a5497a3de1a15f28029d2f810218623e..c787612e12e622174797609fe5337ff57586bf8a 100644 (file)
@@ -17,11 +17,12 @@ 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 use_pagespec
+                 add_depends pagespec_match pagespec_match_list bestlink
+                htmllink readfile writefile pagetype srcfile pagename
+                displaytime will_render gettext urlto targetpage
+                add_underlay pagetitle titlepage linkpage newpagefile
+                inject add_link
                  %config %links %pagestate %wikistate %renderedfiles
                  %pagesources %destsources);
 our $VERSION = 3.00; # plugin interface version, next is ikiwiki version
@@ -1768,18 +1769,10 @@ sub rcs_receive () {
        $hooks{rcs}{rcs_receive}{call}->();
 }
 
-sub add_depends ($$;@) {
+sub add_depends ($$;$) {
        my $page=shift;
        my $pagespec=shift;
-
-       my $deptype=0;
-       if (@_) {
-               my %params=@_;
-               
-               $deptype=$deptype | $DEPEND_PRESENCE if $params{presence};
-               $deptype=$deptype | $DEPEND_LINKS if $params{links};
-       }
-       $deptype=$DEPEND_CONTENT unless $deptype;
+       my $deptype=shift || $DEPEND_CONTENT;
 
        # Is the pagespec a simple page name?
        if ($pagespec =~ /$config{wiki_file_regexp}/ &&
@@ -1791,18 +1784,118 @@ sub add_depends ($$;@) {
        # Analyse the pagespec, and match it against all pages
        # to get a list of influences, and add explicit dependencies
        # for those.
+       #my $sub=pagespec_translate($pagespec);
+       #return if $@;
+       #foreach my $p (keys %pagesources) {
+       #       my $r=$sub->($p, location => $page );
+       #       my %i=$r->influences;
+       #       foreach my $i (keys %i) {
+       #               $depends_simple{$page}{lc $i} |= $i{$i};
+       #       }
+       #}
+       print STDERR "warning: use of add_depends; influences not tracked\n";
+
+       $depends{$page}{$pagespec} |= $deptype;
+       return 1;
+}
+
+sub use_pagespec ($$;@) {
+       my $page=shift;
+       my $pagespec=shift;
+       my %params=@_;
+
        my $sub=pagespec_translate($pagespec);
-       return if $@;
-       foreach my $p (keys %pagesources) {
-               my $r=$sub->($p, location => $page );
-               my %i=$r->influences;
+       error "syntax error in pagespec \"$pagespec\""
+               if $@ || ! defined $sub;
+
+       my @candidates;
+       if (exists $params{limit}) {
+               @candidates=grep { $params{limit}->($_) } keys %pagesources;
+       }
+       else {
+               @candidates=keys %pagesources;
+       }
+
+       if (defined $params{sort}) {
+               my $f;
+               if ($params{sort} eq 'title') {
+                       $f=sub { pagetitle(basename($a)) cmp pagetitle(basename($b)) };
+               }
+               elsif ($params{sort} eq 'title_natural') {
+                       eval q{use Sort::Naturally};
+                       if ($@) {
+                               error(gettext("Sort::Naturally needed for title_natural sort"));
+                       }
+                       $f=sub { Sort::Naturally::ncmp(pagetitle(basename($a)), pagetitle(basename($b))) };
+                }
+               elsif ($params{sort} eq 'mtime') {
+                       $f=sub { $pagemtime{$b} <=> $pagemtime{$a} };
+               }
+               elsif ($params{sort} eq 'age') {
+                       $f=sub { $pagectime{$b} <=> $pagectime{$a} };
+               }
+               else {
+                       error sprintf(gettext("unknown sort type %s"), $params{sort});
+               }
+               @candidates = sort { &$f } @candidates;
+       }
+
+       @candidates=reverse(@candidates) if $params{reverse};
+       
+       my @matches;
+       my $firstfail;
+       my $count=0;
+       foreach my $p (@candidates) {
+               my $r=$sub->($p, location => $page);
+               if ($r) {
+                       push @matches, [$p, $r];
+                       last if defined $params{num} && ++$count == $params{num};
+               }
+               elsif (! defined $firstfail) {
+                       $firstfail=$r;
+               }
+       }
+       
+       $depends{$page}{$pagespec} |= ($params{deptype} || $DEPEND_CONTENT);
+
+       my @ret;
+       if (@matches) {
+               # Add all influences from successful matches.
+               foreach my $m (@matches) {
+                       push @ret, $m->[0];
+                       my %i=$m->[1]->influences;
+                       foreach my $i (keys %i) {
+                               $depends_simple{$page}{lc $i} |= $i{$i};
+                       }
+               }
+       }
+       elsif (defined $firstfail) {
+               # Add influences from one failure. (Which one should not
+               # matter; all should have the same influences.)
+               my %i=$firstfail->influences;
                foreach my $i (keys %i) {
                        $depends_simple{$page}{lc $i} |= $i{$i};
                }
+               error(sprintf(gettext("cannot match pages: %s"), $firstfail));
        }
 
-       $depends{$page}{$pagespec} |= $deptype;
-       return 1;
+       return @ret;
+}
+
+sub deptype (@) {
+       my $deptype=0;
+       foreach my $type (@_) {
+               if ($type eq 'presence') {
+                       $deptype |= $DEPEND_PRESENCE;
+               }
+               elsif ($type eq 'links') { 
+                       $deptype |= $DEPEND_LINKS;
+               }
+               elsif ($type eq 'content') {
+                       $deptype |= $DEPEND_CONTENT;
+               }
+       }
+       return $deptype;
 }
 
 sub file_pruned ($$) {
index 565a0cffac08ae2128f13b6f0721ca068758b62c..12ddebac9fc46d4d4ca153ae2ae431ce9cd02f02 100644 (file)
@@ -13,7 +13,6 @@ ikiwiki (3.14159266) UNRELEASED; urgency=low
   * Added support framework for multiple types of dependencies.
   * Allow declaring that a dependency is only affected by page presence
     or changes to its links.
-    (By passing presence => 1 or links => 1 to add_depends.)
   * pagecount, calendar, postsparkline, progress: Use a presence dependency,
     which makes these directives much less expensive to use, since page
     edits will no longer trigger an unnecessary update.
@@ -34,6 +33,9 @@ ikiwiki (3.14159266) UNRELEASED; urgency=low
     info.
   * Plugins providing PageSpec `match_*` functions should pass additional
     influence information when creating result objects.
+  * Added `use_pagespec` function, that plugins can use to find a list
+    of matching pages and add dependencies and influences, all at once,
+    and efficiently.
 
  -- Joey Hess <joeyh@debian.org>  Sun, 27 Sep 2009 17:40:03 -0400
 
index 2324300790d94ba871177c0fa9089efed13e38c1..3d56507582cdc42c2f4780c6dc244d3a2a94941f 100644 (file)
@@ -609,21 +609,52 @@ 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.
+
+#### `use_pagespec($$;@)`
+
+Passed a page name, and [[ikiwiki/PageSpec]], returns a list of pages
+in the wiki that match the [[ikiwiki/PageSpec]]. 
+
+The page will automatically be made to depend on the specified
+[[ikiwiki/PageSpec]], so `add_depends` does not need to be called. This
+is significantly more efficient than calling `add_depends`
+followed by `pagespec_match_list`. You should use this anytime a plugin
+needs to match a set of pages and generate something based on that list.
+
+Additional named parameters can be specified:
+
+* `deptype` optionally specifies the type of dependency to add. Use the
+  `deptype` function to generate a dependency type.
+* `limit` is a reference to a function, that is called and passed a page,
+  and must return true for the page to be included.
+* `sort` specifies a sort order for the list. See
+  [[ikiwiki/PageSpec/sorting]] for the avilable sort methods.
+* `reverse` if true, sorts in reverse.
+* `num` if nonzero, specifies the maximum number of matching pages that
+  will be returned.
+
+#### `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($$;@)`
 
@@ -984,10 +1015,12 @@ 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. 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.
+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
 
diff --git a/t/use_pagespec.t b/t/use_pagespec.t
new file mode 100755 (executable)
index 0000000..7b90407
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use Test::More tests => 64;
+
+BEGIN { use_ok("IkiWiki"); }
+
+%pagesources=(
+       foo => "foo.mdwn",
+       bar => "bar.mdwn",
+       "post/1" => "post/1.mdwn",
+       "post/2" => "post/2.mdwn",
+       "post/3" => "post/3.mdwn",
+);
+
+is_deeply([use_pagespec("foo", "bar")], ["bar"]);
+is_deeply([sort(use_pagespec("foo", "post/*"))], ["post/1", "post/2", "post/3"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title", reverse => 1)],
+       ["post/3", "post/2", "post/1"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 2)],
+       ["post/1", "post/2"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title", num => 50)],
+       ["post/1", "post/2", "post/3"]);
+is_deeply([use_pagespec("foo", "post/*", sort => "title",
+                         limit => sub { $_[0] !~ /3/}) ],
+       ["post/1", "post/2"]);
+eval { use_pagespec("foo", "beep") };
+ok($@, "fails with error when unable to match anything");
+eval { use_pagespec("foo", "this is not a legal pagespec!") };
+ok($@, "fails with error when pagespec bad");