From bcfb05123c55f9859ea60b18657bdfdd89678e21 Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 9 Aug 2013 01:14:44 +0200 Subject: [PATCH] suggested plugin: blocks (free relationships) --- .../flexible_relationships_between_pages.mdwn | 84 ++++++++++++ .../blocks.pm.mdwn | 129 ++++++++++++++++++ doc/users/chrysn/interests.mdwn | 1 + 3 files changed, 214 insertions(+) create mode 100644 doc/todo/flexible_relationships_between_pages.mdwn create mode 100644 doc/todo/flexible_relationships_between_pages/blocks.pm.mdwn diff --git a/doc/todo/flexible_relationships_between_pages.mdwn b/doc/todo/flexible_relationships_between_pages.mdwn new file mode 100644 index 000000000..ee3dd8d3a --- /dev/null +++ b/doc/todo/flexible_relationships_between_pages.mdwn @@ -0,0 +1,84 @@ +it has been some years since the [[matching different kinds of links]] issue +was tackled, but hardly a plugin is using it. + +in order to enhance on the [[todo/rel attribute for links]] and [[todo/better bug tracking support]] +issues and to provide a more general infrastructure, i'd like to propose a +generic plugin for typed links. following the use case i've developed it for, +i'll call it `blocks` for the moment (but am open to better suggestions). + +outline +======= + +the plugin has a **configuration option** called `blocks_names`, which consists +of pairs of verbs; the typical example is `blocks/blockedby`, but other values +could be `next/prev up/down` or `owner/owns`. + +for each verb in the options, there is a **directive** which is used to state +the relationship; relationships can be declared on both ends, so a page `bugA` +with the contents `\[[!blocks bugB]]` is semantically equivalent to a page +`bugB` with the contents `\[[!blockedby bugA]]`. + +for each verb, there is also a **pagespec** which matches all pages that are +the origin of a relationship to a given page. if `developerA` `\[[!owns +bug1]]`, then if `bug1` contains `\[[!map pages="owns(.)"]]`, it will show the +owning developer. these specs match both ways, ie. if `bug1` `\[[!owner +developerA]]`, the said map directive will still produce the same result. + +details +======= + +* single word relationships vs. symmetric relationships + + with some verbs, it is possible that a relationship is only used in one + direction (eg `index`, even though one could declare it as + `index/isindexof`). + + other verbs are symmetric, eg. `equivalent`, which need different treatment. + +* "taglink" style directives + + the [[plugins/tag]] plugin would be a special case for this plugin (apart + from the autotag and tagdir features). as there is a `\[[!taglink ...]]` + directive, there could be an analogous directive for every single directive. + +* implementation notes + + the way pagespec hooks are implemented required some nasty perl tricks, for + which the people who showed me felt very bad for having spoilt me. indeed, + `no strict refs;` and `*$forward_name = $forward_match;` are not exactly + ideal. a change in the pagespec declaration api (why not just `hook` like + everything else) would make the implementation cleaner. + +* configuration location + + i aimed for static configuration of the `block_names` in the setup file. this + could be made more general like in the [[plugins/shortcut]] plugin, but that + would make things more complex. + +* no html links with `rel=` yet + + as there are no taglink style links between the articles so far, no htmllink + gets rendered that could carry the relationship name in its rel field. + + having the inverse relationship description in backlinks (as in the link + created by the map directive in the example above) would be hard to + implement. (actually, i think it'd be easier to determine the rel values from + the taggedlinks for *every* htmllink than to influence the backlinks in this + plugin). + +* one direction also creates a normal link + + due to the way add\_link treats relationships, the forward relationship is + always going to be reflected in the links/backlinks. a section of + [[todo/matching different kinds of links]] was dismissed with "let's not + worry about it", this plugin might be reason to worry about it again. (i'd + consider what is in @links to be a representation of which hyperlinks are + there, and in this case, none are generated). + +implementation +============== + +there is a working but slightly incomplete (basically where it comes to the +details mentioned above) implementation in [[blocks.pm]]. + +--chrysn diff --git a/doc/todo/flexible_relationships_between_pages/blocks.pm.mdwn b/doc/todo/flexible_relationships_between_pages/blocks.pm.mdwn new file mode 100644 index 000000000..0ef97b2c1 --- /dev/null +++ b/doc/todo/flexible_relationships_between_pages/blocks.pm.mdwn @@ -0,0 +1,129 @@ +#!/usr/bin/perl +# Ikiwiki "blocks" relationship plugin. +package IkiWiki::Plugin::blocks; + +use warnings; +use strict; +use IkiWiki 3.00; + +sub import { + hook(type => "getsetup", id => "blocks", call => \&getsetup); + hook(type => "checkconfig", id => "skeleton", call => \&checkconfig); +} + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => 1, + }, + blocks_names => { + type => "string", + example => "blocks/blockedby", + description => "comma separated list of defined relationship pairs, the forward and backward name separated by a slash", + safe => 1, + rebuild => 1, + }, +} + +sub checkconfig () { + my $blocksnames; + if (defined $config{blocks_names}) { + $blocksnames = $config{blocks_names}; + } else { + $blocksnames = "blocks/blockedby"; + } + + while ( $blocksnames =~ /([^ ]+)/g ) + { + if ( $1 =~ m@([a-zA-Z0-9]+)(/([a-zA-Z0-9]+))?@ ) + { + my $from = $1; + my $to = $3; + hook( + type => "preprocess", + shortcut => 1, # gets interpreted by listdirectives; see doc/bugs/cannot_preview_shortcuts.mdwn / ikiwiki commit 354d22e2 + no_override => 1, + id => $from, + scan => 1, + call => sub { preprocess_blocks($from, 1, @_); } + ); + if ($to) + { + hook( + type => "preprocess", + shortcut => 1, + no_override => 1, + id => $to, + scan => 1, + call => sub { preprocess_blocks($from, 0, @_); } + ); + } + + my $backward_match; my $backward_name; + my $forward_match; my $forward_name; + + $backward_match = sub ($$;@) { + my $page=shift; + my $glob=shift; + return IkiWiki::PageSpec::match_backlink($page, $glob, linktype => $from, @_); + }; + + $backward_name = "IkiWiki::PageSpec::match_$from"; + + if ($to) + { + $forward_match = sub ($$;@) { + my $page=shift; + my $glob=shift; + return IkiWiki::PageSpec::match_link($page, $glob, linktype => $from, @_); + }; + + $forward_name = "IkiWiki::PageSpec::match_$to"; + } + + { + no strict 'refs'; + + if ($to) + { + *$forward_name = $forward_match; + } + *$backward_name = $backward_match; + } + } else { + error gettext("Malformed option in blocks_names"); + } + } +} + +sub preprocess_blocks ($$@) { + # with flip=0, the directive occurring on page A pointing at page B + # means that A $relation B, with flip=1, it means B $relation A + my $relation = shift; + my $flip = shift; + + if (! @_) { + return ""; + } + my %params=@_; + my $page = $params{page}; + delete $params{page}; + delete $params{destpage}; + delete $params{preview}; + + foreach my $blocks (keys %params) { + $blocks=linkpage($blocks); + + # hidden WikiLink + if ( $flip == 0 ) { + add_link($page, $blocks, $relation); + } else { + add_link($blocks, $page, $relation); + } + } + + return ""; +} + +1 diff --git a/doc/users/chrysn/interests.mdwn b/doc/users/chrysn/interests.mdwn index a70089a45..a5c8f33bf 100644 --- a/doc/users/chrysn/interests.mdwn +++ b/doc/users/chrysn/interests.mdwn @@ -14,6 +14,7 @@ these are the topics [[chrysn]] is or was interested in inside ikiwiki: * [[todo/Better bug tracking support]] * [[todo/calendar with "create" links]] * [[todo/credentials page]] +* [[todo/flexible relationships between pages]] * [[todo/inline postform autotitles]] * [[todo/internal definition list support]] * [[todo/mirrorlist with per-mirror usedirs settings]] -- 2.45.0