Merge remote branch 'smcv/ready/sort-package'
authorJoey Hess <joey@gnu.kitenet.net>
Wed, 7 Apr 2010 03:15:33 +0000 (23:15 -0400)
committerJoey Hess <joey@gnu.kitenet.net>
Wed, 7 Apr 2010 03:15:33 +0000 (23:15 -0400)
Conflicts:
debian/NEWS

1  2 
IkiWiki.pm
debian/NEWS
doc/plugins/write.mdwn

diff --combined IkiWiki.pm
index 2415307d456b1f0b2aabcdaa569666e33337475c,da36494fb4e03f3f1808d7ea68f9a329b71ccab8..6d2f4dac3082502ced4073abe9c908fd90c9e5a5
@@@ -14,7 -14,7 +14,7 @@@ use open qw{:utf8 :std}
  use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase
            %pagestate %wikistate %renderedfiles %oldrenderedfiles
            %pagesources %destsources %depends %depends_simple %hooks
 -          %forcerebuild %loaded_plugins};
 +          %forcerebuild %loaded_plugins %typedlinks %oldtypedlinks};
  
  use Exporter q{import};
  our @EXPORT = qw(hook debug error template htmlpage deptype
@@@ -24,7 -24,7 +24,7 @@@
                 add_underlay pagetitle titlepage linkpage newpagefile
                 inject add_link
                   %config %links %pagestate %wikistate %renderedfiles
 -                 %pagesources %destsources);
 +                 %pagesources %destsources %typedlinks);
  our $VERSION = 3.00; # plugin interface version, next is ikiwiki version
  our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE
  our $installdir='/usr'; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE
@@@ -37,6 -37,7 +37,7 @@@ our $DEPEND_LINKS=4
  # Optimisation.
  use Memoize;
  memoize("abs2rel");
+ memoize("sortspec_translate");
  memoize("pagespec_translate");
  memoize("template_file");
  
@@@ -1164,7 -1165,7 +1165,7 @@@ sub htmlize ($$$$) 
        my $content=shift;
        
        my $oneline = $content !~ /\n/;
 -
 +      
        if (exists $hooks{htmlize}{$type}) {
                $content=$hooks{htmlize}{$type}{call}->(
                        page => $page,
        
        if ($oneline) {
                # hack to get rid of enclosing junk added by markdown
 -              # and other htmlizers
 +              # and other htmlizers/sanitizers
                $content=~s/^<p>//i;
 -              $content=~s/<\/p>$//i;
 -              chomp $content;
 +              $content=~s/<\/p>\n*$//i;
        }
  
        return $content;
@@@ -1502,7 -1504,7 +1503,7 @@@ sub loadindex () 
        if (! $config{rebuild}) {
                %pagesources=%pagemtime=%oldlinks=%links=%depends=
                %destsources=%renderedfiles=%pagecase=%pagestate=
 -              %depends_simple=();
 +              %depends_simple=%typedlinks=%oldtypedlinks=();
        }
        my $in;
        if (! open ($in, "<", "$config{wikistatedir}/indexdb")) {
                        if (exists $d->{state}) {
                                $pagestate{$page}=$d->{state};
                        }
 +                      if (exists $d->{typedlinks}) {
 +                              $typedlinks{$page}=$d->{typedlinks};
 +
 +                              while (my ($type, $links) = each %{$typedlinks{$page}}) {
 +                                      next unless %$links;
 +                                      $oldtypedlinks{$page}{$type} = {%$links};
 +                              }
 +                      }
                }
                $oldrenderedfiles{$page}=[@{$d->{dest}}];
        }
@@@ -1624,10 -1618,6 +1625,10 @@@ sub saveindex () 
                        $index{page}{$src}{depends_simple} = $depends_simple{$page};
                }
  
 +              if (exists $typedlinks{$page} && %{$typedlinks{$page}}) {
 +                      $index{page}{$src}{typedlinks} = $typedlinks{$page};
 +              }
 +
                if (exists $pagestate{$page}) {
                        foreach my $id (@hookids) {
                                foreach my $key (keys %{$pagestate{$page}{$id}}) {
@@@ -1807,7 -1797,7 +1808,7 @@@ sub add_depends ($$;$) 
  
        # Add explicit dependencies for influences.
        my $sub=pagespec_translate($pagespec);
 -      return if $@;
 +      return unless defined $sub;
        foreach my $p (keys %pagesources) {
                my $r=$sub->($p, location => $page);
                my $i=$r->influences;
@@@ -1937,19 -1927,74 +1938,79 @@@ sub inject 
        use warnings;
  }
  
 -sub add_link ($$) {
 +sub add_link ($$;$) {
        my $page=shift;
        my $link=shift;
 +      my $type=shift;
  
        push @{$links{$page}}, $link
                unless grep { $_ eq $link } @{$links{$page}};
 +
 +      if (defined $type) {
 +              $typedlinks{$page}{$type}{$link} = 1;
 +      }
  }
  
+ 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;
  
@@@ -2017,7 -2062,7 +2078,7 @@@ sub pagespec_match ($$;@) 
  
        my $sub=pagespec_translate($spec);
        return IkiWiki::ErrorReason->new("syntax error in pagespec \"$spec\"")
 -              if $@ || ! defined $sub;
 +              if ! defined $sub;
        return $sub->($page, @params);
  }
  
@@@ -2035,7 -2080,7 +2096,7 @@@ sub pagespec_match_list ($$;@) 
  
        my $sub=pagespec_translate($pagespec);
        error "syntax error in pagespec \"$pagespec\""
 -              if $@ || ! defined $sub;
 +              if ! defined $sub;
  
        my @candidates;
        if (exists $params{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};
  sub pagespec_valid ($) {
        my $spec=shift;
  
 -      my $sub=pagespec_translate($spec);
 -      return ! $@;
 +      return defined pagespec_translate($spec);
  }
  
  sub glob2re ($) {
@@@ -2228,34 -2255,26 +2270,34 @@@ sub match_link ($$;@) 
  
        $link=derel($link, $params{location});
        my $from=exists $params{location} ? $params{location} : '';
 +      my $linktype=$params{linktype};
 +      my $qualifier='';
 +      if (defined $linktype) {
 +              $qualifier=" with type $linktype";
 +      }
  
        my $links = $IkiWiki::links{$page};
 -      return IkiWiki::FailReason->new("$page has no links", "" => 1)
 +      return IkiWiki::FailReason->new("$page has no links", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
                unless $links && @{$links};
        my $bestlink = IkiWiki::bestlink($from, $link);
        foreach my $p (@{$links}) {
                if (length $bestlink) {
 -                      return IkiWiki::SuccessReason->new("$page links to $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
 -                              if $bestlink eq IkiWiki::bestlink($page, $p);
 +                      if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p}) && $bestlink eq IkiWiki::bestlink($page, $p)) {
 +                              return IkiWiki::SuccessReason->new("$page links to $link$qualifier", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
 +                      }
                }
                else {
 -                      return IkiWiki::SuccessReason->new("$page links to page $p matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
 -                              if match_glob($p, $link, %params);
 +                      if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p}) && match_glob($p, $link, %params)) {
 +                              return IkiWiki::SuccessReason->new("$page links to page $p$qualifier, matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
 +                      }
                        my ($p_rel)=$p=~/^\/?(.*)/;
                        $link=~s/^\///;
 -                      return IkiWiki::SuccessReason->new("$page links to page $p_rel matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
 -                              if match_glob($p_rel, $link, %params);
 +                      if ((!defined $linktype || exists $IkiWiki::typedlinks{$page}{$linktype}{$p_rel}) && match_glob($p_rel, $link, %params)) {
 +                              return IkiWiki::SuccessReason->new("$page links to page $p_rel$qualifier, matching $link", $page => $IkiWiki::DEPEND_LINKS, "" => 1)
 +                      }
                }
        }
 -      return IkiWiki::FailReason->new("$page does not link to $link", "" => 1);
 +      return IkiWiki::FailReason->new("$page does not link to $link$qualifier", $page => $IkiWiki::DEPEND_LINKS, "" => 1);
  }
  
  sub match_backlink ($$;@) {
@@@ -2390,4 -2409,25 +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
diff --combined debian/NEWS
index e1cb004730f0614341797c6c257d12fd510021a0,614eb11f8642272c551401ecf915f2cc7d5b2491..b796154fa2f52506742b2656147d672347401c60
@@@ -1,15 -1,10 +1,19 @@@
 -ikiwiki (3.20100320) UNRELEASED; urgency=low
 +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.
  
 -  The sort="title_natural" option on [[!inline]] etc. now requires the
 -  new sortnaturally plugin. This is not enabled by default, because it requires
 -  the Sort::Naturally module.
 +  Starting from this version, the `tagged()` pagespec only matches tags,
 +  not regular wikilinks. If your wiki accidentially relied on the old,
 +  buggy behavior, you might need to change pagespecs to use `link()`.
  
 - -- Simon McVittie <smcv@debian.org>  Sat, 03 Apr 2010 13:46:08 +0100
 +  To support the above change, all wikis need to be rebuilt on upgrade to
 +  this version. If you listed your wiki in /etc/ikiwiki/wikilist this will
 +  be done automatically when the Debian package is upgraded. Or use 
 +  ikiwiki-mass-rebuild to force a rebuild.
 +
 + -- Simon McVittie <smcv@debian.org>  Tue, 06 Apr 2010 20:53:07 +0100
  
  ikiwiki (3.20091017) unstable; urgency=low
  
diff --combined doc/plugins/write.mdwn
index 71ff1fd29e42e9333fe25674191a8fc32391edfb,f42cc86ae29bc00f26ed655d57bf2f83adbb3869..05ddf2215de9872bbc891d5d288b5a16df55ae6c
@@@ -633,22 -633,6 +633,22 @@@ reference. Do not modify this hash dire
  
        $links{"foo"} = ["bar", "baz"];
  
 +### `%typedlinks`
 +
 +The `%typedlinks` hash records links of specific types. Do not modify this
 +hash directly; call `add_link()`. The keys are page names, and the values
 +are hash references. In each page's hash reference, the keys are link types
 +defined by plugins, and the values are hash references with link targets
 +as keys, and 1 as a dummy value, something like this:
 +
 +      $typedlinks{"foo"} = {
 +              tag => { short_word => 1, metasyntactic_variable => 1 },
 +              next_page => { bar => 1 },
 +      };
 +
 +Ordinary [[WikiLinks|ikiwiki/WikiLink]] appear in `%links`, but not in
 +`%typedlinks`.
 +
  ### `%pagesources`
  
  The `%pagesources` has can be used to look up the source filename
@@@ -955,14 -939,11 +955,14 @@@ Optionally, a third parameter can be pa
  filename of the page. For example, `targetpage("foo", "rss", "feed")`
  will yield something like `foo/feed.rss`.
  
 -### `add_link($$)`
 +### `add_link($$;$)`
  
  This adds a link to `%links`, ensuring that duplicate links are not
  added. Pass it the page that contains the link, and the link text.
  
 +An optional third parameter sets the link type. If not specified,
 +it is an ordinary [[ikiwiki/WikiLink]].
 +
  ## Miscellaneous
  
  ### Internal use pages
@@@ -1129,6 -1110,24 +1129,24 @@@ For example, "backlink(foo)" is influen
  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