From 9ba16feff59560e760107237667d6157998a055f Mon Sep 17 00:00:00 2001 From: Ben Kibbey Date: Tue, 27 Sep 2022 20:09:39 -0700 Subject: [PATCH] docs: Update yat2m. --- doc/yat2m.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 304 insertions(+), 58 deletions(-) diff --git a/doc/yat2m.c b/doc/yat2m.c index 23fc6bab..c2806e39 100644 --- a/doc/yat2m.c +++ b/doc/yat2m.c @@ -1,5 +1,5 @@ /* yat2m.c - Yet Another Texi 2 Man converter - * Copyright (C) 2005, 2013, 2015, 2016 g10 Code GmbH + * Copyright (C) 2005, 2013, 2015, 2016, 2017 g10 Code GmbH * Copyright (C) 2006, 2008, 2011 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify @@ -49,7 +49,7 @@ .B whateever you want @end ifset - alternativly a special comment may be used: + alternatively a special comment may be used: @c man:.B whatever you want @@ -114,7 +114,7 @@ #if MY_GCC_VERSION >= 20500 # define ATTR_PRINTF(f, a) __attribute__ ((format(printf,f,a))) -# define ATTR_NR_PRINTF(f, a) __attribute__ ((noreturn, format(printf,f,a))) +# define ATTR_NR_PRINTF(f, a) __attribute__ ((__noreturn__, format(printf,f,a))) #else # define ATTR_PRINTF(f, a) # define ATTR_NR_PRINTF(f, a) @@ -128,7 +128,11 @@ #define PGM "yat2m" -#define VERSION "1.0" +#ifdef PACKAGE_VERSION +# define VERSION PACKAGE_VERSION +#else +# define VERSION "1.0" +#endif /* The maximum length of a line including the linefeed and one extra character. */ @@ -137,10 +141,36 @@ /* Number of allowed condition nestings. */ #define MAX_CONDITION_NESTING 10 +static char const default_css[] = + "\n"; + + + /* Option flags. */ static int verbose; static int quiet; static int debug; +static int htmlmode; static const char *opt_source; static const char *opt_release; static const char *opt_date; @@ -353,7 +383,7 @@ ascii_strupr (char *string) const char * isodatestring (void) { - static char buffer[11+5]; + static char buffer[36]; struct tm *tp; time_t atime; @@ -482,6 +512,9 @@ evaluate_conditions (const char *fname, int lnr) { int i; + (void)fname; + (void)lnr; + /* for (i=0; i < condition_stack_idx; i++) */ /* inf ("%s:%d: stack[%d] %s %s %c", */ /* fname, lnr, i, condition_stack[i]->isset? "set":"clr", */ @@ -672,6 +705,25 @@ start_page (char *name) } +/* Write a character to FP. */ +static void +writechr (int c, FILE *fp) +{ + putc (c, fp); +} + + +/* Write depending on HTMLMODE either ROFF or HTML to FP. */ +static void +writestr (const char *roff, const char *html, FILE *fp) +{ + const char *s = htmlmode? html : roff; + + if (s) + fputs (s, fp); +} + + /* Write the .TH entry of the current page. Return -1 if there is a problem with the page. */ static int @@ -679,7 +731,9 @@ write_th (FILE *fp) { char *name, *p; - fputs (".\\\" Created from Texinfo source by yat2m " VERSION "\n", fp); + writestr (".\\\" Created from Texinfo source by yat2m " VERSION "\n", + "\n", + fp); name = ascii_strupr (xstrdup (thepage.name)); p = strrchr (name, '.'); @@ -690,15 +744,159 @@ write_th (FILE *fp) return -1; } *p++ = 0; - fprintf (fp, ".TH %s %s %s \"%s\" \"%s\"\n", - name, p, isodatestring (), opt_release, opt_source); + + if (htmlmode) + { + fputs ("\n" + "\n", fp); + fprintf (fp, " %s(%s)\n", name, p); + fputs (default_css, fp); + fputs ("\n" + "\n", fp); + fputs ("
\n", fp); + } + + /* This roff source + * .TH GPG 1 2016-12-20 "GnuPG 2.1.17" "GNU Privacy Guard 2.1" + * is rendered by man like this: + * GPG(1) GNU Privacy Guard 2.1 GPG(1) + * [...] + * GnuPG 2.1.17 2016-12-20 GPG(1) + */ + if (htmlmode) + { + fprintf (fp, "

