SDL: wikiheaders.pl: Added support for export to Unix manpages.

From 5d3f6b0215d301c13b7e4f02a7bc2058b60ffca0 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 6 Jan 2022 15:37:05 -0500
Subject: [PATCH] wikiheaders.pl: Added support for export to Unix manpages.

This mostly works, but likely needs some tweaking as we examine the output
of various pages.

Fixes #5163.
---
 build-scripts/wikiheaders.pl | 279 ++++++++++++++++++++++++++++++++---
 1 file changed, 260 insertions(+), 19 deletions(-)

diff --git a/build-scripts/wikiheaders.pl b/build-scripts/wikiheaders.pl
index 209a0d5fff0..1259087be53 100755
--- a/build-scripts/wikiheaders.pl
+++ b/build-scripts/wikiheaders.pl
@@ -14,6 +14,7 @@
     $copy_direction = 1, next if $_ eq '--copy-to-headers';
     $copy_direction = 1, next if $_ eq '--copy-to-header';
     $copy_direction = -1, next if $_ eq '--copy-to-wiki';
+    $copy_direction = -2, next if $_ eq '--copy-to-manpages';
     $srcpath = $_, next if not defined $srcpath;
     $wikipath = $_, next if not defined $wikipath;
 }
@@ -199,6 +200,9 @@ sub wikify {
 }
 
 
