commit
bab8fec524
105
IkiWiki.pm
105
IkiWiki.pm
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"]]
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue