Merge branch 'master' of ssh://git.ikiwiki.info/srv/git/ikiwiki.info
[ikiwiki] / ikiwiki-transition.in
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 use lib '.'; # For use in nonstandard directory, munged by Makefile.
5 use IkiWiki;
6 use HTML::Entities;
7
8 my $regex = qr{
9         (\\?)           # 1: escape?
10         \[\[(!?)        # directive open; 2: optional prefix
11         ([-\w]+)        # 3: command
12         (               # 4: the parameters (including initial whitespace)
13         \s+
14                 (?:
15                         (?:[-\w]+=)?            # named parameter key?
16                         (?:
17                                 """.*?"""       # triple-quoted value
18                                 |
19                                 "[^"]+"         # single-quoted value
20                                 |
21                                 [^\s\]]+        # unquoted value
22                         )
23                         \s*                     # whitespace or end
24                                                 # of directive
25                 )
26         *)              # 0 or more parameters
27         \]\]            # directive closed
28 }sx;
29
30 sub handle_directive {
31         my $escape = shift;
32         my $prefix = shift;
33         my $directive = shift;
34         my $args = shift;
35
36         if (length $escape) {
37                 return "${escape}[[${prefix}${directive}${args}]]"
38         }
39         if ($directive =~ m/^(if|more|table|template|toggleable)$/) {
40                 $args =~ s{$regex}{handle_directive($1, $2, $3, $4)}eg;
41         }
42         return "[[!${directive}${args}]]"
43 }
44
45 sub prefix_directives {
46         loadsetup(shift);
47
48         IkiWiki::loadplugins();
49         IkiWiki::checkconfig();
50         IkiWiki::loadindex();
51
52         if (! %pagesources) {
53                 error "ikiwiki has not built this wiki yet, cannot transition";
54         }
55
56         foreach my $page (values %pagesources) {
57                 next unless defined pagetype($page) &&
58                             -f $config{srcdir}."/".$page;
59                 my $content=readfile($config{srcdir}."/".$page);
60                 my $oldcontent=$content;
61                 $content=~s{$regex}{handle_directive($1, $2, $3, $4)}eg;
62                 if ($oldcontent ne $content) {
63                         writefile($page, $config{srcdir}, $content);
64                 }
65         }
66 }
67
68 sub indexdb {
69         setstatedir(shift);
70
71         # Note: No lockwiki here because ikiwiki already locks it
72         # before calling this.  
73         if (! IkiWiki::oldloadindex()) {
74                 die "failed to load index\n";
75         }
76         if (! IkiWiki::saveindex()) {
77                 die "failed to save indexdb\n"
78         }
79         if (! IkiWiki::loadindex()) {
80                 die "transition failed, cannot load new indexdb\n";
81         }
82         if (! unlink("$config{wikistatedir}/index")) {
83                 die "unlink failed: $!\n";
84         }
85 }
86
87 sub hashpassword {
88         setstatedir(shift);
89
90         eval q{use IkiWiki::UserInfo};
91         eval q{use Authen::Passphrase::BlowfishCrypt};
92         if ($@) {
93                 error("ikiwiki-transition hashpassword: failed to load Authen::Passphrase, passwords not hashed");
94         }
95
96         IkiWiki::lockwiki();
97         IkiWiki::loadplugin("passwordauth");
98         my $userinfo = IkiWiki::userinfo_retrieve();
99         foreach my $user (keys %{$userinfo}) {
100                 if (ref $userinfo->{$user} &&
101                     exists $userinfo->{$user}->{password} &&
102                     length $userinfo->{$user}->{password} &&
103                     ! exists $userinfo->{$user}->{cryptpassword}) {
104                         IkiWiki::Plugin::passwordauth::setpassword($user, $userinfo->{$user}->{password});
105                 }
106         }
107 }
108
109 sub aggregateinternal {
110         loadsetup(shift);
111         require IkiWiki::Plugin::aggregate;
112         IkiWiki::checkconfig();
113         IkiWiki::Plugin::aggregate::migrate_to_internal();
114 }
115
116 sub setupformat {
117         my $setup=shift;
118
119         loadsetup($setup);
120         IkiWiki::checkconfig();
121         
122         # unpack old-format wrappers setting into new fields
123         my $cgi_seen=0;
124         my $rcs_seen=0;
125         foreach my $wrapper (@{$config{wrappers}}) {
126                 if ($wrapper->{cgi}) {
127                         if ($cgi_seen) {
128                                 die "don't know what to do with second cgi wrapper ".$wrapper->{wrapper}."\n";
129                         }
130                         $cgi_seen++;
131                         print "setting cgi_wrapper to ".$wrapper->{wrapper}."\n";
132                         $config{cgi_wrapper}=$wrapper->{wrapper};
133                         $config{cgi_wrappermode}=$wrapper->{wrappermode}
134                                 if exists $wrapper->{wrappermode};
135                 }
136                 elsif ($config{rcs}) {
137                         if ($rcs_seen) {
138                                 die "don't know what to do with second rcs wrapper ".$wrapper->{wrapper}."\n";
139                         }
140                         $rcs_seen++;
141                         print "setting $config{rcs}_wrapper to ".$wrapper->{wrapper}."\n";
142                         $config{$config{rcs}."_wrapper"}=$wrapper->{wrapper};
143                         $config{$config{rcs}."_wrappermode"}=$wrapper->{wrappermode}
144                                 if exists $wrapper->{wrappermode};
145                 }
146                 else {
147                         die "don't know what to do with wrapper ".$wrapper->{wrapper}."\n";
148                 }
149         }
150
151         IkiWiki::Setup::dump($setup);
152 }
153
154 sub moveprefs {
155         my $setup=shift;
156
157         loadsetup($setup);
158         IkiWiki::checkconfig();
159
160         eval q{use IkiWiki::UserInfo};
161         error $@ if $@;
162
163         foreach my $field (qw{allowed_attachments locked_pages}) {
164                 my $orig=$config{$field};
165                 foreach my $admin (@{$config{adminuser}}) {
166                         my $a=IkiWiki::userinfo_get($admin, $field);
167                         if (defined $a && length $a &&
168                             # might already have been moved
169                             (! defined $orig || $a ne $orig)) {
170                                 if (defined $config{$field} &&
171                                     length $config{$field}) {
172                                         $config{$field}=IkiWiki::pagespec_merge($config{$field}, $a);
173                                 }
174                                 else {
175                                         $config{$field}=$a;
176                                 }
177                         }
178                 }
179         }
180
181         my %banned=map { $_ => 1 } @{$config{banned_users}}, IkiWiki::get_banned_users();
182         $config{banned_users}=[sort keys %banned];
183
184         IkiWiki::Setup::dump($setup);
185 }
186
187 sub deduplinks {
188         loadsetup(shift);
189         IkiWiki::loadplugins();
190         IkiWiki::checkconfig();
191         IkiWiki::loadindex();
192         foreach my $page (keys %links) {
193                 my %l;
194                 $l{$_}=1 foreach @{$links{$page}};
195                 $links{$page}=[keys %l]
196         }
197         IkiWiki::saveindex();
198 }
199
200 sub setstatedir {
201         my $dirorsetup=shift;
202
203         if (! defined $dirorsetup) {
204                 usage();                
205         }
206
207         if (-d $dirorsetup) {
208                 $config{wikistatedir}=$dirorsetup."/.ikiwiki";
209         }
210         elsif (-f $dirorsetup) {
211                 loadsetup($dirorsetup);
212         }
213         else {
214                 error("ikiwiki-transition: $dirorsetup does not exist");
215         }
216
217         if (! -d $config{wikistatedir}) {
218                 error("ikiwiki-transition: $config{wikistatedir} does not exist");
219         }
220 }
221         
222 sub loadsetup {
223         my $setup=shift;
224         if (! defined $setup) {
225                 usage();
226         }
227
228         require IkiWiki::Setup;
229
230         %config = IkiWiki::defaultconfig();
231         IkiWiki::Setup::load($setup);
232 }
233
234 sub usage {
235         print STDERR "Usage: ikiwiki-transition type ...\n";
236         print STDERR "Currently supported transition subcommands:\n";
237         print STDERR "\tprefix_directives setupfile ...\n";
238         print STDERR "\taggregateinternal setupfile\n";
239         print STDERR "\tsetupformat setupfile\n";
240         print STDERR "\tmoveprefs setupfile\n";
241         print STDERR "\thashpassword setupfile|srcdir\n";
242         print STDERR "\tindexdb setupfile|srcdir\n";
243         print STDERR "\tdeduplinks setupfile\n";
244         exit 1;
245 }
246
247 usage() unless @ARGV;
248
249 my $mode=shift;
250 if ($mode eq 'prefix_directives') {
251         prefix_directives(@ARGV);
252 }
253 elsif ($mode eq 'hashpassword') {
254         hashpassword(@ARGV);
255 }
256 elsif ($mode eq 'indexdb') {
257         indexdb(@ARGV);
258 }
259 elsif ($mode eq 'aggregateinternal') {
260         aggregateinternal(@ARGV);
261 }
262 elsif ($mode eq 'setupformat') {
263         setupformat(@ARGV);
264 }
265 elsif ($mode eq 'moveprefs') {
266         moveprefs(@ARGV);
267 }
268 elsif ($mode eq 'deduplinks') {
269         deduplinks(@ARGV);
270 }
271 else {
272         usage();
273 }
274
275 package IkiWiki;
276
277 # A slightly modified version of the old loadindex function.
278 sub oldloadindex {
279         %oldrenderedfiles=%pagectime=();
280         if (! $config{rebuild}) {
281                 %pagesources=%pagemtime=%oldlinks=%links=%depends=
282                         %destsources=%renderedfiles=%pagecase=%pagestate=();
283         }
284         open (my $in, "<", "$config{wikistatedir}/index") || return;
285         while (<$in>) {
286                 chomp;
287                 my %items;
288                 $items{link}=[];
289                 $items{dest}=[];
290                 foreach my $i (split(/ /, $_)) {
291                         my ($item, $val)=split(/=/, $i, 2);
292                         push @{$items{$item}}, decode_entities($val);
293                 }
294
295                 next unless exists $items{src}; # skip bad lines for now
296
297                 my $page=pagename($items{src}[0]);
298                 if (! $config{rebuild}) {
299                         $pagesources{$page}=$items{src}[0];
300                         $pagemtime{$page}=$items{mtime}[0];
301                         $oldlinks{$page}=[@{$items{link}}];
302                         $links{$page}=[@{$items{link}}];
303                         $depends{$page}={ $items{depends}[0] => $IkiWiki::DEPEND_CONTENT } if exists $items{depends};
304                         $destsources{$_}=$page foreach @{$items{dest}};
305                         $renderedfiles{$page}=[@{$items{dest}}];
306                         $pagecase{lc $page}=$page;
307                         foreach my $k (grep /_/, keys %items) {
308                                 my ($id, $key)=split(/_/, $k, 2);
309                                 $pagestate{$page}{decode_entities($id)}{decode_entities($key)}=$items{$k}[0];
310                         }
311                 }
312                 $oldrenderedfiles{$page}=[@{$items{dest}}];
313                 $pagectime{$page}=$items{ctime}[0];
314         }
315
316         # saveindex relies on %hooks being populated, else it won't save
317         # the page state owned by a given hook. But no plugins are loaded
318         # by this program, so populate %hooks with all hook ids that
319         # currently have page state.
320         foreach my $page (keys %pagemtime) {
321                 foreach my $id (keys %{$pagestate{$page}}) {
322                         $hooks{_dummy}{$id}=1;
323                 }
324         }
325         
326         return close($in);
327 }
328
329 # Used to be in IkiWiki/UserInfo, but only used here now.
330 sub get_banned_users () {
331         my @ret;
332         my $userinfo=userinfo_retrieve();
333         foreach my $user (keys %{$userinfo}) {
334                 push @ret, $user if $userinfo->{$user}->{banned};
335         }
336         return @ret;
337 }
338
339 # Used to be in IkiWiki, but only used here (to migrate admin prefs into the
340 # setup file) now.
341 sub pagespec_merge ($$) {
342         my $a=shift;
343         my $b=shift;
344
345         return $a if $a eq $b;
346         return "($a) or ($b)";
347 }
348
349 1