+my $dewikify_mode = 'md';
+my $dewikify_manpage_code_indent = 1;
+
 sub dewikify_chunk {
     my $wikitype = shift;
     my $str = shift;
@@ -207,30 +211,67 @@ sub dewikify_chunk {
 
     #print("\n\nDEWIKIFY CHUNK:\n\n$str\n\n\n");
 
-    if ($wikitype eq 'mediawiki') {
-        # Doxygen supports Markdown (and it just simply looks better than MediaWiki
-        # when looking at the raw headers), so do some conversions here as necessary.
+    if ($dewikify_mode eq 'md') {
+        if ($wikitype eq 'mediawiki') {
+            # Doxygen supports Markdown (and it just simply looks better than MediaWiki
+            # when looking at the raw headers), so do some conversions here as necessary.
 
-        $str =~ s/\[\[(SDL_[a-zA-Z0-9_]+)\]\]/$1/gms;  # Dump obvious wikilinks.
+            $str =~ s/\[\[(SDL_[a-zA-Z0-9_]+)\]\]/$1/gms;  # Dump obvious wikilinks.
 
-        # <code></code> is also popular.  :/
-        $str =~ s/\<code>(.*?)<\/code>/`$1`/gms;
+            # <code></code> is also popular.  :/
+            $str =~ s/\<code>(.*?)<\/code>/`$1`/gms;
 
-        # bold+italic
-        $str =~ s/\'''''(.*?)'''''/***$1***/gms;
+            # bold+italic
+            $str =~ s/'''''(.*?)'''''/***$1***/gms;
 
-        # bold
-        $str =~ s/\'''(.*?)'''/**$1**/gms;
+            # bold
+            $str =~ s/'''(.*?)'''/**$1**/gms;
 
-        # italic
-        $str =~ s/\''(.*?)''/*$1*/gms;
+            # italic
+            $str =~ s/''(.*?)''/*$1*/gms;
 
-        # bullets
-        $str =~ s/^\* /- /gm;
-    }
+            # bullets
+            $str =~ s/^\* /- /gm;
+        }
+
+        if (defined $code) {
+            $str .= "```$codelang$code```";
+        }
+    } elsif ($dewikify_mode eq 'manpage') {
+        $str =~ s/\./\\[char46]/gms;  # make sure these can't become control codes.
+        if ($wikitype eq 'mediawiki') {
+            $str =~ s/\s*\[\[(SDL_[a-zA-Z0-9_]+)\]\]\s*/\n.BR $1\n/gms;  # Dump obvious wikilinks.
+
+            # <code></code> is also popular.  :/
+            $str =~ s/\s*\<code>(.*?)<\/code>\s*/\n.BR $1\n/gms;
+
+            # bold+italic
+            $str =~ s/\s*'''''(.*?)'''''\s*/\n.BI $1\n/gms;
 
-    if (defined $code) {
-        $str .= "```$codelang$code```";
+            # bold
+            $str =~ s/\s*'''(.*?)'''\s*/\n.B $1\n/gms;
+
+            # italic
+            $str =~ s/\s*''(.*?)''\s*/\n.I $1\n/gms;
+
+            # bullets
+            $str =~ s/^\* /\n\\\(bu /gm;
+        } else {
+            die("Unexpected wikitype when converting to manpages\n");   # !!! FIXME: need to handle Markdown wiki pages.
+        }
+
+        if (defined $code) {
+            $code =~ s/\A\n+//gms;
+            $code =~ s/\n+\Z//gms;
+            if ($dewikify_manpage_code_indent) {
+                $str .= "\n.IP\n"
+            } else {
+                $str .= "\n.PP\n"
+            }
+            $str .= ".EX\n$code\n.EE\n.PP\n";
+        }
+    } else {
+        die("Unexpected dewikify_mode\n");
     }
 
     #print("\n\nDEWIKIFY CHUNK DONE:\n\n$str\n\n\n");
@@ -260,7 +301,7 @@ sub dewikify {
 }
 
 sub usage {
-    die("USAGE: $0 <source code git clone path> <wiki git clone path> [--copy-to-headers|--copy-to-wiki] [--warn-about-missing]\n\n");
+    die("USAGE: $0 <source code git clone path> <wiki git clone path> [--copy-to-headers|--copy-to-wiki|--copy-to-manpages] [--warn-about-missing]\n\n");
 }
 
 usage() if not defined $srcpath;
@@ -551,6 +592,7 @@ sub usage {
 if ($copy_direction == 1) {  # --copy-to-headers
     my %changed_headers = ();
 
+    $dewikify_mode = 'md';
     $wordwrap_mode = 'md';   # the headers use Markdown format.
 
     foreach (keys %headerfuncs) {
@@ -740,7 +782,7 @@ sub usage {
         my $fn = $_;
         next if not $headerfuncshasdoxygen{$fn};
         my $wikitype = defined $wikitypes{$fn} ? $wikitypes{$fn} : 'mediawiki';  # default to MediaWiki for new stuff FOR NOW.
-        die("Unexpected wikitype '$wikitype'\n") if (($wikitype ne 'mediawiki') and ($wikitype ne 'md'));
+        die("Unexpected wikitype '$wikitype'\n") if (($wikitype ne 'mediawiki') and ($wikitype ne 'md') and ($wikitype ne 'manpage'));
 
         #print("$fn\n"); next;
 
@@ -1009,6 +1051,205 @@ sub usage {
         close(FH);
         rename($path, "$wikipath/$_.${wikitype}") or die("Can't rename '$path' to '$wikipath/$_.${wikitype}': $!\n");
     }
+
+} elsif ($copy_direction == -2) { # --copy-to-manpages
+    # This only takes from the wiki data, since it has sections we omit from the headers, like code examples.
+
+    my $manpath = "$srcpath/man";
+    mkdir($manpath);
+
+    $dewikify_mode = 'manpage';
+    $wordwrap_mode = 'manpage';
+
+    my $introtxt = '';
+    if (0) {
+    open(FH, '<', "$srcpath/LICENSE.txt") or die("Can't open '$srcpath/LICENSE.txt': $!\n");
+    while (<FH>) {
+        chomp;
+        $introtxt .= ".\\\" $_\n";
+    }
+    close(FH);
+    }
+
+    my $gitrev = `cd "$srcpath" ; git rev-list HEAD~..`;
+    chomp($gitrev);
+
+    open(FH, '<', "$srcpath/include/SDL_version.h") or die("Can't open '$srcpath/include/SDL_version.h': $!\n");
+    my $majorver = 0;
+    my $minorver = 0;
+    my $patchver = 0;
+    while (<FH>) {
+        chomp;
+        if (/\A\#define SDL_MAJOR_VERSION\s+(\d+)\Z/) {
+            $majorver = int($1);
+        } elsif (/\A\#define SDL_MINOR_VERSION\s+(\d+)\Z/) {
+            $minorver = int($1);
+        } elsif (/\A\#define SDL_PATCHLEVEL\s+(\d+)\Z/) {
+            $patchver = int($1);
+        }
+    }
+    close(FH);
+    my $sdlversion = "$majorver.$minorver.$patchver";
+
+    foreach (keys %headerfuncs) {
+        my $fn = $_;
+        next if not defined $wikifuncs{$fn};  # don't have a page for that function, skip it.
+        my $wikitype = $wikitypes{$fn};
+        my $sectionsref = $wikifuncs{$fn};
+        my $remarks = %$sectionsref{'Remarks'};
+        my $params = %$sectionsref{'Function Parameters'};
+        my $returns = %$sectionsref{'Return Value'};
+        my $version = %$sectionsref{'Version'};
+        my $related = %$sectionsref{'Related Functions'};
+        my $examples = %$sectionsref{'Code Examples'};
+        my $deprecated = %$sectionsref{'Deprecated'};
+        my $brief = %$sectionsref{'[Brief]'};
+        my $decl = $headerdecls{$fn};
+        my $str = '';
+
+        $brief = "$brief";
+        $brief =~ s/\A[\s\n]*\= .*? \=\s*?\n+//ms;
+        $brief =~ s/\A[\s\n]*\=\= .*? \=\=\s*?\n+//ms;
+        $brief =~ s/\A(.*?\.) /$1\n/;  # \brief should only be one sentence, delimited by a period+space. Split if necessary.
+        my @briefsplit = split /\n/, $brief;
+        $brief = shift @briefsplit;
+        $brief = dewikify($wikitype, $brief);
+
+        if (defined $remarks) {
+            $remarks = dewikify($wikitype, join("\n", @briefsplit) . $remarks);
+        }
+
+        $str .= $introtxt;
+
+        $str .= ".\\\" This manpage content is licensed under Creative Commons\n";
+        $str .= ".\\\"  Attribution 4.0 International (CC BY 4.0)\n";
+        $str .= ".\\\"   https://creativecommons.org/licenses/by/4.0/\n";
+        $str .= ".\\\" This manpage was generated from SDL's wiki page for $fn:\n";
+        $str .= ".\\\"   https://wiki.libsdl.org/$fn\n";
+        $str .= ".\\\" Generated with SDL/build-scripts/wikiheaders.pl\n";
+        $str .= ".\\\"  revision $gitrev\n" if $gitrev ne '';
+        $str .= ".\\\" Please report issues in this manpage's content at:\n";
+        $str .= ".\\\"   https://github.com/libsdl-org/sdlwiki/issues/new?title=Feedback%20on%20page%20$fn\n";
+        $str .= ".\\\" Please report issues in the generation of this manpage from the wiki at:\n";
+        $str .= ".\\\"   https://github.com/libsdl-org/SDL/issues/new?title=Misgenerated%20manpage%20for%20$fn\n";
+        $str .= ".\\\" SDL can be found at https://libsdl.org/\n";
+
+        $str .= ".TH $fn 3 \"SDL $sdlversion\" \"Simple Directmedia Layer\" \"SDL$majorver FUNCTIONS\"\n";
+        $str .= ".SH NAME\n";
+
+        $str .= "$fn";
+        $str .= " \\- $brief" if (defined $brief);
+        $str .= "\n";
+
+        $str .= ".SH SYNOPSIS\n";
+        $str .= ".nf\n";
+        $str .= ".B #include \\(dqSDL.h\\(dq\n";
+        $str .= ".PP\n";
+
+        my @decllines = split /\n/, $decl;
+        foreach (@decllines) {
+            $str .= ".BI \"$_\n";
+        }
+        $str .= ".fi\n";
+
+        if (defined $remarks) {
+            $str .= ".SH DESCRIPTION\n";
+            $str .= $remarks . "\n";
+        }
+
+        if (defined $deprecated) {
+            $str .= ".SH DEPRECATED\n";
+            $str .= dewikify($wikitype, $deprecated) . "\n";
+        }
+
+        if (defined $params) {
+            $str .= ".SH FUNCTION PARAMETERS\n";
+            my @lines = split /\n/, $params;
+            if ($wikitype eq 'mediawiki') {
+                die("Unexpected data parsing MediaWiki table") if (shift @lines ne '{|');  # Dump the '{|' start
+                while (scalar(@lines) >= 3) {
+                    my $name = shift @lines;
+                    my $desc = shift @lines;
+                    my $terminator = shift @lines;  # the '|-' or '|}' line.
+                    last if ($terminator ne '|-') and ($terminator ne '|}');  # we seem to have run out of table.
+                    $name =~ s/\A\|\s*//;
+                    $name =~ s/\A\*\*(.*?)\*\*/$1/;
+                    $name =~ s/\A\'\'\'(.*?)\'\'\'/$1/;
+                    $desc =~ s/\A\|\s*//;
+                    $desc = dewikify($wikitype, $desc);
+                    #print STDERR "FN: $fn   NAME: $name   DESC: $desc TERM: $terminator\n";
+
+                    $str .= ".TP\n";
+                    $str .= ".I $name\n";
+                    $str .= "$desc\n";
+                }
+            } else {
+                die("write me");
+            }
+        }
+
+        if (defined $returns) {
+            $str .= ".SH RETURN VALUE\n";
+            $str .= dewikify($wikitype, $returns) . "\n";
+        }
+
+        if (defined $examples) {
+            $str .= ".SH CODE EXAMPLES\n";
+            $dewikify_manpage_code_indent = 0;
+            $str .= dewikify($wikitype, $examples) . "\n";
+            $dewikify_manpage_code_indent = 1;
+        }
+
+        if (defined $version) {
+            $str .= ".SH AVAILABILITY\n";
+            $str .= dewikify($wikitype, $version) . "\n";
+        }
+
+        if (defined $related) {
+            $str .= ".SH SEE ALSO\n";
+            # !!! FIXME: lots of code duplication in all of these.
+            my $v = dewikify($wikitype, $related);
+            my @desclines = split /\n/, $v;
+            my $nextstr = '';
+            foreach (@desclines) {
+                s/\A(\:|\* )//;
+                s/\(\)\Z//;  # Convert "SDL_Func()" to "SDL_Func"
+                s/\A\.BR\s+//;  # dewikify added this, but we want to handle it.
+                s/\A\s+//;
+                s/\s+\Z//;
+                next if $_ eq '';
+                $str .= "$nextstr.BR $_ (3)";
+                $nextstr = ",\n";
+            }
+            $str .= "\n";
+        }
+
+        if (0) {
+        $str .= ".SH COPYRIGHT\n";
+        $str .= "This manpage is licensed under\n";
+        $str .= ".UR https://creativecommons.org/licenses/by/4.0/\n";
+        $str .= "Creative Commons Attribution 4.0 International (CC BY 4.0)\n";
+        $str .= ".UE\n";
+        $str .= ".PP\n";
+        $str .= "This manpage was generated from\n";
+        $str .= ".UR https://wiki.libsdl.org/$fn\n";
+        $str .= "SDL's wiki\n";
+        $str .= ".UE\n";
+        $str .= "using SDL/build-scripts/wikiheaders.pl";
+        $str .= " revision $gitrev" if $gitrev ne '';
+        $str .= ".\n";
+        $str .= "Please report issues in this manpage at\n";
+        $str .= ".UR https://github.com/libsdl-org/sdlwiki/issues/new\n";
+        $str .= "our bugtracker!\n";
+        $str .= ".UE\n";
+        }
+
+        my $path = "$manpath/$_.3.tmp";
+        open(FH, '>', $path) or die("Can't open '$path': $!\n");
+        print FH $str;
+        close(FH);
+        rename($path, "$manpath/$_.3") or die("Can't rename '$path' to '$manpath/$_.3': $!\n");
+    }
 }
 
 # end of wikiheaders.pl ...