diff options
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | TODO | 6 | ||||
-rw-r--r-- | main.c | 6 | ||||
-rw-r--r-- | main.h | 2 | ||||
-rw-r--r-- | mandoc.1 | 32 | ||||
-rw-r--r-- | mdoc_markdown.c | 1448 |
6 files changed, 1490 insertions, 9 deletions
@@ -104,6 +104,7 @@ SRCS = att.c \ mdoc_html.c \ mdoc_macro.c \ mdoc_man.c \ + mdoc_markdown.c \ mdoc_state.c \ mdoc_term.c \ mdoc_validate.c \ @@ -252,8 +253,6 @@ MANDOC_HTML_OBJS = eqn_html.o \ mdoc_html.o \ tbl_html.o -MANDOC_MAN_OBJS = mdoc_man.o - MANDOC_TERM_OBJS = eqn_term.o \ man_term.o \ mdoc_term.o \ @@ -279,6 +278,8 @@ MAIN_OBJS = $(MANDOC_HTML_OBJS) \ $(DBA_OBJS) \ main.o \ manpath.o \ + mdoc_man.o \ + mdoc_markdown.o \ out.o \ tag.o \ tree.o @@ -273,12 +273,6 @@ are mere guesses, and some may be wrong. - kettenis wants base roff, ms, and me Fri, 1 Jan 2010 22:13:15 +0100 (CET) loc ** exist ** algo ** size *** imp * -- Vsevolod Stakhov (FreeBSD) needs either a markdown output formatter - for mandoc -mdoc or a markdown to mdoc converter because they - have to maintain manuals needed both in markdown and mdoc format. - Look at the libsoldout (markdown -> whatever) - loc * exist * algo * size ** imp ** - --- compatibility checks ----------------------------------------------- - is .Bk implemented correctly in modern groff? @@ -67,6 +67,7 @@ enum outt { OUTT_TREE, /* -Ttree */ OUTT_MAN, /* -Tman */ OUTT_HTML, /* -Thtml */ + OUTT_MARKDOWN, /* -Tmarkdown */ OUTT_LINT, /* -Tlint */ OUTT_PS, /* -Tps */ OUTT_PDF /* -Tpdf */ @@ -766,6 +767,9 @@ parse(struct curparse *curp, int fd, const char *file) case OUTT_PS: terminal_mdoc(curp->outdata, man); break; + case OUTT_MARKDOWN: + markdown_mdoc(curp->outdata, man); + break; default: break; } @@ -950,6 +954,8 @@ toptions(struct curparse *curp, char *arg) curp->outtype = OUTT_MAN; else if (0 == strcmp(arg, "html")) curp->outtype = OUTT_HTML; + else if (0 == strcmp(arg, "markdown")) + curp->outtype = OUTT_MARKDOWN; else if (0 == strcmp(arg, "utf8")) curp->outtype = OUTT_UTF8; else if (0 == strcmp(arg, "locale")) @@ -49,3 +49,5 @@ void pspdf_free(void *); void terminal_mdoc(void *, const struct roff_man *); void terminal_man(void *, const struct roff_man *); void terminal_sepline(void *); + +void markdown_mdoc(void *, const struct roff_man *); @@ -250,7 +250,7 @@ The utility accepts the following .Fl T arguments, which correspond to output modes: -.Bl -tag -width "-T locale" +.Bl -tag -width "-T markdown" .It Fl T Cm ascii Produce 7-bit ASCII output. See @@ -274,6 +274,12 @@ Produce format output. See .Sx Man Output . +.It Fl T Cm markdown +Produce output in +.Sy markdown +format. +See +.Sx Markdown Output . .It Fl T Cm pdf Produce PDF output. See @@ -443,6 +449,30 @@ The parser is also run, and as usual, the level controls which .Sx DIAGNOSTICS are displayed before copying the input to the output. +.Ss Markdown Output +Translate +.Xr mdoc 7 +input to the +.Sy markdown +format conforming to +.Lk http://daringfireball.net/projects/markdown/syntax.text\ + "John Gruber's 2004 specification" . +.Pp +Markdown is a very weak markup language, so all semantic markup is +lost, and even part of the presentational markup may be lost. +Do not use this as an intermediate step in converting to HTML; +instead, use +.Fl T Cm html +directly. +.Pp +The +.Xr man 7 , +.Xr tbl 7 , +and +.Xr eqn 7 +input languages are not supported by +.Fl T Cm markdown +output mode. .Ss PDF Output PDF-1.1 output may be generated by .Fl T Cm pdf . diff --git a/mdoc_markdown.c b/mdoc_markdown.c new file mode 100644 index 00000000..6fcaceb3 --- /dev/null +++ b/mdoc_markdown.c @@ -0,0 +1,1448 @@ +/* $Id$ */ +/* + * Copyright (c) 2017 Ingo Schwarze <schwarze@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <sys/types.h> + +#include <assert.h> +#include <ctype.h> +#include <stdio.h> +#include <string.h> + +#include "mandoc_aux.h" +#include "mandoc.h" +#include "roff.h" +#include "mdoc.h" +#include "main.h" + +struct md_act { + int (*cond)(struct roff_node *n); + int (*pre)(struct roff_node *n); + void (*post)(struct roff_node *n); + const char *prefix; /* pre-node string constant */ + const char *suffix; /* post-node string constant */ +}; + +static void md_nodelist(struct roff_node *); +static void md_node(struct roff_node *); +static const char *md_stack(char c); +static void md_preword(void); +static void md_rawword(const char *); +static void md_word(const char *); +static void md_named(const char *); +static void md_char(unsigned char); + +static int md_cond_head(struct roff_node *); +static int md_cond_body(struct roff_node *); + +static int md_pre_raw(struct roff_node *); +static int md_pre_word(struct roff_node *); +static int md_pre_skip(struct roff_node *); +static void md_pre_syn(struct roff_node *); +static int md_pre_Ap(struct roff_node *); +static int md_pre_Bd(struct roff_node *); +static int md_pre_Bk(struct roff_node *); +static int md_pre_Bl(struct roff_node *); +static int md_pre_D1(struct roff_node *); +static int md_pre_Dl(struct roff_node *); +static int md_pre_En(struct roff_node *); +static int md_pre_Eo(struct roff_node *); +static int md_pre_Fa(struct roff_node *); +static int md_pre_Fd(struct roff_node *); +static int md_pre_Fn(struct roff_node *); +static int md_pre_Fo(struct roff_node *); +static int md_pre_In(struct roff_node *); +static int md_pre_It(struct roff_node *); +static int md_pre_Lk(struct roff_node *); +static int md_pre_Nd(struct roff_node *); +static int md_pre_Nm(struct roff_node *); +static int md_pre_No(struct roff_node *); +static int md_pre_Ns(struct roff_node *); +static int md_pre_Pp(struct roff_node *); +static int md_pre_Rs(struct roff_node *); +static int md_pre_Sh(struct roff_node *); +static int md_pre_Sm(struct roff_node *); +static int md_pre_Vt(struct roff_node *); +static int md_pre_Xr(struct roff_node *); +static int md_pre__T(struct roff_node *); +static int md_pre_br(struct roff_node *); + +static void md_post_raw(struct roff_node *); +static void md_post_word(struct roff_node *); +static void md_post_pc(struct roff_node *); +static void md_post_Bk(struct roff_node *); +static void md_post_Bl(struct roff_node *); +static void md_post_D1(struct roff_node *); +static void md_post_En(struct roff_node *); +static void md_post_Eo(struct roff_node *); +static void md_post_Fa(struct roff_node *); +static void md_post_Fd(struct roff_node *); +static void md_post_Fn(struct roff_node *); +static void md_post_Fo(struct roff_node *); +static void md_post_In(struct roff_node *); +static void md_post_It(struct roff_node *); +static void md_post_Lb(struct roff_node *); +static void md_post_Nm(struct roff_node *); +static void md_post_Pf(struct roff_node *); +static void md_post_Vt(struct roff_node *); +static void md_post__T(struct roff_node *); + +static const struct md_act md_acts[MDOC_MAX + 1] = { + { NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */ + { NULL, NULL, NULL, NULL, NULL }, /* Dd */ + { NULL, NULL, NULL, NULL, NULL }, /* Dt */ + { NULL, NULL, NULL, NULL, NULL }, /* Os */ + { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */ + { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */ + { NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */ + { md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */ + { md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */ + { md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */ + { NULL, NULL, NULL, NULL, NULL }, /* Ed */ + { md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */ + { NULL, NULL, NULL, NULL, NULL }, /* El */ + { NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */ + { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */ + { NULL, NULL, NULL, NULL, NULL }, /* An */ + { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */ + { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */ + { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */ + { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */ + { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */ + { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */ + { NULL, NULL, NULL, NULL, NULL }, /* Ex */ + { NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */ + { NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */ + { NULL, md_pre_raw, md_post_raw, "**-", "**" }, /* Fl */ + { NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */ + { NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */ + { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */ + { NULL, md_pre_In, md_post_In, "*", "*" }, /* In */ + { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */ + { md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */ + { NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */ + { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */ + { NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ot */ + { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */ + { NULL, NULL, NULL, NULL, NULL }, /* Rv */ + { NULL, NULL, NULL, NULL, NULL }, /* St */ + { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */ + { NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */ + { NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %A */ + { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %D */ + { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */ + { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %N */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %O */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %P */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %R */ + { NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %V */ + { NULL, NULL, NULL, NULL, NULL }, /* Ac */ + { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */ + { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */ + { NULL, NULL, NULL, NULL, NULL }, /* At */ + { NULL, NULL, NULL, NULL, NULL }, /* Bc */ + { NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */ + { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */ + { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */ + { NULL, NULL, NULL, NULL, NULL }, /* Bsx */ + { NULL, NULL, NULL, NULL, NULL }, /* Bx */ + { NULL, NULL, NULL, NULL, NULL }, /* Db */ + { NULL, NULL, NULL, NULL, NULL }, /* Dc */ + { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */ + { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */ + { NULL, NULL, NULL, NULL, NULL }, /* Ec */ + { NULL, NULL, NULL, NULL, NULL }, /* Ef */ + { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */ + { md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */ + { NULL, NULL, NULL, NULL, NULL }, /* Fx */ + { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */ + { NULL, md_pre_No, NULL, NULL, NULL }, /* No */ + { NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */ + { NULL, NULL, NULL, NULL, NULL }, /* Nx */ + { NULL, NULL, NULL, NULL, NULL }, /* Ox */ + { NULL, NULL, NULL, NULL, NULL }, /* Pc */ + { NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */ + { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */ + { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */ + { NULL, NULL, NULL, NULL, NULL }, /* Qc */ + { md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */ + { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */ + { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */ + { NULL, NULL, NULL, NULL, NULL }, /* Re */ + { md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */ + { NULL, NULL, NULL, NULL, NULL }, /* Sc */ + { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */ + { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */ + { NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */ + { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */ + { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */ + { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */ + { NULL, NULL, NULL, NULL, NULL }, /* Ux */ + { NULL, NULL, NULL, NULL, NULL }, /* Xc */ + { NULL, NULL, NULL, NULL, NULL }, /* Xo */ + { NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */ + { NULL, NULL, NULL, NULL, NULL }, /* Fc */ + { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */ + { NULL, NULL, NULL, NULL, NULL }, /* Oc */ + { NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */ + { NULL, NULL, NULL, NULL, NULL }, /* Ek */ + { NULL, NULL, NULL, NULL, NULL }, /* Bt */ + { NULL, NULL, NULL, NULL, NULL }, /* Hf */ + { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */ + { NULL, NULL, NULL, NULL, NULL }, /* Ud */ + { NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */ + { NULL, md_pre_Pp, NULL, NULL, NULL }, /* Lp */ + { NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */ + { NULL, md_pre_raw, md_post_raw, "<", ">" }, /* Mt */ + { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */ + { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */ + { NULL, NULL, NULL, NULL, NULL }, /* Brc */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %C */ + { NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */ + { md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */ + { NULL, NULL, NULL, NULL, NULL }, /* Dx */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */ + { NULL, md_pre_br, NULL, NULL, NULL }, /* br */ + { NULL, md_pre_Pp, NULL, NULL, NULL }, /* sp */ + { NULL, NULL, md_post_pc, NULL, NULL }, /* %U */ + { NULL, NULL, NULL, NULL, NULL }, /* Ta */ + { NULL, NULL, NULL, NULL, NULL }, /* ll */ + { NULL, NULL, NULL, NULL, NULL }, /* ROOT */ +}; + +static int outflags; +#define MD_spc (1 << 0) /* Blank character before next word. */ +#define MD_spc_force (1 << 1) /* Even before trailing punctuation. */ +#define MD_nonl (1 << 2) /* Prevent linebreak in markdown code. */ +#define MD_nl (1 << 3) /* Break markdown code line. */ +#define MD_br (1 << 4) /* Insert an output line break. */ +#define MD_sp (1 << 5) /* Insert a paragraph break. */ +#define MD_Sm (1 << 6) /* Horizontal spacing mode. */ +#define MD_Bk (1 << 7) /* Word keep mode. */ + +static int escflags; /* Escape in generated markdown code: */ +#define ESC_BOL (1 << 0) /* "#*+-" near the beginning of a line. */ +#define ESC_NUM (1 << 1) /* "." after a leading number. */ +#define ESC_HYP (1 << 2) /* "(" immediately after "]". */ +#define ESC_PAR (1 << 3) /* ")" when "(" is open. */ +#define ESC_SQU (1 << 4) /* "]" when "[" is open. */ +#define ESC_FON (1 << 5) /* "*" immediately after unrelated "*". */ + +static int code_blocks, quote_blocks, list_blocks; +static int outcount; + +void +markdown_mdoc(void *arg, const struct roff_man *mdoc) +{ + outflags = MD_Sm; + md_word(mdoc->meta.title); + if (mdoc->meta.msec != NULL) { + outflags &= ~MD_spc; + md_word("("); + md_word(mdoc->meta.msec); + md_word(")"); + } + md_word("-"); + md_word(mdoc->meta.vol); + if (mdoc->meta.arch != NULL) { + md_word("("); + md_word(mdoc->meta.arch); + md_word(")"); + } + outflags |= MD_sp; + + md_nodelist(mdoc->first->child); + + outflags |= MD_sp; + md_word(mdoc->meta.os); + md_word("-"); + md_word(mdoc->meta.date); + putchar('\n'); +} + +static void +md_nodelist(struct roff_node *n) +{ + while (n != NULL) { + md_node(n); + n = n->next; + } +} + +static void +md_node(struct roff_node *n) +{ + const struct md_act *act; + int cond, process_children; + + if (n->flags & NODE_NOPRT) + return; + + if (outflags & MD_nonl) + outflags &= ~(MD_nl | MD_sp); + else if (outflags & MD_spc && n->flags & NODE_LINE) + outflags |= MD_nl; + + act = NULL; + cond = 0; + process_children = 1; + n->flags &= ~NODE_ENDED; + + switch (n->type) { + case ROFFT_TEXT: + if (n->flags & NODE_DELIMC) + outflags &= ~(MD_spc | MD_spc_force); + else if (outflags & MD_Sm) + outflags |= MD_spc_force; + md_word(n->string); + if (n->flags & NODE_DELIMO) + outflags &= ~(MD_spc | MD_spc_force); + else if (outflags & MD_Sm) + outflags |= MD_spc; + break; + default: + act = md_acts + n->tok; + cond = act->cond == NULL || (*act->cond)(n); + if (cond && act->pre != NULL && + (n->end == ENDBODY_NOT || n->child != NULL)) + process_children = (*act->pre)(n); + break; + } + + if (process_children && n->child != NULL) + md_nodelist(n->child); + + if (n->flags & NODE_ENDED) + return; + + if (cond && act->post != NULL) + (*act->post)(n); + + if (n->end != ENDBODY_NOT) + n->body->flags |= NODE_ENDED; +} + +static const char * +md_stack(char c) +{ + static char *stack; + static size_t sz; + static size_t cur; + + switch (c) { + case '\0': + break; + case (char)-1: + assert(cur); + stack[--cur] = '\0'; + break; + default: + if (cur + 1 >= sz) { + sz += 8; + stack = mandoc_realloc(stack, sz); + } + stack[cur] = c; + stack[++cur] = '\0'; + break; + } + return stack == NULL ? "" : stack; +} + +/* + * Handle vertical and horizontal spacing. + */ +static void +md_preword(void) +{ + /* + * If a list block is nested inside a code block or a blockquote, + * blank lines for paragraph breaks no longer work; instead, + * they terminate the list. Work around this markdown issue + * by using mere line breaks instead. + */ + if (list_blocks && outflags & MD_sp) { + outflags &= ~MD_sp; + outflags |= MD_br; + } + + /* End the old line if requested. */ + + if (outflags & MD_sp) + putchar('\n'); + else if (outflags & MD_br) { + putchar(' '); + putchar(' '); +#ifdef DEBUG + putchar(':'); + putchar(':'); + putchar(' '); + putchar(' '); +#endif + } + + /* Start a new line if necessary. */ + + if (outflags & (MD_nl | MD_br | MD_sp)) { + putchar('\n'); + fputs(md_stack('\0'), stdout); + outflags &= ~(MD_nl | MD_br | MD_sp); + escflags = ESC_BOL; + outcount = 0; + + /* Handle horizontal spacing. */ + + } else if (outflags & MD_spc) { + if (outflags & MD_Bk) + fputs(" ", stdout); + else + putchar(' '); + escflags &= ~ESC_FON; + outcount++; + } + + outflags &= ~(MD_spc_force | MD_nonl); + if (outflags & MD_Sm) + outflags |= MD_spc; + else + outflags &= ~MD_spc; +} + +/* + * Print markdown syntax elements. + * Can also be used for constant strings when neither escaping + * nor delimiter handling is required. + */ +static void +md_rawword(const char *s) +{ + md_preword(); + + if (*s == 0) + return; + + if (escflags & ESC_FON) { + escflags &= ~ESC_FON; + if (*s == '*' && !code_blocks) + fputs("‌", stdout); + } + + while (*s != '\0') { + switch(*s) { + case '(': + escflags |= ESC_PAR; + break; + case ')': + escflags |= ~ESC_PAR; + break; + case '*': + if (s[1] == '\0') + escflags |= ESC_FON; + break; + case '[': + escflags |= ESC_SQU; + break; + case ']': + escflags |= ESC_HYP; + escflags &= ~ESC_SQU; + break; + default: + break; + } + md_char(*s++); + } +} + +/* + * Print text and mdoc(7) syntax elements. + */ +static void +md_word(const char *s) +{ + const char *seq, *prevfont, *currfont, *nextfont; + char c; + int bs, sz, uc; + + /* No spacing before closing delimiters. */ + if (s[0] != '\0' && s[1] == '\0' && + strchr("!),.:;?]", s[0]) != NULL && + (outflags & MD_spc_force) == 0) + outflags &= ~MD_spc; + + md_preword(); + + /* No spacing after opening delimiters. */ + if ((s[0] == '(' || s[0] == '[') && s[1] == '\0') + outflags &= ~MD_spc; + + prevfont = currfont = ""; + while ((c = *s++) != '\0') { + bs = 0; + switch(c) { + case ASCII_NBRSP: + if (code_blocks) + c = ' '; + else { + md_named("nbsp"); + c = '\0'; + } + break; + case ASCII_HYPH: + bs = escflags & ESC_BOL && !code_blocks; + c = '-'; + break; + case ASCII_BREAK: + continue; + case '#': + case '+': + case '-': + bs = escflags & ESC_BOL && !code_blocks; + break; + case '(': + bs = escflags & ESC_HYP && !code_blocks; + break; + case ')': + bs = escflags & ESC_PAR && !code_blocks; + break; + case '*': + case '[': + case '_': + case '`': + bs = !code_blocks; + break; + case '.': + bs = escflags & ESC_NUM && !code_blocks; + break; + case '<': + if (code_blocks == 0) { + md_named("lt"); + c = '\0'; + } + break; + case '=': + if (escflags & ESC_BOL && !code_blocks) { + md_named("equals"); + c = '\0'; + } + break; + case '>': + if (code_blocks == 0) { + md_named("gt"); + c = '\0'; + } + break; + case '\\': + uc = 0; + nextfont = NULL; + switch (mandoc_escape(&s, &seq, &sz)) { + case ESCAPE_UNICODE: + uc = mchars_num2uc(seq + 1, sz - 1); + break; + case ESCAPE_NUMBERED: + uc = mchars_num2char(seq, sz); + break; + case ESCAPE_SPECIAL: + uc = mchars_spec2cp(seq, sz); + break; + case ESCAPE_FONTBOLD: + nextfont = "**"; + break; + case ESCAPE_FONTITALIC: + nextfont = "*"; + break; + case ESCAPE_FONTBI: + nextfont = "***"; + break; + case ESCAPE_FONT: + case ESCAPE_FONTROMAN: + nextfont = ""; + break; + case ESCAPE_FONTPREV: + nextfont = prevfont; + break; + case ESCAPE_NOSPACE: + case ESCAPE_SKIPCHAR: + case ESCAPE_OVERSTRIKE: + /* XXX not implemented */ + /* FALLTHROUGH */ + case ESCAPE_ERROR: + default: + break; + } + if (nextfont != NULL && !code_blocks) { + if (*currfont != '\0') { + outflags &= ~MD_spc; + md_rawword(currfont); + } + prevfont = currfont; + currfont = nextfont; + if (*currfont != '\0') { + outflags &= ~MD_spc; + md_rawword(currfont); + } + } + if (uc) { + if ((uc < 0x20 && uc != 0x09) || + (uc > 0x7E && uc < 0xA0)) + uc = 0xFFFD; + if (code_blocks) { + seq = mchars_uc2str(uc); + fputs(seq, stdout); + outcount += strlen(seq); + } else { + printf("&#%d;", uc); + outcount++; + } + escflags &= ~ESC_FON; + } + c = '\0'; + break; + case ']': + bs = escflags & ESC_SQU && !code_blocks; + escflags |= ESC_HYP; + break; + default: + break; + } + if (bs) + putchar('\\'); + md_char(c); + } + if (*currfont != '\0') { + outflags &= ~MD_spc; + md_rawword(currfont); + } +} + +/* + * Print a single HTML named character reference. + */ +static void +md_named(const char *s) +{ + printf("&%s;", s); + escflags &= ~ESC_FON; + outcount++; +} + +/* + * Print a single raw character and maintain certain escape flags. + */ +static void +md_char(unsigned char c) +{ + if (c != '\0') { + putchar(c); + if (c == '*') + escflags |= ESC_FON; + else + escflags &= ~ESC_FON; + outcount++; + } + if (c != ']') + escflags &= ~ESC_HYP; + if (c == ' ' || c == '\t' || c == '>') + return; + if (isdigit(c) == 0) + escflags &= ~ESC_NUM; + else if (escflags & ESC_BOL) + escflags |= ESC_NUM; + escflags &= ~ESC_BOL; +} + +static int +md_cond_head(struct roff_node *n) +{ + return n->type == ROFFT_HEAD; +} + +static int +md_cond_body(struct roff_node *n) +{ + return n->type == ROFFT_BODY; +} + +static int +md_pre_raw(struct roff_node *n) +{ + const char *prefix; + + if ((prefix = md_acts[n->tok].prefix) != NULL) { + md_rawword(prefix); + outflags &= ~MD_spc; + } + return 1; +} + +static void +md_post_raw(struct roff_node *n) +{ + const char *suffix; + + if ((suffix = md_acts[n->tok].suffix) != NULL) { + outflags &= ~(MD_spc | MD_nl); + md_rawword(suffix); + } +} + +static int +md_pre_word(struct roff_node *n) +{ + const char *prefix; + + if ((prefix = md_acts[n->tok].prefix) != NULL) { + md_word(prefix); + outflags &= ~MD_spc; + } + return 1; +} + +static void +md_post_word(struct roff_node *n) +{ + const char *suffix; + + if ((suffix = md_acts[n->tok].suffix) != NULL) { + outflags &= ~(MD_spc | MD_nl); + md_word(suffix); + } +} + +static void +md_post_pc(struct roff_node *n) +{ + md_post_raw(n); + if (n->parent->tok != MDOC_Rs) + return; + if (n->next != NULL) { + md_word(","); + if (n->prev != NULL && + n->prev->tok == n->tok && + n->next->tok == n->tok) + md_word("and"); + } else { + md_word("."); + outflags |= MD_nl; + } +} + +static int +md_pre_skip(struct roff_node *n) +{ + return 0; +} + +static void +md_pre_syn(struct roff_node *n) +{ + if (n->prev == NULL || ! (n->flags & NODE_SYNPRETTY)) + return; + + if (n->prev->tok == n->tok && + n->tok != MDOC_Ft && + n->tok != MDOC_Fo && + n->tok != MDOC_Fn) { + outflags |= MD_br; + return; + } + + switch (n->prev->tok) { + case MDOC_Fd: + case MDOC_Fn: + case MDOC_Fo: + case MDOC_In: + case MDOC_Vt: + outflags |= MD_sp; + break; + case MDOC_Ft: + if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) { + outflags |= MD_sp; + break; + } + /* FALLTHROUGH */ + default: + outflags |= MD_br; + break; + } +} + +static int +md_pre_Ap(struct roff_node *n) +{ + outflags &= ~MD_spc; + md_word("'"); + outflags &= ~MD_spc; + return 0; +} + +static int +md_pre_Bd(struct roff_node *n) +{ + switch (n->norm->Bd.type) { + case DISP_unfilled: + case DISP_literal: + return md_pre_Dl(n); + default: + return md_pre_D1(n); + } +} + +static int +md_pre_Bk(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_BLOCK: + return 1; + case ROFFT_BODY: + outflags |= MD_Bk; + return 1; + default: + return 0; + } +} + +static void +md_post_Bk(struct roff_node *n) +{ + if (n->type == ROFFT_BODY) + outflags &= ~MD_Bk; +} + +static int +md_pre_Bl(struct roff_node *n) +{ + n->norm->Bl.count = 0; + if (n->norm->Bl.type == LIST_column) + md_pre_Dl(n); + outflags |= MD_sp; + return 1; +} + +static void +md_post_Bl(struct roff_node *n) +{ + n->norm->Bl.count = 0; + if (n->norm->Bl.type == LIST_column) + md_post_D1(n); + outflags |= MD_sp; +} + +static int +md_pre_D1(struct roff_node *n) +{ + /* + * Markdown blockquote syntax does not work inside code blocks. + * The best we can do is fall back to another nested code block. + */ + if (code_blocks) { + md_stack('\t'); + code_blocks++; + } else { + md_stack('>'); + quote_blocks++; + } + outflags |= MD_sp; + return 1; +} + +static void +md_post_D1(struct roff_node *n) +{ + md_stack((char)-1); + if (code_blocks) + code_blocks--; + else + quote_blocks--; + outflags |= MD_sp; +} + +static int +md_pre_Dl(struct roff_node *n) +{ + /* + * Markdown code block syntax does not work inside blockquotes. + * The best we can do is fall back to another nested blockquote. + */ + if (quote_blocks) { + md_stack('>'); + quote_blocks++; + } else { + md_stack('\t'); + code_blocks++; + } + outflags |= MD_sp; + return 1; +} + +static int +md_pre_En(struct roff_node *n) +{ + if (n->norm->Es == NULL || + n->norm->Es->child == NULL) + return 1; + + md_word(n->norm->Es->child->string); + outflags &= ~MD_spc; + return 1; +} + +static void +md_post_En(struct roff_node *n) +{ + if (n->norm->Es == NULL || + n->norm->Es->child == NULL || + n->norm->Es->child->next == NULL) + return; + + outflags &= ~MD_spc; + md_word(n->norm->Es->child->next->string); +} + +static int +md_pre_Eo(struct roff_node *n) +{ + if (n->end == ENDBODY_NOT && + n->parent->head->child == NULL && + n->child != NULL && + n->child->end != ENDBODY_NOT) + md_preword(); + else if (n->end != ENDBODY_NOT ? n->child != NULL : + n->parent->head->child != NULL && (n->child != NULL || + (n->parent->tail != NULL && n->parent->tail->child != NULL))) + outflags &= ~(MD_spc | MD_nl); + return 1; +} + +static void +md_post_Eo(struct roff_node *n) +{ + int body, tail; + + if (n->end != ENDBODY_NOT) { + outflags |= MD_spc; + return; + } + + body = n->child != NULL || n->parent->head->child != NULL; + tail = n->parent->tail != NULL && n->parent->tail->child != NULL; + + if (body && tail) + outflags &= ~MD_spc; + else if ( ! (body || tail)) + md_preword(); + else if ( ! tail) + outflags |= MD_spc; +} + +static int +md_pre_Fa(struct roff_node *n) +{ + int am_Fa; + + am_Fa = n->tok == MDOC_Fa; + + if (am_Fa) + n = n->child; + + while (n != NULL) { + md_rawword("*"); + outflags &= ~MD_spc; + md_node(n); + outflags &= ~MD_spc; + md_rawword("*"); + if ((n = n->next) != NULL) + md_word(","); + } + return 0; +} + +static void +md_post_Fa(struct roff_node *n) +{ + if (n->next != NULL && n->next->tok == MDOC_Fa) + md_word(","); +} + +static int +md_pre_Fd(struct roff_node *n) +{ + md_pre_syn(n); + md_pre_raw(n); + return 1; +} + +static void +md_post_Fd(struct roff_node *n) +{ + md_post_raw(n); + outflags |= MD_br; +} + +static int +md_pre_Fn(struct roff_node *n) +{ + md_pre_syn(n); + + if ((n = n->child) == NULL) + return 0; + + md_rawword("**"); + outflags &= ~MD_spc; + md_node(n); + outflags &= ~MD_spc; + md_rawword("**"); + outflags &= ~MD_spc; + md_word("("); + + if ((n = n->next) != NULL) + md_pre_Fa(n); + return 0; +} + +static void +md_post_Fn(struct roff_node *n) +{ + md_word(")"); + if (n->flags & NODE_SYNPRETTY) { + md_word(";"); + outflags |= MD_sp; + } +} + +static int +md_pre_Fo(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_BLOCK: + md_pre_syn(n); + break; + case ROFFT_HEAD: + if (n->child == NULL) + return 0; + md_pre_raw(n); + break; + case ROFFT_BODY: + outflags &= ~(MD_spc | MD_nl); + md_word("("); + break; + default: + break; + } + return 1; +} + +static void +md_post_Fo(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_HEAD: + if (n->child != NULL) + md_post_raw(n); + break; + case ROFFT_BODY: + md_post_Fn(n); + break; + default: + break; + } +} + +static int +md_pre_In(struct roff_node *n) +{ + if (n->flags & NODE_SYNPRETTY) { + md_pre_syn(n); + md_pre_raw(n); + md_rawword("*"); + outflags &= ~MD_spc; + md_word("#include <"); + outflags &= ~MD_spc; + } else { + md_word("<"); + outflags &= ~MD_spc; + md_pre_raw(n); + } + return 1; +} + +static void +md_post_In(struct roff_node *n) +{ + if (n->flags & NODE_SYNPRETTY) { + outflags &= ~MD_spc; + md_rawword(">*"); + md_post_raw(n); + outflags |= MD_nl; + } else { + md_post_raw(n); + outflags &= ~MD_spc; + md_rawword(">"); + } +} + +static int +md_pre_It(struct roff_node *n) +{ + struct roff_node *bln; + + switch (n->type) { + case ROFFT_BLOCK: + return 1; + + case ROFFT_HEAD: + bln = n->parent->parent; + if (bln->norm->Bl.comp == 0) + outflags |= MD_sp; + outflags |= MD_nl; + + switch (bln->norm->Bl.type) { + case LIST_item: + outflags |= MD_br; + return 0; + case LIST_inset: + case LIST_diag: + case LIST_ohang: + outflags |= MD_br; + return 1; + case LIST_tag: + case LIST_hang: + outflags |= MD_sp; + return 1; + case LIST_bullet: + md_rawword("*\t"); + break; + case LIST_dash: + case LIST_hyphen: + md_rawword("-\t"); + break; + case LIST_enum: + md_preword(); + printf("%d.\t", ++bln->norm->Bl.count); + escflags &= ~ESC_FON; + break; + default: + return 0; + } + outflags &= ~MD_spc; + outflags |= MD_nonl; + outcount = 0; + md_stack('\t'); + if (code_blocks || quote_blocks) + list_blocks++; + return 0; + + case ROFFT_BODY: + bln = n->parent->parent; + switch (bln->norm->Bl.type) { + case LIST_ohang: + outflags |= MD_br; + break; + case LIST_tag: + case LIST_hang: + md_pre_D1(n); + break; + default: + break; + } + return 1; + + default: + return 0; + } +} + +static void +md_post_It(struct roff_node *n) +{ + struct roff_node *bln; + int i, nc; + + if (n->type != ROFFT_BODY) + return; + + bln = n->parent->parent; + switch (bln->norm->Bl.type) { + case LIST_bullet: + case LIST_dash: + case LIST_hyphen: + case LIST_enum: + md_stack((char)-1); + if (code_blocks || quote_blocks) + list_blocks--; + break; + case LIST_tag: + case LIST_hang: + md_post_D1(n); + break; + + case LIST_column: + if (n->next == NULL) + break; + + /* Calculate the array index of the current column. */ + + i = 0; + while ((n = n->prev) != NULL && n->type != ROFFT_HEAD) + i++; + + /* + * If a width was specified for this column, + * subtract what printed, and + * add the same spacing as in mdoc_term.c. + */ + + nc = bln->norm->Bl.ncols; + i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount + + (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1; + if (i < 1) + i = 1; + while (i-- > 0) + putchar(' '); + + outflags &= ~MD_spc; + escflags &= ~ESC_FON; + outcount = 0; + break; + + default: + break; + } +} + +static void +md_post_Lb(struct roff_node *n) +{ + if (n->sec == SEC_LIBRARY) + outflags |= MD_br; +} + +static int +md_pre_Lk(struct roff_node *n) +{ + const struct roff_node *link, *descr; + + if ((link = n->child) == NULL) + return 0; + + if ((descr = link->next) != NULL) { + md_rawword("["); + outflags &= ~MD_spc; + while (descr != NULL) { + md_word(descr->string); + descr = descr->next; + } + outflags &= ~MD_spc; + md_rawword("]("); + } else + md_rawword("<"); + + outflags &= ~MD_spc; + md_word(link->string); + outflags &= ~MD_spc; + md_rawword(link->next == NULL ? ">" : ")"); + return 0; +} + +static int +md_pre_Nd(struct roff_node *n) +{ + outflags &= ~MD_nl; + outflags |= MD_spc; + md_word("-"); + return 1; +} + +static int +md_pre_Nm(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_BLOCK: + outflags |= MD_Bk; + md_pre_syn(n); + break; + case ROFFT_HEAD: + case ROFFT_ELEM: + md_pre_raw(n); + break; + default: + break; + } + return 1; +} + +static void +md_post_Nm(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_BLOCK: + outflags &= ~MD_Bk; + break; + case ROFFT_HEAD: + case ROFFT_ELEM: + md_post_raw(n); + break; + default: + break; + } +} + +static int +md_pre_No(struct roff_node *n) +{ + outflags |= MD_spc_force; + return 1; +} + +static int +md_pre_Ns(struct roff_node *n) +{ + outflags &= ~MD_spc; + return 0; +} + +static void +md_post_Pf(struct roff_node *n) +{ + if (n->next != NULL && (n->next->flags & NODE_LINE) == 0) + outflags &= ~MD_spc; +} + +static int +md_pre_Pp(struct roff_node *n) +{ + outflags |= MD_sp; + return 0; +} + +static int +md_pre_Rs(struct roff_node *n) +{ + if (n->sec == SEC_SEE_ALSO) + outflags |= MD_sp; + return 1; +} + +static int +md_pre_Sh(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_HEAD: + outflags |= MD_sp; + md_rawword(n->tok == MDOC_Sh ? "#" : "##"); + break; + case ROFFT_BODY: + outflags |= MD_sp; + break; + default: + break; + } + return 1; +} + +static int +md_pre_Sm(struct roff_node *n) +{ + if (n->child == NULL) + outflags ^= MD_Sm; + else if (strcmp("on", n->child->string) == 0) + outflags |= MD_Sm; + else + outflags &= ~MD_Sm; + + if (outflags & MD_Sm) + outflags |= MD_spc; + + return 0; +} + +static int +md_pre_Vt(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_BLOCK: + md_pre_syn(n); + return 1; + case ROFFT_BODY: + case ROFFT_ELEM: + md_pre_raw(n); + return 1; + default: + return 0; + } +} + +static void +md_post_Vt(struct roff_node *n) +{ + switch (n->type) { + case ROFFT_BODY: + case ROFFT_ELEM: + md_post_raw(n); + break; + default: + break; + } +} + +static int +md_pre_Xr(struct roff_node *n) +{ + n = n->child; + if (n == NULL) + return 0; + md_node(n); + n = n->next; + if (n == NULL) + return 0; + outflags &= ~MD_spc; + md_word("("); + md_node(n); + md_word(")"); + return 0; +} + +static int +md_pre__T(struct roff_node *n) +{ + if (n->parent != NULL && n->parent->tok == MDOC_Rs && + n->parent->norm->Rs.quote_T) + md_word("\""); + else + md_rawword("*"); + outflags &= ~MD_spc; + return 1; +} + +static void +md_post__T(struct roff_node *n) +{ + outflags &= ~MD_spc; + if (n->parent != NULL && n->parent->tok == MDOC_Rs && + n->parent->norm->Rs.quote_T) + md_word("\""); + else + md_rawword("*"); + md_post_pc(n); +} + +static int +md_pre_br(struct roff_node *n) +{ + outflags |= MD_br; + return 0; +} |