X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/156f70912213b6520e9056050a8827de66e80176..6223b697b4a75e0162878e8181b5df3184c7a0a3:/IkiWiki/Plugin/trail.pm diff --git a/IkiWiki/Plugin/trail.pm b/IkiWiki/Plugin/trail.pm index e9b4d9cd4..d5fb2b5d6 100644 --- a/IkiWiki/Plugin/trail.pm +++ b/IkiWiki/Plugin/trail.pm @@ -1,6 +1,6 @@ #!/usr/bin/perl # Copyright © 2008-2011 Joey Hess -# Copyright © 2009-2011 Simon McVittie +# Copyright © 2009-2012 Simon McVittie # Licensed under the GNU GPL, version 2, or any later version published by the # Free Software Foundation package IkiWiki::Plugin::trail; @@ -12,54 +12,47 @@ use IkiWiki 3.00; sub import { hook(type => "getsetup", id => "trail", call => \&getsetup); hook(type => "needsbuild", id => "trail", call => \&needsbuild); - hook(type => "preprocess", id => "trail", call => \&preprocess_trail, scan => 1); - hook(type => "preprocess", id => "trailinline", call => \&preprocess_trailinline, scan => 1); + hook(type => "preprocess", id => "trailoptions", call => \&preprocess_trailoptions, scan => 1); hook(type => "preprocess", id => "trailitem", call => \&preprocess_trailitem, scan => 1); + hook(type => "preprocess", id => "trailitems", call => \&preprocess_trailitems, scan => 1); hook(type => "preprocess", id => "traillink", call => \&preprocess_traillink, scan => 1); hook(type => "pagetemplate", id => "trail", call => \&pagetemplate); + hook(type => "build_affected", id => "trail", call => \&build_affected); } -=head1 Page state - -If a page C<$T> is a trail, then it can have - -=over - -=item * C<$pagestate{$T}{trail}{contents}> - -Reference to an array of pagespecs or links in the trail. - -=item * C<$pagestate{$T}{trail}{sort}> - -A [[ikiwiki/pagespec/sorting]] order; if absent or undef, the trail is in -the order given by the links that form it - -=item * C<$pagestate{$T}{trail}{circular}> - -True if this trail is circular (i.e. going "next" from the last item is -allowed, and takes you back to the first) - -=item * C<$pagestate{$T}{trail}{reverse}> - -True if C is to be reversed. - -=back - -If a page C<$M> is a member of a trail C<$T>, then it has - -=over - -=item * C<$pagestate{$M}{trail}{item}{$T}[0]> - -The page before this one in C<$T> at the last rebuild, or undef. - -=item * C<$pagestate{$M}{trail}{item}{$T}[1]> - -The page after this one in C<$T> at the last refresh, or undef. - -=back - -=cut +# Page state +# +# If a page $T is a trail, then it can have +# +# * $pagestate{$T}{trail}{contents} +# Reference to an array of lists each containing either: +# - [pagenames => "page1", "page2"] +# Those literal pages +# - [link => "link"] +# A link specification, pointing to the same page that [[link]] +# would select +# - [pagespec => "posts/*", "age", 0] +# A match by pagespec; the third array element is the sort order +# and the fourth is whether to reverse sorting +# +# * $pagestate{$T}{trail}{sort} +# A sorting order; if absent or undef, the trail is in the order given +# by the links that form it +# +# * $pagestate{$T}{trail}{circular} +# True if this trail is circular (i.e. going "next" from the last item is +# allowed, and takes you back to the first) +# +# * $pagestate{$T}{trail}{reverse} +# True if C is to be reversed. +# +# If a page $M is a member of a trail $T, then it has +# +# * $pagestate{$M}{trail}{item}{$T}[0] +# The page before this one in C<$T> at the last rebuild, or undef. +# +# * $pagestate{$M}{trail}{item}{$T}[1] +# The page after this one in C<$T> at the last refresh, or undef. sub getsetup () { return @@ -69,12 +62,20 @@ sub getsetup () { }, } +# Cache of pages' old titles, so we can tell whether they changed +my %old_trail_titles; + sub needsbuild (@) { my $needsbuild=shift; + foreach my $page (keys %pagestate) { if (exists $pagestate{$page}{trail}) { if (exists $pagesources{$page} && grep { $_ eq $pagesources{$page} } @$needsbuild) { + # Remember its title, so we can know whether + # it changed. + $old_trail_titles{$page} = title_of($page); + # Remove state, it will be re-added # if the preprocessor directive is still # there during the rebuild. {item} is the @@ -85,31 +86,13 @@ sub needsbuild (@) { } } } + return $needsbuild; } -=for wiki - -The `trail` directive is supplied by the [[plugins/contrib/trail]] -plugin. It sets options for the trail represented by this page. Example usage: - - \[[!trail sort="meta(date)" circular="no" pages="blog/posts/*"]] - -The available options are: - -* `sort`: sets a [[ikiwiki/pagespec/sorting]] order; if not specified, the - items of the trail are ordered according to the first link to each item - found on the trail page +my $scanned = 0; -* `circular`: if set to `yes` or `1`, the trail is made into a loop by - making the last page's "next" link point to the first page, and the first - page's "previous" link point to the last page - -* `pages`: add the given pages to the trail - -=cut - -sub preprocess_trail (@) { +sub preprocess_trailoptions (@) { my %params = @_; if (exists $params{circular}) { @@ -125,73 +108,21 @@ sub preprocess_trail (@) { $pagestate{$params{page}}{trail}{reverse} = $params{reverse}; } - if (exists $params{pages}) { - push @{$pagestate{$params{page}}{trail}{contents}}, "pagespec $params{pages}"; - } - - if (exists $params{pagenames}) { - my @list = map { "link $_" } split ' ', $params{pagenames}; - push @{$pagestate{$params{page}}{trail}{contents}}, @list; - } - return ""; } -=for wiki - -The `trailinline` directive is supplied by the [[plugins/contrib/trail]] -plugin. It behaves like the [[trail]] and [[inline]] directives combined. -Like [[inline]], it includes the selected pages into the page with the -directive and/or an RSS or Atom feed; like [[trail]], it turns the -included pages into a "trail" in which each page has a link to the -previous and next pages. - - \[[!inline sort="meta(date)" circular="no" pages="blog/posts/*"]] - -All the options for the [[inline]] and [[trail]] directives are valid. - -The `show`, `skip` and `feedshow` options from [[inline]] do not apply -to the trail. - -* `sort`: sets a [[ikiwiki/pagespec/sorting]] order; if not specified, the - items of the trail are ordered according to the first link to each item - found on the trail page - -* `circular`: if set to `yes` or `1`, the trail is made into a loop by - making the last page's "next" link point to the first page, and the first - page's "previous" link point to the last page - -* `pages`: add the given pages to the trail - -=cut - -sub preprocess_trailinline (@) { - preprocess_trail(@_); - return unless defined wantarray; +sub preprocess_trailitem (@) { + my $link = shift; + shift; - if (IkiWiki->can("preprocess_inline")) { - return IkiWiki::preprocess_inline(@_); + # avoid collecting everything in the preprocess stage if we already + # did in the scan stage + if (defined wantarray) { + return "" if $scanned; } else { - error("trailinline directive requires the inline plugin"); + $scanned = 1; } -} - -=for wiki - -The `trailitem` directive is supplied by the [[plugins/contrib/trail]] plugin. -It is used like this: - - \[[!trailitem some_other_page]] - -to add `some_other_page` to the trail represented by this page, without -generating a visible hyperlink. - -=cut - -sub preprocess_trailitem (@) { - my $link = shift; - shift; my %params = @_; my $trail = $params{page}; @@ -199,28 +130,41 @@ sub preprocess_trailitem (@) { $link = linkpage($link); add_link($params{page}, $link, 'trail'); - push @{$pagestate{$params{page}}{trail}{contents}}, "link $link"; + push @{$pagestate{$params{page}}{trail}{contents}}, [link => $link]; return ""; } -=for wiki - -The `traillink` directive is supplied by the [[plugins/contrib/trail]] plugin. -It generates a visible [[ikiwiki/WikiLink]], and also adds the linked page to -the trail represented by the page containing the directive. +sub preprocess_trailitems (@) { + my %params = @_; -In its simplest form, the first parameter is like the content of a WikiLink: + # avoid collecting everything in the preprocess stage if we already + # did in the scan stage + if (defined wantarray) { + return "" if $scanned; + } + else { + $scanned = 1; + } - \[[!traillink some_other_page]] + # trail members from a pagespec ought to be in some sort of order, + # and path is a nice obvious default + $params{sort} = 'path' unless exists $params{sort}; + $params{reverse} = 'no' unless exists $params{reverse}; -The displayed text can also be overridden, either with a `|` symbol or with -a `text` parameter: + if (exists $params{pages}) { + push @{$pagestate{$params{page}}{trail}{contents}}, + ["pagespec" => $params{pages}, $params{sort}, + IkiWiki::yesno($params{reverse})]; + } - \[[!traillink Click_here_to_start_the_trail|some_other_page]] - \[[!traillink some_other_page text="Click here to start the trail"]] + if (exists $params{pagenames}) { + push @{$pagestate{$params{page}}{trail}{contents}}, + [pagenames => (split ' ', $params{pagenames})]; + } -=cut + return ""; +} sub preprocess_traillink (@) { my $link = shift; @@ -242,7 +186,18 @@ sub preprocess_traillink (@) { $link = linkpage($2); add_link($params{page}, $link, 'trail'); - push @{$pagestate{$params{page}}{trail}{contents}}, "link $link"; + + # avoid collecting everything in the preprocess stage if we already + # did in the scan stage + my $already; + if (defined wantarray) { + $already = $scanned; + } + else { + $scanned = 1; + } + + push @{$pagestate{$params{page}}{trail}{contents}}, [link => $link] unless $already; if (defined $linktext) { $linktext = pagetitle($linktext); @@ -284,6 +239,12 @@ sub trails_differ { if (! exists $new->{$trail}) { return 1; } + + if (exists $old_trail_titles{$trail} && + title_of($trail) ne $old_trail_titles{$trail}) { + return 1; + } + my ($old_p, $old_n) = @{$old->{$trail}}; my ($new_p, $new_n) = @{$new->{$trail}}; $old_p = "" unless defined $old_p; @@ -293,9 +254,20 @@ sub trails_differ { if ($old_p ne $new_p) { return 1; } + + if (exists $old_trail_titles{$old_p} && + title_of($old_p) ne $old_trail_titles{$old_p}) { + return 1; + } + if ($old_n ne $new_n) { return 1; } + + if (exists $old_trail_titles{$old_n} && + title_of($old_n) ne $old_trail_titles{$old_n}) { + return 1; + } } foreach my $trail (keys %$new) { @@ -309,14 +281,9 @@ sub trails_differ { my $done_prerender = 0; -my %origsubs; - sub prerender { return if $done_prerender; - $origsubs{render_backlinks} = \&IkiWiki::render_backlinks; - inject(name => "IkiWiki::render_backlinks", call => \&render_backlinks); - %trail_to_members = (); %member_to_trails = (); @@ -326,13 +293,27 @@ sub prerender { my $members = []; my @contents = @{$pagestate{$trail}{trail}{contents}}; - foreach my $c (@contents) { - if ($c =~ m/^pagespec (.*)$/) { - push @$members, pagespec_match_list($trail, $1); + if ($c->[0] eq 'pagespec') { + push @$members, pagespec_match_list($trail, + $c->[1], sort => $c->[2], + reverse => $c->[3]); + } + elsif ($c->[0] eq 'pagenames') { + my @pagenames = @$c; + shift @pagenames; + foreach my $page (@pagenames) { + if (exists $pagesources{$page}) { + push @$members, $page; + } + else { + # rebuild trail if it turns up + add_depends($trail, $page, deptype("presence")); + } + } } - elsif ($c =~ m/^link (.*)$/) { - my $best = bestlink($trail, $1); + elsif ($c->[0] eq 'link') { + my $best = bestlink($trail, $c->[1]); push @$members, $best if length $best; } } @@ -363,8 +344,6 @@ sub prerender { $prev = $members->[$i - 1] if $i > 0; my $next = $members->[$i + 1]; - add_depends($member, $trail); - $member_to_trails{$member}{$trail} = [$prev, $next]; } @@ -380,7 +359,7 @@ sub prerender { if (exists $pagestate{$member}{trail}{item} && ! exists $member_to_trails{$member}) { $rebuild_trail_members{$member} = 1; - delete $pagestate{$member}{trailitem}; + delete $pagestate{$member}{trail}{item}; } } @@ -401,18 +380,20 @@ sub prerender { $done_prerender = 1; } -# This is called at about the right time that we can hijack it to render -# extra pages. -sub render_backlinks ($) { - my $blc = shift; +sub build_affected { + my %affected; - foreach my $member (keys %rebuild_trail_members) { - next unless exists $pagesources{$member}; + # In principle we might not have done this yet, although in practice + # at least the trail itself has probably changed, and its template + # almost certainly contains TRAILS or TRAILLOOP, triggering our + # prerender as a side-effect. + prerender(); - IkiWiki::render($pagesources{$member}, sprintf(gettext("building %s, its previous or next page has changed"), $member)); + foreach my $member (keys %rebuild_trail_members) { + $affected{$member} = sprintf(gettext("building %s, its previous or next page has changed"), $member); } - $origsubs{render_backlinks}($blc); + return %affected; } sub title_of ($) { @@ -430,6 +411,8 @@ sub pagetemplate (@) { my $page = $params{page}; my $template = $params{template}; + return unless length $page; + if ($template->query(name => 'trails') && ! $recursive) { prerender(); @@ -455,13 +438,11 @@ sub pagetemplate (@) { my ($prevurl, $nexturl, $prevtitle, $nexttitle); if (defined $prev) { - add_depends($params{destpage}, $prev); $prevurl = urlto($prev, $page); $prevtitle = title_of($prev); } if (defined $next) { - add_depends($params{destpage}, $next); $nexturl = urlto($next, $page); $nexttitle = title_of($next); }