Made arg_parse more readable.
[gnupg.git] / doc / yat2m.c
index e45168a..a936fef 100644 (file)
@@ -1,10 +1,10 @@
 /* yat2m.c - Yet Another Texi 2 Man converter
  *     Copyright (C) 2005 g10 Code GmbH
- *      Copyright (C) 2006 2006 Free Software Foundation, Inc.
+ *      Copyright (C) 2006, 2008 Free Software Foundation, Inc.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
@@ -13,9 +13,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301, USA.
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 /*
     go into the man page. These macros need to be used without leading
     left space. Processing starts after a "manpage" macro has been
     seen.  "mansect" identifies the section and yat2m make sure to
-    emit the sections in the proper order.  To insert verbatim troff
-    markup, the follwing texinfo code may be used:
+    emit the sections in the proper order.  Note that @mansect skips
+    the next input line if that line begins with @section, @subsection or
+    @chapheading.
+
+    To insert verbatim troff markup, the following texinfo code may be
+    used:
 
       @ifset manverb
       .B whateever you want
 
       @c man:.B whatever you want
 
-    This is useful in case you need just one line.  @section is
-    ignored, however @subsection gets rendered as ".SS".  @menu is
-    completely skipped. Several man pages may be extracted from one
-    file, either using the --store or the --select option.
-    Makefile snippet from GnuPG:
+    This is useful in case you need just one line. If you want to
+    include parts only in the man page but keep the texinfo
+    translation you may use:
+
+      @ifset isman
+      stuff to be rendered only on man pages
+      @end ifset
+
+    or to exclude stuff from man pages:
+
+      @ifclear isman
+      stuff not to be rendered on man pages
+      @end ifclear
+
+    the keyword @section is ignored, however @subsection gets rendered
+    as ".SS".  @menu is completely skipped. Several man pages may be
+    extracted from one file, either using the --store or the --select
+    option.
 
 
 */
@@ -72,7 +87,7 @@
 
 
 #define PGM "yat2m"
-#define VERSION "0.5"
+#define VERSION "1.0"
 
 /* The maximum length of a line including the linefeed and one extra
    character. */
@@ -85,13 +100,30 @@ static int debug;
 static const char *opt_source; 
 static const char *opt_release; 
 static const char *opt_select;
+static const char *opt_include;
 static int opt_store;
 
+/* The only define we understand is -D gpgone.  Thus we need a simple
+   boolean tro track it. */
+static int gpgone_defined;
 
 /* Flag to keep track whether any error occurred.  */
 static int any_error;
 
 
+/* Object to keep macro definitions.  */
+struct macro_s
+{
+  struct macro_s *next;
+  char *value;  /* Malloced value. */
+  char name[1];
+};
+typedef struct macro_s *macro_t;
+
+/* List of all defined macros. */
+static macro_t macrolist;
+
+
 /* Object to store one line of content.  */
 struct line_buffer_s
 {
@@ -129,13 +161,14 @@ static struct
 } thepage;
 
 
-/* The list of standard section names.  */
+/* The list of standard section names.  COMMANDS and ASSUAN are GnuPG
+   specific. */
 static const char * const standard_sections[] = 
   { "NAME",  "SYNOPSIS",  "DESCRIPTION",
     "RETURN VALUE", "EXIT STATUS", "ERROR HANDLING", "ERRORS",
-    "OPTIONS", "USAGE", "EXAMPLES", "FILES",
+    "COMMANDS", "OPTIONS", "USAGE", "EXAMPLES", "FILES",
     "ENVIRONMENT", "DIAGNOSTICS", "SECURITY", "CONFORMING TO",
-    "NOTES", "BUGS", "AUTHOR", "SEE ALSO", NULL };
+    "ASSUAN", "NOTES", "BUGS", "AUTHOR", "SEE ALSO", NULL };
 
 
 /*-- Local prototypes.  --*/
@@ -335,7 +368,6 @@ add_content (const char *sectname, char *line, int verbatim)
   section_buffer_t sect;
   line_buffer_t lb;
 
-  
   sect = get_section_buffer (sectname);
   if (sect->last_line && !sect->last_line->verbatim == !verbatim)
     {
@@ -382,6 +414,8 @@ write_th (FILE *fp)
 {
   char *name, *p;
 
+  fputs (".\\\" Created from Texinfo source by yat2m " VERSION "\n", fp);
+
   name = ascii_strupr (xstrdup (thepage.name));
   p = strrchr (name, '.');
   if (!p || !p[1])
@@ -415,11 +449,13 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
   } cmdtbl[] = {
     { "command", 0, "\\fB", "\\fR" },
     { "code",    0, "\\fB", "\\fR" },
+    { "sc",      0, "\\fB", "\\fR" },
     { "var",     0, "\\fI", "\\fR" },
-    { "samp",    0, "\n'",  "'\n"  },
-    { "file",    0, "`\\fI","\\fR'" }, 
-    { "env",     0, "`\\fI","\\fR'" }, 
+    { "samp",    0, "\\(aq", "\\(aq"  },
+    { "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" },
@@ -436,17 +472,18 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
     { "opindex", 1 },
     { "cpindex", 1 },
     { "cindex",  1 },
-    { "node",    1 },
     { "noindent", 0 },
     { "section", 1 },
+    { "chapter", 1 },
     { "subsection", 6, "\n.SS " },
     { "chapheading", 0},
     { "item",    2, ".TP\n.B " },
     { "itemx",   2, ".TP\n.B " },
     { "table",   3 }, 
+    { "itemize",   3 }, 
+    { "bullet",  0, "* " },
     { "end",     4 },
     { "quotation",1, ".RS\n\\fB" },
-    { "ifset",   1 },
     { NULL }
   };
   size_t n;
@@ -528,8 +565,20 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
     }
   else
     {
-      inf ("texinfo command `%s' not supported (%.*s)", command,
-           ((s = memchr (rest, '\n', len)), (s? (s-rest) : len)), rest);
+      macro_t m;
+
+      for (m = macrolist; m ; m = m->next)
+        if (!strcmp (m->name, command))
+            break;
+      if (m)
+        {
+          proc_texi_buffer (fp, m->value, strlen (m->value),
+                            table_level, eol_action);
+          ignore_args = 1; /* Parameterized macros are not yet supported. */
+        }
+      else
+        inf ("texinfo command `%s' not supported (%.*s)", command,
+             ((s = memchr (rest, '\n', len)), (s? (s-rest) : len)), rest);
     }
 
   if (*rest == '{')
@@ -628,9 +677,21 @@ proc_texi_buffer (FILE *fp, const char *line, size_t len,
             }
           *eol_action = 0;
         }
+      else if (*s == '\\')
+        fputs ("\\\\", fp);
       else
         putc (*s, fp);
     }
+
+  if (in_cmd > 1)
+    {
+      cmdbuf[cmdidx] = 0;
+      n = proc_texi_cmd (fp, cmdbuf, s, len, table_level, eol_action);
+      assert (n <= len);
+      s += n; len -= n;
+      s--; len++;
+      in_cmd = 0;
+    }
 }
 
 
