Allow sorting to be combined and/or reversed
authorSimon McVittie <smcv@debian.org>
Wed, 24 Mar 2010 23:51:48 +0000 (23:51 +0000)
committerSimon McVittie <smcv@debian.org>
Wed, 24 Mar 2010 23:51:48 +0000 (23:51 +0000)
IkiWiki.pm
doc/ikiwiki/pagespec/sorting.mdwn
doc/plugins/write.mdwn
t/pagespec_match_list.t

index 1a4dc47dd8e0fbe2782c8db49f7a1d05bdba7b37..ce8fdd454dacf3af686a3f49953ab0381ba429dc 100644 (file)
@@ -2005,6 +2005,64 @@ 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;
@@ -2034,31 +2092,9 @@ sub pagespec_match_list ($$;@) {
        }
 
        if (defined $params{sort}) {
-               my $f;
+               my $f = get_sort_function($params{sort});
 
-               if (exists $hooks{sort}{$params{sort}}{call}) {
-                       $f = sub { $hooks{sort}{$params{sort}}{call}($a, $b) };
-               }
-               elsif ($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 = sort { $f->($a, $b) } @candidates;
        }
 
        @candidates=reverse(@candidates) if $params{reverse};
index 61516bec53338f12d98d2d8e705a188d69eba3ef..f27972d4ebad13becf442eec9c4b35eec41ca0ef 100644 (file)
@@ -15,6 +15,10 @@ orders can be specified.
   full title was set.
 """]]
 
+In addition, you can combine several sort orders and/or reverse the order of
+sorting, with a string like `age -title` (which would sort by age, then by
+title in reverse order if two pages have the same age).
+
 Plugins can add additional sort orders, so more might be available on this
 wiki.
 
index bfa6617bd554d7f35b2f2f6b253abcf605db1245..1010e76e400bab3e479c081873437d37db0095f1 100644 (file)
@@ -593,7 +593,9 @@ function of the ikiwiki wrapper when it is being generated.
        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 as arguments, and
+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.
 
@@ -603,6 +605,19 @@ For instance, the built-in `title` sort order could be reimplemented as
                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;`
index b34ee769f6c139425da64ce0e836131254c14458..309961f1c52f78e57dbd5000b65257feda408443 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 use warnings;
 use strict;
-use Test::More tests => 89;
+use Test::More tests => 90;
 
 BEGIN { use_ok("IkiWiki"); }
 
@@ -20,6 +20,13 @@ hook(type => "sort", id => "path", call => sub { $_[0] cmp $_[1] });
        "post/2" => "post/2.mdwn",
        "post/3" => "post/3.mdwn",
 );
+$IkiWiki::pagectime{foo} = 2;
+$IkiWiki::pagectime{foo2} = 2;
+$IkiWiki::pagectime{foo3} = 1;
+$IkiWiki::pagectime{bar} = 3;
+$IkiWiki::pagectime{"post/1"} = 6;
+$IkiWiki::pagectime{"post/2"} = 6;
+$IkiWiki::pagectime{"post/3"} = 6;
 $links{foo}=[qw{post/1 post/2}];
 $links{foo2}=[qw{bar}];
 $links{foo3}=[qw{bar}];
@@ -38,6 +45,9 @@ is_deeply([pagespec_match_list("foo", "post/*", sort => "title",
        ["post/1", "post/2"]);
 is_deeply([pagespec_match_list("foo", "*", sort => "path", num => 2)],
        ["bar", "foo"]);
+is_deeply([pagespec_match_list("foo", "foo* or bar*",
+               sort => "-age title")], # oldest first, break ties by title
+       ["foo3", "foo", "foo2", "bar"]);
 my $r=eval { pagespec_match_list("foo", "beep") };
 ok(eval { pagespec_match_list("foo", "beep") } == 0);
 ok(! $@, "does not fail with error when unable to match anything");