" "%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 ("%s
\n", name); + "\n", "\n\n" }, { "smallexample", 1, ".RS 2\n.nf\n", NULL, "\n
\n", "\n\n" }, { "asis", 7 }, { "anchor", 7 }, { "cartouche", 1 }, { "ref", 0, "[", "]" }, { "xref", 0, "See: [", "]" }, { "pxref", 0, "see: [", "]" }, { "uref", 0, "(\\fB", "\\fR)" }, { "footnote",0, " ([", "])" }, { "emph", 0, "\\fI", "\\fR", "", "" }, { "w", 1 }, { "c", 5 }, { "efindex", 1 }, { "opindex", 1 }, { "cpindex", 1 }, { "cindex", 1 }, { "noindent", 0 }, { "section", 1 }, { "chapter", 1 }, - { "subsection", 6, "\n.SS " }, + { "subsection", 6, "\n.SS ", NULL, "
", fp); writestr (line, line, fp); + if (htmlmode && *line) + fputs ("
", fp); writechr ('\n', fp); return; } - proc_texi_buffer (fp, line, strlen (line), table_level, &eol_action); + proc_texi_buffer (fp, line, strlen (line), table_level, &eol_action, sect); writechr ('\n', fp); } /* Write all the lines LINES to FP. */ static void -write_content (FILE *fp, line_buffer_t lines) +write_content (FILE *fp, section_buffer_t sect) { line_buffer_t line; int table_level = 0; + line_buffer_t lines = sect->lines; + + /* inf ("===BEGIN==============================================="); */ + /* for (line = lines; line; line = line->next) */ + /* inf ("line='%s'", line->line); */ + /* inf ("===END==============================================="); */ + for (line = lines; line; line = line->next) { if (line->verbatim) { /* FIXME: IN HTML mode we need to employ a parser for roff * markup. */ writestr (line->line, line->line, fp); writechr ('\n', fp); } else { /* fputs ("TEXI---", fp); */ /* fputs (line->line, fp); */ /* fputs ("---\n", fp); */ - parse_texi_line (fp, line->line, &table_level); + parse_texi_line (fp, line->line, &table_level, sect); } } } static int is_standard_section (const char *name) { int i; const char *s; for (i=0; (s=standard_sections[i]); i++) if (!strcmp (s, name)) return 1; return 0; } /* Finish a page; that is sort the data and write it out to the file. */ static void finish_page (void) { FILE *fp; section_buffer_t sect = NULL; int idx; const char *s; int i; if (!thepage.name) return; /* No page active. */ if (verbose) inf ("finishing page '%s'", thepage.name); if (opt_select) { if (!strcmp (opt_select, thepage.name)) { inf ("selected '%s'", thepage.name ); fp = stdout; } else { fp = fopen ( "/dev/null", "w" ); if (!fp) die ("failed to open /dev/null: %s\n", strerror (errno)); } } else if (opt_store) { + char *fname = xstrconcat (thepage.name, htmlmode? ".html":NULL, NULL); if (verbose) - inf ("writing '%s'", thepage.name ); - fp = fopen ( thepage.name, "w" ); + inf ("writing '%s'", fname); + fp = fopen (fname, "w" ); if (!fp) - die ("failed to create '%s': %s\n", thepage.name, strerror (errno)); + die ("failed to create '%s': %s\n", fname, strerror (errno)); + free (fname); } else fp = stdout; if (write_th (fp)) goto leave; for (idx=0; (s=standard_sections[idx]); idx++) { for (i=0; i < thepage.n_sections; i++) { sect = thepage.sections + i; if (sect->name && !strcmp (s, sect->name)) break; } if (i == thepage.n_sections) sect = NULL; if (sect) { write_sh (fp, sect->name); - write_content (fp, sect->lines); + write_content (fp, sect); /* Now continue with all non standard sections directly following this one. */ for (i++; i < thepage.n_sections; i++) { sect = thepage.sections + i; if (sect->name && is_standard_section (sect->name)) break; if (sect->name) { write_sh (fp, sect->name); - write_content (fp, sect->lines); + write_content (fp, sect); } } } } write_sh (fp, NULL); if (write_bottom (fp)) goto leave; leave: if (fp != stdout) fclose (fp); free (thepage.name); thepage.name = NULL; /* FIXME: Cleanup the content. */ } /* Parse one Texinfo file and create manpages according to the embedded instructions. */ static void 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 skip_to_end = 0; /* Used to skip over menu entries. */ int skip_sect_line = 0; /* Skip after @mansect. */ int item_indent = 0; /* How far is the current @item indented. */ /* 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)) { size_t n = strlen (line); int got_line = 0; char *p, *pend; lnr++; if (!n || line[n-1] != '\n') { err ("%s:%d: trailing linefeed missing, line too long or " "embedded Nul character", fname, lnr); break; } line[--n] = 0; /* Kludge to allow indentation of tables. */ for (p=line; *p == ' ' || *p == '\t'; p++) ; if (*p) { if (*p == '@' && !strncmp (p+1, "item", 4)) item_indent = p - line; /* Set a new indent level. */ else if (p - line < item_indent) item_indent = 0; /* Switch off indention. */ if (item_indent) { memmove (line, line+item_indent, n - item_indent + 1); n -= item_indent; } } if (*line == '@') { for (p=line+1, n=1; *p && *p != ' ' && *p != '\t'; p++) n++; while (*p == ' ' || *p == '\t') p++; } else p = line; /* 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])) { if (macrovalueused) macrovalue[--macrovalueused] = 0; /* Kill the last LF. */ macrovalue[macrovalueused] = 0; /* Terminate macro. */ macrovalue = xrealloc (macrovalue, macrovalueused+1); set_macro (macroname, macrovalue); macrovalue = NULL; free (macroname); macroname = NULL; } else { 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')) { for (p=line+7; *p == ' ' || *p == '\t'; p++) ; if (!*p) { err ("%s:%d: name missing after \"@ifset\"", fname, lnr); continue; } 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) { 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) && (line[4]==' '||line[4]=='\t') && !strncmp (p, "ifset", 5) && (p[5]==' '||p[5]=='\t'||!p[5])) { pop_condition (1, fname, lnr); 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])) { pop_condition (0, fname, lnr); continue; } } /* Take action on ifset/ifclear. */ if (!cond_is_active) 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 (cond_in_verbatim) { got_line = 1; } else if (n == 6 && !memcmp (line, "@macro", 6)) { macroname = xstrdup (p); 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); *section_name = NULL; finish_page (); start_page (p); in_pause = 0; } else if (n == 8 && !memcmp (line, "@mansect", 8)) { if (!thepage.name) err ("%s:%d: section outside of a man page", fname, lnr); else { free (*section_name); *section_name = ascii_strupr (xstrdup (p)); in_pause = 0; skip_sect_line = 1; } } else if (n == 9 && !memcmp (line, "@manpause", 9)) { if (!*section_name) err ("%s:%d: pausing outside of a man section", fname, lnr); else if (in_pause) err ("%s:%d: already pausing", fname, lnr); else in_pause = 1; } else if (n == 8 && !memcmp (line, "@mancont", 8)) { if (!*section_name) err ("%s:%d: continue outside of a man section", fname, lnr); else if (!in_pause) err ("%s:%d: continue while not pausing", fname, lnr); else in_pause = 0; } else if (n == 5 && !memcmp (line, "@menu", 5) && (line[5]==' '||line[5]=='\t'||!line[5])) { skip_to_end = 1; } else if (n == 8 && !memcmp (line, "@include", 8) && (line[8]==' '||line[8]=='\t'||!line[8])) { 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, in_pause); fclose (incfp); } free (incname); } 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) got_line = 1; 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); } if (ferror (fp)) err ("%s:%d: read error: %s", fname, lnr, strerror (errno)); free (macroname); free (macrovalue); free (line); } static void 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 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, §ion_name, 0); free (section_name); finish_page (); } int main (int argc, char **argv) { int last_argc = -1; const char *s; 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++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--")) { argc--; argv++; break; } else if (!strcmp (*argv, "--help")) { 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" " --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 MACRO define MACRO to 1\n\n" "With no FILE, or when FILE is -, read standard input.\n\n" "Report bugs to