Annotation of mandoc/mdocterm.c, Revision 1.45
1.45 ! kristaps 1: /* $Id: mdocterm.c,v 1.44 2009/03/15 07:18:10 kristaps Exp $ */
1.1 kristaps 2: /*
3: * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the
7: * above copyright notice and this permission notice appear in all
8: * copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11: * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12: * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13: * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14: * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15: * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16: * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17: * PERFORMANCE OF THIS SOFTWARE.
18: */
1.37 kristaps 19: #include <sys/types.h>
20:
1.1 kristaps 21: #include <assert.h>
1.3 kristaps 22: #include <ctype.h>
1.1 kristaps 23: #include <err.h>
24: #include <getopt.h>
1.3 kristaps 25: #include <stdio.h>
1.1 kristaps 26: #include <stdlib.h>
1.3 kristaps 27: #include <string.h>
1.43 kristaps 28: #include <unistd.h>
1.5 kristaps 29:
1.2 kristaps 30: #include "mmain.h"
1.1 kristaps 31: #include "term.h"
32:
1.43 kristaps 33: struct nroffopt {
34: int fl_h;
35: int fl_i;
36: char *arg_m;
37: char *arg_n;
38: char *arg_o;
39: char *arg_r;
40: char *arg_T;
41: struct termp *termp; /* Ephemeral. */
42: };
43:
1.42 kristaps 44: struct termseq {
1.27 kristaps 45: const char *enc;
46: int sym;
47: };
48:
1.43 kristaps 49: dead_pre void punt(struct nroffopt *, char *) dead_post;
50: static int option(void *, int, char *);
51: static int optsopt(struct termp *, char *);
1.3 kristaps 52: static void body(struct termp *,
1.12 kristaps 53: struct termpair *,
1.3 kristaps 54: const struct mdoc_meta *,
55: const struct mdoc_node *);
56: static void header(struct termp *,
57: const struct mdoc_meta *);
58: static void footer(struct termp *,
59: const struct mdoc_meta *);
60:
61: static void pword(struct termp *, const char *, size_t);
1.25 kristaps 62: static void pescape(struct termp *, const char *,
63: size_t *, size_t);
64: static void nescape(struct termp *,
1.12 kristaps 65: const char *, size_t);
1.3 kristaps 66: static void chara(struct termp *, char);
1.25 kristaps 67: static void stringa(struct termp *,
68: const char *, size_t);
69: static void symbola(struct termp *, enum tsym);
1.38 kristaps 70: static void sanity(const struct mdoc_node *);
1.3 kristaps 71:
72: #ifdef __linux__
73: extern size_t strlcat(char *, const char *, size_t);
74: extern size_t strlcpy(char *, const char *, size_t);
75: #endif
76:
1.42 kristaps 77: static struct termseq termenc1[] = {
1.27 kristaps 78: { "\\", TERMSYM_SLASH },
79: { "\'", TERMSYM_RSQUOTE },
80: { "`", TERMSYM_LSQUOTE },
81: { "-", TERMSYM_HYPHEN },
82: { " ", TERMSYM_SPACE },
83: { ".", TERMSYM_PERIOD },
84: { "&", TERMSYM_BREAK },
85: { "e", TERMSYM_SLASH },
86: { "q", TERMSYM_DQUOTE },
1.41 kristaps 87: { "|", TERMSYM_BREAK },
1.27 kristaps 88: { NULL, 0 }
89: };
90:
1.42 kristaps 91: static struct termseq termenc2[] = {
1.39 kristaps 92: { "rC", TERMSYM_RBRACE },
93: { "lC", TERMSYM_LBRACE },
1.27 kristaps 94: { "rB", TERMSYM_RBRACK },
95: { "lB", TERMSYM_LBRACK },
1.31 kristaps 96: { "ra", TERMSYM_RANGLE },
97: { "la", TERMSYM_LANGLE },
1.27 kristaps 98: { "Lq", TERMSYM_LDQUOTE },
99: { "lq", TERMSYM_LDQUOTE },
100: { "Rq", TERMSYM_RDQUOTE },
101: { "rq", TERMSYM_RDQUOTE },
102: { "oq", TERMSYM_LSQUOTE },
103: { "aq", TERMSYM_RSQUOTE },
104:
105: { "<-", TERMSYM_LARROW },
106: { "->", TERMSYM_RARROW },
107: { "ua", TERMSYM_UARROW },
108: { "da", TERMSYM_DARROW },
109:
110: { "bu", TERMSYM_BULLET },
111: { "Ba", TERMSYM_BAR },
112: { "ba", TERMSYM_BAR },
113: { "co", TERMSYM_COPY },
114: { "Am", TERMSYM_AMP },
115:
116: { "Le", TERMSYM_LE },
117: { "<=", TERMSYM_LE },
118: { "Ge", TERMSYM_GE },
1.31 kristaps 119: { ">=", TERMSYM_GE },
1.27 kristaps 120: { "==", TERMSYM_EQ },
121: { "Ne", TERMSYM_NEQ },
122: { "!=", TERMSYM_NEQ },
123: { "Pm", TERMSYM_PLUSMINUS },
124: { "+-", TERMSYM_PLUSMINUS },
125: { "If", TERMSYM_INF2 },
126: { "if", TERMSYM_INF },
127: { "Na", TERMSYM_NAN },
128: { "na", TERMSYM_NAN },
129: { "**", TERMSYM_ASTERISK },
130: { "Gt", TERMSYM_GT },
131: { "Lt", TERMSYM_LT },
132:
133: { "aa", TERMSYM_ACUTE },
134: { "ga", TERMSYM_GRAVE },
135:
136: { "en", TERMSYM_EN },
137: { "em", TERMSYM_EM },
138:
139: { "Pi", TERMSYM_PI },
140: { NULL, 0 }
141: };
142:
1.43 kristaps 143: /* FIXME: abstract to dynamically-compiled table. */
1.42 kristaps 144: static struct termsym termsym_ascii[TERMSYM_MAX] = {
1.25 kristaps 145: { "]", 1 }, /* TERMSYM_RBRACK */
146: { "[", 1 }, /* TERMSYM_LBRACK */
147: { "<-", 2 }, /* TERMSYM_LARROW */
148: { "->", 2 }, /* TERMSYM_RARROW */
149: { "^", 1 }, /* TERMSYM_UARROW */
150: { "v", 1 }, /* TERMSYM_DARROW */
151: { "`", 1 }, /* TERMSYM_LSQUOTE */
152: { "\'", 1 }, /* TERMSYM_RSQUOTE */
153: { "\'", 1 }, /* TERMSYM_SQUOTE */
154: { "``", 2 }, /* TERMSYM_LDQUOTE */
155: { "\'\'", 2 }, /* TERMSYM_RDQUOTE */
156: { "\"", 1 }, /* TERMSYM_DQUOTE */
157: { "<", 1 }, /* TERMSYM_LT */
158: { ">", 1 }, /* TERMSYM_GT */
159: { "<=", 2 }, /* TERMSYM_LE */
160: { ">=", 2 }, /* TERMSYM_GE */
161: { "==", 2 }, /* TERMSYM_EQ */
162: { "!=", 2 }, /* TERMSYM_NEQ */
163: { "\'", 1 }, /* TERMSYM_ACUTE */
164: { "`", 1 }, /* TERMSYM_GRAVE */
165: { "pi", 2 }, /* TERMSYM_PI */
166: { "+=", 2 }, /* TERMSYM_PLUSMINUS */
167: { "oo", 2 }, /* TERMSYM_INF */
168: { "infinity", 8 }, /* TERMSYM_INF2 */
169: { "NaN", 3 }, /* TERMSYM_NAN */
170: { "|", 1 }, /* TERMSYM_BAR */
171: { "o", 1 }, /* TERMSYM_BULLET */
1.27 kristaps 172: { "&", 1 }, /* TERMSYM_AMP */
173: { "--", 2 }, /* TERMSYM_EM */
174: { "-", 1 }, /* TERMSYM_EN */
175: { "(C)", 3 }, /* TERMSYM_COPY */
176: { "*", 1 }, /* TERMSYM_ASTERISK */
177: { "\\", 1 }, /* TERMSYM_SLASH */
178: { "-", 1 }, /* TERMSYM_HYPHEN */
179: { " ", 1 }, /* TERMSYM_SPACE */
180: { ".", 1 }, /* TERMSYM_PERIOD */
181: { "", 0 }, /* TERMSYM_BREAK */
1.31 kristaps 182: { "<", 1 }, /* TERMSYM_LANGLE */
183: { ">", 1 }, /* TERMSYM_RANGLE */
1.39 kristaps 184: { "{", 1 }, /* TERMSYM_LBRACE */
185: { "}", 1 }, /* TERMSYM_RBRACE */
1.25 kristaps 186: };
187:
1.1 kristaps 188: int
189: main(int argc, char *argv[])
190: {
1.34 kristaps 191: struct mmain *p;
1.2 kristaps 192: const struct mdoc *mdoc;
1.43 kristaps 193: struct nroffopt nroff;
1.34 kristaps 194: struct termp termp;
1.43 kristaps 195: int c;
196: char *in;
1.2 kristaps 197:
1.42 kristaps 198: (void)memset(&termp, 0, sizeof(struct termp));
1.43 kristaps 199: (void)memset(&nroff, 0, sizeof(struct nroffopt));
1.42 kristaps 200:
1.43 kristaps 201: termp.maxrmargin = termp.rmargin = 78; /* FIXME */
202: termp.maxcols = 1024; /* FIXME */
1.42 kristaps 203: termp.offset = termp.col = 0;
204: termp.flags = TERMP_NOSPACE;
205: termp.symtab = termsym_ascii;
1.43 kristaps 206:
207: nroff.termp = &termp;
1.42 kristaps 208:
1.2 kristaps 209: p = mmain_alloc();
1.43 kristaps 210:
1.42 kristaps 211: c = mmain_getopt(p, argc, argv, "[-Ooption...]",
1.43 kristaps 212: "[infile]", "him:n:o:r:T:O:", &nroff, option);
1.1 kristaps 213:
1.43 kristaps 214: /* FIXME: this needs to accept multiple outputs. */
215: argv += c;
216: if ((argc -= c) > 0)
217: in = *argv++;
218: else
219: in = "-";
220:
221: mmain_prepare(p, in);
222:
223: if (NULL == (mdoc = mmain_process(p))) {
224: if (TERMP_NOPUNT & termp.iflags)
225: mmain_exit(p, 1);
226: mmain_free(p);
227: punt(&nroff, in);
228: /* NOTREACHED */
229: }
1.3 kristaps 230:
231: if (NULL == (termp.buf = malloc(termp.maxcols)))
232: err(1, "malloc");
233:
1.15 kristaps 234: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 235: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 236: footer(&termp, mdoc_meta(mdoc));
237:
238: free(termp.buf);
239:
240: mmain_exit(p, 0);
241: /* NOTREACHED */
242: }
243:
244:
1.43 kristaps 245: static int
246: optsopt(struct termp *p, char *arg)
247: {
248: char *v;
1.45 ! kristaps 249: char *toks[] = { "nopunt", NULL };
1.43 kristaps 250:
251: while (*arg)
252: switch (getsubopt(&arg, toks, &v)) {
253: case (0):
254: p->iflags |= TERMP_NOPUNT;
255: break;
256: default:
257: warnx("unknown -O argument");
258: return(0);
259: }
260:
261: return(1);
262: }
263:
264:
265: static int
266: option(void *ptr, int c, char *arg)
1.42 kristaps 267: {
1.43 kristaps 268: struct termp *termp;
269: struct nroffopt *nroff;
1.42 kristaps 270:
1.43 kristaps 271: nroff = (struct nroffopt *)ptr;
272: termp = nroff->termp;
1.42 kristaps 273:
1.43 kristaps 274: switch (c) {
275: case ('h'):
276: nroff->fl_h = 1;
277: break;
278: case ('i'):
279: nroff->fl_i = 1;
280: break;
281: case ('m'):
282: nroff->arg_m = arg;
283: break;
284: case ('n'):
285: nroff->arg_n = arg;
286: break;
287: case ('o'):
288: nroff->arg_o = arg;
289: break;
290: case ('r'):
291: nroff->arg_r = arg;
292: break;
293: case ('T'):
294: nroff->arg_T = arg;
295: break;
296: case ('O'):
297: return(optsopt(termp, arg));
298: default:
299: break;
1.42 kristaps 300: }
301:
1.43 kristaps 302: return(1);
1.42 kristaps 303: }
304:
305:
1.25 kristaps 306: /*
307: * Flush a line of text. A "line" is loosely defined as being something
308: * that should be followed by a newline, regardless of whether it's
309: * broken apart by newlines getting there. A line can also be a
310: * fragment of a columnar list.
311: *
312: * Specifically, a line is whatever's in p->buf of length p->col, which
313: * is zeroed after this function returns.
314: *
315: * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
316: * critical importance here. Their behaviour follows:
317: *
318: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
319: * offset value. This is useful when doing columnar lists where the
320: * prior column has right-padded.
321: *
322: * - TERMP_NOBREAK: this is the most important and is used when making
323: * columns. In short: don't print a newline and instead pad to the
324: * right margin. Used in conjunction with TERMP_NOLPAD.
325: *
326: * In-line line breaking:
327: *
328: * If TERMP_NOBREAK is specified and the line overruns the right
329: * margin, it will break and pad-right to the right margin after
330: * writing. If maxrmargin is violated, it will break and continue
331: * writing from the right-margin, which will lead to the above
332: * scenario upon exit.
333: *
334: * Otherwise, the line will break at the right margin. Extremely long
335: * lines will cause the system to emit a warning (TODO: hyphenate, if
336: * possible).
337: */
1.3 kristaps 338: void
339: flushln(struct termp *p)
340: {
1.25 kristaps 341: size_t i, j, vsz, vis, maxvis, mmax, bp;
1.3 kristaps 342:
343: /*
344: * First, establish the maximum columns of "visible" content.
345: * This is usually the difference between the right-margin and
346: * an indentation, but can be, for tagged lists or columns, a
347: * small set of values.
348: */
349:
350: assert(p->offset < p->rmargin);
351: maxvis = p->rmargin - p->offset;
1.25 kristaps 352: mmax = p->maxrmargin - p->offset;
353: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.3 kristaps 354: vis = 0;
355:
356: /*
357: * If in the standard case (left-justified), then begin with our
358: * indentation, otherwise (columns, etc.) just start spitting
359: * out text.
360: */
361:
362: if ( ! (p->flags & TERMP_NOLPAD))
363: /* LINTED */
364: for (j = 0; j < p->offset; j++)
365: putchar(' ');
366:
367: for (i = 0; i < p->col; i++) {
368: /*
369: * Count up visible word characters. Control sequences
1.23 kristaps 370: * (starting with the CSI) aren't counted. A space
371: * generates a non-printing word, which is valid (the
372: * space is printed according to regular spacing rules).
1.3 kristaps 373: */
374:
375: /* LINTED */
376: for (j = i, vsz = 0; j < p->col; j++) {
1.45 ! kristaps 377: if (isspace((u_char)p->buf[j]))
1.3 kristaps 378: break;
1.45 ! kristaps 379: else if (8 == p->buf[j])
1.42 kristaps 380: j += 1;
1.45 ! kristaps 381: else
1.3 kristaps 382: vsz++;
383: }
384:
385: /*
1.25 kristaps 386: * Do line-breaking. If we're greater than our
387: * break-point and already in-line, break to the next
388: * line and start writing. If we're at the line start,
389: * then write out the word (TODO: hyphenate) and break
390: * in a subsequent loop invocation.
1.3 kristaps 391: */
392:
1.22 kristaps 393: if ( ! (TERMP_NOBREAK & p->flags)) {
1.25 kristaps 394: if (vis && vis + vsz > bp) {
1.22 kristaps 395: putchar('\n');
396: for (j = 0; j < p->offset; j++)
397: putchar(' ');
398: vis = 0;
1.25 kristaps 399: } else if (vis + vsz > bp)
400: warnx("word breaks right margin");
401:
402: /* TODO: hyphenate. */
403:
404: } else {
405: if (vis && vis + vsz > bp) {
406: putchar('\n');
407: for (j = 0; j < p->rmargin; j++)
408: putchar(' ');
1.30 kristaps 409: vis = p->rmargin - p->offset;
1.25 kristaps 410: } else if (vis + vsz > bp)
411: warnx("word breaks right margin");
412:
413: /* TODO: hyphenate. */
1.24 kristaps 414: }
1.3 kristaps 415:
416: /*
417: * Write out the word and a trailing space. Omit the
1.25 kristaps 418: * space if we're the last word in the line or beyond
419: * our breakpoint.
1.3 kristaps 420: */
421:
422: for ( ; i < p->col; i++) {
1.33 kristaps 423: if (isspace((u_char)p->buf[i]))
1.3 kristaps 424: break;
425: putchar(p->buf[i]);
426: }
427: vis += vsz;
1.25 kristaps 428: if (i < p->col && vis <= bp) {
1.3 kristaps 429: putchar(' ');
430: vis++;
431: }
432: }
433:
1.25 kristaps 434: /*
435: * If we've overstepped our maximum visible no-break space, then
436: * cause a newline and offset at the right margin.
437: */
438:
1.22 kristaps 439: if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
1.32 kristaps 440: if ( ! (TERMP_NONOBREAK & p->flags)) {
441: putchar('\n');
442: for (i = 0; i < p->rmargin; i++)
443: putchar(' ');
444: }
1.22 kristaps 445: p->col = 0;
446: return;
447: }
448:
1.3 kristaps 449: /*
450: * If we're not to right-marginalise it (newline), then instead
451: * pad to the right margin and stay off.
452: */
453:
1.32 kristaps 454: if (p->flags & TERMP_NOBREAK) {
455: if ( ! (TERMP_NONOBREAK & p->flags))
456: for ( ; vis < maxvis; vis++)
457: putchar(' ');
458: } else
1.3 kristaps 459: putchar('\n');
460:
461: p->col = 0;
462: }
463:
464:
1.25 kristaps 465: /*
466: * A newline only breaks an existing line; it won't assert vertical
467: * space. All data in the output buffer is flushed prior to the newline
468: * assertion.
469: */
1.3 kristaps 470: void
471: newln(struct termp *p)
472: {
1.1 kristaps 473:
1.3 kristaps 474: p->flags |= TERMP_NOSPACE;
1.12 kristaps 475: if (0 == p->col) {
476: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 477: return;
1.12 kristaps 478: }
1.3 kristaps 479: flushln(p);
1.11 kristaps 480: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 481: }
482:
483:
1.25 kristaps 484: /*
485: * Asserts a vertical space (a full, empty line-break between lines).
486: * Note that if used twice, this will cause two blank spaces and so on.
487: * All data in the output buffer is flushed prior to the newline
488: * assertion.
489: */
1.3 kristaps 490: void
491: vspace(struct termp *p)
492: {
493:
494: newln(p);
495: putchar('\n');
496: }
497:
498:
1.25 kristaps 499: /*
500: * Break apart a word into "pwords" (partial-words, usually from
501: * breaking up a phrase into individual words) and, eventually, put them
502: * into the output buffer. If we're a literal word, then don't break up
503: * the word and put it verbatim into the output buffer.
504: */
1.3 kristaps 505: void
506: word(struct termp *p, const char *word)
507: {
508: size_t i, j, len;
509:
510: if (p->flags & TERMP_LITERAL) {
511: pword(p, word, strlen(word));
512: return;
513: }
514:
1.40 kristaps 515: if (0 == (len = strlen(word)))
516: errx(1, "blank line not in literal context");
1.3 kristaps 517:
518: if (mdoc_isdelim(word)) {
519: if ( ! (p->flags & TERMP_IGNDELIM))
520: p->flags |= TERMP_NOSPACE;
521: p->flags &= ~TERMP_IGNDELIM;
522: }
523:
524: /* LINTED */
525: for (j = i = 0; i < len; i++) {
1.33 kristaps 526: if ( ! isspace((u_char)word[i])) {
1.3 kristaps 527: j++;
528: continue;
1.20 kristaps 529: }
530:
531: /* Escaped spaces don't delimit... */
1.33 kristaps 532: if (i > 0 && isspace((u_char)word[i]) &&
1.25 kristaps 533: '\\' == word[i - 1]) {
1.20 kristaps 534: j++;
535: continue;
1.1 kristaps 536: }
1.20 kristaps 537:
1.3 kristaps 538: if (0 == j)
539: continue;
540: assert(i >= j);
541: pword(p, &word[i - j], j);
542: j = 0;
543: }
544: if (j > 0) {
545: assert(i >= j);
546: pword(p, &word[i - j], j);
547: }
548: }
549:
550:
1.25 kristaps 551: /*
552: * This is the main function for printing out nodes. It's constituted
553: * of PRE and POST functions, which correspond to prefix and infix
554: * processing. The termpair structure allows data to persist between
555: * prefix and postfix invocations.
556: */
1.3 kristaps 557: static void
1.12 kristaps 558: body(struct termp *p, struct termpair *ppair,
559: const struct mdoc_meta *meta,
1.3 kristaps 560: const struct mdoc_node *node)
561: {
562: int dochild;
1.9 kristaps 563: struct termpair pair;
1.3 kristaps 564:
1.38 kristaps 565: /* Some quick sanity-checking. */
566:
567: sanity(node);
568:
1.3 kristaps 569: /* Pre-processing. */
570:
571: dochild = 1;
1.12 kristaps 572: pair.ppair = ppair;
1.9 kristaps 573: pair.type = 0;
1.11 kristaps 574: pair.offset = pair.rmargin = 0;
1.10 kristaps 575: pair.flag = 0;
1.12 kristaps 576: pair.count = 0;
1.3 kristaps 577:
578: if (MDOC_TEXT != node->type) {
579: if (termacts[node->tok].pre)
1.9 kristaps 580: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 581: dochild = 0;
582: } else /* MDOC_TEXT == node->type */
1.35 kristaps 583: word(p, node->string);
1.3 kristaps 584:
585: /* Children. */
586:
1.10 kristaps 587: if (TERMPAIR_FLAG & pair.type)
588: p->flags |= pair.flag;
1.9 kristaps 589:
1.3 kristaps 590: if (dochild && node->child)
1.12 kristaps 591: body(p, &pair, meta, node->child);
1.3 kristaps 592:
1.10 kristaps 593: if (TERMPAIR_FLAG & pair.type)
594: p->flags &= ~pair.flag;
1.9 kristaps 595:
1.3 kristaps 596: /* Post-processing. */
597:
598: if (MDOC_TEXT != node->type)
599: if (termacts[node->tok].post)
1.9 kristaps 600: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 601:
602: /* Siblings. */
1.1 kristaps 603:
1.3 kristaps 604: if (node->next)
1.12 kristaps 605: body(p, ppair, meta, node->next);
1.3 kristaps 606: }
607:
608:
609: static void
610: footer(struct termp *p, const struct mdoc_meta *meta)
611: {
612: struct tm *tm;
613: char *buf, *os;
614:
615: if (NULL == (buf = malloc(p->rmargin)))
616: err(1, "malloc");
617: if (NULL == (os = malloc(p->rmargin)))
618: err(1, "malloc");
619:
620: tm = localtime(&meta->date);
621:
1.7 kristaps 622: #ifdef __OpenBSD__
623: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
624: #else
1.3 kristaps 625: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
626: #endif
627: err(1, "strftime");
628:
1.15 kristaps 629: (void)strlcpy(os, meta->os, p->rmargin);
1.3 kristaps 630:
1.16 kristaps 631: /*
632: * This is /slightly/ different from regular groff output
633: * because we don't have page numbers. Print the following:
634: *
635: * OS MDOCDATE
636: */
637:
1.15 kristaps 638: vspace(p);
1.3 kristaps 639:
1.15 kristaps 640: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
641: p->rmargin = p->maxrmargin - strlen(buf);
642: p->offset = 0;
1.3 kristaps 643:
1.15 kristaps 644: word(p, os);
645: flushln(p);
1.3 kristaps 646:
1.15 kristaps 647: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
648: p->offset = p->rmargin;
649: p->rmargin = p->maxrmargin;
650: p->flags &= ~TERMP_NOBREAK;
651:
652: word(p, buf);
653: flushln(p);
1.1 kristaps 654:
1.3 kristaps 655: free(buf);
656: free(os);
1.1 kristaps 657: }
658:
659:
1.3 kristaps 660: static void
661: header(struct termp *p, const struct mdoc_meta *meta)
662: {
1.34 kristaps 663: char *buf, *title, *bufp;
1.18 kristaps 664:
665: p->rmargin = p->maxrmargin;
666: p->offset = 0;
1.3 kristaps 667:
668: if (NULL == (buf = malloc(p->rmargin)))
669: err(1, "malloc");
670: if (NULL == (title = malloc(p->rmargin)))
671: err(1, "malloc");
672:
1.16 kristaps 673: /*
674: * The header is strange. It has three components, which are
675: * really two with the first duplicated. It goes like this:
676: *
677: * IDENTIFIER TITLE IDENTIFIER
678: *
679: * The IDENTIFIER is NAME(SECTION), which is the command-name
680: * (if given, or "unknown" if not) followed by the manual page
681: * section. These are given in `Dt'. The TITLE is a free-form
682: * string depending on the manual volume. If not specified, it
683: * switches on the manual section.
684: */
685:
1.34 kristaps 686: assert(meta->vol);
687: (void)strlcpy(buf, meta->vol, p->rmargin);
1.13 kristaps 688:
1.34 kristaps 689: if (meta->arch) {
690: (void)strlcat(buf, " (", p->rmargin);
691: (void)strlcat(buf, meta->arch, p->rmargin);
692: (void)strlcat(buf, ")", p->rmargin);
693: }
1.13 kristaps 694:
1.34 kristaps 695: (void)snprintf(title, p->rmargin, "%s(%d)",
696: meta->title, meta->msec);
1.13 kristaps 697:
1.16 kristaps 698: for (bufp = title; *bufp; bufp++)
1.33 kristaps 699: *bufp = toupper((u_char)*bufp);
1.16 kristaps 700:
1.13 kristaps 701: p->offset = 0;
702: p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15 kristaps 703: p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3 kristaps 704:
1.13 kristaps 705: word(p, title);
706: flushln(p);
1.3 kristaps 707:
1.15 kristaps 708: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13 kristaps 709: p->offset = p->rmargin;
1.15 kristaps 710: p->rmargin = p->maxrmargin - strlen(title);
1.3 kristaps 711:
1.13 kristaps 712: word(p, buf);
713: flushln(p);
1.3 kristaps 714:
1.13 kristaps 715: p->offset = p->rmargin;
716: p->rmargin = p->maxrmargin;
717: p->flags &= ~TERMP_NOBREAK;
1.15 kristaps 718: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3 kristaps 719:
1.13 kristaps 720: word(p, title);
721: flushln(p);
1.3 kristaps 722:
1.13 kristaps 723: p->rmargin = p->maxrmargin;
724: p->offset = 0;
725: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 726:
727: free(title);
728: free(buf);
729: }
1.25 kristaps 730:
731:
732: /*
733: * Determine the symbol indicated by an escape sequences, that is, one
734: * starting with a backslash. Once done, we pass this value into the
735: * output buffer by way of the symbol table.
736: */
737: static void
738: nescape(struct termp *p, const char *word, size_t len)
739: {
1.42 kristaps 740: struct termseq *enc;
1.25 kristaps 741:
742: switch (len) {
743: case (1):
1.27 kristaps 744: enc = termenc1;
1.25 kristaps 745: break;
746: case (2):
1.27 kristaps 747: enc = termenc2;
1.25 kristaps 748: break;
749: default:
1.27 kristaps 750: warnx("unsupported %zu-byte escape sequence", len);
751: return;
1.25 kristaps 752: }
1.27 kristaps 753:
754: for ( ; enc->enc; enc++)
755: if (0 == memcmp(enc->enc, word, len)) {
756: symbola(p, enc->sym);
757: return;
758: }
759:
760: warnx("unsupported %zu-byte escape sequence", len);
1.25 kristaps 761: }
762:
763:
764: /*
765: * Handle an escape sequence: determine its length and pass it to the
766: * escape-symbol look table. Note that we assume mdoc(3) has validated
767: * the escape sequence (we assert upon badly-formed escape sequences).
768: */
769: static void
770: pescape(struct termp *p, const char *word, size_t *i, size_t len)
771: {
772: size_t j;
773:
1.36 kristaps 774: if (++(*i) >= len) {
775: warnx("ignoring bad escape sequence");
776: return;
777: }
1.25 kristaps 778:
779: if ('(' == word[*i]) {
780: (*i)++;
1.36 kristaps 781: if (*i + 1 >= len) {
782: warnx("ignoring bad escape sequence");
783: return;
784: }
1.25 kristaps 785: nescape(p, &word[*i], 2);
786: (*i)++;
787: return;
788:
789: } else if ('*' == word[*i]) {
790: (*i)++;
1.36 kristaps 791: if (*i >= len) {
792: warnx("ignoring bad escape sequence");
793: return;
794: }
1.25 kristaps 795: switch (word[*i]) {
796: case ('('):
797: (*i)++;
1.36 kristaps 798: if (*i + 1 >= len) {
799: warnx("ignoring bad escape sequence");
800: return;
801: }
1.25 kristaps 802: nescape(p, &word[*i], 2);
803: (*i)++;
804: return;
805: case ('['):
806: break;
807: default:
808: nescape(p, &word[*i], 1);
809: return;
810: }
811:
812: } else if ('[' != word[*i]) {
813: nescape(p, &word[*i], 1);
814: return;
815: }
816:
817: (*i)++;
818: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
819: /* Loop... */ ;
820:
1.36 kristaps 821: if (0 == word[*i]) {
822: warnx("ignoring bad escape sequence");
823: return;
824: }
1.25 kristaps 825: nescape(p, &word[*i - j], j);
826: }
827:
828:
829: /*
830: * Handle pwords, partial words, which may be either a single word or a
831: * phrase that cannot be broken down (such as a literal string). This
832: * handles word styling.
833: */
834: static void
835: pword(struct termp *p, const char *word, size_t len)
836: {
837: size_t i;
838:
839: if ( ! (TERMP_NOSPACE & p->flags) &&
840: ! (TERMP_LITERAL & p->flags))
841: chara(p, ' ');
842:
843: if ( ! (p->flags & TERMP_NONOSPACE))
844: p->flags &= ~TERMP_NOSPACE;
845:
846: /*
1.42 kristaps 847: * If ANSI (word-length styling), then apply our style now,
848: * before the word.
1.25 kristaps 849: */
850:
851: for (i = 0; i < len; i++) {
852: if ('\\' == word[i]) {
853: pescape(p, word, &i, len);
854: continue;
855: }
1.42 kristaps 856:
1.45 ! kristaps 857: if (TERMP_STYLE & p->flags) {
1.42 kristaps 858: if (TERMP_BOLD & p->flags) {
859: chara(p, word[i]);
860: chara(p, 8);
861: }
862: if (TERMP_UNDER & p->flags) {
863: chara(p, '_');
864: chara(p, 8);
865: }
866: }
867:
1.25 kristaps 868: chara(p, word[i]);
869: }
870: }
871:
872:
873: /*
874: * Add a symbol to the output line buffer.
875: */
876: static void
877: symbola(struct termp *p, enum tsym sym)
878: {
879:
880: assert(p->symtab[sym].sym);
881: stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
882: }
883:
884:
885: /*
886: * Like chara() but for arbitrary-length buffers. Resize the buffer by
887: * a factor of two (if the buffer is less than that) or the buffer's
888: * size.
889: */
890: static void
891: stringa(struct termp *p, const char *c, size_t sz)
892: {
893: size_t s;
894:
1.27 kristaps 895: if (0 == sz)
896: return;
897:
1.25 kristaps 898: s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
899:
900: assert(c);
901: if (p->col + sz >= p->maxcols) {
902: p->buf = realloc(p->buf, s);
903: if (NULL == p->buf)
904: err(1, "realloc");
905: p->maxcols = s;
906: }
907:
908: (void)memcpy(&p->buf[p->col], c, sz);
909: p->col += sz;
910: }
911:
912:
913: /*
914: * Insert a single character into the line-buffer. If the buffer's
915: * space is exceeded, then allocate more space by doubling the buffer
916: * size.
917: */
918: static void
919: chara(struct termp *p, char c)
920: {
921:
922: if (p->col + 1 >= p->maxcols) {
923: p->buf = realloc(p->buf, p->maxcols * 2);
924: if (NULL == p->buf)
925: err(1, "malloc");
926: p->maxcols *= 2;
927: }
928: p->buf[(p->col)++] = c;
929: }
1.38 kristaps 930:
931:
932: static void
933: sanity(const struct mdoc_node *n)
934: {
935:
936: switch (n->type) {
937: case (MDOC_TEXT):
938: if (n->child)
939: errx(1, "regular form violated (1)");
940: if (NULL == n->parent)
941: errx(1, "regular form violated (2)");
942: if (NULL == n->string)
943: errx(1, "regular form violated (3)");
944: switch (n->parent->type) {
945: case (MDOC_TEXT):
946: /* FALLTHROUGH */
947: case (MDOC_ROOT):
948: errx(1, "regular form violated (4)");
949: /* NOTREACHED */
950: default:
951: break;
952: }
953: break;
954: case (MDOC_ELEM):
955: if (NULL == n->parent)
956: errx(1, "regular form violated (5)");
957: switch (n->parent->type) {
958: case (MDOC_TAIL):
959: /* FALLTHROUGH */
960: case (MDOC_BODY):
961: /* FALLTHROUGH */
962: case (MDOC_HEAD):
963: break;
964: default:
965: errx(1, "regular form violated (6)");
966: /* NOTREACHED */
967: }
968: if (n->child) switch (n->child->type) {
969: case (MDOC_TEXT):
970: break;
971: default:
972: errx(1, "regular form violated (7(");
973: /* NOTREACHED */
974: }
975: break;
976: case (MDOC_HEAD):
977: /* FALLTHROUGH */
978: case (MDOC_BODY):
979: /* FALLTHROUGH */
980: case (MDOC_TAIL):
981: if (NULL == n->parent)
982: errx(1, "regular form violated (8)");
983: if (MDOC_BLOCK != n->parent->type)
984: errx(1, "regular form violated (9)");
985: if (n->child) switch (n->child->type) {
986: case (MDOC_BLOCK):
987: /* FALLTHROUGH */
988: case (MDOC_ELEM):
989: /* FALLTHROUGH */
990: case (MDOC_TEXT):
991: break;
992: default:
993: errx(1, "regular form violated (a)");
994: /* NOTREACHED */
995: }
996: break;
997: case (MDOC_BLOCK):
998: if (NULL == n->parent)
999: errx(1, "regular form violated (b)");
1000: if (NULL == n->child)
1001: errx(1, "regular form violated (c)");
1002: switch (n->parent->type) {
1003: case (MDOC_ROOT):
1004: /* FALLTHROUGH */
1005: case (MDOC_HEAD):
1006: /* FALLTHROUGH */
1007: case (MDOC_BODY):
1008: /* FALLTHROUGH */
1009: case (MDOC_TAIL):
1010: break;
1011: default:
1012: errx(1, "regular form violated (d)");
1013: /* NOTREACHED */
1014: }
1015: switch (n->child->type) {
1016: case (MDOC_ROOT):
1017: /* FALLTHROUGH */
1018: case (MDOC_ELEM):
1019: errx(1, "regular form violated (e)");
1020: /* NOTREACHED */
1021: default:
1022: break;
1023: }
1024: break;
1025: case (MDOC_ROOT):
1026: if (n->parent)
1027: errx(1, "regular form violated (f)");
1028: if (NULL == n->child)
1029: errx(1, "regular form violated (10)");
1030: switch (n->child->type) {
1031: case (MDOC_BLOCK):
1032: break;
1033: default:
1034: errx(1, "regular form violated (11)");
1035: /* NOTREACHED */
1036: }
1037: break;
1038: }
1039: }
1040:
1.43 kristaps 1041:
1042: dead_pre void
1043: punt(struct nroffopt *nroff, char *in)
1044: {
1045: char *args[32];
1046: char arg0[32], argm[32];
1047: int i;
1048:
1049: warnx("punting to nroff!");
1050:
1051: i = 0;
1052:
1053: (void)strlcpy(arg0, "nroff", 32);
1054: args[i++] = arg0;
1055:
1056: if (nroff->fl_h)
1057: args[i++] = "-h";
1058: if (nroff->fl_i)
1059: args[i++] = "-i";
1060:
1061: if (nroff->arg_m) {
1062: (void)strlcpy(argm, "-m", 32);
1063: (void)strlcat(argm, nroff->arg_m, 32);
1064: args[i++] = argm;
1065: } else
1066: args[i++] = "-mandoc";
1067:
1068: args[i++] = in;
1069: args[i++] = (char *)NULL;
1070:
1071: (void)execvp("nroff", args);
1072: errx(1, "exec");
1073: /* NOTREACHED */
1074: }
1075:
CVSweb