WIP
[ikiwiki] / IkiWiki / Plugin / tag.pm
1 #!/usr/bin/perl
2 # Ikiwiki tag plugin.
3 package IkiWiki::Plugin::tag;
4
5 use warnings;
6 use strict;
7 use IkiWiki 3.00;
8
9 sub import {
10         hook(type => "getopt", id => "tag", call => \&getopt);
11         hook(type => "getsetup", id => "tag", call => \&getsetup);
12         hook(type => "checkconfig", id => "tag", call => \&checkconfig);
13         hook(type => "preprocess", id => "tag", call => \&preprocess_tag, scan => 1);
14         hook(type => "preprocess", id => "taglink", call => \&preprocess_taglink, scan => 1);
15         hook(type => "pagetemplate", id => "tag", call => \&pagetemplate);
16 }
17
18 sub getopt () {
19         eval q{use Getopt::Long};
20         error($@) if $@;
21         Getopt::Long::Configure('pass_through');
22         GetOptions("tagbase=s" => \$config{tagbase});
23 }
24
25 sub getsetup () {
26         return
27                 plugin => {
28                         safe => 1,
29                         rebuild => undef,
30                 },
31                 tagbase => {
32                         type => "string",
33                         example => "tag",
34                         description => "parent page tags are located under",
35                         safe => 1,
36                         rebuild => 1,
37                 },
38                 tag_autocreate => {
39                         type => "boolean",
40                         example => 1,
41                         description => "autocreate new tag pages?",
42                         safe => 1,
43                         rebuild => undef,
44                 },
45                 tagtypes => {
46                         type => "string",
47                         # TODO document that this can be either an array or a hash with hashes as values
48                         description => "extra categorization types",
49                         example => "[category, column]"
50                 }
51 }
52
53 sub tagtypes() {
54         if (defined $config{tagtypes}) {
55                 my $tagtypes = $config{tagtypes};
56                 if (ref($tagtypes) eq 'HASH') {
57                         return %$tagtypes;
58                 } else {
59                         my %hash = ();
60                         foreach my $tagtype (@$tagtypes) {
61                                 $hash{$tagtype} = {};
62                         }
63                         return %hash;
64                 }
65         } else {
66                 return ();
67         }
68 }
69
70 # Custom tagtypes have an automatic tagbase equal to the tagtype itself,
71 # unless overridden
72 sub tagbase($) {
73         my $tagtype=shift;
74         if ($tagtype eq 'tag') {
75                 if (defined $config{tagbase}) {
76                         return $config{tagbase};
77                 } else {
78                         return undef;
79                 }
80         } else {
81                 my %tagtypes = tagtypes();
82                 if (exists $tagtypes{tagbase}) {
83                         return $tagtypes{tagbase};
84                 } else {
85                         return $tagtype;
86                 }
87         }
88 }
89
90 sub tag_autocreate($) {
91         my $tagtype=shift;
92         if ($tagtype eq 'tag') {
93                 return $config{tag_autocreate};
94         } else {
95                 my %tagtypes = tagtypes();
96                 if (exists $tagtypes{tag_autocreate}) {
97                         return $tagtypes{tag_autocreate};
98                 } else {
99                         return $config{tag_autocreate};
100                 }
101         }
102 }
103
104 sub checkconfig() {
105         my %tagtypes = tagtypes();
106         while (my ($tagtype, $conf) =  each %tagtypes) {
107                 my $directive = (defined $conf->{directive}) ? $conf->{directive} : $tagtype ;
108                 if ($directive) {
109                         debug("defining '$tagtype' tagtype with directive '$directive'");
110                         hook(type => "preprocess", id => $directive, call => sub { preprocess_custom_tag($tagtype, @_) });
111                 } else {
112                         debug("defining '$tagtype' tagtype with no directive");
113                 }
114         }
115 }
116
117 sub taglink ($;$) {
118         my $tag=shift;
119         my $tagtype=shift || 'tag';
120         my $tagbase=tagbase($tagtype);
121
122
123         if ($tag !~ m{^/} && $tagbase) {
124                 $tag="/".$tagbase."/".$tag;
125                 $tag=~y#/#/#s; # squash dups
126         }
127
128         return $tag;
129 }
130
131 sub htmllink_tag ($$$;@) {
132         my $page=shift;
133         my $destpage=shift;
134         my $tag=shift;
135         my %opts=@_;
136
137         return htmllink($page, $destpage, taglink($tag), %opts);
138 }
139
140 sub gentag ($;$) {
141         my $tag=shift;
142         my $tagtype=shift || 'tag';
143
144         my $debug_ac = tag_autocreate($tagtype);
145         my $debug_tb = tagbase($tagtype);
146         debug("gentag $tag;$tagtype ac=$debug_ac tb=$debug_tb");
147
148         if (tag_autocreate($tagtype) ||
149             (tagbase($tagtype) && ! tag_autocreate($tagtype))) {
150             debug("gentag $tag;$tagtype TRUE");
151                 my $tagpage=taglink($tag,$tagtype);
152                 if ($tagpage=~/^\.\/(.*)/) {
153                         $tagpage=$1;
154                 }
155                 else {
156                         $tagpage=~s/^\///;
157                 }
158
159                 my $tagfile = newpagefile($tagpage, $config{default_pageext});
160
161                 add_autofile($tagfile, $tagtype, sub {
162                         my $message=sprintf(gettext("creating %s page %s"), $tagtype, $tagpage);
163                         debug($message);
164
165                         my $template=template("autotag.tmpl");
166                         $template->param(tagname => IkiWiki::basename($tag));
167                         $template->param(tag => $tag);
168                         $template->param(tagtype => $tagtype);
169                         writefile($tagfile, $config{srcdir}, $template->output);
170                         if ($config{rcs}) {
171                                 IkiWiki::disable_commit_hook();
172                                 IkiWiki::rcs_add($tagfile);
173                                 IkiWiki::rcs_commit_staged($message, undef, undef);
174                                 IkiWiki::enable_commit_hook();
175                         }
176                 });
177         }
178 }
179
180 sub preprocess_custom_tag ($@) {
181         my $tagtype=shift;
182         if (! @_) {
183                 return "";
184         }
185         my %params=@_;
186         my $page = $params{page};
187         delete $params{page};
188         delete $params{destpage};
189         delete $params{preview};
190
191         foreach my $tag (keys %params) {
192                 $tag=linkpage($tag);
193
194                 # hidden WikiLink
195                 add_link($page, taglink($tag,$tagtype), $tagtype);
196
197                 gentag($tag, $tagtype);
198         }
199
200         return "";
201 }
202
203 sub preprocess_tag(@) {
204         return preprocess_custom_tag('tag', @_);
205 }
206
207 sub preprocess_taglink (@) {
208         if (! @_) {
209                 return "";
210         }
211         my %params=@_;
212         return join(" ", map {
213                 if (/(.*)\|(.*)/) {
214                         my $tag=linkpage($2);
215                         add_link($params{page}, taglink($tag), 'tag');
216                         gentag($tag);
217                         return htmllink_tag($params{page}, $params{destpage}, $tag,
218                                 linktext => pagetitle($1));
219                 }
220                 else {
221                         my $tag=linkpage($_);
222                         add_link($params{page}, taglink($tag), 'tag');
223                         gentag($tag);
224                         return htmllink_tag($params{page}, $params{destpage}, $tag);
225                 }
226         }
227         grep {
228                 $_ ne 'page' && $_ ne 'destpage' && $_ ne 'preview'
229         } keys %params);
230 }
231
232 sub pagetemplate (@) {
233         my %params=@_;
234         my $page=$params{page};
235         my $destpage=$params{destpage};
236         my $template=$params{template};
237
238         my $tags = $typedlinks{$page}{tag};
239
240         $template->param(tags => [
241                 map { 
242                         link => htmllink_tag($page, $destpage, $_, rel => "tag")
243                 }, sort keys %$tags
244         ]) if defined $tags && %$tags && $template->query(name => "tags");
245
246         if ($template->query(name => "categories")) {
247                 # It's an rss/atom template. Add any categories.
248                 if (defined $tags && %$tags) {
249                         $template->param(categories => [map { category => $_ },
250                                 sort keys %$tags]);
251                 }
252         }
253 }
254
255 package IkiWiki::PageSpec;
256
257 sub match_tagged ($$;@) {
258         my $page=shift;
259         my $glob=IkiWiki::Plugin::tag::taglink(shift);
260         return match_link($page, $glob, linktype => 'tag', @_);
261 }
262
263 1