%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
$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}/ &&
# 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 ($$) {
* 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.
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
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($$;@)`
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
--- /dev/null
+#!/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");