gpg: New import option "import-export".
[gnupg.git] / doc / yat2m.c
index aaa7ea6..3de908c 100644 (file)
@@ -1,5 +1,5 @@
 /* yat2m.c - Yet Another Texi 2 Man converter
- *     Copyright (C) 2005 g10 Code GmbH
+ *     Copyright (C) 2005, 2013, 2015 g10 Code GmbH
  *      Copyright (C) 2006, 2008, 2011 Free Software Foundation, Inc.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -17,7 +17,7 @@
  */
 
 /*
-    This is a simple textinfo to man page converter.  It needs some
+    This is a simple texinfo to man page converter.  It needs some
     special markup in th e texinfo and tries best to get a create man
     page.  It has been designed for the GnuPG man pages and thus only
     a few texinfo commands are supported.
     detects the number of white spaces in front of an @item and remove
     this number of spaces from all following lines until a new @item
     is found or there are less spaces than for the last @item.
+
+    Note that @* does only work correctly if used at the end of an
+    input line.
+
 */
 
 #include <stdio.h>
    character. */
 #define LINESIZE 1024
 
+/* Number of allowed condition nestings.  */
+#define MAX_CONDITION_NESTING  10
+
 /* Option flags. */
 static int verbose;
 static int quiet;
 static int debug;
 static const char *opt_source;
 static const char *opt_release;
+static const char *opt_date;
 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;
 
@@ -129,7 +133,7 @@ static int any_error;
 struct macro_s
 {
   struct macro_s *next;
-  char *value;  /* Malloced value. */
+  char *value;    /* Malloced value. */
   char name[1];
 };
 typedef struct macro_s *macro_t;
@@ -137,6 +141,27 @@ typedef struct macro_s *macro_t;
 /* List of all defined macros. */
 static macro_t macrolist;
 
+/* List of variables set by @set. */
+static macro_t variablelist;
+
+/* List of global macro names.  The value part is not used.  */
+static macro_t predefinedmacrolist;
+
+/* Object to keep track of @isset and @ifclear.  */
+struct condition_s
+{
+  int manverb;   /* "manverb" needs special treatment.  */
+  int isset;     /* This is an @isset condition.  */
+  char name[1];  /* Name of the condition macro.  */
+};
+typedef struct condition_s *condition_t;
+
+/* The stack used to evaluate conditions.  And the current states. */
+static condition_t condition_stack[MAX_CONDITION_NESTING];
+static int condition_stack_idx;
+static int cond_is_active;     /* State of ifset/ifclear */
+static int cond_in_verbatim;   /* State of "manverb".  */
+
 
 /* Object to store one line of content.  */
 struct line_buffer_s
@@ -299,8 +324,12 @@ isodatestring (void)
 {
   static char buffer[11+5];
   struct tm *tp;
-  time_t atime = time (NULL);
+  time_t atime;
 
+  if (opt_date && *opt_date)
+    atime = strtoul (opt_date, NULL, 10);
+  else
+    atime = time (NULL);
   if (atime < 0)
     strcpy (buffer, "????" "-??" "-??");
   else
@@ -313,7 +342,198 @@ isodatestring (void)
 }
 
 
