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