@@ -696,7 +757,7 @@ static void
 finish_page (void)
 {
   FILE *fp;
-  section_buffer_t sect;
+  section_buffer_t sect = NULL;
   int idx;
   const char *s;
   int i;
@@ -781,13 +842,26 @@ finish_page (void)
 /* Parse one Texinfo file and create manpages according to the
    embedded instructions.  */
 static void
-parse_file (const char *fname, FILE *fp, char **section_name)
+parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
 {
   char *line;
   int lnr = 0;
+  /* Fixme: The following state variables don't carry over to include
+     files. */
   int in_verbatim = 0;
-  int in_pause = 0;
   int skip_to_end = 0;        /* Used to skip over menu entries. */
+  int skip_sect_line = 0;     /* Skip after @mansect.  */
+  int ifset_nesting = 0;      /* How often a ifset has been seen. */
+  int ifclear_nesting = 0;    /* How often a ifclear has been seen. */
+  int in_gpgone = 0;          /* Keep track of "@ifset gpgone" parts.  */
+  int not_in_gpgone = 0;      /* Keep track of "@ifclear gpgone" parts.  */
+  int not_in_man = 0;         /* Keep track of "@ifclear isman" parts.  */
+
+  /* Helper to define a macro. */
+  char *macroname = NULL;     
+  char *macrovalue = NULL; 
+  size_t macrovaluesize = 0;
+  size_t macrovalueused = 0;
 
   line = xmalloc (LINESIZE);
   while (fgets (line, LINESIZE, fp))
@@ -799,45 +873,199 @@ parse_file (const char *fname, FILE *fp, char **section_name)
       lnr++;
       if (!n || line[n-1] != '\n')
         {
-          err ("%s:$d: trailing linefeed missing, line too long or "
+          err ("%s:%d: trailing linefeed missing, line too long or "
                "embedded Nul character", fname, lnr);
           break;
         }
       line[--n] = 0;
-      /* We only parse lines we need and ignore the rest.  There are a
-         few macros used to control this as well as one @ifset
-         command.  Parts we know about are saved away into containers
-         separate for each section. */
+
       if (*line == '@')
         {
           for (p=line+1, n=1; *p && *p != ' ' && *p != '\t'; p++)
             n++;
           while (*p == ' ' || *p == '\t')
             p++;
+        }
+      else
+        p = line;
 
-          if (skip_to_end 
-              &&n == 4 && !memcmp (line, "@end", 4)
-              && (line[4]==' '||line[4]=='\t'||!line[4]))
+      /* Take action on macro.  */
+      if (macroname)
+        {
+          if (n == 4 && !memcmp (line, "@end", 4)
+              && (line[4]==' '||line[4]=='\t'||!line[4])
+              && !strncmp (p, "macro", 5)
+              && (p[5]==' '||p[5]=='\t'||!p[5]))
             {
-              skip_to_end = 0;
+              macro_t m;
+
+              if (macrovalueused)
+                macrovalue[--macrovalueused] = 0; /* Kill the last LF. */
+              macrovalue[macrovalueused] = 0;     /* Terminate macro. */
+              macrovalue = xrealloc (macrovalue, macrovalueused+1);
+              
+              for (m= macrolist; m; m = m->next)
+                if (!strcmp (m->name, macroname))
+                  break;
+              if (m)
+                free (m->value);
+              else
+                {
+                  m = xcalloc (1, sizeof *m + strlen (macroname));
+                  strcpy (m->name, macroname);
+                  m->next = macrolist;
+                  macrolist = m;
+                }
+              m->value = macrovalue;
+              macrovalue = NULL;
+              free (macroname);
+              macroname = NULL;
             }
-          else if (n == 6 && !memcmp (line, "@ifset", 6)
-              && !strncmp (p, "manverb", 7) && (p[7]==' '||p[7]=='\t'||!p[7]))
+          else
             {
-              if (in_verbatim)
-                err ("%s:%d: nested \"@ifset manverb\"", fname, lnr);
-              else
-                in_verbatim = 1;
+              if (macrovalueused + strlen (line) + 2 >= macrovaluesize)
+                {
+                  macrovaluesize += strlen (line) + 256;
+                  macrovalue = xrealloc (macrovalue,  macrovaluesize);
+                }
+              strcpy (macrovalue+macrovalueused, line);
+              macrovalueused += strlen (line);
+              macrovalue[macrovalueused++] = '\n';
+            }
+          continue;
+        }
+
+
+      if (n >= 5 && !memcmp (line, "@node", 5)
+          && (line[5]==' '||line[5]=='\t'||!line[5]))
+        {
+          /* Completey ignore @node lines.  */
+          continue;
+        }
+
+
+      if (skip_sect_line)
+        {
+          skip_sect_line = 0;
+          if (!strncmp (line, "@section", 8)
+              || !strncmp (line, "@subsection", 11)
+              || !strncmp (line, "@chapheading", 12))
+            continue;
+        }
+
+      /* We only parse lines we need and ignore the rest.  There are a
+         few macros used to control this as well as one @ifset
+         command.  Parts we know about are saved away into containers
+         separate for each section. */
+
+      /* First process ifset/ifclear commands. */
+      if (*line == '@')
+        {
+          if (n == 6 && !memcmp (line, "@ifset", 6)
+                   && (line[6]==' '||line[6]=='\t'))
+            {
+              ifset_nesting++;
+
+              if (!strncmp (p, "manverb", 7) && (p[7]==' '||p[7]=='\t'||!p[7]))
+                {
+                  if (in_verbatim)
+                    err ("%s:%d: nested \"@ifset manverb\"", fname, lnr);
+                  else
+                    in_verbatim = ifset_nesting;
+                }
+              else if (!strncmp (p, "gpgone", 6)
+                       && (p[6]==' '||p[6]=='\t'||!p[6]))
+                {
+                  if (in_gpgone)
+                    err ("%s:%d: nested \"@ifset gpgone\"", fname, lnr);
+                  else
+                    in_gpgone = ifset_nesting;
+                }
+              continue;
             }
-          else if (in_verbatim && n == 4 && !memcmp (line, "@end", 4)
+          else if (n == 4 && !memcmp (line, "@end", 4)
+                   && (line[4]==' '||line[4]=='\t')
                    && !strncmp (p, "ifset", 5)
                    && (p[5]==' '||p[5]=='\t'||!p[5]))
             {
-              in_verbatim = 0;
+              if (in_verbatim && ifset_nesting == in_verbatim)
+                in_verbatim = 0;
+              if (in_gpgone && ifset_nesting == in_gpgone)
+                in_gpgone = 0;
+
+              if (ifset_nesting)
+                ifset_nesting--;
+              else
+                err ("%s:%d: unbalanced \"@end ifset\"", fname, lnr);
+              continue;
+            }
+          else if (n == 8 && !memcmp (line, "@ifclear", 8)
+                   && (line[8]==' '||line[8]=='\t'))
+            {
+              ifclear_nesting++;
+
+              if (!strncmp (p, "gpgone", 6)
+                  && (p[6]==' '||p[6]=='\t'||!p[6]))
+                {
+                  if (not_in_gpgone)
+                    err ("%s:%d: nested \"@ifclear gpgone\"", fname, lnr);
+                  else
+                    not_in_gpgone = ifclear_nesting;
+                }
+
+              else if (!strncmp (p, "isman", 5)
+                       && (p[5]==' '||p[5]=='\t'||!p[5]))
+                {
+                  if (not_in_man)
+                    err ("%s:%d: nested \"@ifclear isman\"", fname, lnr);
+                  else
+                    not_in_man = ifclear_nesting;
+                }
+
+              continue;
+            }
+          else if (n == 4 && !memcmp (line, "@end", 4)
+                   && (line[4]==' '||line[4]=='\t')
+                   && !strncmp (p, "ifclear", 7)
+                   && (p[7]==' '||p[7]=='\t'||!p[7]))
+            {
+              if (not_in_gpgone && ifclear_nesting == not_in_gpgone)
+                not_in_gpgone = 0;
+              if (not_in_man && ifclear_nesting == not_in_man)
+                not_in_man = 0;
+
+              if (ifclear_nesting)
+                ifclear_nesting--;
+              else
+                err ("%s:%d: unbalanced \"@end ifclear\"", fname, lnr);
+              continue;
+            }
+        }
+
+      /* Take action on ifset/ifclear.  */
+      if ( (in_gpgone && !gpgone_defined)
+           || (not_in_gpgone && gpgone_defined)
+           || not_in_man)
+        continue;
+
+      /* Process commands. */
+      if (*line == '@')
+        {
+          if (skip_to_end
+              && n == 4 && !memcmp (line, "@end", 4)
+              && (line[4]==' '||line[4]=='\t'||!line[4]))
+            {
+              skip_to_end = 0;
             }
           else if (in_verbatim)
             {
-              got_line = 1;
+                got_line = 1;
+            }
+          else if (n == 6 && !memcmp (line, "@macro", 6))
+            {
+              macroname = xstrdup (p);
+              macrovalue = xmalloc ((macrovaluesize = 1024));
+              macrovalueused = 0;
             }
           else if (n == 8 && !memcmp (line, "@manpage", 8))
             {
@@ -856,6 +1084,7 @@ parse_file (const char *fname, FILE *fp, char **section_name)
                   free (*section_name);
                   *section_name = ascii_strupr (xstrdup (p));
                   in_pause = 0;
+                  skip_sect_line = 1;
                 }
             }
           else if (n == 9 && !memcmp (line, "@manpause", 9))
@@ -887,16 +1116,34 @@ parse_file (const char *fname, FILE *fp, char **section_name)
               char *incname = xstrdup (p);
               FILE *incfp = fopen (incname, "r");
 
+              if (!incfp && opt_include && *opt_include && *p != '/')
+                {
+                  free (incname);
+                  incname = xmalloc (strlen (opt_include) + 1
+                                     + strlen (p) + 1);
+                  strcpy (incname, opt_include);
+                  if ( incname[strlen (incname)-1] != '/' )
+                    strcat (incname, "/");
+                  strcat (incname, p);
+                  incfp = fopen (incname, "r");
+                }
+
               if (!incfp)
                 err ("can't open include file `%s':%s",
                      incname, strerror (errno));
               else
                 {
-                  parse_file (incname, incfp, section_name);
+                  parse_file (incname, incfp, section_name, in_pause);
                   fclose (incfp);
                 }
+              free (incname);
             }
-          else
+          else if (n == 4 && !memcmp (line, "@bye", 4)
+                   && (line[4]==' '||line[4]=='\t'||!line[4]))
+            {
+              break;
+            }
+          else if (!skip_to_end)
             got_line = 1;
         }
       else if (!skip_to_end)
@@ -910,6 +1157,8 @@ parse_file (const char *fname, FILE *fp, char **section_name)
     }
   if (ferror (fp))
     err ("%s:%d: read error: %s", fname, lnr, strerror (errno));
+  free (macroname);
+  free (macrovalue);
   free (line);
 }
 