+/* Add NAME to the list of predefined macros which are global for all
+   files.  */
+static void
+add_predefined_macro (const char *name)
+{
+  macro_t m;
+
+  for (m=predefinedmacrolist; m; m = m->next)
+    if (!strcmp (m->name, name))
+      break;
+  if (!m)
+    {
+      m = xcalloc (1, sizeof *m + strlen (name));
+      strcpy (m->name, name);
+      m->next = predefinedmacrolist;
+      predefinedmacrolist = m;
+    }
+}
+
+
+/* Create or update a macro with name MACRONAME and set its values TO
+   MACROVALUE.  Note that ownership of the macro value is transferred
+   to this function.  */
+static void
+set_macro (const char *macroname, char *macrovalue)
+{
+  macro_t m;
+
+  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;
+}
+
+
+/* Create or update a variable with name and value given in NAMEANDVALUE.  */
+static void
+set_variable (char *nameandvalue)
+{
+  macro_t m;
+  const char *value;
+  char *p;
+
+  for (p = nameandvalue; *p && *p != ' ' && *p != '\t'; p++)
+    ;
+  if (!*p)
+    value = "";
+  else
+    {
+      *p++ = 0;
+      while (*p == ' ' || *p == '\t')
+        p++;
+      value = p;
+    }
 
+  for (m=variablelist; m; m = m->next)
+    if (!strcmp (m->name, nameandvalue))
+      break;
+  if (m)
+    free (m->value);
+  else
+    {
+      m = xcalloc (1, sizeof *m + strlen (nameandvalue));
+      strcpy (m->name, nameandvalue);
+      m->next = variablelist;
+      variablelist = m;
+    }
+  m->value = xstrdup (value);
+}
+
+
+/* Return true if the macro or variable NAME is set, i.e. not the
+   empty string and not evaluating to 0.  */
+static int
+macro_set_p (const char *name)
+{
+  macro_t m;
+
+  for (m = macrolist; m ; m = m->next)
+    if (!strcmp (m->name, name))
+      break;
+  if (!m)
+    for (m = variablelist; m ; m = m->next)
+      if (!strcmp (m->name, name))
+        break;
+  if (!m || !m->value || !*m->value)
+    return 0;
+  if ((*m->value & 0x80) || !isdigit (*m->value))
+    return 1; /* Not a digit but some other string.  */
+  return !!atoi (m->value);
+}
+
+
+/* Evaluate the current conditions.  */
+static void
+evaluate_conditions (const char *fname, int lnr)
+{
+  int i;
+
+  /* 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", */
+  /*        condition_stack[i]->name, */
+  /*        (macro_set_p (condition_stack[i]->name) */
+  /*         ^ !condition_stack[i]->isset)? 't':'f'); */
+
+  cond_is_active = 1;
+  cond_in_verbatim = 0;
+  if (condition_stack_idx)
+    {
+      for (i=0; i < condition_stack_idx; i++)
+        {
+          if (condition_stack[i]->manverb)
+            cond_in_verbatim = (macro_set_p (condition_stack[i]->name)
+                                ^ !condition_stack[i]->isset);
+          else if (!(macro_set_p (condition_stack[i]->name)
+                     ^ !condition_stack[i]->isset))
+            {
+              cond_is_active = 0;
+              break;
+            }
+        }
+    }
+
+  /* inf ("%s:%d:   active=%d verbatim=%d", */
+  /*      fname, lnr, cond_is_active, cond_in_verbatim); */
+}
+
+
+/* Push a condition with condition macro NAME onto the stack.  If
+   ISSET is true, a @isset condition is pushed.  */
+static void
+push_condition (const char *name, int isset, const char *fname, int lnr)
+{
+  condition_t cond;
+  int manverb = 0;
+
+  if (condition_stack_idx >= MAX_CONDITION_NESTING)
+    {
+      err ("%s:%d: condition nested too deep", fname, lnr);
+      return;
+    }
+
+  if (!strcmp (name, "manverb"))
+    {
+      if (!isset)
+        {
+          err ("%s:%d: using \"@ifclear manverb\" is not allowed", fname, lnr);
+          return;
+        }
+      manverb = 1;
+    }
+
+  cond = xcalloc (1, sizeof *cond + strlen (name));
+  cond->manverb = manverb;
+  cond->isset = isset;
+  strcpy (cond->name, name);
+
+  condition_stack[condition_stack_idx++] = cond;
+  evaluate_conditions (fname, lnr);
+}
+
+
+/* Remove the last condition from the stack.  ISSET is used for error
+   reporting.  */
+static void
+pop_condition (int isset, const char *fname, int lnr)
+{
+  if (!condition_stack_idx)
+    {
+      err ("%s:%d: unbalanced \"@end %s\"",
+           fname, lnr, isset?"isset":"isclear");
+      return;
+    }
+  condition_stack_idx--;
+  free (condition_stack[condition_stack_idx]);
+  condition_stack[condition_stack_idx] = NULL;
+  evaluate_conditions (fname, lnr);
+}
+
+
+\f
 /* Return a section buffer for the section NAME.  Allocate a new buffer
    if this is a new section.  Keep track of the sections in THEPAGE.
    This function may reallocate the section array in THEPAGE.  */
