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