1#!/usr/bin/perl -w 2 3use warnings; 4use strict; 5use Text::Wrap; 6 7my $srcpath = undef; 8my $wikipath = undef; 9my $warn_about_missing = 0; 10my $copy_direction = 0; 11 12foreach (@ARGV) { 13 $warn_about_missing = 1, next if $_ eq '--warn-about-missing'; 14 $copy_direction = 1, next if $_ eq '--copy-to-headers'; 15 $copy_direction = 1, next if $_ eq '--copy-to-header'; 16 $copy_direction = -1, next if $_ eq '--copy-to-wiki'; 17 $srcpath = $_, next if not defined $srcpath; 18 $wikipath = $_, next if not defined $wikipath; 19} 20 21my $wordwrap_mode = 'mediawiki'; 22sub wordwrap_atom { # don't call this directly. 23 my $str = shift; 24 return fill('', '', $str); 25} 26 27sub wordwrap_with_bullet_indent { # don't call this directly. 28 my $bullet = shift; 29 my $str = shift; 30 my $retval = ''; 31 32 #print("WORDWRAP BULLET ('$bullet'):\n\n$str\n\n"); 33 34 # You _can't_ (at least with Pandoc) have a bullet item with a newline in 35 # MediaWiki, so _remove_ wrapping! 36 if ($wordwrap_mode eq 'mediawiki') { 37 $retval = "$bullet$str"; 38 $retval =~ s/\n/ /gms; 39 $retval =~ s/\s+$//gms; 40 #print("WORDWRAP BULLET DONE:\n\n$retval\n\n"); 41 return "$retval\n"; 42 } 43 44 my $bulletlen = length($bullet); 45 46 # wrap it and then indent each line to be under the bullet. 47 $Text::Wrap::columns -= $bulletlen; 48 my @wrappedlines = split /\n/, wordwrap_atom($str); 49 $Text::Wrap::columns += $bulletlen; 50 51 my $prefix = $bullet; 52 my $usual_prefix = ' ' x $bulletlen; 53 54 foreach (@wrappedlines) { 55 $retval .= "$prefix$_\n"; 56 $prefix = $usual_prefix; 57 } 58 59 return $retval; 60} 61 62sub wordwrap_one_paragraph { # don't call this directly. 63 my $retval = ''; 64 my $p = shift; 65 #print "\n\n\nPARAGRAPH: [$p]\n\n\n"; 66 if ($p =~ s/\A([\*\-] )//) { # bullet list, starts with "* " or "- ". 67 my $bullet = $1; 68 my $item = ''; 69 my @items = split /\n/, $p; 70 foreach (@items) { 71 if (s/\A([\*\-] )//) { 72 $retval .= wordwrap_with_bullet_indent($bullet, $item); 73 $item = ''; 74 } 75 s/\A\s*//; 76 $item .= "$_\n"; # accumulate lines until we hit the end or another bullet. 77 } 78 if ($item ne '') { 79 $retval .= wordwrap_with_bullet_indent($bullet, $item); 80 } 81 } else { 82 $retval = wordwrap_atom($p) . "\n"; 83 } 84 85 return $retval; 86} 87 88sub wordwrap_paragraphs { # don't call this directly. 89 my $str = shift; 90 my $retval = ''; 91 my @paragraphs = split /\n\n/, $str; 92 foreach (@paragraphs) { 93 next if $_ eq ''; 94 $retval .= wordwrap_one_paragraph($_); 95 $retval .= "\n"; 96 } 97 return $retval; 98} 99 100my $wordwrap_default_columns = 76; 101sub wordwrap { 102 my $str = shift; 103 my $columns = shift; 104 105 $columns = $wordwrap_default_columns if not defined $columns; 106 $columns += $wordwrap_default_columns if $columns < 0; 107 $Text::Wrap::columns = $columns; 108 109 my $retval = ''; 110 111 #print("\n\nWORDWRAP:\n\n$str\n\n\n"); 112 113 $str =~ s/\A\n+//ms; 114 115 while ($str =~ s/(.*?)(\`\`\`.*?\`\`\`|\<syntaxhighlight.*?\<\/syntaxhighlight\>)//ms) { 116 #print("\n\nWORDWRAP BLOCK:\n\n$1\n\n ===\n\n$2\n\n\n"); 117 $retval .= wordwrap_paragraphs($1); # wrap it. 118 $retval .= "$2\n\n"; # don't wrap it. 119 } 120 121 $retval .= wordwrap_paragraphs($str); # wrap what's left. 122 $retval =~ s/\n+\Z//ms; 123 124 #print("\n\nWORDWRAP DONE:\n\n$retval\n\n\n"); 125 return $retval; 126} 127 128# This assumes you're moving from Markdown (in the Doxygen data) to Wiki, which 129# is why the 'md' section is so sparse. 130sub wikify_chunk { 131 my $wikitype = shift; 132 my $str = shift; 133 my $codelang = shift; 134 my $code = shift; 135 136 #print("\n\nWIKIFY CHUNK:\n\n$str\n\n\n"); 137 138 if ($wikitype eq 'mediawiki') { 139 # convert `code` things first, so they aren't mistaken for other markdown items. 140 my $codedstr = ''; 141 while ($str =~ s/\A(.*?)\`(.*?)\`//ms) { 142 my $codeblock = $2; 143 $codedstr .= wikify_chunk($wikitype, $1, undef, undef); 144 # Convert obvious SDL things to wikilinks, even inside `code` blocks. 145 $codeblock =~ s/\b(SDL_[a-zA-Z0-9_]+)/[[$1]]/gms; 146 $codedstr .= "<code>$codeblock</code>"; 147 } 148 149 # Convert obvious SDL things to wikilinks. 150 $str =~ s/\b(SDL_[a-zA-Z0-9_]+)/[[$1]]/gms; 151 152 # Make some Markdown things into MediaWiki... 153 154 # bold+italic 155 $str =~ s/\*\*\*(.*?)\*\*\*/'''''$1'''''/gms; 156 157 # bold 158 $str =~ s/\*\*(.*?)\*\*/'''$1'''/gms; 159 160 # italic 161 $str =~ s/\*(.*?)\*/''$1''/gms; 162 163 # bullets 164 $str =~ s/^\- /* /gm; 165 166 $str = $codedstr . $str; 167 168 if (defined $code) { 169 $str .= "<syntaxhighlight lang='$codelang'>$code<\/syntaxhighlight>"; 170 } 171 } elsif ($wikitype eq 'md') { 172 # Convert obvious SDL things to wikilinks. 173 $str =~ s/\b(SDL_[a-zA-Z0-9_]+)/[$1]($1)/gms; 174 if (defined $code) { 175 $str .= "```$codelang$code```"; 176 } 177 } 178 179 #print("\n\nWIKIFY CHUNK DONE:\n\n$str\n\n\n"); 180 181 return $str; 182} 183 184sub wikify { 185 my $wikitype = shift; 186 my $str = shift; 187 my $retval = ''; 188 189 #print("WIKIFY WHOLE:\n\n$str\n\n\n"); 190 191 while ($str =~ s/\A(.*?)\`\`\`(c\+\+|c)(.*?)\`\`\`//ms) { 192 $retval .= wikify_chunk($wikitype, $1, $2, $3); 193 } 194 $retval .= wikify_chunk($wikitype, $str, undef, undef); 195 196 #print("WIKIFY WHOLE DONE:\n\n$retval\n\n\n"); 197 198 return $retval; 199} 200 201 202sub dewikify_chunk { 203 my $wikitype = shift; 204 my $str = shift; 205 my $codelang = shift; 206 my $code = shift; 207 208 #print("\n\nDEWIKIFY CHUNK:\n\n$str\n\n\n"); 209 210 if ($wikitype eq 'mediawiki') { 211 # Doxygen supports Markdown (and it just simply looks better than MediaWiki 212 # when looking at the raw headers), so do some conversions here as necessary. 213 214 $str =~ s/\[\[(SDL_[a-zA-Z0-9_]+)\]\]/$1/gms; # Dump obvious wikilinks. 215 216 # <code></code> is also popular. :/ 217 $str =~ s/\<code>(.*?)<\/code>/`$1`/gms; 218 219 # bold+italic 220 $str =~ s/\'''''(.*?)'''''/***$1***/gms; 221 222 # bold 223 $str =~ s/\'''(.*?)'''/**$1**/gms; 224 225 # italic 226 $str =~ s/\''(.*?)''/*$1*/gms; 227 228 # bullets 229 $str =~ s/^\* /- /gm; 230 } 231 232 if (defined $code) { 233 $str .= "```$codelang$code```"; 234 } 235 236 #print("\n\nDEWIKIFY CHUNK DONE:\n\n$str\n\n\n"); 237 238 return $str; 239} 240 241sub dewikify { 242 my $wikitype = shift; 243 my $str = shift; 244 return '' if not defined $str; 245 246 #print("DEWIKIFY WHOLE:\n\n$str\n\n\n"); 247 248 $str =~ s/\A[\s\n]*\= .*? \=\s*?\n+//ms; 249 $str =~ s/\A[\s\n]*\=\= .*? \=\=\s*?\n+//ms; 250 251 my $retval = ''; 252 while ($str =~ s/\A(.*?)<syntaxhighlight lang='?(.*?)'?>(.*?)<\/syntaxhighlight\>//ms) { 253 $retval .= dewikify_chunk($wikitype, $1, $2, $3); 254 } 255 $retval .= dewikify_chunk($wikitype, $str, undef, undef); 256 257 #print("DEWIKIFY WHOLE DONE:\n\n$retval\n\n\n"); 258 259 return $retval; 260} 261 262sub usage { 263 die("USAGE: $0 <source code git clone path> <wiki git clone path> [--copy-to-headers|--copy-to-wiki] [--warn-about-missing]\n\n"); 264} 265 266usage() if not defined $srcpath; 267usage() if not defined $wikipath; 268#usage() if $copy_direction == 0; 269 270my @standard_wiki_sections = ( 271 'Draft', 272 '[Brief]', 273 'Deprecated', 274 'Syntax', 275 'Function Parameters', 276 'Return Value', 277 'Remarks', 278 'Version', 279 'Code Examples', 280 'Related Functions' 281); 282 283# Sections that only ever exist in the wiki and shouldn't be deleted when 284# not found in the headers. 285my %only_wiki_sections = ( # The ones don't mean anything, I just need to check for key existence. 286 'Draft', 1, 287 'Code Examples', 1 288); 289 290 291my %headers = (); # $headers{"SDL_audio.h"} -> reference to an array of all lines of text in SDL_audio.h. 292my %headerfuncs = (); # $headerfuncs{"SDL_OpenAudio"} -> string of header documentation for SDL_OpenAudio, with comment '*' bits stripped from the start. Newlines embedded! 293my %headerdecls = (); 294my %headerfuncslocation = (); # $headerfuncslocation{"SDL_OpenAudio"} -> name of header holding SDL_OpenAudio define ("SDL_audio.h" in this case). 295my %headerfuncschunk = (); # $headerfuncschunk{"SDL_OpenAudio"} -> offset in array in %headers that should be replaced for this function. 296my %headerfuncshasdoxygen = (); # $headerfuncschunk{"SDL_OpenAudio"} -> 1 if there was no existing doxygen for this function. 297 298my $incpath = "$srcpath/include"; 299opendir(DH, $incpath) or die("Can't opendir '$incpath': $!\n"); 300while (readdir(DH)) { 301 my $dent = $_; 302 next if not $dent =~ /\ASDL.*?\.h\Z/; # just SDL*.h headers. 303 open(FH, '<', "$incpath/$dent") or die("Can't open '$incpath/$dent': $!\n"); 304 305 my @contents = (); 306 307 while (<FH>) { 308 chomp; 309 my $decl; 310 my @templines; 311 my $str; 312 my $has_doxygen = 1; 313 if (/\A\s*extern\s+(SDL_DEPRECATED\s+|)DECLSPEC/) { # a function declaration without a doxygen comment? 314 @templines = (); 315 $decl = $_; 316 $str = ''; 317 $has_doxygen = 0; 318 } elsif (not /\A\/\*\*\s*\Z/) { # not doxygen comment start? 319 push @contents, $_; 320 next; 321 } else { # Start of a doxygen comment, parse it out. 322 @templines = ( $_ ); 323 while (<FH>) { 324 chomp; 325 push @templines, $_; 326 last if /\A\s*\*\/\Z/; 327 if (s/\A\s*\*\s*\`\`\`/```/) { # this is a hack, but a lot of other code relies on the whitespace being trimmed, but we can't trim it in code blocks... 328 $str .= "$_\n"; 329 while (<FH>) { 330 chomp; 331 push @templines, $_; 332 s/\A\s*\*\s?//; 333 if (s/\A\s*\`\`\`/```/) { 334 $str .= "$_\n"; 335 last; 336 } else { 337 $str .= "$_\n"; 338 } 339 } 340 } else { 341 s/\A\s*\*\s*//; 342 $str .= "$_\n"; 343 } 344 } 345 346 $decl = <FH>; 347 $decl = '' if not defined $decl; 348 chomp($decl); 349 if (not $decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)DECLSPEC/) { 350 #print "Found doxygen but no function sig:\n$str\n\n"; 351 foreach (@templines) { 352 push @contents, $_; 353 } 354 push @contents, $decl; 355 next; 356 } 357 } 358 359 my @decllines = ( $decl ); 360 361 if (not $decl =~ /\)\s*;/) { 362 while (<FH>) { 363 chomp; 364 push @decllines, $_; 365 s/\A\s+//; 366 s/\s+\Z//; 367 $decl .= " $_"; 368 last if /\)\s*;/; 369 } 370 } 371 372 $decl =~ s/\s+\);\Z/);/; 373 $decl =~ s/\s+\Z//; 374 #print("DECL: [$decl]\n"); 375 376 my $fn = ''; 377 if ($decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)DECLSPEC\s+(const\s+|)(unsigned\s+|)(.*?)\s*(\*?)\s*SDLCALL\s+(.*?)\s*\((.*?)\);/) { 378 $fn = $6; 379 #$decl =~ s/\A\s*extern\s+DECLSPEC\s+(.*?)\s+SDLCALL/$1/; 380 } else { 381 #print "Found doxygen but no function sig:\n$str\n\n"; 382 foreach (@templines) { 383 push @contents, $_; 384 } 385 foreach (@decllines) { 386 push @contents, $_; 387 } 388 next; 389 } 390 391 $decl = ''; # build this with the line breaks, since it looks better for syntax highlighting. 392 foreach (@decllines) { 393 if ($decl eq '') { 394 $decl = $_; 395 $decl =~ s/\Aextern\s+(SDL_DEPRECATED\s+|)DECLSPEC\s+(.*?)\s+(\*?)SDLCALL\s+/$2$3 /; 396 } else { 397 my $trimmed = $_; 398 # !!! FIXME: trim space for SDL_DEPRECATED if it was used, too. 399 $trimmed =~ s/\A\s{24}//; # 24 for shrinking to match the removed "extern DECLSPEC SDLCALL " 400 $decl .= $trimmed; 401 } 402 $decl .= "\n"; 403 } 404 405 #print("$fn:\n$str\n\n"); 406 407 # There might be multiple declarations of a function due to #ifdefs, 408 # and only one of them will have documentation. If we hit an 409 # undocumented one before, delete the placeholder line we left for 410 # it so it doesn't accumulate a new blank line on each run. 411 my $skipfn = 0; 412 if (defined $headerfuncshasdoxygen{$fn}) { 413 if ($headerfuncshasdoxygen{$fn} == 0) { # An undocumented declaration already exists, nuke its placeholder line. 414 delete $contents[$headerfuncschunk{$fn}]; # delete DOES NOT RENUMBER existing elements! 415 } else { # documented function already existed? 416 $skipfn = 1; # don't add this copy to the list of functions. 417 if ($has_doxygen) { 418 print STDERR "WARNING: Function '$fn' appears to be documented in multiple locations. Only keeping the first one we saw!\n"; 419 } 420 push @contents, join("\n", @decllines); # just put the existing declation in as-is. 421 } 422 } 423 424 if (!$skipfn) { 425 $headerfuncs{$fn} = $str; 426 $headerdecls{$fn} = $decl; 427 $headerfuncslocation{$fn} = $dent; 428 $headerfuncschunk{$fn} = scalar(@contents); 429 $headerfuncshasdoxygen{$fn} = $has_doxygen; 430 push @contents, join("\n", @templines); 431 push @contents, join("\n", @decllines); 432 } 433 434 } 435 close(FH); 436 437 $headers{$dent} = \@contents; 438} 439closedir(DH); 440 441 442# !!! FIXME: we need to parse enums and typedefs and structs and defines and and and and and... 443# !!! FIXME: (but functions are good enough for now.) 444 445my %wikitypes = (); # contains string of wiki page extension, like $wikitypes{"SDL_OpenAudio"} == 'mediawiki' 446my %wikifuncs = (); # contains references to hash of strings, each string being the full contents of a section of a wiki page, like $wikifuncs{"SDL_OpenAudio"}{"Remarks"}. 447my %wikisectionorder = (); # contains references to array, each array item being a key to a wikipage section in the correct order, like $wikisectionorder{"SDL_OpenAudio"}[2] == 'Remarks' 448opendir(DH, $wikipath) or die("Can't opendir '$wikipath': $!\n"); 449while (readdir(DH)) { 450 my $dent = $_; 451 my $type = ''; 452 if ($dent =~ /\ASDL.*?\.(md|mediawiki)\Z/) { 453 $type = $1; 454 } else { 455 next; # only dealing with wiki pages. 456 } 457 458 open(FH, '<', "$wikipath/$dent") or die("Can't open '$wikipath/$dent': $!\n"); 459 460 my $current_section = '[start]'; 461 my @section_order = ( $current_section ); 462 my $fn = $dent; 463 $fn =~ s/\..*\Z//; 464 my %sections = (); 465 $sections{$current_section} = ''; 466 467 while (<FH>) { 468 chomp; 469 my $orig = $_; 470 s/\A\s*//; 471 s/\s*\Z//; 472 473 if ($type eq 'mediawiki') { 474 if (/\A\= (.*?) \=\Z/) { 475 $current_section = ($1 eq $fn) ? '[Brief]' : $1; 476 die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; 477 push @section_order, $current_section; 478 $sections{$current_section} = ''; 479 } elsif (/\A\=\= (.*?) \=\=\Z/) { 480 $current_section = ($1 eq $fn) ? '[Brief]' : $1; 481 die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; 482 push @section_order, $current_section; 483 $sections{$current_section} = ''; 484 next; 485 } elsif (/\A\-\-\-\-\Z/) { 486 $current_section = '[footer]'; 487 die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; 488 push @section_order, $current_section; 489 $sections{$current_section} = ''; 490 next; 491 } 492 } elsif ($type eq 'md') { 493 if (/\A\#+ (.*?)\Z/) { 494 $current_section = ($1 eq $fn) ? '[Brief]' : $1; 495 die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; 496 push @section_order, $current_section; 497 $sections{$current_section} = ''; 498 next; 499 } elsif (/\A\-\-\-\-\Z/) { 500 $current_section = '[footer]'; 501 die("Doubly-defined section '$current_section' in '$dent'!\n") if defined $sections{$current_section}; 502 push @section_order, $current_section; 503 $sections{$current_section} = ''; 504 next; 505 } 506 } else { 507 die("Unexpected wiki file type. Fixme!\n"); 508 } 509 510 $sections{$current_section} .= "$orig\n"; 511 } 512 close(FH); 513 514 foreach (keys %sections) { 515 $sections{$_} =~ s/\A\n+//; 516 $sections{$_} =~ s/\n+\Z//; 517 $sections{$_} .= "\n"; 518 } 519 520 if (0) { 521 foreach (@section_order) { 522 print("$fn SECTION '$_':\n"); 523 print($sections{$_}); 524 print("\n\n"); 525 } 526 } 527 528 $wikitypes{$fn} = $type; 529 $wikifuncs{$fn} = \%sections; 530 $wikisectionorder{$fn} = \@section_order; 531} 532closedir(DH); 533 534 535if ($warn_about_missing) { 536 foreach (keys %wikifuncs) { 537 my $fn = $_; 538 if (not defined $headerfuncs{$fn}) { 539 print("WARNING: $fn defined in the wiki but not the headers!\n"); 540 } 541 } 542 543 foreach (keys %headerfuncs) { 544 my $fn = $_; 545 if (not defined $wikifuncs{$fn}) { 546 print("WARNING: $fn defined in the headers but not the wiki!\n"); 547 } 548 } 549} 550 551if ($copy_direction == 1) { # --copy-to-headers 552 my %changed_headers = (); 553 554 $wordwrap_mode = 'md'; # the headers use Markdown format. 555 556 foreach (keys %headerfuncs) { 557 my $fn = $_; 558 next if not defined $wikifuncs{$fn}; # don't have a page for that function, skip it. 559 my $wikitype = $wikitypes{$fn}; 560 my $sectionsref = $wikifuncs{$fn}; 561 my $remarks = %$sectionsref{'Remarks'}; 562 my $params = %$sectionsref{'Function Parameters'}; 563 my $returns = %$sectionsref{'Return Value'}; 564 my $version = %$sectionsref{'Version'}; 565 my $related = %$sectionsref{'Related Functions'}; 566 my $deprecated = %$sectionsref{'Deprecated'}; 567 my $brief = %$sectionsref{'[Brief]'}; 568 my $addblank = 0; 569 my $str = ''; 570 571 $headerfuncshasdoxygen{$fn} = 1; # Added/changed doxygen for this header. 572 573 $brief = dewikify($wikitype, $brief); 574 $brief =~ s/\A(.*?\.) /$1\n/; # \brief should only be one sentence, delimited by a period+space. Split if necessary. 575 my @briefsplit = split /\n/, $brief; 576 $brief = shift @briefsplit; 577 578 if (defined $remarks) { 579 $remarks = join("\n", @briefsplit) . dewikify($wikitype, $remarks); 580 } 581 582 if (defined $brief) { 583 $str .= "\n" if $addblank; $addblank = 1; 584 $str .= wordwrap($brief) . "\n"; 585 } 586 587 if (defined $remarks) { 588 $str .= "\n" if $addblank; $addblank = 1; 589 $str .= wordwrap($remarks) . "\n"; 590 } 591 592 if (defined $deprecated) { 593 # !!! FIXME: lots of code duplication in all of these. 594 $str .= "\n" if $addblank; $addblank = 1; 595 my $v = dewikify($wikitype, $deprecated); 596 my $whitespacelen = length("\\deprecated") + 1; 597 my $whitespace = ' ' x $whitespacelen; 598 $v = wordwrap($v, -$whitespacelen); 599 my @desclines = split /\n/, $v; 600 my $firstline = shift @desclines; 601 $str .= "\\deprecated $firstline\n"; 602 foreach (@desclines) { 603 $str .= "${whitespace}$_\n"; 604 } 605 } 606 607 if (defined $params) { 608 $str .= "\n" if $addblank; $addblank = (defined $returns) ? 0 : 1; 609 my @lines = split /\n/, dewikify($wikitype, $params); 610 if ($wikitype eq 'mediawiki') { 611 die("Unexpected data parsing MediaWiki table") if (shift @lines ne '{|'); # Dump the '{|' start 612 while (scalar(@lines) >= 3) { 613 my $name = shift @lines; 614 my $desc = shift @lines; 615 my $terminator = shift @lines; # the '|-' or '|}' line. 616 last if ($terminator ne '|-') and ($terminator ne '|}'); # we seem to have run out of table. 617 $name =~ s/\A\|\s*//; 618 $name =~ s/\A\*\*(.*?)\*\*/$1/; 619 $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/; 620 $desc =~ s/\A\|\s*//; 621 #print STDERR "FN: $fn NAME: $name DESC: $desc TERM: $terminator\n"; 622 my $whitespacelen = length($name) + 8; 623 my $whitespace = ' ' x $whitespacelen; 624 $desc = wordwrap($desc, -$whitespacelen); 625 my @desclines = split /\n/, $desc; 626 my $firstline = shift @desclines; 627 $str .= "\\param $name $firstline\n"; 628 foreach (@desclines) { 629 $str .= "${whitespace}$_\n"; 630 } 631 } 632 } else { 633 die("write me"); 634 } 635 } 636 637 if (defined $returns) { 638 $str .= "\n" if $addblank; $addblank = 1; 639 my $r = dewikify($wikitype, $returns); 640 my $retstr = "\\returns"; 641 if ($r =~ s/\AReturn(s?) //) { 642 $retstr = "\\return$1"; 643 } 644 645 my $whitespacelen = length($retstr) + 1; 646 my $whitespace = ' ' x $whitespacelen; 647 $r = wordwrap($r, -$whitespacelen); 648 my @desclines = split /\n/, $r; 649 my $firstline = shift @desclines; 650 $str .= "$retstr $firstline\n"; 651 foreach (@desclines) { 652 $str .= "${whitespace}$_\n"; 653 } 654 } 655 656 if (defined $version) { 657 # !!! FIXME: lots of code duplication in all of these. 658 $str .= "\n" if $addblank; $addblank = 1; 659 my $v = dewikify($wikitype, $version); 660 my $whitespacelen = length("\\since") + 1; 661 my $whitespace = ' ' x $whitespacelen; 662 $v = wordwrap($v, -$whitespacelen); 663 my @desclines = split /\n/, $v; 664 my $firstline = shift @desclines; 665 $str .= "\\since $firstline\n"; 666 foreach (@desclines) { 667 $str .= "${whitespace}$_\n"; 668 } 669 } 670 671 if (defined $related) { 672 # !!! FIXME: lots of code duplication in all of these. 673 $str .= "\n" if $addblank; $addblank = 1; 674 my $v = dewikify($wikitype, $related); 675 my @desclines = split /\n/, $v; 676 foreach (@desclines) { 677 s/\A(\:|\* )//; 678 s/\(\)\Z//; # Convert "SDL_Func()" to "SDL_Func" 679 $str .= "\\sa $_\n"; 680 } 681 } 682 683 my $header = $headerfuncslocation{$fn}; 684 my $contentsref = $headers{$header}; 685 my $chunk = $headerfuncschunk{$fn}; 686 687 my @lines = split /\n/, $str; 688 689 my $addnewline = (($chunk > 0) && ($$contentsref[$chunk-1] ne '')) ? "\n" : ''; 690 691 my $output = "$addnewline/**\n"; 692 foreach (@lines) { 693 chomp; 694 s/\s*\Z//; 695 if ($_ eq '') { 696 $output .= " *\n"; 697 } else { 698 $output .= " * $_\n"; 699 } 700 } 701 $output .= " */"; 702 703 #print("$fn:\n$output\n\n"); 704 705 $$contentsref[$chunk] = $output; 706 #$$contentsref[$chunk+1] = $headerdecls{$fn}; 707 708 $changed_headers{$header} = 1; 709 } 710 711 foreach (keys %changed_headers) { 712 my $header = $_; 713 714 # this is kinda inefficient, but oh well. 715 my @removelines = (); 716 foreach (keys %headerfuncslocation) { 717 my $fn = $_; 718 next if $headerfuncshasdoxygen{$fn}; 719 next if $headerfuncslocation{$fn} ne $header; 720 # the index of the blank line we put before the function declaration in case we needed to replace it with new content from the wiki. 721 push @removelines, $headerfuncschunk{$fn}; 722 } 723 724 my $contentsref = $headers{$header}; 725 foreach (@removelines) { 726 delete $$contentsref[$_]; # delete DOES NOT RENUMBER existing elements! 727 } 728 729 my $path = "$incpath/$header.tmp"; 730 open(FH, '>', $path) or die("Can't open '$path': $!\n"); 731 foreach (@$contentsref) { 732 print FH "$_\n" if defined $_; 733 } 734 close(FH); 735 rename($path, "$incpath/$header") or die("Can't rename '$path' to '$incpath/$header': $!\n"); 736 } 737 738} elsif ($copy_direction == -1) { # --copy-to-wiki 739 foreach (keys %headerfuncs) { 740 my $fn = $_; 741 next if not $headerfuncshasdoxygen{$fn}; 742 my $wikitype = defined $wikitypes{$fn} ? $wikitypes{$fn} : 'mediawiki'; # default to MediaWiki for new stuff FOR NOW. 743 die("Unexpected wikitype '$wikitype'\n") if (($wikitype ne 'mediawiki') and ($wikitype ne 'md')); 744 745 #print("$fn\n"); next; 746 747 $wordwrap_mode = $wikitype; 748 749 my $raw = $headerfuncs{$fn}; # raw doxygen text with comment characters stripped from start/end and start of each line. 750 next if not defined $raw; 751 $raw =~ s/\A\s*\\brief\s+//; # Technically we don't need \brief (please turn on JAVADOC_AUTOBRIEF if you use Doxygen), so just in case one is present, strip it. 752 753 my @doxygenlines = split /\n/, $raw; 754 my $brief = ''; 755 while (@doxygenlines) { 756 last if $doxygenlines[0] =~ /\A\\/; # some sort of doxygen command, assume we're past the general remarks. 757 last if $doxygenlines[0] =~ /\A\s*\Z/; # blank line? End of paragraph, done. 758 my $l = shift @doxygenlines; 759 chomp($l); 760 $l =~ s/\A\s*//; 761 $l =~ s/\s*\Z//; 762 $brief .= "$l "; 763 } 764 765 $brief =~ s/\A(.*?\.) /$1\n\n/; # \brief should only be one sentence, delimited by a period+space. Split if necessary. 766 my @briefsplit = split /\n/, $brief; 767 $brief = wikify($wikitype, shift @briefsplit) . "\n"; 768 @doxygenlines = (@briefsplit, @doxygenlines); 769 770 my $remarks = ''; 771 # !!! FIXME: wordwrap and wikify might handle this, now. 772 while (@doxygenlines) { 773 last if $doxygenlines[0] =~ /\A\\/; # some sort of doxygen command, assume we're past the general remarks. 774 my $l = shift @doxygenlines; 775 if ($l =~ /\A\`\`\`/) { # syntax highlighting, don't reformat. 776 $remarks .= "$l\n"; 777 while ((@doxygenlines) && (not $l =~ /\`\`\`\Z/)) { 778 $l = shift @doxygenlines; 779 $remarks .= "$l\n"; 780 } 781 } else { 782 $l =~ s/\A\s*//; 783 $l =~ s/\s*\Z//; 784 $remarks .= "$l\n"; 785 } 786 } 787 788 #print("REMARKS:\n\n $remarks\n\n"); 789 790 $remarks = wordwrap(wikify($wikitype, $remarks)); 791 $remarks =~ s/\A\s*//; 792 $remarks =~ s/\s*\Z//; 793 794 my $decl = $headerdecls{$fn}; 795 #$decl =~ s/\*\s+SDLCALL/ *SDLCALL/; # Try to make "void * Function" become "void *Function" 796 #$decl =~ s/\A\s*extern\s+(SDL_DEPRECATED\s+|)DECLSPEC\s+(.*?)\s+(\*?)SDLCALL/$2$3/; 797 798 my $syntax = ''; 799 if ($wikitype eq 'mediawiki') { 800 $syntax = "<syntaxhighlight lang='c'>\n$decl</syntaxhighlight>\n"; 801 } elsif ($wikitype eq 'md') { 802 $syntax = "```c\n$decl\n```\n"; 803 } else { die("Expected wikitype '$wikitype'\n"); } 804 805 my %sections = (); 806 $sections{'[Brief]'} = $brief; # include this section even if blank so we get a title line. 807 $sections{'Remarks'} = "$remarks\n" if $remarks ne ''; 808 $sections{'Syntax'} = $syntax; 809 810 my @params = (); # have to parse these and build up the wiki tables after, since Markdown needs to know the length of the largest string. :/ 811 812 while (@doxygenlines) { 813 my $l = shift @doxygenlines; 814 if ($l =~ /\A\\param\s+(.*?)\s+(.*)\Z/) { 815 my $arg = $1; 816 my $desc = $2; 817 while (@doxygenlines) { 818 my $subline = $doxygenlines[0]; 819 $subline =~ s/\A\s*//; 820 last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. 821 shift @doxygenlines; # dump this line from the array; we're using it. 822 if ($subline eq '') { # empty line, make sure it keeps the newline char. 823 $desc .= "\n"; 824 } else { 825 $desc .= " $subline"; 826 } 827 } 828 829 $desc =~ s/[\s\n]+\Z//ms; 830 831 # We need to know the length of the longest string to make Markdown tables, so we just store these off until everything is parsed. 832 push @params, $arg; 833 push @params, $desc; 834 } elsif ($l =~ /\A\\r(eturns?)\s+(.*)\Z/) { 835 my $retstr = "R$1"; # "Return" or "Returns" 836 my $desc = $2; 837 while (@doxygenlines) { 838 my $subline = $doxygenlines[0]; 839 $subline =~ s/\A\s*//; 840 last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. 841 shift @doxygenlines; # dump this line from the array; we're using it. 842 if ($subline eq '') { # empty line, make sure it keeps the newline char. 843 $desc .= "\n"; 844 } else { 845 $desc .= " $subline"; 846 } 847 } 848 $desc =~ s/[\s\n]+\Z//ms; 849 $sections{'Return Value'} = wordwrap("$retstr " . wikify($wikitype, $desc)) . "\n"; 850 } elsif ($l =~ /\A\\deprecated\s+(.*)\Z/) { 851 my $desc = $1; 852 while (@doxygenlines) { 853 my $subline = $doxygenlines[0]; 854 $subline =~ s/\A\s*//; 855 last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. 856 shift @doxygenlines; # dump this line from the array; we're using it. 857 if ($subline eq '') { # empty line, make sure it keeps the newline char. 858 $desc .= "\n"; 859 } else { 860 $desc .= " $subline"; 861 } 862 } 863 $desc =~ s/[\s\n]+\Z//ms; 864 $sections{'Deprecated'} = wordwrap(wikify($wikitype, $desc)) . "\n"; 865 } elsif ($l =~ /\A\\since\s+(.*)\Z/) { 866 my $desc = $1; 867 while (@doxygenlines) { 868 my $subline = $doxygenlines[0]; 869 $subline =~ s/\A\s*//; 870 last if $subline =~ /\A\\/; # some sort of doxygen command, assume we're past this thing. 871 shift @doxygenlines; # dump this line from the array; we're using it. 872 if ($subline eq '') { # empty line, make sure it keeps the newline char. 873 $desc .= "\n"; 874 } else { 875 $desc .= " $subline"; 876 } 877 } 878 $desc =~ s/[\s\n]+\Z//ms; 879 $sections{'Version'} = wordwrap(wikify($wikitype, $desc)) . "\n"; 880 } elsif ($l =~ /\A\\sa\s+(.*)\Z/) { 881 my $sa = $1; 882 $sa =~ s/\(\)\Z//; # Convert "SDL_Func()" to "SDL_Func" 883 $sections{'Related Functions'} = '' if not defined $sections{'Related Functions'}; 884 if ($wikitype eq 'mediawiki') { 885 $sections{'Related Functions'} .= ":[[$sa]]\n"; 886 } elsif ($wikitype eq 'md') { 887 $sections{'Related Functions'} .= "* [$sa](/$sa)\n"; 888 } else { die("Expected wikitype '$wikitype'\n"); } 889 } 890 } 891 892 # Make sure this ends with a double-newline. 893 $sections{'Related Functions'} .= "\n" if defined $sections{'Related Functions'}; 894 895 # We can build the wiki table now that we have all the data. 896 if (scalar(@params) > 0) { 897 my $str = ''; 898 if ($wikitype eq 'mediawiki') { 899 while (scalar(@params) > 0) { 900 my $arg = shift @params; 901 my $desc = wikify($wikitype, shift @params); 902 $str .= ($str eq '') ? "{|\n" : "|-\n"; 903 $str .= "|'''$arg'''\n"; 904 $str .= "|$desc\n"; 905 } 906 $str .= "|}\n"; 907 } elsif ($wikitype eq 'md') { 908 my $longest_arg = 0; 909 my $longest_desc = 0; 910 my $which = 0; 911 foreach (@params) { 912 if ($which == 0) { 913 my $len = length($_) + 4; 914 $longest_arg = $len if ($len > $longest_arg); 915 $which = 1; 916 } else { 917 my $len = length(wikify($wikitype, $_)); 918 $longest_desc = $len if ($len > $longest_desc); 919 $which = 0; 920 } 921 } 922 923 # Markdown tables are sort of obnoxious. 924 $str .= '| ' . (' ' x ($longest_arg+4)) . ' | ' . (' ' x $longest_desc) . " |\n"; 925 $str .= '| ' . ('-' x ($longest_arg+4)) . ' | ' . ('-' x $longest_desc) . " |\n"; 926 927 while (@params) { 928 my $arg = shift @params; 929 my $desc = wikify($wikitype, shift @params); 930 $str .= "| **$arg** " . (' ' x ($longest_arg - length($arg))) . "| $desc" . (' ' x ($longest_desc - length($desc))) . " |\n"; 931 } 932 } else { 933 die("Unexpected wikitype!\n"); # should have checked this elsewhere. 934 } 935 $sections{'Function Parameters'} = $str; 936 } 937 938 my $path = "$wikipath/$_.${wikitype}.tmp"; 939 open(FH, '>', $path) or die("Can't open '$path': $!\n"); 940 941 my $sectionsref = $wikifuncs{$fn}; 942 943 foreach (@standard_wiki_sections) { 944 # drop sections we either replaced or removed from the original wiki's contents. 945 if (not defined $only_wiki_sections{$_}) { 946 delete($$sectionsref{$_}); 947 } 948 } 949 950 my $wikisectionorderref = $wikisectionorder{$fn}; 951 952 # Make sure there's a footer in the wiki that puts this function in CategoryAPI... 953 if (not $$sectionsref{'[footer]'}) { 954 $$sectionsref{'[footer]'} = ''; 955 push @$wikisectionorderref, '[footer]'; 956 } 957 958 # !!! FIXME: This won't be CategoryAPI if we eventually handle things other than functions. 959 my $footer = $$sectionsref{'[footer]'}; 960 if ($wikitype eq 'mediawiki') { 961 $footer =~ s/\[\[CategoryAPI\]\],?\s*//g; 962 $footer = '[[CategoryAPI]]' . (($footer eq '') ? "\n" : ", $footer"); 963 } elsif ($wikitype eq 'md') { 964 $footer =~ s/\[CategoryAPI\]\(CategoryAPI\),?\s*//g; 965 $footer = '[CategoryAPI](CategoryAPI)' . (($footer eq '') ? '' : ', ') . $footer; 966 } else { die("Unexpected wikitype '$wikitype'\n"); } 967 $$sectionsref{'[footer]'} = $footer; 968 969 my $prevsectstr = ''; 970 my @ordered_sections = (@standard_wiki_sections, defined $wikisectionorderref ? @$wikisectionorderref : ()); # this copies the arrays into one. 971 foreach (@ordered_sections) { 972 my $sect = $_; 973 next if $sect eq '[start]'; 974 next if (not defined $sections{$sect} and not defined $$sectionsref{$sect}); 975 my $section = defined $sections{$sect} ? $sections{$sect} : $$sectionsref{$sect}; 976 if ($sect eq '[footer]') { 977 # Make sure previous section ends with two newlines. 978 if (substr($prevsectstr, -1) ne "\n") { 979 print FH "\n\n"; 980 } elsif (substr($prevsectstr, -2) ne "\n\n") { 981 print FH "\n"; 982 } 983 print FH "----\n"; # It's the same in Markdown and MediaWiki. 984 } elsif ($sect eq '[Brief]') { 985 if ($wikitype eq 'mediawiki') { 986 print FH "= $fn =\n\n"; 987 } elsif ($wikitype eq 'md') { 988 print FH "# $fn\n\n"; 989 } else { die("Unexpected wikitype '$wikitype'\n"); } 990 } else { 991 if ($wikitype eq 'mediawiki') { 992 print FH "\n== $sect ==\n\n"; 993 } elsif ($wikitype eq 'md') { 994 print FH "\n## $sect\n\n"; 995 } else { die("Unexpected wikitype '$wikitype'\n"); } 996 } 997 998 my $sectstr = defined $sections{$sect} ? $sections{$sect} : $$sectionsref{$sect}; 999 print FH $sectstr; 1000 1001 $prevsectstr = $sectstr; 1002 1003 # make sure these don't show up twice. 1004 delete($sections{$sect}); 1005 delete($$sectionsref{$sect}); 1006 } 1007 1008 print FH "\n\n"; 1009 close(FH); 1010 rename($path, "$wikipath/$_.${wikitype}") or die("Can't rename '$path' to '$wikipath/$_.${wikitype}': $!\n"); 1011 } 1012} 1013 1014# end of wikiheaders.pl ... 1015 1016