" + "%s(%s) " + "%s " + "%s(%s)" + "

\n", + name, p, opt_source, name, p); + /* Note that the HTML footer is written by write_bottom(). */ + + } + else + fprintf (fp, ".TH %s %s %s \"%s\" \"%s\"\n", + name, p, isodatestring (), opt_release, opt_source); + free (name); return 0; } +/* In HTML mode we need to render a footer. */ +static int +write_bottom (FILE *fp) +{ + char *name, *p; + + if (!htmlmode) + return 0; + + name = ascii_strupr (xstrdup (thepage.name)); + p = strrchr (name, '.'); + if (!p || !p[1]) + { + err ("no section name in man page '%s'", thepage.name); + free (name); + return -1; + } + *p++ = 0; + + /* This roff source + * .TH GPG 1 2016-12-20 "GnuPG 2.1.17" "GNU Privacy Guard 2.1" + * is rendered by man to this footer: + * GnuPG 2.1.17 2016-12-20 GPG(1) + */ + fprintf (fp, "

" + "%s " + "%s " + "%s(%s)" + "

\n", + opt_release, isodatestring (), name, p); + fputs ("
\n", fp); + fputs ("\n" + "\n", fp); + + free (name); + return 0; +} + + +/* Write the .SH header. With NULL passed for NAME just close a + * section in html mode if there is an open section. */ +static void +write_sh (FILE *fp, const char *name) +{ + static int in_section; + + if (htmlmode && in_section) + fprintf (fp, "\n"); + in_section = 0; + + if (name) + { + if (htmlmode) + fprintf (fp, + "
\n" + "

%s

\n", name); + else + fprintf (fp, ".SH %s\n", name); + in_section = 1; + } +} + +/* Render a @item line to HTML. (LINE,LEN) gives the arguments of + * @item. Use NULL for LINE to close a possible open
  • . ITEMX + * flags a @itemx line. */ +static void +write_html_item (FILE *fp, const char *line, size_t len, int itemx) +{ + static int in_li; + const char *rest; + size_t n, n0; + int eol_action = 0; + int table_level = 0; + + if (!itemx && in_li) + { + fprintf (fp, "
  • \n"); + in_li = 0; + } + + if (line) + { + /* Trim the LF and skip leading spaces. */ + if (len && line[len-1] == '\n') + len--; + for (; len && (*line == ' ' || *line == '\t'); len--, line++) + ; + if (len) + { + rest = line; + for (n=0; n < len && !(*rest == ' ' || *rest == '\t'); n++, rest++) + ; + n0 = n; + for (; n < len && (*rest == ' ' || *rest == '\t'); n++, rest++) + ; + len -= n; + /* Now the first word is (LINE,N0) and the args are (REST,LEN) */ + fprintf (fp, "%s%.*s", + itemx? " ":"
  • ", (int)n0, line); + if (len) + { + fputs (" ", fp); + proc_texi_buffer (fp, rest, len, &table_level, &eol_action); + fputs ("", fp); + } + fputs ("\n", fp); + in_li = 1; + } + } +} + + /* Process the texinfo command COMMAND (without the leading @) and - write output if needed to FP. REST is the remainer of the line + write output if needed to FP. REST is the remainder of the line which should either point to an opening brace or to a white space. The function returns the number of characters already processed from REST. LEN is the usable length of REST. TABLE_LEVEL is used to @@ -712,20 +910,23 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, int what; /* What to do with this command. */ const char *lead_in; /* String to print with a opening brace. */ const char *lead_out;/* String to print with the closing brace. */ + const char *html_in; /* Same as LEAD_IN but for HTML. */ + const char *html_out;/* Same as LEAD_OUT but for HTML. */ } cmdtbl[] = { - { "command", 0, "\\fB", "\\fR" }, - { "code", 0, "\\fB", "\\fR" }, - { "url", 0, "\\fB", "\\fR" }, - { "sc", 0, "\\fB", "\\fR" }, - { "var", 0, "\\fI", "\\fR" }, - { "samp", 0, "\\(aq", "\\(aq" }, + { "command", 0, "\\fB", "\\fR", "", "" }, + { "code", 0, "\\fB", "\\fR", "", "" }, + { "url", 0, "\\fB", "\\fR", "", "" }, + { "sc", 0, "\\fB", "\\fR", "", "" }, + { "var", 0, "\\fI", "\\fR", "", "" }, + { "samp", 0, "\\(oq", "\\(cq" }, + { "kbd", 0, "\\(oq", "\\(cq" }, { "file", 0, "\\(oq\\fI","\\fR\\(cq" }, { "env", 0, "\\(oq\\fI","\\fR\\(cq" }, { "acronym", 0 }, { "dfn", 0 }, - { "option", 0, "\\fB", "\\fR" }, - { "example", 1, ".RS 2\n.nf\n" }, - { "smallexample", 1, ".RS 2\n.nf\n" }, + { "option", 0, "\\fB", "\\fR", "", "" }, + { "example", 1, ".RS 2\n.nf\n", NULL, "\n
    \n", "\n
    \n" }, + { "smallexample", 1, ".RS 2\n.nf\n", NULL, "\n
    \n", "\n
    \n" }, { "asis", 7 }, { "anchor", 7 }, { "cartouche", 1 }, @@ -734,7 +935,7 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, { "pxref", 0, "see: [", "]" }, { "uref", 0, "(\\fB", "\\fR)" }, { "footnote",0, " ([", "])" }, - { "emph", 0, "\\fI", "\\fR" }, + { "emph", 0, "\\fI", "\\fR", "", "" }, { "w", 1 }, { "c", 5 }, { "efindex", 1 }, @@ -748,7 +949,7 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, { "chapheading", 0}, { "item", 2, ".TP\n.B " }, { "itemx", 2, ".TQ\n.B " }, - { "table", 3 }, + { "table", 3, NULL, NULL, "
      \n", "
    \n" }, { "itemize", 3 }, { "bullet", 0, "* " }, { "*", 0, "\n.br"}, @@ -762,26 +963,36 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, int i; const char *s; const char *lead_out = NULL; + const char *html_out = NULL; int ignore_args = 0; for (i=0; cmdtbl[i].name && strcmp (cmdtbl[i].name, command); i++) ; if (cmdtbl[i].name) { - s = cmdtbl[i].lead_in; - if (s) - fputs (s, fp); + writestr (cmdtbl[i].lead_in, cmdtbl[i].html_in, fp); lead_out = cmdtbl[i].lead_out; + html_out = cmdtbl[i].html_out; switch (cmdtbl[i].what) { case 1: /* Throw away the entire line. */ s = memchr (rest, '\n', len); return s? (s-rest)+1 : len; case 2: /* Handle @item. */ + if (htmlmode) + { + s = memchr (rest, '\n', len); + n = s? (s-rest)+1 : len; + write_html_item (fp, rest, n, !strcmp(cmdtbl[i].name, "itemx")); + return n; + } break; case 3: /* Handle table. */ if (++(*table_level) > 1) - fputs (".RS\n", fp); + { + write_html_item (fp, NULL, 0, 0); + writestr (".RS\n", "
      \n", fp); + } /* Now throw away the entire line. */ s = memchr (rest, '\n', len); return s? (s-rest)+1 : len; @@ -792,25 +1003,27 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, if (n >= 5 && !memcmp (s, "table", 5) && (!n || s[5] == ' ' || s[5] == '\t' || s[5] == '\n')) { + if (htmlmode) + write_html_item (fp, NULL, 0, 0); if ((*table_level)-- > 1) - fputs (".RE\n", fp); + writestr (".RE\n", "
    \n", fp); else - fputs (".P\n", fp); + writestr (".P\n", "\n", fp); } else if (n >= 7 && !memcmp (s, "example", 7) && (!n || s[7] == ' ' || s[7] == '\t' || s[7] == '\n')) { - fputs (".fi\n.RE\n", fp); + writestr (".fi\n.RE\n", "\n", fp); } else if (n >= 12 && !memcmp (s, "smallexample", 12) && (!n || s[12] == ' ' || s[12] == '\t' || s[12] == '\n')) { - fputs (".fi\n.RE\n", fp); + writestr (".fi\n.RE\n", "\n", fp); } else if (n >= 9 && !memcmp (s, "quotation", 9) && (!n || s[9] == ' ' || s[9] == '\t' || s[9] == '\n')) { - fputs ("\\fR\n.RE\n", fp); + writestr ("\\fR\n.RE\n", "xx", fp); } /* Now throw away the entire line. */ s = memchr (rest, '\n', len); @@ -820,9 +1033,22 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, ; if (n >= 4 && !memcmp (s, "man:", 4)) { - for (s+=4, n-=4; n && *s != '\n'; n--, s++) - putc (*s, fp); - putc ('\n', fp); + s += 4; + n -= 4; + if (htmlmode) + { + if (!strncmp (s, ".RE\n", 4) + || !strncmp (s, ".RS\n", 4)) + ; + else + inf ("unknown special comment \"man:\""); + } + else + { + for (; n && *s != '\n'; n--, s++) + writechr (*s, fp); + writechr ('\n', fp); + } } /* Now throw away the entire line. */ s = memchr (rest, '\n', len); @@ -853,18 +1079,20 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, } else { - size_t len = s - (rest + 1); + size_t rlen = s - (rest + 1); macro_t m; for (m = variablelist; m; m = m->next) - if (strlen (m->name) == len - &&!strncmp (m->name, rest+1, len)) - break; + { + if (strlen (m->name) == rlen + && !strncmp (m->name, rest+1, rlen)) + break; + } if (m) - fputs (m->value, fp); + writestr (m->value, m->value, fp); else inf ("texinfo variable '%.*s' is not set", - (int)len, rest+1); + (int)rlen, rest+1); } } break; @@ -909,8 +1137,7 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len, else n = 0; - if (lead_out) - fputs (lead_out, fp); + writestr (lead_out, html_out, fp); return n; } @@ -937,16 +1164,16 @@ proc_texi_buffer (FILE *fp, const char *line, size_t len, switch (*s) { case '@': case '{': case '}': - putc (*s, fp); in_cmd = 0; + writechr (*s, fp); in_cmd = 0; break; case ':': /* Not ending a sentence flag. */ in_cmd = 0; break; case '.': case '!': case '?': /* Ending a sentence. */ - putc (*s, fp); in_cmd = 0; + writechr (*s, fp); in_cmd = 0; break; case ' ': case '\t': case '\n': /* Non collapsing spaces. */ - putc (*s, fp); in_cmd = 0; + writechr (*s, fp); in_cmd = 0; break; default: cmdidx = 0; @@ -979,17 +1206,17 @@ proc_texi_buffer (FILE *fp, const char *line, size_t len, switch (*eol_action) { case 1: /* Create a dummy paragraph. */ - fputs ("\n\\ \n", fp); + writestr ("\n\\ \n", "\n<-- dummy par -->\n", fp); break; default: - putc (*s, fp); + writechr (*s, fp); } *eol_action = 0; } else if (*s == '\\') - fputs ("\\\\", fp); + writestr ("\\\\", "\\\\", fp); else - putc (*s, fp); + writechr (*s, fp); } if (in_cmd > 1) @@ -1013,12 +1240,13 @@ parse_texi_line (FILE *fp, const char *line, int *table_level) /* A quick test whether there are any texinfo commands. */ if (!strchr (line, '@')) { - fputs (line, fp); - putc ('\n', fp); + /* FIXME: In html mode escape HTML stuff. */ + writestr (line, line, fp); + writechr ('\n', fp); return; } proc_texi_buffer (fp, line, strlen (line), table_level, &eol_action); - putc ('\n', fp); + writechr ('\n', fp); } @@ -1033,8 +1261,10 @@ write_content (FILE *fp, line_buffer_t lines) { if (line->verbatim) { - fputs (line->line, fp); - putc ('\n', fp); + /* FIXME: IN HTML mode we need to employ a parser for roff + * markup. */ + writestr (line->line, line->line, fp); + writechr ('\n', fp); } else { @@ -1093,7 +1323,8 @@ finish_page (void) } else if (opt_store) { - inf ("writing '%s'", thepage.name ); + if (verbose) + inf ("writing '%s'", thepage.name ); fp = fopen ( thepage.name, "w" ); if (!fp) die ("failed to create '%s': %s\n", thepage.name, strerror (errno)); @@ -1117,7 +1348,7 @@ finish_page (void) if (sect) { - fprintf (fp, ".SH %s\n", sect->name); + write_sh (fp, sect->name); write_content (fp, sect->lines); /* Now continue with all non standard sections directly following this one. */ @@ -1128,7 +1359,7 @@ finish_page (void) break; if (sect->name) { - fprintf (fp, ".SH %s\n", sect->name); + write_sh (fp, sect->name); write_content (fp, sect->lines); } } @@ -1136,6 +1367,9 @@ finish_page (void) } } + write_sh (fp, NULL); + if (write_bottom (fp)) + goto leave; leave: if (fp != stdout) @@ -1478,6 +1712,7 @@ int main (int argc, char **argv) { int last_argc = -1; + const char *s; opt_source = "GNU"; opt_release = ""; @@ -1505,6 +1740,7 @@ main (int argc, char **argv) puts ( "Usage: " PGM " [OPTION] [FILE]\n" "Extract man pages from a Texinfo source.\n\n" + " --html render output as HTML\n" " --source NAME use NAME as source field\n" " --release STRING use STRING as the release field\n" " --date EPOCH use EPOCH as publication date\n" @@ -1514,20 +1750,25 @@ main (int argc, char **argv) " --debug enable additional debug output\n" " --help display this help and exit\n" " -I DIR also search in include DIR\n" - " -D gpgone the only usable define\n\n" + " -D MACRO define MACRO to 1\n\n" "With no FILE, or when FILE is -, read standard input.\n\n" - "Report bugs to ."); + "Report bugs to ."); exit (0); } else if (!strcmp (*argv, "--version")) { puts (PGM " " VERSION "\n" - "Copyright (C) 2005 g10 Code GmbH\n" + "Copyright (C) 2005, 2017 g10 Code GmbH\n" "This program comes with ABSOLUTELY NO WARRANTY.\n" "This is free software, and you are welcome to redistribute it\n" "under certain conditions. See the file COPYING for details."); exit (0); } + else if (!strcmp (*argv, "--html")) + { + htmlmode = 1; + argc--; argv++; + } else if (!strcmp (*argv, "--verbose")) { verbose = 1; @@ -1611,6 +1852,11 @@ main (int argc, char **argv) if (argc > 1) die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n"); + /* Take care of supplied timestamp for reproducible builds. See + * https://reproducible-builds.org/specs/source-date-epoch/ */ + if (!opt_date && (s = getenv ("SOURCE_DATE_EPOCH")) && *s) + opt_date = s; + /* Start processing. */ if (argc && strcmp (*argv, "-")) { -- 2.11.4.GIT