Merge remote branch 'smcv/ready/sort-package'

Conflicts:
	debian/NEWS
master
Joey Hess 2010-04-06 23:15:33 -04:00
commit bab8fec524
9 changed files with 259 additions and 26 deletions

View File

@ -37,6 +37,7 @@ our $DEPEND_LINKS=4;
# Optimisation.
use Memoize;
memoize("abs2rel");
memoize("sortspec_translate");
memoize("pagespec_translate");
memoize("template_file");
@ -1950,6 +1951,66 @@ sub add_link ($$;$) {
}
}
sub sortspec_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::SortSpec::{"cmp_$word"}) {
if (defined $params) {
push @data, $params;
$code .= "IkiWiki::SortSpec::cmp_$word(\$data[$#data])";
}
else {
$code .= "IkiWiki::SortSpec::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;
@ -2050,27 +2111,8 @@ sub pagespec_match_list ($$;@) {
}
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 = IkiWiki::SortSpec::sort_pages($params{sort},
@candidates);
}
@candidates=reverse(@candidates) if $params{reverse};
@ -2390,4 +2432,25 @@ sub match_ip ($$;@) {
}
}
package IkiWiki::SortSpec;
# This is in the SortSpec namespace so that the $a and $b that sort() uses
# $IkiWiki::SortSpec::a and $IkiWiki::SortSpec::b, so that plugins' cmp
# functions can access them easily.
sub sort_pages
{
my $f = IkiWiki::sortspec_translate(shift);
return sort $f @_;
}
sub cmp_title {
IkiWiki::pagetitle(IkiWiki::basename($a))
cmp
IkiWiki::pagetitle(IkiWiki::basename($b))
}
sub cmp_mtime { $IkiWiki::pagemtime{$b} <=> $IkiWiki::pagemtime{$a} }
sub cmp_age { $IkiWiki::pagectime{$b} <=> $IkiWiki::pagectime{$a} }
1

View File

