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