@@ -919,7 +1168,15 @@ top_parse_file (const char *fname, FILE *fp)
 {
   char *section_name = NULL;  /* Name of the current section or NULL
                                  if not in a section.  */
-  parse_file (fname, fp, &section_name);
+  while (macrolist)
+    {
+      macro_t m = macrolist->next;
+      free (m->value);
+      free (m);
+      macrolist = m;
+    }
+
+  parse_file (fname, fp, &section_name, 0);
   free (section_name);
   finish_page ();
 }
@@ -956,7 +1213,9 @@ main (int argc, char **argv)
                 "  --select NAME    only output pages with @manpage NAME\n"
                 "  --verbose        enable extra informational output\n"
                 "  --debug          enable additional debug output\n"
-                "  --help           display this help and exit\n\n"
+                "  --help           display this help and exit\n"
+                "  -I DIR           also search in include DIR\n"
+                "  -D gpgone        the only useable define\n\n"
                 "With no FILE, or when FILE is -, read standard input.\n\n"
                 "Report bugs to <bugs@g10code.com>.");
           exit (0);
@@ -1021,6 +1280,25 @@ main (int argc, char **argv)
               argc--; argv++;
             }
         }
+      else if (!strcmp (*argv, "-I"))
+        {
+          argc--; argv++;
+          if (argc)
+            {
+              opt_include = *argv;
+              argc--; argv++;
+            }
+        }
+      else if (!strcmp (*argv, "-D"))
+        {
+          argc--; argv++;
+          if (argc)
+            {
+              if (!strcmp (*argv, "gpgone"))
+                gpgone_defined = 1;
+              argc--; argv++;
+            }
+        }
     }          
  
   if (argc > 1)