SDL: wikiheaders: Automatically categorize API symbols by subsystem. (08059)

From 0805990668bdf92f4602520c9b7fed6edd90b448 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 14 May 2024 10:48:39 -0400
Subject: [PATCH] wikiheaders: Automatically categorize API symbols by
 subsystem.

---
 .wikiheaders-options         |  5 +++++
 build-scripts/wikiheaders.pl | 36 ++++++++++++++++++++++++++++++++----
 include/SDL.h                |  3 ++-
 include/SDL_cpuinfo.h        |  2 ++
 include/SDL_gamecontroller.h |  2 ++
 include/SDL_guid.h           |  2 ++
 include/SDL_hidapi.h         |  2 ++
 include/SDL_loadso.h         |  2 ++
 include/SDL_rwops.h          |  2 ++
 include/SDL_stdinc.h         |  2 ++
 include/SDL_syswm.h          |  2 ++
 include/begin_code.h         |  2 ++
 12 files changed, 57 insertions(+), 5 deletions(-)

diff --git a/.wikiheaders-options b/.wikiheaders-options
index 970e0c6524554..9e6716ac56633 100644
--- a/.wikiheaders-options
+++ b/.wikiheaders-options
@@ -17,3 +17,8 @@ warn_about_missing = 0
 wikipreamble = (This is the legacy documentation for stable SDL2, the current stable version; [SDL3](https://wiki.libsdl.org/SDL3/) is the current development version.)
 wikiheaderfiletext = Defined in [%fname%](https://github.com/libsdl-org/SDL/blob/SDL2/include/%fname%)
 manpageheaderfiletext = Defined in %fname%
+
+# All SDL_config_*.h headers are uncategorized, in case something slips in from them.
+# All SDL_test_* headers become "Test" categories, everything else just converts like SDL_audio.h -> Audio
+# A handful of others we fix up in the header itself with /* WIKI CATEGORY: x */ comments.
+headercategoryeval = s/\ASDL_config_.*?\.h\Z//; s/\ASDL_test_.*?\.h\Z/Test/; s/\ASDL_?(.*?)\.h\Z/$1/; ucfirst();
diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl
index d7288d02142ad..185701b479dcf 100755
--- a/build-scripts/wikiheaders.pl
+++ b/build-scripts/wikiheaders.pl
@@ -31,6 +31,7 @@
 my $wikipreamble = undef;
 my $wikiheaderfiletext = 'Defined in %fname%';
 my $manpageheaderfiletext = 'Defined in %fname%';
+my $headercategoryeval = undef;
 my $changeformat = undef;
 my $manpath = undef;
 my $gitrev = undef;
@@ -70,6 +71,8 @@
 if (defined $optionsfname) {
     open OPTIONS, '<', $optionsfname or die("Failed to open options file '$optionsfname': $!\n");
     while (<OPTIONS>) {
+        next if /\A\s*\#/;  # Skip lines that start with (optional whitespace, then) '#' as comments.
+
         chomp;
         if (/\A(.*?)\=(.*)\Z/) {
             my $key = $1;
@@ -99,6 +102,7 @@
             $wikipreamble = $val, next if $key eq 'wikipreamble';
             $wikiheaderfiletext = $val, next if $key eq 'wikiheaderfiletext';
             $manpageheaderfiletext = $val, next if $key eq 'manpageheaderfiletext';
+            $headercategoryeval = $val, next if $key eq 'headercategoryeval';
         }
     }
     close(OPTIONS);
@@ -617,6 +621,7 @@ sub usage {
 my %headersymschunk = ();   # $headersymschunk{"SDL_OpenAudio"} -> offset in array in %headers that should be replaced for this symbol.
 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 %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"}.
@@ -689,6 +694,19 @@ sub print_undocumented_section {
     next if not $dent =~ /$selectheaderregex/;  # just selected headers.
     open(FH, '<', "$incpath/$dent") or die("Can't open '$incpath/$dent': $!\n");
 
+    # You can optionally set a wiki category with Perl code in .wikiheaders-options that gets eval()'d per-header,
+    # and also if you put `/* WIKI CATEGORY: blah */` on a line by itself, it'll change the category for any symbols
+    # below it in the same file. If no category is set, one won't be added for the symbol (beyond the standard CategoryFunction, etc)
+    my $current_wiki_category = undef;
+    if (defined $headercategoryeval) {
+        $_ = $dent;
+        $current_wiki_category = eval($headercategoryeval);
+        if (($current_wiki_category eq '') || ($current_wiki_category eq '-')) {
+            $current_wiki_category = undef;
+        }
+        #print("CATEGORY FOR '$dent' IS " . (defined($current_wiki_category) ? "'$current_wiki_category'" : '(undef)') . "\n");
+    }
+
     my @contents = ();
     my $ignoring_lines = 0;
     my $header_comment = -1;
@@ -722,6 +740,11 @@ sub print_undocumented_section {
             $ignoring_lines = 1;
             push @contents, $_;
             next;
+        } elsif (/\A\s*\/\*\s*WIKI CATEGORY:\s*(.*?)\s*\*\/\s*\Z/) {
+            $current_wiki_category = (($1 eq '') || ($1 eq '-')) ? undef : $1;
+            #print("CATEGORY FOR '$dent' CHANGED TO " . (defined($current_wiki_category) ? "'$current_wiki_category'" : '(undef)') . "\n");
+            push @contents, $_;
+            next;
         } elsif (/\A\s*extern\s+(SDL_DEPRECATED\s+|)(SDLMAIN_)?DECLSPEC/) {  # a function declaration without a doxygen comment?
             $symtype = 1;   # function declaration
             @templines = ();
@@ -1110,6 +1133,7 @@ sub print_undocumented_section {
         }
 
         if (!$skipsym) {
+            $headersymscategory{$sym} = $current_wiki_category if defined $current_wiki_category;
             $headersyms{$sym} = $str;
             $headerdecls{$sym} = $decl;
             $headersymslocation{$sym} = $dent;
@@ -1140,6 +1164,7 @@ sub print_undocumented_section {
     my $sym = $dent;
     $sym =~ s/\..*\Z//;
 
+    # (There are other pages to ignore, but these are known ones to not bother parsing.)
     # Ignore FrontPage.
     next if $sym eq 'FrontPage';
 
@@ -1840,14 +1865,17 @@ sub print_undocumented_section {
             die("Unexpected symbol type $symtype!");
         }
 
+        my $symcategory = $headersymscategory{$sym};
         if ($wikitype eq 'mediawiki') {
             $footer =~ s/\[\[CategoryAPI\]\],?\s*//g;
             $footer =~ s/\[\[CategoryAPI${symtypename}\]\],?\s*//g;
-            $footer = "[[CategoryAPI]], [[CategoryAPI$symtypename]]" . (($footer eq '') ? "\n" : ", $footer");
+            $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 = "[CategoryAPI](CategoryAPI), [CategoryAPI$symtypename](CategoryAPI$symtypename)" . (($footer eq '') ? '' : ', ') . $footer;
+            $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;
 
@@ -1939,8 +1967,8 @@ sub print_undocumented_section {
         }
 
         print FH "# $sym\n\nPlease refer to [$refersto]($refersto) for details.\n\n";
-        #print FH "----\n";
-        #print FH "[CategoryAPI](CategoryAPI)\n\n";
+        print FH "----\n";
+        print FH "[CategoryAPI](CategoryAPI), [CategoryAPIMacro](CategoryAPIMacro)\n\n";
 
         close(FH);
     }
diff --git a/include/SDL.h b/include/SDL.h
index 20c903b2fe4f6..c3eecbc54cf4a 100644
--- a/include/SDL.h
+++ b/include/SDL.h
@@ -25,7 +25,6 @@
  *  Main include header for the SDL library
  */
 
-
 #ifndef SDL_h_
 #define SDL_h_
 
@@ -70,6 +69,8 @@
 extern "C" {
 #endif
 
+/* WIKI CATEGORY: Init */
+
 /* As of version 0.5, SDL is loaded dynamically into the application */
 
 /**
diff --git a/include/SDL_cpuinfo.h b/include/SDL_cpuinfo.h
index 2a9dd380c2e7b..e8ac85dab6d73 100644
--- a/include/SDL_cpuinfo.h
+++ b/include/SDL_cpuinfo.h
@@ -30,6 +30,8 @@
 
 #include "SDL_stdinc.h"
 
+/* WIKI CATEGORY: CPUInfo */
+
 /* Need to do this here because intrin.h has C++ code in it */
 /* Visual Studio 2005 has a bug where intrin.h conflicts with winnt.h */
 #if defined(_MSC_VER) && (_MSC_VER >= 1500) && (defined(_M_IX86) || defined(_M_X64))
diff --git a/include/SDL_gamecontroller.h b/include/SDL_gamecontroller.h
index 3d60047e3c25f..04043a5db0f4d 100644
--- a/include/SDL_gamecontroller.h
+++ b/include/SDL_gamecontroller.h
@@ -25,6 +25,8 @@
  *  Include file for SDL game controller event handling
  */
 
+/* WIKI CATEGORY: GameController */
+
 #ifndef SDL_gamecontroller_h_
 #define SDL_gamecontroller_h_
 
diff --git a/include/SDL_guid.h b/include/SDL_guid.h
index 224647cc44976..ec6be97085fdf 100644
--- a/include/SDL_guid.h
+++ b/include/SDL_guid.h
@@ -25,6 +25,8 @@
  *  Include file for handling SDL_GUID values.
  */
 
+/* WIKI CATEGORY: GUID */
+
 #ifndef SDL_guid_h_
 #define SDL_guid_h_
 
diff --git a/include/SDL_hidapi.h b/include/SDL_hidapi.h
index e6270bd9ddad3..320bdb503fc05 100644
--- a/include/SDL_hidapi.h
+++ b/include/SDL_hidapi.h
@@ -59,6 +59,8 @@
  * on iOS or tvOS to avoid a dependency on the CoreBluetooth framework.
  */
 
+/* WIKI CATEGORY: HIDAPI */
+
 #ifndef SDL_hidapi_h_
 #define SDL_hidapi_h_
 
diff --git a/include/SDL_loadso.h b/include/SDL_loadso.h
index 4edc22e9e756c..d5d9992adfb6b 100644
--- a/include/SDL_loadso.h
+++ b/include/SDL_loadso.h
@@ -38,6 +38,8 @@
  *      the results you expect. :)
  */
 
+/* WIKI CATEGORY: LoadSO */
+
 #ifndef SDL_loadso_h_
 #define SDL_loadso_h_
 
diff --git a/include/SDL_rwops.h b/include/SDL_rwops.h
index b481a4b3c763f..3d51a85e2e07e 100644
--- a/include/SDL_rwops.h
+++ b/include/SDL_rwops.h
@@ -26,6 +26,8 @@
  *  data streams.  It can easily be extended to files, memory, etc.
  */
 
+/* WIKI CATEGORY: RWOPS */
+
 #ifndef SDL_rwops_h_
 #define SDL_rwops_h_
 
diff --git a/include/SDL_stdinc.h b/include/SDL_stdinc.h
index 63f01591c348b..cbf759863c196 100644
--- a/include/SDL_stdinc.h
+++ b/include/SDL_stdinc.h
@@ -25,6 +25,8 @@
  *  This is a general header that includes C language support.
  */
 
+/* WIKI CATEGORY: StdInc */
+
 #ifndef SDL_stdinc_h_
 #define SDL_stdinc_h_
 
diff --git a/include/SDL_syswm.h b/include/SDL_syswm.h
index f42150618dbe0..705bf6b018f8c 100644
--- a/include/SDL_syswm.h
+++ b/include/SDL_syswm.h
@@ -42,6 +42,8 @@
  *  you can enable it with SDL_EventState().
  */
 
+/* WIKI CATEGORY: SYSWM */
+
 struct SDL_SysWMinfo;
 
 #if !defined(SDL_PROTOTYPES_ONLY)
diff --git a/include/begin_code.h b/include/begin_code.h
index a47a7d2b6413c..1ee7567783a79 100644
--- a/include/begin_code.h
+++ b/include/begin_code.h
@@ -27,6 +27,8 @@
  *  If you don't like ugly C preprocessor code, don't look at this file. :)
  */
 
+/* WIKI CATEGORY: BeginCode */
+
 /* This shouldn't be nested -- included it around code only. */
 #ifdef SDL_begin_code_h
 #error Nested inclusion of begin_code.h