Reimplement extensible sorting mechanisms, in the same way as pagespecs
parent
60edd2dc31
commit
b86276ffed
145
IkiWiki.pm
145
IkiWiki.pm
|
@ -37,6 +37,7 @@ our $DEPEND_LINKS=4;
|
|||
# Optimisation.
|
||||
use Memoize;
|
||||
memoize("abs2rel");
|
||||
memoize("cmpspec_translate");
|
||||
memoize("pagespec_translate");
|
||||
memoize("template_file");
|
||||
|
||||
|
@ -1934,6 +1935,70 @@ sub add_link ($$) {
|
|||
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;
|
||||
|
||||
|
@ -2005,64 +2070,6 @@ sub pagespec_match ($$;@) {
|
|||
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;
|
||||
|
@ -2092,7 +2099,7 @@ sub pagespec_match_list ($$;@) {
|
|||
}
|
||||
|
||||
if (defined $params{sort}) {
|
||||
my $f = get_sort_function($params{sort});
|
||||
my $f = cmpspec_translate($params{sort});
|
||||
|
||||
@candidates = sort { $f->($a, $b) } @candidates;
|
||||
}
|
||||
|
@ -2407,4 +2414,24 @@ sub match_ip ($$;@) {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -13,7 +13,6 @@ sub import {
|
|||
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 () {
|
||||
|
@ -299,10 +298,6 @@ sub titlesort {
|
|||
return pagetitle(IkiWiki::basename($_[0]));
|
||||
}
|
||||
|
||||
sub sort_meta_title {
|
||||
return titlesort($_[0]) cmp titlesort($_[1]);
|
||||
}
|
||||
|
||||
sub match {
|
||||
my $field=shift;
|
||||
my $page=shift;
|
||||
|
@ -353,4 +348,10 @@ sub match_copyright ($$;@) {
|
|||
IkiWiki::Plugin::meta::match("copyright", @_);
|
||||
}
|
||||
|
||||
sub cmp_meta_title {
|
||||
IkiWiki::Plugin::meta::titlesort($_[0])
|
||||
cmp
|
||||
IkiWiki::Plugin::meta::titlesort($_[1])
|
||||
}
|
||||
|
||||
1
|
||||
|
|
|
@ -588,36 +588,6 @@ describes the plugin as a whole. For example:
|
|||
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;`
|
||||
|
@ -1140,6 +1110,29 @@ For example, "backlink(foo)" is influenced by the contents of page foo;
|
|||
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
|
||||
|
|
|
@ -9,7 +9,11 @@ BEGIN { use_ok("IkiWiki"); }
|
|||
$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",
|
||||
|
|
Loading…
Reference in New Issue