@ -88,7 +88,18 @@ sub preprocess (@) {
# Metadata collection that needs to happen during the scan pass.
if ($key eq 'title') {
$pagestate{$page}{meta}{title}=HTML::Entities::encode_numeric($value);
my $encoded = HTML::Entities::encode_numeric($value);
$pagestate{$page}{meta}{title} = $encoded;
if (exists $params{sortas}) {
$pagestate{$page}{meta}{titlesort}=$params{sortas};
}
elsif ($encoded ne $value) {
$pagestate{$page}{meta}{titlesort}=$value;
}
else {
delete $pagestate{$page}{meta}{titlesort};
}
return "";
}
elsif ($key eq 'description') {
@ -116,6 +127,12 @@ sub preprocess (@) {
}
elsif ($key eq 'author') {
$pagestate{$page}{meta}{author}=$value;
if (exists $params{sortas}) {
$pagestate{$page}{meta}{authorsort}=$params{sortas};
}
else {
delete $pagestate{$page}{meta}{authorsort};
}
# fallthorough
}
elsif ($key eq 'authorurl') {
@ -282,6 +299,33 @@ sub pagetemplate (@) {
}
}
sub get_sort_key {
my $page = $_[0];
my $meta = $_[1];
# e.g. titlesort (also makes sense for author)
my $key = $pagestate{$page}{meta}{$meta . "sort"};
return $key if defined $key;
# e.g. title
$key = $pagestate{$page}{meta}{$meta};
return $key if defined $key;
# fall back to closer-to-core things
if ($meta eq 'title') {
return pagetitle(IkiWiki::basename($page));
}
elsif ($meta eq 'date') {
return $IkiWiki::pagectime{$page};
}
elsif ($meta eq 'updated') {
return $IkiWiki::pagemtime{$page};
}
else {
return '';
}
}
sub match {
my $field=shift;
my $page=shift;
@ -332,4 +376,27 @@ sub match_copyright ($$;@) {
IkiWiki::Plugin::meta::match("copyright", @_);
}
package IkiWiki::SortSpec;
sub cmp_meta {
my $meta = $_[0];
error(gettext("sort=meta requires a parameter")) unless defined $meta;
if ($meta eq 'updated' || $meta eq 'date') {
return IkiWiki::Plugin::meta::get_sort_key($a, $meta)
<=>
IkiWiki::Plugin::meta::get_sort_key($b, $meta);
}
return IkiWiki::Plugin::meta::get_sort_key($a, $meta)
cmp
IkiWiki::Plugin::meta::get_sort_key($b, $meta);
}
# A prototype of how sort=title could behave in 4.0 or something
sub cmp_meta_title {
$_[0] = 'title';
return cmp_meta(@_);
}
1

View File

@ -0,0 +1,32 @@
#!/usr/bin/perl
# Sort::Naturally-powered title_natural sort order for IkiWiki
package IkiWiki::Plugin::sortnaturally;
use IkiWiki 3.00;
no warnings;
sub import {
hook(type => "getsetup", id => "sortnaturally", call => \&getsetup);
}
sub getsetup {
return
plugin => {
safe => 1,
rebuild => 1,
},
}
sub checkconfig () {
eval q{use Sort::Naturally};
error $@ if $@;
}
package IkiWiki::SortSpec;
sub cmp_title_natural {
Sort::Naturally::ncmp(IkiWiki::pagetitle(IkiWiki::basename($a)),
IkiWiki::pagetitle(IkiWiki::basename($b)))
}
1;

4
debian/NEWS vendored
View File

@ -1,4 +1,8 @@
ikiwiki (3.20100406) unstable; urgency=low
The title_natural sort method (as used by the inline directive, etc)
have been moved to the new sortnaturally plugin, which is not enabled
by default since it requires the Sort::Naturally perl module.
Starting from this version, the `tagged()` pagespec only matches tags,
not regular wikilinks. If your wiki accidentially relied on the old,

View File

@ -23,6 +23,13 @@ Supported fields:
be set to a true value in the template; this can be used to format things
differently in this case.
An optional `sortas` parameter will be used preferentially when
[[ikiwiki/pagespec/sorting]] by `meta(title)`:
\[[!meta title="The Beatles" sortas="Beatles, The"]]
\[[!meta title="David Bowie" sortas="Bowie, David"]]
* license
Specifies a license for the page, for example, "GPL". Can contain
@ -37,6 +44,11 @@ Supported fields:
Specifies the author of a page.
An optional `sortas` parameter will be used preferentially when
[[ikiwiki/pagespec/sorting]] by `meta(author)`:
\[[!meta author="Joey Hess" sortas="Hess, Joey"]]
* authorurl
Specifies an url for the author of a page.

View File

@ -5,9 +5,23 @@ orders can be specified.
* `age` - List pages from the most recently created to the oldest.
* `mtime` - List pages with the most recently modified first.
* `title` - Order by title.
* `title_natural` - Only available if [[!cpan Sort::Naturally]] is
installed. Orders by title, but numbers in the title are treated
* `title` - Order by title (page name).
[[!if test="enabled(sortnaturally)" then="""
* `title_natural` - Orders by title, but numbers in the title are treated
as such, ("1 2 9 10 20" instead of "1 10 2 20 9")
"""]]
[[!if test="enabled(meta)" then="""
* `meta(title)` - Order according to the `\[[!meta title="foo" sortas="bar"]]`
or `\[[!meta title="foo"]]` [[ikiwiki/directive]], or the page name if no
full title was set. `meta(author)`, `meta(date)`, `meta(updated)`, etc.
also work.
"""]]
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.
[[!meta robots="noindex, follow"]]

View File

@ -0,0 +1,5 @@
[[!template id=plugin name=sortnaturally core=1 author="[[chrysn]], [[smcv]]"]]
[[!tag type/meta]]
This plugin provides the `title_natural` [[ikiwiki/pagespec/sorting]] order,
which uses Sort::Naturally to sort numbered pages in a more natural order.

View File

@ -1129,6 +1129,24 @@ 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::SortSpec package named `cmp_foo`, which will be used when sorting
by `foo` or `foo(...)` is requested.
The names of pages to be compared are in the global variables `$a` and `$b`
in the IkiWiki::SortSpec package. The function should return the same thing
as Perl's `cmp` and `<=>` operators: negative if `$a` is less than `$b`,
positive if `$a` 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.
The function will also be passed one or more parameters. The first is
`undef` if invoked as `foo`, or the parameter `"bar"` if invoked as `foo(bar)`;
it may also be passed additional, named parameters.
### Setup plugins
The ikiwiki setup file is loaded using a pluggable mechanism. If you look

View File

@ -1,7 +1,7 @@
#!/usr/bin/perl
use warnings;
use strict;
use Test::More tests => 88;
use Test::More tests => 90;
BEGIN { use_ok("IkiWiki"); }
@ -9,6 +9,12 @@ BEGIN { use_ok("IkiWiki"); }
$config{srcdir}=$config{destdir}="/dev/null";
IkiWiki::checkconfig();
{
package IkiWiki::SortSpec;
sub cmp_path { $a cmp $b }
}
%pagesources=(
foo => "foo.mdwn",
foo2 => "foo2.mdwn",
@ -18,6 +24,13 @@ IkiWiki::checkconfig();
"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}];
@ -34,6 +47,11 @@ is_deeply([pagespec_match_list("foo", "post/*", sort => "title", num => 50)],
is_deeply([pagespec_match_list("foo", "post/*", sort => "title",
filter => sub { $_[0] =~ /3/}) ],
["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");