@@ -414,7 +634,7 @@ static void
 start_page (char *name)
 {
   if (verbose)
-    inf ("starting page `%s'", name);
+    inf ("starting page '%s'", name);
   assert (!thepage.name);
   thepage.name = xstrdup (name);
   thepage.n_sections = 0;
@@ -434,13 +654,14 @@ write_th (FILE *fp)
   p = strrchr (name, '.');
   if (!p || !p[1])
     {
-      err ("no section name in man page `%s'", thepage.name);
+      err ("no section name in man page '%s'", thepage.name);
       free (name);
       return -1;
     }
   *p++ = 0;
   fprintf (fp, ".TH %s %s %s \"%s\" \"%s\"\n",
            name, p, isodatestring (), opt_release, opt_source);
+  free (name);
   return 0;
 }
 
@@ -463,6 +684,7 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
   } 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"  },
@@ -483,6 +705,7 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
     { "emph",    0, "\\fI", "\\fR" },
     { "w",       1 },
     { "c",       5 },
+    { "efindex", 1 },
     { "opindex", 1 },
     { "cpindex", 1 },
     { "cindex",  1 },
@@ -496,8 +719,11 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
     { "table",   3 },
     { "itemize",   3 },
     { "bullet",  0, "* " },
+    { "*",       0, "\n.br"},
+    { "/",       0 },
     { "end",     4 },
     { "quotation",1, ".RS\n\\fB" },
+    { "value", 8 },
     { NULL }
   };
   size_t n;
@@ -573,11 +799,46 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
         case 7:
           ignore_args = 1;
           break;
+        case 8:
+          ignore_args = 1;
+          if (*rest != '{')
+            {
+              err ("opening brace for command '%s' missing", command);
+              return len;
+            }
+          else
+            {
+              /* Find closing brace.  */
+              for (s=rest+1, n=1; *s && n < len; s++, n++)
+                if (*s == '}')
+                  break;
+              if (*s != '}')
+                {
+                  err ("closing brace for command '%s' not found", command);
+                  return len;
+                }
+              else
+                {
+                  size_t len = 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 (m)
+                    fputs (m->value, fp);
+                  else
+                    inf ("texinfo variable '%.*s' is not set",
+                         (int)len, rest+1);
+                }
+            }
+          break;
         default:
           break;
         }
     }
-  else
+  else /* macro */
     {
       macro_t m;
 
@@ -591,7 +852,7 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
           ignore_args = 1; /* Parameterized macros are not yet supported. */
         }
       else
-        inf ("texinfo command `%s' not supported (%.*s)", command,
+        inf ("texinfo command '%s' not supported (%.*s)", command,
              ((s = memchr (rest, '\n', len)), (s? (s-rest) : len)), rest);
     }
 
