From: Simon McVittie Date: Thu, 25 Mar 2010 23:31:53 +0000 (+0000) Subject: Reimplement extensible sorting mechanisms, in the same way as pagespecs X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/commitdiff_plain/b86276ffed7ee001b35cd610e5d56e5afb4088cf?hp=60edd2dc3157f756f4f7a213ee15836fe7bbb769 Reimplement extensible sorting mechanisms, in the same way as pagespecs --- diff --git a/IkiWiki.pm b/IkiWiki.pm index ce8fdd454..a89c14058 100644 --- a/IkiWiki.pm +++ b/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 diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm index a470041c9..e8cc1e392 100644 --- a/IkiWiki/Plugin/meta.pm +++ b/IkiWiki/Plugin/meta.pm @@ -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 diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 1010e76e4..de2b47015 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -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 diff --git a/t/pagespec_match_list.t b/t/pagespec_match_list.t index 309961f1c..743ae4637 100755 --- a/t/pagespec_match_list.t +++ b/t/pagespec_match_list.t @@ -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",