SDL_ttf: wikiheaders: Updated to latest from SDL3.

From eb5883c74a7fdaed530f94026476d1499c191f9e Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 16 May 2024 11:45:36 -0400
Subject: [PATCH] wikiheaders: Updated to latest from SDL3.

---
 build-scripts/wikiheaders.pl | 288 ++++++++++++++++++++++++++---------
 1 file changed, 216 insertions(+), 72 deletions(-)

diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl
index 6e421970..2669e33a 100755
--- a/build-scripts/wikiheaders.pl
+++ b/build-scripts/wikiheaders.pl
@@ -622,7 +622,7 @@ sub usage {
 my %headersymshasdoxygen = ();   # $headersymshasdoxygen{"SDL_OpenAudio"} -> 1 if there was no existing doxygen for this function.
 my %headersymstype = ();   # $headersymstype{"SDL_OpenAudio"} -> 1 (function), 2 (macro), 3 (struct), 4 (enum), 5 (other typedef)
 my %headersymscategory = ();   # $headersymscategory{"SDL_OpenAudio"} -> 'Audio' ... this is set with a `/* WIKI CATEGEORY: Audio */` comment in the headers that sets it on all symbols until a new comment changes it. So usually, once at the top of the header file.
-
+my %headercategorydocs = ();   # $headercategorydocs{"Audio"} -> (fake) symbol for this category's documentation. Undefined if not documented.
 my %wikitypes = ();  # contains string of wiki page extension, like $wikitypes{"SDL_OpenAudio"} == 'mediawiki'
 my %wikisyms = ();  # contains references to hash of strings, each string being the full contents of a section of a wiki page, like $wikisyms{"SDL_OpenAudio"}{"Remarks"}.
 my %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'
@@ -710,6 +710,7 @@ sub print_undocumented_section {
     my @contents = ();
     my $ignoring_lines = 0;
     my $header_comment = -1;
+    my $saw_category_doxygen = -1;
     my $lineno = 0;
     while (<FH>) {
         chomp;
@@ -762,6 +763,8 @@ sub print_undocumented_section {
             add_coverage_gap($_, $dent, $lineno) if ($header_comment == 0);
             next;
         } else {   # Start of a doxygen comment, parse it out.
+            my $is_category_doxygen = 0;
+
             @templines = ( $_ );
             while (<FH>) {
                 chomp;
@@ -783,43 +786,76 @@ sub print_undocumented_section {
                         }
                     }
                 } else {
-                    s/\A\s*\*\s*//;
+                    s/\A\s*\*\s*//;   # Strip off the " * " at the start of the comment line.
+
+                    # To add documentation to Category Pages, the rule is it has to
+                    # be the first Doxygen comment in the header, and it must start with `# CategoryX`
+                    # (otherwise we'll treat it as documentation for whatever's below it). `X` is
+                    # the category name, which doesn't _necessarily_ have to match
+                    # $current_wiki_category, but it probably should.
+                    #
+                    # For compatibility with Doxygen, if there's a `\file` here instead of
+                    # `# CategoryName`, we'll accept it and use the $current_wiki_category if set.
+                    if ($saw_category_doxygen == -1) {
+                        $saw_category_doxygen = defined($current_wiki_category) && /\A\\file\s+/;
+                        if ($saw_category_doxygen) {
+                            $_ = "# Category$current_wiki_category";
+                        } else {
+                            $saw_category_doxygen = /\A\# Category/;
+                        }
+                        $is_category_doxygen = $saw_category_doxygen;
+                    }
+
                     $str .= "$_\n";
                 }
             }
 
-            $decl = <FH>;
-            $lineno++ if defined $decl;
-            $decl = '' if not defined $decl;
-            chomp($decl);
-            if ($decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_)?DECLSPEC/) {
-                $symtype = 1;   # function declaration
-            } elsif ($decl =~ /\A\s*SDL_FORCE_INLINE/) {
-                $symtype = 1;   # (forced-inline) function declaration
-            } elsif ($decl =~ /\A\s*\#\s*define\s+/) {
-                $symtype = 2;   # macro
-            } elsif ($decl =~ /\A\s*(typedef\s+|)(struct|union)/) {
-                $symtype = 3;   # struct or union
-            } elsif ($decl =~ /\A\s*(typedef\s+|)enum/) {
-                $symtype = 4;   # enum
-            } elsif ($decl =~ /\A\s*typedef\s+.*\Z/) {
-                $symtype = 5;   # other typedef
+            if ($is_category_doxygen) {
+                $str =~ s/\s*\Z//;
+                $decl = '';
+                $symtype = -1;  # not a symbol at all.
             } else {
-                #print "Found doxygen but no function sig:\n$str\n\n";
-                foreach (@templines) {
-                    push @contents, $_;
-                    add_coverage_gap($_, $dent, $lineno);
+                $decl = <FH>;
+                $lineno++ if defined $decl;
+                $decl = '' if not defined $decl;
+                chomp($decl);
+                if ($decl =~ /\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_)?DECLSPEC/) {
+                    $symtype = 1;   # function declaration
+                } elsif ($decl =~ /\A\s*SDL_FORCE_INLINE/) {
+                    $symtype = 1;   # (forced-inline) function declaration
+                } elsif ($decl =~ /\A\s*\#\s*define\s+/) {
+                    $symtype = 2;   # macro
+                } elsif ($decl =~ /\A\s*(typedef\s+|)(struct|union)/) {
+                    $symtype = 3;   # struct or union
+                } elsif ($decl =~ /\A\s*(typedef\s+|)enum/) {
+                    $symtype = 4;   # enum
+                } elsif ($decl =~ /\A\s*typedef\s+.*\Z/) {
+                    $symtype = 5;   # other typedef
+                } else {
+                    #print "Found doxygen but no function sig:\n$str\n\n";
+                    foreach (@templines) {
+                        push @contents, $_;
+                        add_coverage_gap($_, $dent, $lineno);
+                    }
+                    push @contents, $decl;
+                    add_coverage_gap($decl, $dent, $lineno);
+                    next;
                 }
-                push @contents, $decl;
-                add_coverage_gap($decl, $dent, $lineno);
-                next;
             }
         }
 
         my @decllines = ( $decl );
         my $sym = '';
 
-        if ($symtype == 1) {  # a function
+        if ($symtype == -1) {  # category documentation with no symbol attached.
+            @decllines = ();
+            if ($str =~ /^#\s*Category(.*?)\s*$/m) {
+                $sym = "[category documentation] $1";  # make a fake, unique symbol that's not valid C.
+            } else {
+                die("Unexpected category documentation line '$str' in '$incpath/$dent' ...?");
+            }
+            $headercategorydocs{$current_wiki_category} = $sym;
+        } elsif ($symtype == 1) {  # a function
             my $is_forced_inline = ($decl =~ /\A\s*SDL_FORCE_INLINE/);
 
             if ($is_forced_inline) {
@@ -1128,7 +1164,7 @@ sub print_undocumented_section {
                 if ($has_doxygen) {
                     print STDERR "WARNING: Symbol '$sym' appears to be documented in multiple locations. Only keeping the first one we saw!\n";
                 }
-                push @contents, join("\n", @decllines);  # just put the existing declation in as-is.
+                push @contents, join("\n", @decllines) if (scalar(@decllines) > 0);  # just put the existing declation in as-is.
             }
         }
 
@@ -1141,7 +1177,7 @@ sub print_undocumented_section {
             $headersymshasdoxygen{$sym} = $has_doxygen;
             $headersymstype{$sym} = $symtype;
             push @contents, join("\n", @templines);
-            push @contents, join("\n", @decllines);
+            push @contents, join("\n", @decllines) if (scalar(@decllines) > 0);
         }
 
     }
@@ -1168,11 +1204,41 @@ sub print_undocumented_section {
     # Ignore FrontPage.
     next if $sym eq 'FrontPage';
 
-    # Ignore "Category*" pages.
-    next if ($sym =~ /\ACategory/);
-
     open(FH, '<', "$wikipath/$dent") or die("Can't open '$wikipath/$dent': $!\n");
 
+    if ($sym =~ /\ACategory(.*?)\Z/) {  # Special case for Category pages.
+        # Find the end of the category documentation in the existing file and append everything else to the new file.
+        my $cat = $1;
+        my $docstr = '';
+        my $notdocstr = '';
+        my $docs = 1;
+        while (<FH>) {
+            chomp;
+            if ($docs) {
+                $docs = 0 if /\A\-\-\-\-\Z/;  # Hit a footer? We're done.
+                $docs = 0 if /\A<!\-\-/;  # Hit an HTML comment? We're done.
+            }
+            if ($docs) {
+                $docstr .= "$_\n";
+            } else {
+                $notdocstr .= "$_\n";
+            }
+        }
+        close(FH);
+
+        $docstr =~ s/\s*\Z//;
+
+        $sym = "[category documentation] $cat";  # make a fake, unique symbol that's not valid C.
+        $wikitypes{$sym} = $type;
+        my %sections = ();
+        $sections{'Remarks'} = $docstr;
+        $sections{'[footer]'} = $notdocstr;
+        $wikisyms{$sym} = \%sections;
+        my @section_order = ( 'Remarks', '[footer]' );
+        $wikisectionorder{$sym} = \@section_order;
+        next;
+    }
+
     my $current_section = '[start]';
     my @section_order = ( $current_section );
     my %sections = ();
@@ -1325,7 +1391,9 @@ sub print_undocumented_section {
         my $params = undef;
         my $paramstr = undef;
 
-        if (($symtype == 1) || (($symtype == 5))) {  # we'll assume a typedef (5) with a \param is a function pointer typedef.
+        if ($symtype == -1) {  # category documentation block.
+            # nothing to be done here.
+        } elsif (($symtype == 1) || (($symtype == 5))) {  # we'll assume a typedef (5) with a \param is a function pointer typedef.
             $params = $sectionsref->{'Function Parameters'};
             $paramstr = '\param';
         } elsif ($symtype == 2) {
@@ -1520,7 +1588,7 @@ sub print_undocumented_section {
         }
         $output .= " */";
 
-        #print("$sym:\n$output\n\n");
+        #print("$sym:\n[$output]\n\n");
 
         $$contentsref[$chunk] = $output;
         #$$contentsref[$chunk+1] = $headerdecls{$sym};
@@ -1569,6 +1637,7 @@ sub print_undocumented_section {
             closedir(DH);
         }
     }
+
 } elsif ($copy_direction == -1) { # --copy-to-wiki
 
     if (defined $changeformat) {
@@ -1579,6 +1648,7 @@ sub print_undocumented_section {
     foreach (keys %headersyms) {
         my $sym = $_;
         next if not $headersymshasdoxygen{$sym};
+        next if $sym =~ /\A\[category documentation\]/;   # not real symbols, we handle this elsewhere.
         my $symtype = $headersymstype{$sym};
         my $origwikitype = defined $wikitypes{$sym} ? $wikitypes{$sym} : 'md';  # default to MarkDown for new stuff.
         my $wikitype = (defined $changeformat) ? $changeformat : $origwikitype;
@@ -1812,7 +1882,7 @@ sub print_undocumented_section {
             $sections{'Function Parameters'} = $str;
         }
 
-        my $path = "$wikipath/$_.${wikitype}.tmp";
+        my $path = "$wikipath/$sym.${wikitype}.tmp";
         open(FH, '>', $path) or die("Can't open '$path': $!\n");
 
         my $sectionsref = $wikisyms{$sym};
@@ -1848,44 +1918,46 @@ sub print_undocumented_section {
             }
         }
 
-        my $footer = $$sectionsref{'[footer]'};
-
-        my $symtypename;
-        if ($symtype == 1) {
-            $symtypename = 'Function';
-        } elsif ($symtype == 2) {
-            $symtypename = 'Macro';
-        } elsif ($symtype == 3) {
-            $symtypename = 'Struct';
-        } elsif ($symtype == 4) {
-            $symtypename = 'Enum';
-        } elsif ($symtype == 5) {
-            $symtypename = 'Datatype';
-        } else {
-            die("Unexpected symbol type $symtype!");
-        }
+        if ($symtype != -1) {  # Don't do these in category documentation block
+            my $footer = $$sectionsref{'[footer]'};
 
-        my $symcategory = $headersymscategory{$sym};
-        if ($wikitype eq 'mediawiki') {
-            $footer =~ s/\[\[CategoryAPI\]\],?\s*//g;
-            $footer =~ s/\[\[CategoryAPI${symtypename}\]\],?\s*//g;
-            $footer =~ s/\[\[Category${symcategory}\]\],?\s*//g if defined $symcategory;
-            $footer = "[[CategoryAPI]], [[CategoryAPI$symtypename]]" . (defined $symcategory ? ", [[Category$symcategory]]" : '') . (($footer eq '') ? "\n" : ", $footer");
-        } elsif ($wikitype eq 'md') {
-            $footer =~ s/\[CategoryAPI\]\(CategoryAPI\),?\s*//g;
-            $footer =~ s/\[CategoryAPI${symtypename}\]\(CategoryAPI${symtypename}\),?\s*//g;
-            $footer =~ s/\[Category${symcategory}\]\(Category${symcategory}\),?\s*//g if defined $symcategory;
-            $footer = "[CategoryAPI](CategoryAPI), [CategoryAPI$symtypename](CategoryAPI$symtypename)" . (defined $symcategory ? ", [Category$symcategory](Category$symcategory)" : '') . (($footer eq '') ? '' : ', ') . $footer;
-        } else { die("Unexpected wikitype '$wikitype'"); }
-        $$sectionsref{'[footer]'} = $footer;
+            my $symtypename;
+            if ($symtype == 1) {
+                $symtypename = 'Function';
+            } elsif ($symtype == 2) {
+                $symtypename = 'Macro';
+            } elsif ($symtype == 3) {
+                $symtypename = 'Struct';
+            } elsif ($symtype == 4) {
+                $symtypename = 'Enum';
+            } elsif ($symtype == 5) {
+                $symtypename = 'Datatype';
+            } else {
+                die("Unexpected symbol type $symtype!");
+            }
 
-        if (defined $wikipreamble) {
-            my $wikified_preamble = wikify($wikitype, $wikipreamble);
+            my $symcategory = $headersymscategory{$sym};
             if ($wikitype eq 'mediawiki') {
-                print FH "====== $wikified_preamble ======\n";
+                $footer =~ s/\[\[CategoryAPI\]\],?\s*//g;
+                $footer =~ s/\[\[CategoryAPI${symtypename}\]\],?\s*//g;
+                $footer =~ s/\[\[Category${symcategory}\]\],?\s*//g if defined $symcategory;
+                $footer = "[[CategoryAPI]], [[CategoryAPI$symtypename]]" . (defined $symcategory ? ", [[Category$symcategory]]" : '') . (($footer eq '') ? "\n" : ", $footer");
             } elsif ($wikitype eq 'md') {
-                print FH "###### $wikified_preamble\n";
+                $footer =~ s/\[CategoryAPI\]\(CategoryAPI\),?\s*//g;
+                $footer =~ s/\[CategoryAPI${symtypename}\]\(CategoryAPI${symtypename}\),?\s*//g;
+                $footer =~ s/\[Category${symcategory}\]\(Category${symcategory}\),?\s*//g if defined $symcategory;
+                $footer = "[CategoryAPI](CategoryAPI), [CategoryAPI$symtypename](CategoryAPI$symtypename)" . (defined $symcategory ? ", [Category$symcategory](Category$symcategory)" : '') . (($footer eq '') ? '' : ', ') . $footer;
             } else { die("Unexpected wikitype '$wikitype'"); }
+            $$sectionsref{'[footer]'} = $footer;
+
+            if (defined $wikipreamble) {
+                my $wikified_preamble = wikify($wikitype, $wikipreamble);
+                if ($wikitype eq 'mediawiki') {
+                    print FH "====== $wikified_preamble ======\n";
+                } elsif ($wikitype eq 'md') {
+                    print FH "###### $wikified_preamble\n";
+                } else { die("Unexpected wikitype '$wikitype'"); }
+            }
         }
 
         my $prevsectstr = '';
@@ -1925,11 +1997,13 @@ sub print_undocumented_section {
                     }
                 }
 
-                if ($wikitype eq 'mediawiki') {
-                    print FH  "\n== $sectname ==\n\n";
-                } elsif ($wikitype eq 'md') {
-                    print FH "\n## $sectname\n\n";
-                } else { die("Unexpected wikitype '$wikitype'"); }
+                if ($symtype != -1) {  # Not for category documentation block
+                    if ($wikitype eq 'mediawiki') {
+                        print FH  "\n== $sectname ==\n\n";
+                    } elsif ($wikitype eq 'md') {
+                        print FH "\n## $sectname\n\n";
+                    } else { die("Unexpected wikitype '$wikitype'"); }
+                }
             }
 
             my $sectstr = defined $sections{$sect} ? $sections{$sect} : $$sectionsref{$sect};
@@ -1973,6 +2047,74 @@ sub print_undocumented_section {
         close(FH);
     }
 
+    # Write out Category pages...
+    foreach (keys %headercategorydocs) {
+        my $cat = $_;
+        my $sym = $headercategorydocs{$cat};  # fake symbol
+        my $raw = $headersyms{$sym};  # raw doxygen text with comment characters stripped from start/end and start of each line.
+        my $wikitype = defined($wikitypes{$sym}) ? $wikitypes{$sym} : 'md';
+        my $path = "$wikipath/Category$cat.$wikitype";
+
+        $raw = wordwrap(wikify($wikitype, $raw));
+
+        my $tmppath = "$path.tmp";
+        open(FH, '>', $tmppath) or die("Can't open '$tmppath': $!\n");
+        print FH "$raw\n\n";
+
+        if (! -f $path) {  # Doesn't exist at all? Write out a template file.
+            # If writing from scratch, it's always a Markdown file.
+            die("Unexpected wikitype '$wikitype'!") if $wikitype ne 'md';
+            print FH <<__EOF__
+
+<!-- END CATEGORY DOCUMENTATION -->
+
+## Functions
+
+<!-- DO NOT HAND-EDIT CATEGORY LISTS, THEY ARE AUTOGENERATED AND WILL BE OVERWRITTEN, BASED ON TAGS IN INDIVIDUAL PAGE FOOTERS. EDIT THOSE INSTEAD. -->
+<!-- BEGIN CATEGORY LIST: Category$cat, CategoryAPIFunction -->
+<!-- END CATEGORY LIST -->
+
+## Datatypes
+
+<!-- DO NOT HAND-EDIT CATEGORY LISTS, THEY ARE AUTOGENERATED AND WILL BE OVERWRITTEN, BASED ON TAGS IN INDIVIDUAL PAGE FOOTERS. EDIT THOSE INSTEAD. -->
+<!-- BEGIN CATEGORY LIST: Category$cat, CategoryAPIDatatype -->
+<!-- END CATEGORY LIST -->
+
+## Structs
+
+<!-- DO NOT HAND-EDIT CATEGORY LISTS, THEY ARE AUTOGENERATED AND WILL BE OVERWRITTEN, BASED ON TAGS IN INDIVIDUAL PAGE FOOTERS. EDIT THOSE INSTEAD. -->
+<!-- BEGIN CATEGORY LIST: Category$cat, CategoryAPIStruct -->
+<!-- END CATEGORY LIST -->
+
+## Enums
+
+<!-- DO NOT HAND-EDIT CATEGORY LISTS, THEY ARE AUTOGENERATED AND WILL BE OVERWRITTEN, BASED ON TAGS IN INDIVIDUAL PAGE FOOTERS. EDIT THOSE INSTEAD. -->
+<!-- BEGIN CATEGORY LIST: Category$cat, CategoryAPIEnum -->
+<!-- END CATEGORY LIST -->
+
+## Macros
+
+<!-- DO NOT HAND-EDIT CATEGORY LISTS, THEY ARE AUTOGENERATED AND WILL BE OVERWRITTEN, BASED ON TAGS IN INDIVIDUAL PAGE FOOTERS. EDIT THOSE INSTEAD. -->
+<!-- BEGIN CATEGORY LIST: Category$cat, CategoryAPIMacro -->
+<!-- END CATEGORY LIST -->
+
+----
+[CategoryAPICategory](CategoryAPICategory)
+
+__EOF__
+;
+        } else {
+            my $endstr = $wikisyms{$sym}->{'[footer]'};
+            if (defined($endstr)) {
+                print FH $endstr;
+            }
+        }
+
+        close(FH);
+        rename($tmppath, $path) or die("Can't rename '$tmppath' to '$path': $!\n");
+    }
+
+    # Write out READMEs...
     if (defined $readmepath) {
         if ( -d $readmepath ) {
             mkdir($wikireadmepath);  # just in case
@@ -2053,6 +2195,7 @@ sub print_undocumented_section {
     foreach (keys %headersyms) {
         my $sym = $_;
         next if not defined $wikisyms{$sym};  # don't have a page for that function, skip it.
+        next if $sym =~ /\A\[category documentation\]/;   # not real symbols
         my $symtype = $headersymstype{$sym};
         my $wikitype = $wikitypes{$sym};
         my $sectionsref = $wikisyms{$sym};
@@ -2399,6 +2542,7 @@ sub print_undocumented_section {
     foreach (@headersymskeys) {
         my $sym = $_;
         next if not defined $wikisyms{$sym};  # don't have a page for that function, skip it.
+        next if $sym =~ /\A\[category documentation\]/;   # not real symbols.
         my $symtype = $headersymstype{$sym};
         my $wikitype = $wikitypes{$sym};
         my $sectionsref = $wikisyms{$sym};