@@ -605,7 +866,7 @@ proc_texi_cmd (FILE *fp, const char *command, const char *rest, size_t len,
           i--;
       if (i)
         {
-          err ("closing brace for command `%s' not found", command);
+          err ("closing brace for command '%s' not found", command);
           return len;
         }
       if (n > 2 && !ignore_args)
@@ -780,13 +1041,13 @@ finish_page (void)
     return; /* No page active.  */
 
   if (verbose)
-    inf ("finishing page `%s'", thepage.name);
+    inf ("finishing page '%s'", thepage.name);
 
   if (opt_select)
     {
       if (!strcmp (opt_select, thepage.name))
         {
-          inf ("selected `%s'", thepage.name );
+          inf ("selected '%s'", thepage.name );
           fp = stdout;
         }
       else
@@ -798,10 +1059,10 @@ finish_page (void)
     }
   else if (opt_store)
     {
-      inf ("writing `%s'", thepage.name );
+      inf ("writing '%s'", thepage.name );
       fp = fopen ( thepage.name, "w" );
       if (!fp)
-        die ("failed to create `%s': %s\n", thepage.name, strerror (errno));
+        die ("failed to create '%s': %s\n", thepage.name, strerror (errno));
     }
   else
     fp = stdout;
@@ -862,14 +1123,8 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
   int lnr = 0;
   /* Fixme: The following state variables don't carry over to include
      files. */
-  int in_verbatim = 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.  */
   int item_indent = 0;        /* How far is the current @item indented.  */
 
   /* Helper to define a macro. */
@@ -883,7 +1138,7 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
     {
       size_t n = strlen (line);
       int got_line = 0;
-      char *p;
+      char *p, *pend;
 
       lnr++;
       if (!n || line[n-1] != '\n')
@@ -930,26 +1185,12 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
               && !strncmp (p, "macro", 5)
               && (p[5]==' '||p[5]=='\t'||!p[5]))
             {
-              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;
+              set_macro (macroname, macrovalue);
               macrovalue = NULL;
               free (macroname);
               macroname = NULL;
@@ -997,23 +1238,33 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
           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]))
+              for (p=line+7; *p == ' ' || *p == '\t'; p++)
+                ;
+              if (!*p)
                 {
-                  if (in_verbatim)
-                    err ("%s:%d: nested \"@ifset manverb\"", fname, lnr);
-                  else
-                    in_verbatim = ifset_nesting;
+                  err ("%s:%d: name missing after \"@ifset\"", fname, lnr);
+                  continue;
                 }
-              else if (!strncmp (p, "gpgone", 6)
-                       && (p[6]==' '||p[6]=='\t'||!p[6]))
+              for (pend=p; *pend && *pend != ' ' && *pend != '\t'; pend++)
+                ;
+              *pend = 0;  /* Ignore rest of the line.  */
+              push_condition (p, 1, fname, lnr);
+              continue;
+            }
+          else if (n == 8 && !memcmp (line, "@ifclear", 8)
+                   && (line[8]==' '||line[8]=='\t'))
+            {
+              for (p=line+9; *p == ' ' || *p == '\t'; p++)
+                ;
+              if (!*p)
                 {
-                  if (in_gpgone)
-                    err ("%s:%d: nested \"@ifset gpgone\"", fname, lnr);
-                  else
-                    in_gpgone = ifset_nesting;
+                  err ("%s:%d: name missing after \"@ifsclear\"", fname, lnr);
+                  continue;
                 }
+              for (pend=p; *pend && *pend != ' ' && *pend != '\t'; pend++)
+                ;
+              *pend = 0;  /* Ignore rest of the line.  */
+              push_condition (p, 0, fname, lnr);
               continue;
             }
           else if (n == 4 && !memcmp (line, "@end", 4)
@@ -1021,40 +1272,7 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
                    && !strncmp (p, "ifset", 5)
                    && (p[5]==' '||p[5]=='\t'||!p[5]))
             {
-              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;
-                }
-
+              pop_condition (1, fname, lnr);
               continue;
             }
           else if (n == 4 && !memcmp (line, "@end", 4)
@@ -1062,23 +1280,13 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
                    && !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);
+              pop_condition (0, fname, lnr);
               continue;
             }
         }
 
       /* Take action on ifset/ifclear.  */
