now changed the subset plugin interface
[ikiwiki] / doc / todo / pagespec_aliases.mdwn
1 [[!tag patch wishlist]]I quite often find myself repeating a boiler-plate
2 [[ikiwiki/pagespec]] chunk, e.g.
3
4     and !*.png and !*.jpg...
5
6 it would be quite nice if I could conveniently bundle them together into a
7 pagespec "alias", and instead write
8
9     and !image()...
10
11 I wrote the following plugin to achieve this:
12
13     commit f3a9dd113338fe5d2b717de1dc69679ff74e2f8d
14     Author: Jon Dowland <jmtd@debian.org>
15     Date:   Tue May 3 17:40:16 2011 +0100
16     
17         new plugin: alias.pm - pagespec aliases
18     
19     diff --git a/IkiWiki/Plugin/alias.pm b/IkiWiki/Plugin/alias.pm
20     new file mode 100644
21     index 0000000..b8d4574
22     --- /dev/null
23     +++ b/IkiWiki/Plugin/alias.pm
24     @@ -0,0 +1,47 @@
25     +package IkiWiki::Plugin::alias;
26     +
27     +use warnings;
28     +use strict;
29     +use IkiWiki '3.00';
30     +
31     +sub import {
32     +  hook(type => "getsetup", id=> "alias", call => \&getsetup);
33     +  hook(type => "checkconfig", id=> "alias", call => \&checkconfig);
34     +}
35     +
36     +sub getsetup () {
37     +    return
38     +        plugin => {
39     +            description => "allows the definition of pagespec aliases",
40     +            safe => 1,
41     +            rebuild => 1,
42     +            section => "misc",
43     +        },
44     +        pagespec_aliases => {
45     +            type => "string",
46     +            example => {"image" => "*jpg or *jpeg or *png or *gif or *ico" },
47     +            description => "a set of mappings from alias name to pagespec",
48     +            safe => 1,
49     +            rebuild => 0,
50     +        },
51     +}
52     +
53     +sub checkconfig () {
54     +    no strict 'refs';
55     +    no warnings 'redefine';
56     +
57     +    if ($config{pagespec_aliases}) {
58     +        foreach my $key (keys %{$config{pagespec_aliases}}) {
59     +            my $value = ${$config{pagespec_aliases}}{$key};
60     +            # XXX: validate key?
61     +            my $subname = "IkiWiki::PageSpec::match_$key";
62     +            *{ $subname } = sub {
63     +              my $path = shift;
64     +              return IkiWiki::pagespec_match($path, $value);
65     +            }
66     +        }
67     +    }
68     +}
69     +
70     +1;
71
72 I need to reflect on this a bit more before I send a pull request.  In
73 particular I imagine the strict/warnings stuff will make you puke.  Also, I'm
74 not sure whether I should name-grab 'alias' since [[todo/alias_directive]] is
75 an existing wishlist item.
76
77 > I think it would make sense to have "pagespec" in the name somehow.
78
79 > > Good idea, how about `pagespecalias`? — [[Jon]]
80
81 >
82 > No, the strict/warnings does not make me puke. Have you read my perl
83 > code? :-P
84
85 > Note that your XXX is right. It would be a security hole to not validate
86 > `$key`, as anyone with websetup access could cause it to run arbitrary
87 > perl code.
88
89 > Well, except that websetup doesn't currently support configuring hashes
90 > like used here. Which is a pity, but has led me to try to avoid using
91 > such hashes in the setup file.
92
93 > > If I removed the `getsetup` subroutine, it would not be exposed via
94 > > website, is that right?  I suppose it doesn't hurt to validate key, even if
95 > > this risk was not there.  Is the use of a hash here a blocker for adoption?
96 > > — [[Jon]]
97
98 > Have you considered not defining the pagespec aliases in the setup file, but
99 > instead as directives on pages in the wiki? Using pagestate could store
100 > up the aliases that have been defined. It could however, be hard to get
101 > the dependencies right; any page that uses a pagespec containing 
102 > an alias `foo` would need to somehow depend on the page where the alias
103 > was defined. --[[Joey]] 
104
105 > > I haven't thought the dependency issue through beyond "that might be hard".
106 > > Personally, I don't like defining stuff like this in pages, but I appreciate
107 > > some do.  There could be some complex scenarios where some pages rely on a
108 > > pagespec alias defined on others; and could have their meanings changed by
109 > > changing the definition.  A user might have permission to edit a page with a
110 > > definition on it but not on the pages that use it, and similar subtle permission
111 > > bugs.  I'm also not sure what the failure mode is if someone redefines an alias,
112 > > and whether there'd be an unpredictable precedence problem.
113 > > How about both methods? — [[Jon]]
114
115 Here's an example setup chunk:
116
117      pagespec_aliases:
118        image: "*.png or *.jpg or *.jpeg or *.gif or *.ico"
119        helper: "*.css or *.js"
120        boring: "image() or helper()"
121
122 The above demonstrates self-referential dynamic pagespec aliases.  It doesn't work,
123 however, to add ' or internal()' to `boring`, for some reason.
124
125 -- [[Jon]]
126
127 > Probably needs to be `or internal(*)` --[[Joey]] 
128
129 > > Ah yes, could be, thanks. — [[Jon]]
130
131 > another useful pagespec alias for large maps:
132
133        basewiki: "sandbox or templates or templates/* or ikiwiki or ikiwiki/* or shortcuts or recentchanges or wikiicons/*"
134
135 > -- [[Jon]]
136
137 >> Useful indeed! --[[Joey]] 
138
139 ---------------------------
140
141 Based on the above, I have written an experimental plugin called "subset".
142 It's in my "ikiplugins" repo on github, in the "experimental" branch.
143 <https://github.com/rubykat/ikiplugins/blob/experimental/IkiWiki/Plugin/subset.pm>
144
145 It takes Joey's suggestion of defining the subsets (aliases) as directives;
146 I took the example of the [[plugins/shortcut]] plugin and designated a single special page as the one where the directives are defined,
147 though unlike "shortcut" I haven't hardcoded the name of the page; it defaults to "subsets" but it can be re-defined in the config.
148
149 I've also added a feature which one might call subset-caching; I had to override `pagespec_match_list` to do it, however.
150 An extra parameter added to `pagespec_match_list` called `subset` which
151
152 * limits the result to look *only* within the set of pages defined by the subset (uses the "list" option to pagespec_match_list to do this)
153 * caches the result of the subset search so that the second time subset "foo" is used, it uses the stored result of the first search for "foo".
154
155 This speeds things up if one is using a particular subset more than once, which one probably is if one bothered to define the subset in the first place.
156 The speed increase is most dramatic when the site has a large number of pages and the number of pages in the subset is small.
157 (this is similar to the "trail" concept I used in my [[plugins/contrib/report]] plugin, but not quite the same)
158
159 Note that things like [[plugins/map]] can't make use of "subset" (yet) because they don't pass along all the parameters they're given.
160 But [[plugins/contrib/report]] actually works without alteration because it does pass along all the parameters.
161
162 Unfortunately I haven't figured out how to do the dependencies - I'd really appreciate help on that.
163
164 --[[KathrynAndersen]]
165
166 > > Cool!  I like the caching idea.  I'm not sure about the name.  I don't like defining
167 > > stuff in pages, but I appreciate this is a matter of taste, and would be happy with
168 > > supporting both. — [[Jon]]
169
170 >>> I've now gone and completely re-done "subset" so that it is less like an alias, but it a bit clearer and simpler:
171 >>> instead of having a separate "match_" function for every alias, I simply have one function, "match_subset"
172 >>> which takes the name of the subset.  Thus a \[[!subset name="foo"...]] would be called `subset(foo)` rather than `foo()`.
173
174 >>> There are a few reasons for this:<br/>
175 >>> (a) it's more secure not to be evaluating code on the fly<br/>
176 >>> (b) it's simpler<br/>
177 >>> (c) (and this was my main reason) it makes it possible to do caching without having to have a separate "subset" argument.
178 >>> I've done a bit of a hack for this: basically, the PageSpec is checked to see if the very start of the PageSpec is `subset(foo) and` or if the whole pagespec is just `subset(foo)` and if either of those is true, then it does the subset caching stuff.
179 >>> The reason I check for "and" is that if it is "subset(foo) or something" then it would be an error to use the subset cache in that case.
180 >>> The reason I just check the start of the PageSpec is because I don't want to have to do complex parsing of the PageSpec.
181
182 >>> As for defining subsets in the config rather than on pages, I perfectly understand that desire, and I could probably add that in.
183
184 >>> As for the name "subset"... well, it's even less like an alias now, and "alias" is already a reserved name.  What other names would you suggest?
185
186 >>>--[[KathrynAndersen]]