# Optimisation.
use Memoize;
memoize("abs2rel");
+memoize("cmpspec_translate");
memoize("pagespec_translate");
memoize("template_file");
unless grep { $_ eq $link } @{$links{$page}};
}
+sub cmpspec_translate ($) {
+ my $spec = shift;
+
+ my $code = "";
+ my @data;
+ while ($spec =~ m{
+ \s*
+ (-?) # group 1: perhaps negated
+ \s*
+ ( # group 2: a word
+ \w+\([^\)]*\) # command(params)
+ |
+ [^\s]+ # or anything else
+ )
+ \s*
+ }gx) {
+ my $negated = $1;
+ my $word = $2;
+ my $params = undef;
+
+ if ($word =~ m/^(\w+)\((.*)\)$/) {
+ # command with parameters
+ $params = $2;
+ $word = $1;
+ }
+ elsif ($word !~ m/^\w+$/) {
+ error(sprintf(gettext("invalid sort type %s"), $word));
+ }
+
+ if (length $code) {
+ $code .= " || ";
+ }
+
+ if ($negated) {
+ $code .= "-";
+ }
+
+ if (exists $IkiWiki::PageSpec::{"cmp_$word"}) {
+ if (exists $IkiWiki::PageSpec::{"check_cmp_$word"}) {
+ $IkiWiki::PageSpec::{"check_cmp_$word"}->($params);
+ }
+
+ if (defined $params) {
+ push @data, $params;
+ $code .= "IkiWiki::PageSpec::cmp_$word(\@_, \$data[$#data])";
+ }
+ else {
+ $code .= "IkiWiki::PageSpec::cmp_$word(\@_, undef)";
+ }
+ }
+ else {
+ error(sprintf(gettext("unknown sort type %s"), $word));
+ }
+ }
+
+ if (! length $code) {
+ # undefined sorting method... sort arbitrarily
+ return sub { 0 };
+ }
+
+ no warnings;
+ return eval 'sub { '.$code.' }';
+}
+
sub pagespec_translate ($) {
my $spec=shift;
return $sub->($page, @params);
}
-sub get_sort_function {
- my $method = $_[0];
-
- if ($method =~ m/\s/) {
- my @methods = map { get_sort_function($_) } split(' ', $method);
-
- return sub {
- foreach my $method (@methods) {
- my $answer = $method->($_[0], $_[1]);
- return $answer if $answer;
- }
-
- return 0;
- };
- }
-
- my $sense = 1;
-
- if ($method =~ s/^-//) {
- $sense = -1;
- }
-
- my $token = $method;
- my $parameter = undef;
-
- if ($method =~ m/^(\w+)\((.*)\)$/) {
- $token = $1;
- $parameter = $2;
- }
-
- if (exists $hooks{sort}{$token}{call}) {
- my $callback = $hooks{sort}{$token}{call};
- return sub { $sense * $callback->($_[0], $_[1], $parameter) };
- }
-
- if ($method eq 'title') {
- return sub { $sense * (pagetitle(basename($_[0])) cmp pagetitle(basename($_[1]))) };
- }
-
- if ($method eq 'title_natural') {
- eval q{use Sort::Naturally};
- if ($@) {
- error(gettext("Sort::Naturally needed for title_natural sort"));
- }
- return sub { $sense * Sort::Naturally::ncmp(pagetitle(basename($_[0])), pagetitle(basename($_[1]))) };
- }
-
- if ($method eq 'mtime') {
- return sub { $sense * ($pagemtime{$_[1]} <=> $pagemtime{$_[0]}) };
- }
-
- if ($method eq 'age') {
- return sub { $sense * ($pagectime{$_[1]} <=> $pagectime{$_[0]}) };
- }
-
- error sprintf(gettext("unknown sort type %s"), $method);
-}
-
sub pagespec_match_list ($$;@) {
my $page=shift;
my $pagespec=shift;
}
if (defined $params{sort}) {
- my $f = get_sort_function($params{sort});
+ my $f = cmpspec_translate($params{sort});
@candidates = sort { $f->($a, $b) } @candidates;
}
}
}
+sub cmp_title {
+ IkiWiki::pagetitle(IkiWiki::basename($_[0]))
+ cmp
+ IkiWiki::pagetitle(IkiWiki::basename($_[1]))
+}
+
+sub cmp_mtime { $IkiWiki::pagemtime{$_[1]} <=> $IkiWiki::pagemtime{$_[0]} }
+sub cmp_age { $IkiWiki::pagectime{$_[1]} <=> $IkiWiki::pagectime{$_[0]} }
+
+sub check_cmp_title_natural {
+ eval q{use Sort::Naturally};
+ if ($@) {
+ error(gettext("Sort::Naturally needed for title_natural sort"));
+ }
+}
+sub cmp_title_natural {
+ Sort::Naturally::ncmp(IkiWiki::pagetitle(IkiWiki::basename($_[0])),
+ IkiWiki::pagetitle(IkiWiki::basename($_[1])))
+}
+
1
hook(type => "needsbuild", id => "meta", call => \&needsbuild);
hook(type => "preprocess", id => "meta", call => \&preprocess, scan => 1);
hook(type => "pagetemplate", id => "meta", call => \&pagetemplate);
- hook(type => "sort", id => "meta_title", call => \&sort_meta_title);
}
sub getsetup () {
return pagetitle(IkiWiki::basename($_[0]));
}
-sub sort_meta_title {
- return titlesort($_[0]) cmp titlesort($_[1]);
-}
-
sub match {
my $field=shift;
my $page=shift;
IkiWiki::Plugin::meta::match("copyright", @_);
}
+sub cmp_meta_title {
+ IkiWiki::Plugin::meta::titlesort($_[0])
+ cmp
+ IkiWiki::Plugin::meta::titlesort($_[1])
+}
+
1
This hook is used to inject C code (which it returns) into the `main`
function of the ikiwiki wrapper when it is being generated.
-### sort
-
- hook(type => "sort", id => "foo", call => \&sort_by_foo);
-
-This hook adds an additional [[ikiwiki/pagespec/sorting]] order or overrides
-an existing one.
-
-The callback is given two page names followed by the parameter as arguments, and
-returns negative, zero or positive if the first page should come before,
-close to (i.e. undefined order), or after the second page.
-
-For instance, the built-in `title` sort order could be reimplemented as
-
- sub sort_by_title {
- pagetitle(basename($_[0])) cmp pagetitle(basename($_[1]));
- }
-
-and to sort by an arbitrary `meta` value, you could use:
-
- # usage: sort="meta(description)"
- sub sort_by_meta {
- my $param = $_[2];
- error "sort=meta requires a parameter" unless defined $param;
- my $left = $pagestate{$_[0]}{meta}{$param};
- $left = "" unless defined $left;
- my $right = $pagestate{$_[1]}{meta}{$param};
- $right = "" unless defined $right;
- return $left cmp $right;
- }
-
## Exported variables
Several variables are exported to your plugin when you `use IkiWiki;`
they match; "created_before(foo)" is influenced by the metadata of foo;
while "glob(*)" is not influenced by the contents of any page.
+### Sorting plugins
+
+Similarly, it's possible to write plugins that add new functions as
+[[ikiwiki/pagespec/sorting]] methods. To achieve this, add a function to
+the IkiWiki::PageSpec package named `cmp_foo`, which will be used when sorting
+by `foo` or `foo(...)` is requested.
+
+The function will be passed three or more parameters. The first two are
+page names, and the third is `undef` if invoked as `foo`, or the parameter
+`"bar"` if invoked as `foo(bar)`. It may also be passed additional, named
+parameters.
+
+It should return the same thing as Perl's `cmp` and `<=>` operators: negative
+if the first argument is less than the second, positive if the first argument
+is greater, or zero if they are considered equal. It may also raise an
+error using `error`, for instance if it needs a parameter but one isn't
+provided.
+
+You can also define a function called `check_cmp_foo` in the same package.
+If you do, it will be called while preparing to sort by `foo` or `foo(bar)`,
+with argument `undef` or `"bar"` respectively; it may raise an error using
+`error`, if sorting like that isn't going to work.
+
### Setup plugins
The ikiwiki setup file is loaded using a pluggable mechanism. If you look
$config{srcdir}=$config{destdir}="/dev/null";
IkiWiki::checkconfig();
-hook(type => "sort", id => "path", call => sub { $_[0] cmp $_[1] });
+{
+ package IkiWiki::PageSpec;
+
+ sub cmp_path { $_[0] cmp $_[1] }
+}
%pagesources=(
foo => "foo.mdwn",