-      if ( (in_gpgone && !gpgone_defined)
-           || (not_in_gpgone && gpgone_defined)
-           || not_in_man)
+      if (!cond_is_active)
         continue;
 
       /* Process commands. */
@@ -1090,7 +1298,7 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
             {
               skip_to_end = 0;
             }
-          else if (in_verbatim)
+          else if (cond_in_verbatim)
             {
                 got_line = 1;
             }
@@ -1100,6 +1308,10 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
               macrovalue = xmalloc ((macrovaluesize = 1024));
               macrovalueused = 0;
             }
+          else if (n == 4 && !memcmp (line, "@set", 4))
+            {
+              set_variable (p);
+            }
           else if (n == 8 && !memcmp (line, "@manpage", 8))
             {
               free (*section_name);
@@ -1162,7 +1374,7 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
                 }
 
               if (!incfp)
-                err ("can't open include file `%s':%s",
+                err ("can't open include file '%s': %s",
                      incname, strerror (errno));
               else
                 {
@@ -1182,7 +1394,7 @@ parse_file (const char *fname, FILE *fp, char **section_name, int in_pause)
       else if (!skip_to_end)
         got_line = 1;
 
-      if (got_line && in_verbatim)
+      if (got_line && cond_in_verbatim)
         add_content (*section_name, line, 1);
       else if (got_line && thepage.name && *section_name && !in_pause)
         add_content (*section_name, line, 0);
@@ -1201,13 +1413,26 @@ top_parse_file (const char *fname, FILE *fp)
 {
   char *section_name = NULL;  /* Name of the current section or NULL
                                  if not in a section.  */
+  macro_t m;
+
   while (macrolist)
     {
-      macro_t m = macrolist->next;
-      free (m->value);
-      free (m);
-      macrolist = m;
+      macro_t next = macrolist->next;
+      free (macrolist->value);
+      free (macrolist);
+      macrolist = next;
     }
+  while (variablelist)
+    {
+      macro_t next = variablelist->next;
+      free (variablelist->value);
+      free (variablelist);
+      variablelist = next;
+    }
+  for (m=predefinedmacrolist; m; m = m->next)
+    set_macro (m->name, xstrdup ("1"));
+  cond_is_active = 1;
+  cond_in_verbatim = 0;
 
   parse_file (fname, fp, &section_name, 0);
   free (section_name);
@@ -1223,6 +1448,12 @@ main (int argc, char **argv)
   opt_source = "GNU";
   opt_release = "";
 
+  /* Define default macros.  The trick is that these macros are not
+     defined when using the actual texinfo renderer. */
+  add_predefined_macro ("isman");
+  add_predefined_macro ("manverb");
+
+  /* Option parsing.  */
   if (argc)
     {
       argc--; argv++;
@@ -1242,13 +1473,14 @@ main (int argc, char **argv)
                 "Extract man pages from a Texinfo source.\n\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"
                 "  --store          write output using @manpage name\n"
                 "  --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"
                 "  -I DIR           also search in include DIR\n"
-                "  -D gpgone        the only useable define\n\n"
+                "  -D gpgone        the only usable define\n\n"
                 "With no FILE, or when FILE is -, read standard input.\n\n"
                 "Report bugs to <bugs@g10code.com>.");
           exit (0);
@@ -1295,6 +1527,15 @@ main (int argc, char **argv)
               argc--; argv++;
             }
         }
+      else if (!strcmp (*argv, "--date"))
+        {
+          argc--; argv++;
+          if (argc)
+            {
+              opt_date = *argv;
+              argc--; argv++;
+            }
+        }
       else if (!strcmp (*argv, "--store"))
         {
           opt_store = 1;
@@ -1327,8 +1568,7 @@ main (int argc, char **argv)
           argc--; argv++;
           if (argc)
             {
-              if (!strcmp (*argv, "gpgone"))
-                gpgone_defined = 1;
+              add_predefined_macro (*argv);
               argc--; argv++;
             }
         }