Annotation of mandoc/mdocterm.c, Revision 1.21
1.21 ! kristaps 1: /* $Id: mdocterm.c,v 1.20 2009/02/28 20:13:06 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.18 kristaps 19: #include <sys/utsname.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.1 kristaps 28:
1.7 kristaps 29: #ifndef __OpenBSD__
1.5 kristaps 30: #include <time.h>
31: #endif
32:
1.2 kristaps 33: #include "mmain.h"
1.1 kristaps 34: #include "term.h"
35:
1.16 kristaps 36: #define TERMSYM_RBRACK "]"
37: #define TERMSYM_LBRACK "["
38: #define TERMSYM_LARROW "<-"
39: #define TERMSYM_RARROW "->"
40: #define TERMSYM_UARROW "^"
1.17 kristaps 41: #define TERMSYM_DARROW "v"
1.16 kristaps 42: #define TERMSYM_LSQUOTE "`"
43: #define TERMSYM_RSQUOTE "\'"
44: #define TERMSYM_SQUOTE "\'"
45: #define TERMSYM_LDQUOTE "``"
46: #define TERMSYM_RDQUOTE "\'\'"
47: #define TERMSYM_DQUOTE "\""
48: #define TERMSYM_LT "<"
49: #define TERMSYM_GT ">"
50: #define TERMSYM_LE "<="
51: #define TERMSYM_GE ">="
52: #define TERMSYM_EQ "=="
53: #define TERMSYM_NEQ "!="
54: #define TERMSYM_ACUTE "\'"
55: #define TERMSYM_GRAVE "`"
56: #define TERMSYM_PI "pi"
57: #define TERMSYM_PLUSMINUS "+="
1.17 kristaps 58: #define TERMSYM_INF "oo"
59: #define TERMSYM_INF2 "infinity"
1.16 kristaps 60: #define TERMSYM_NAN "NaN"
61: #define TERMSYM_BAR "|"
62: #define TERMSYM_BULLET "o"
63:
1.7 kristaps 64: #ifdef __NetBSD__
65: #define xisspace(x) isspace((int)(x))
66: #else
67: #define xisspace(x) isspace((x))
68: #endif
69:
1.3 kristaps 70: enum termstyle {
71: STYLE_CLEAR,
72: STYLE_BOLD,
73: STYLE_UNDERLINE
74: };
75:
76: static void body(struct termp *,
1.12 kristaps 77: struct termpair *,
1.3 kristaps 78: const struct mdoc_meta *,
79: const struct mdoc_node *);
80: static void header(struct termp *,
81: const struct mdoc_meta *);
82: static void footer(struct termp *,
83: const struct mdoc_meta *);
84:
85: static void pword(struct termp *, const char *, size_t);
86: static void pescape(struct termp *,
87: const char *, size_t *, size_t);
1.12 kristaps 88: static void nescape(struct termp *,
89: const char *, size_t);
1.3 kristaps 90: static void chara(struct termp *, char);
1.4 kristaps 91: static void stringa(struct termp *, const char *);
1.3 kristaps 92: static void style(struct termp *, enum termstyle);
93:
94: #ifdef __linux__
95: extern size_t strlcat(char *, const char *, size_t);
96: extern size_t strlcpy(char *, const char *, size_t);
97: #endif
98:
99:
1.1 kristaps 100: int
101: main(int argc, char *argv[])
102: {
1.2 kristaps 103: struct mmain *p;
104: const struct mdoc *mdoc;
1.3 kristaps 105: struct termp termp;
1.2 kristaps 106:
107: p = mmain_alloc();
1.1 kristaps 108:
1.3 kristaps 109: if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
1.2 kristaps 110: mmain_exit(p, 1);
1.1 kristaps 111:
1.3 kristaps 112: if (NULL == (mdoc = mmain_mdoc(p)))
113: mmain_exit(p, 1);
114:
1.19 kristaps 115: termp.maxrmargin = 78; /* XXX */
1.3 kristaps 116: termp.rmargin = termp.maxrmargin;
117: termp.maxcols = 1024;
118: termp.offset = termp.col = 0;
119: termp.flags = TERMP_NOSPACE;
120:
121: if (NULL == (termp.buf = malloc(termp.maxcols)))
122: err(1, "malloc");
123:
1.15 kristaps 124: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 125: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 126: footer(&termp, mdoc_meta(mdoc));
127:
128: free(termp.buf);
129:
130: mmain_exit(p, 0);
131: /* NOTREACHED */
132: }
133:
134:
135: void
136: flushln(struct termp *p)
137: {
138: size_t i, j, vsz, vis, maxvis;
139:
140: /*
141: * First, establish the maximum columns of "visible" content.
142: * This is usually the difference between the right-margin and
143: * an indentation, but can be, for tagged lists or columns, a
144: * small set of values.
145: */
146:
147: assert(p->offset < p->rmargin);
148: maxvis = p->rmargin - p->offset;
149: vis = 0;
150:
151: /*
152: * If in the standard case (left-justified), then begin with our
153: * indentation, otherwise (columns, etc.) just start spitting
154: * out text.
155: */
156:
157: if ( ! (p->flags & TERMP_NOLPAD))
158: /* LINTED */
159: for (j = 0; j < p->offset; j++)
160: putchar(' ');
161:
162: /*
163: * If we're literal, print out verbatim.
164: */
165: if (p->flags & TERMP_LITERAL) {
166: for (i = 0; i < p->col; i++)
167: putchar(p->buf[i]);
168: putchar('\n');
169: p->col = 0;
170: return;
171: }
172:
173: for (i = 0; i < p->col; i++) {
174: /*
175: * Count up visible word characters. Control sequences
176: * (starting with the CSI) aren't counted.
177: */
1.7 kristaps 178: assert( ! xisspace(p->buf[i]));
1.3 kristaps 179:
180: /* LINTED */
181: for (j = i, vsz = 0; j < p->col; j++) {
1.7 kristaps 182: if (xisspace(p->buf[j]))
1.3 kristaps 183: break;
184: else if (27 == p->buf[j]) {
185: assert(j + 4 <= p->col);
186: j += 3;
187: } else
188: vsz++;
189: }
190: assert(vsz > 0);
191:
192: /*
193: * If a word is too long and we're within a line, put it
194: * on the next line. Puke if we're being asked to write
195: * something that will exceed the right margin (i.e.,
196: * from a fresh line or when we're not allowed to break
197: * the line with TERMP_NOBREAK).
198: */
199:
1.16 kristaps 200: /* FIXME: allow selective right-margin breaking. */
201:
1.14 kristaps 202: if (vis && vis + vsz > maxvis) {
1.3 kristaps 203: if (p->flags & TERMP_NOBREAK)
204: errx(1, "word breaks right margin");
205: putchar('\n');
206: for (j = 0; j < p->offset; j++)
207: putchar(' ');
208: vis = 0;
1.14 kristaps 209: } else if (vis + vsz > maxvis)
1.3 kristaps 210: errx(1, "word breaks right margin");
211:
212: /*
213: * Write out the word and a trailing space. Omit the
214: * space if we're the last word in the line.
215: */
216:
217: for ( ; i < p->col; i++) {
1.7 kristaps 218: if (xisspace(p->buf[i]))
1.3 kristaps 219: break;
220: putchar(p->buf[i]);
221: }
222: vis += vsz;
223: if (i < p->col) {
224: putchar(' ');
225: vis++;
226: }
227: }
228:
229: /*
230: * If we're not to right-marginalise it (newline), then instead
231: * pad to the right margin and stay off.
232: */
233:
234: if (p->flags & TERMP_NOBREAK) {
1.14 kristaps 235: if ( ! (p->flags & TERMP_NORPAD))
236: for ( ; vis < maxvis; vis++)
237: putchar(' ');
1.3 kristaps 238: } else
239: putchar('\n');
240:
241: p->col = 0;
242: }
243:
244:
245: void
246: newln(struct termp *p)
247: {
1.1 kristaps 248:
1.3 kristaps 249: /*
250: * A newline only breaks an existing line; it won't assert
251: * vertical space.
252: */
253: p->flags |= TERMP_NOSPACE;
1.12 kristaps 254: if (0 == p->col) {
255: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 256: return;
1.12 kristaps 257: }
1.3 kristaps 258: flushln(p);
1.11 kristaps 259: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 260: }
261:
262:
263: void
264: vspace(struct termp *p)
265: {
266:
267: /*
268: * Asserts a vertical space (a full, empty line-break between
269: * lines).
270: */
271: newln(p);
272: putchar('\n');
273: }
274:
275:
276: static void
1.4 kristaps 277: stringa(struct termp *p, const char *s)
278: {
279:
280: /* XXX - speed up if not passing to chara. */
281: for ( ; *s; s++)
282: chara(p, *s);
283: }
284:
285:
286: static void
1.3 kristaps 287: chara(struct termp *p, char c)
288: {
289:
1.16 kristaps 290: /*
291: * Insert a single character into the line-buffer. If the
292: * buffer's space is exceeded, then allocate more space.
293: */
294: if (p->col + 1 >= p->maxcols) {
295: p->buf = realloc(p->buf, p->maxcols * 2);
296: if (NULL == p->buf)
297: err(1, "malloc");
298: p->maxcols *= 2;
299: }
1.3 kristaps 300: p->buf[(p->col)++] = c;
301: }
302:
303:
304: static void
305: style(struct termp *p, enum termstyle esc)
306: {
307:
308: if (p->col + 4 >= p->maxcols)
309: errx(1, "line overrun");
310:
311: p->buf[(p->col)++] = 27;
312: p->buf[(p->col)++] = '[';
313: switch (esc) {
314: case (STYLE_CLEAR):
315: p->buf[(p->col)++] = '0';
316: break;
317: case (STYLE_BOLD):
318: p->buf[(p->col)++] = '1';
319: break;
320: case (STYLE_UNDERLINE):
321: p->buf[(p->col)++] = '4';
322: break;
323: default:
324: abort();
325: /* NOTREACHED */
326: }
327: p->buf[(p->col)++] = 'm';
328: }
329:
330:
331: static void
1.12 kristaps 332: nescape(struct termp *p, const char *word, size_t len)
333: {
334:
335: switch (len) {
1.16 kristaps 336: case (1):
337: if ('q' == word[0])
338: stringa(p, TERMSYM_DQUOTE);
339: break;
1.12 kristaps 340: case (2):
341: if ('r' == word[0] && 'B' == word[1])
1.16 kristaps 342: stringa(p, TERMSYM_RBRACK);
1.12 kristaps 343: else if ('l' == word[0] && 'B' == word[1])
1.16 kristaps 344: stringa(p, TERMSYM_LBRACK);
1.17 kristaps 345: else if ('l' == word[0] && 'q' == word[1])
346: stringa(p, TERMSYM_LDQUOTE);
347: else if ('r' == word[0] && 'q' == word[1])
348: stringa(p, TERMSYM_RDQUOTE);
349: else if ('o' == word[0] && 'q' == word[1])
350: stringa(p, TERMSYM_LSQUOTE);
351: else if ('a' == word[0] && 'q' == word[1])
352: stringa(p, TERMSYM_RSQUOTE);
1.12 kristaps 353: else if ('<' == word[0] && '-' == word[1])
1.16 kristaps 354: stringa(p, TERMSYM_LARROW);
1.12 kristaps 355: else if ('-' == word[0] && '>' == word[1])
1.16 kristaps 356: stringa(p, TERMSYM_RARROW);
1.12 kristaps 357: else if ('b' == word[0] && 'u' == word[1])
1.16 kristaps 358: stringa(p, TERMSYM_BULLET);
359: else if ('<' == word[0] && '=' == word[1])
360: stringa(p, TERMSYM_LE);
361: else if ('>' == word[0] && '=' == word[1])
362: stringa(p, TERMSYM_GE);
1.17 kristaps 363: else if ('=' == word[0] && '=' == word[1])
364: stringa(p, TERMSYM_EQ);
365: else if ('+' == word[0] && '-' == word[1])
366: stringa(p, TERMSYM_PLUSMINUS);
1.16 kristaps 367: else if ('u' == word[0] && 'a' == word[1])
368: stringa(p, TERMSYM_UARROW);
1.17 kristaps 369: else if ('d' == word[0] && 'a' == word[1])
370: stringa(p, TERMSYM_DARROW);
1.16 kristaps 371: else if ('a' == word[0] && 'a' == word[1])
372: stringa(p, TERMSYM_ACUTE);
373: else if ('g' == word[0] && 'a' == word[1])
374: stringa(p, TERMSYM_GRAVE);
1.17 kristaps 375: else if ('!' == word[0] && '=' == word[1])
1.16 kristaps 376: stringa(p, TERMSYM_NEQ);
1.17 kristaps 377: else if ('i' == word[0] && 'f' == word[1])
378: stringa(p, TERMSYM_INF);
379: else if ('n' == word[0] && 'a' == word[1])
380: stringa(p, TERMSYM_NAN);
381: else if ('b' == word[0] && 'a' == word[1])
382: stringa(p, TERMSYM_BAR);
383:
384: /* Deprecated forms. */
385: else if ('B' == word[0] && 'a' == word[1])
386: stringa(p, TERMSYM_BAR);
387: else if ('I' == word[0] && 'f' == word[1])
388: stringa(p, TERMSYM_INF2);
389: else if ('G' == word[0] && 'e' == word[1])
390: stringa(p, TERMSYM_GE);
391: else if ('G' == word[0] && 't' == word[1])
392: stringa(p, TERMSYM_GT);
393: else if ('L' == word[0] && 'e' == word[1])
394: stringa(p, TERMSYM_LE);
395: else if ('L' == word[0] && 'q' == word[1])
396: stringa(p, TERMSYM_LDQUOTE);
1.16 kristaps 397: else if ('L' == word[0] && 't' == word[1])
398: stringa(p, TERMSYM_LT);
1.17 kristaps 399: else if ('N' == word[0] && 'a' == word[1])
400: stringa(p, TERMSYM_NAN);
401: else if ('N' == word[0] && 'e' == word[1])
402: stringa(p, TERMSYM_NEQ);
403: else if ('P' == word[0] && 'i' == word[1])
404: stringa(p, TERMSYM_PI);
1.16 kristaps 405: else if ('P' == word[0] && 'm' == word[1])
406: stringa(p, TERMSYM_PLUSMINUS);
1.17 kristaps 407: else if ('R' == word[0] && 'q' == word[1])
408: stringa(p, TERMSYM_RDQUOTE);
1.12 kristaps 409: break;
410: default:
411: break;
412: }
413: }
414:
415:
416: static void
1.3 kristaps 417: pescape(struct termp *p, const char *word, size_t *i, size_t len)
418: {
1.12 kristaps 419: size_t j;
1.3 kristaps 420:
421: (*i)++;
422: assert(*i < len);
423:
1.16 kristaps 424: /*
425: * Handle an escape sequence. This must manage both groff-style
426: * escapes and mdoc-style escapes.
427: */
428:
1.3 kristaps 429: if ('(' == word[*i]) {
430: /* Two-character escapes. */
431: (*i)++;
432: assert(*i + 1 < len);
1.12 kristaps 433: nescape(p, &word[*i], 2);
1.3 kristaps 434: (*i)++;
435: return;
436:
1.16 kristaps 437: } else if ('*' == word[*i]) {
438: (*i)++;
439: assert(*i < len);
440: switch (word[*i]) {
441: case ('('):
442: (*i)++;
443: assert(*i + 1 < len);
444: nescape(p, &word[*i], 2);
445: (*i)++;
446: return;
447: default:
448: break;
449: }
450: nescape(p, &word[*i], 1);
451: return;
452:
1.3 kristaps 453: } else if ('[' != word[*i]) {
454: /* One-character escapes. */
455: switch (word[*i]) {
456: case ('\\'):
457: /* FALLTHROUGH */
458: case ('\''):
459: /* FALLTHROUGH */
460: case ('`'):
461: /* FALLTHROUGH */
462: case ('-'):
463: /* FALLTHROUGH */
1.13 kristaps 464: case (' '):
465: /* FALLTHROUGH */
1.3 kristaps 466: case ('.'):
467: chara(p, word[*i]);
1.21 ! kristaps 468: break;
! 469: case ('e'):
! 470: chara(p, '\\');
! 471: break;
1.1 kristaps 472: default:
1.3 kristaps 473: break;
474: }
475: return;
476: }
1.12 kristaps 477:
478: (*i)++;
479: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
480: /* Loop... */ ;
481:
482: nescape(p, &word[*i - j], j);
1.3 kristaps 483: }
484:
485:
486: static void
487: pword(struct termp *p, const char *word, size_t len)
488: {
489: size_t i;
490:
491: /*assert(len > 0);*/ /* Can be, if literal. */
492:
1.16 kristaps 493: /*
494: * Handle pwords, partial words, which may be either a single
495: * word or a phrase that cannot be broken down (such as a
496: * literal string). This handles word styling.
497: */
498:
1.3 kristaps 499: if ( ! (p->flags & TERMP_NOSPACE) &&
500: ! (p->flags & TERMP_LITERAL))
501: chara(p, ' ');
502:
1.13 kristaps 503: if ( ! (p->flags & TERMP_NONOSPACE))
504: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 505:
1.16 kristaps 506: /*
507: * XXX - if literal and underlining, this will underline the
508: * spaces between literal words.
509: */
510:
1.3 kristaps 511: if (p->flags & TERMP_BOLD)
512: style(p, STYLE_BOLD);
513: if (p->flags & TERMP_UNDERLINE)
514: style(p, STYLE_UNDERLINE);
515:
516: for (i = 0; i < len; i++) {
517: if ('\\' == word[i]) {
518: pescape(p, word, &i, len);
519: continue;
520: }
521: chara(p, word[i]);
522: }
523:
524: if (p->flags & TERMP_BOLD ||
525: p->flags & TERMP_UNDERLINE)
526: style(p, STYLE_CLEAR);
527: }
528:
529:
530: void
531: word(struct termp *p, const char *word)
532: {
533: size_t i, j, len;
534:
1.16 kristaps 535: /*
536: * Break apart a word into tokens. If we're a literal word,
537: * then don't. This doesn't handle zero-length words (there
538: * should be none) and makes sure that pword doesn't get spaces
539: * or nil words unless literal.
540: */
541:
1.3 kristaps 542: if (p->flags & TERMP_LITERAL) {
543: pword(p, word, strlen(word));
544: return;
545: }
546:
547: len = strlen(word);
548: assert(len > 0);
549:
550: if (mdoc_isdelim(word)) {
551: if ( ! (p->flags & TERMP_IGNDELIM))
552: p->flags |= TERMP_NOSPACE;
553: p->flags &= ~TERMP_IGNDELIM;
554: }
555:
556: /* LINTED */
557: for (j = i = 0; i < len; i++) {
1.7 kristaps 558: if ( ! xisspace(word[i])) {
1.3 kristaps 559: j++;
560: continue;
1.20 kristaps 561: }
562:
563: /* Escaped spaces don't delimit... */
564: if (i > 0 && xisspace(word[i]) && '\\' == word[i - 1]) {
565: j++;
566: continue;
1.1 kristaps 567: }
1.20 kristaps 568:
1.3 kristaps 569: if (0 == j)
570: continue;
571: assert(i >= j);
572: pword(p, &word[i - j], j);
573: j = 0;
574: }
575: if (j > 0) {
576: assert(i >= j);
577: pword(p, &word[i - j], j);
578: }
579: }
580:
581:
582: static void
1.12 kristaps 583: body(struct termp *p, struct termpair *ppair,
584: const struct mdoc_meta *meta,
1.3 kristaps 585: const struct mdoc_node *node)
586: {
587: int dochild;
1.9 kristaps 588: struct termpair pair;
1.3 kristaps 589:
1.16 kristaps 590: /*
591: * This is the main function for printing out nodes. It's
592: * constituted of PRE and POST functions, which correspond to
593: * prefix and infix processing.
594: */
595:
1.3 kristaps 596: /* Pre-processing. */
597:
598: dochild = 1;
1.12 kristaps 599: pair.ppair = ppair;
1.9 kristaps 600: pair.type = 0;
1.11 kristaps 601: pair.offset = pair.rmargin = 0;
1.10 kristaps 602: pair.flag = 0;
1.12 kristaps 603: pair.count = 0;
1.3 kristaps 604:
605: if (MDOC_TEXT != node->type) {
606: if (termacts[node->tok].pre)
1.9 kristaps 607: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 608: dochild = 0;
609: } else /* MDOC_TEXT == node->type */
610: word(p, node->data.text.string);
611:
612: /* Children. */
613:
1.10 kristaps 614: if (TERMPAIR_FLAG & pair.type)
615: p->flags |= pair.flag;
1.9 kristaps 616:
1.3 kristaps 617: if (dochild && node->child)
1.12 kristaps 618: body(p, &pair, meta, node->child);
1.3 kristaps 619:
1.10 kristaps 620: if (TERMPAIR_FLAG & pair.type)
621: p->flags &= ~pair.flag;
1.9 kristaps 622:
1.3 kristaps 623: /* Post-processing. */
624:
625: if (MDOC_TEXT != node->type)
626: if (termacts[node->tok].post)
1.9 kristaps 627: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 628:
629: /* Siblings. */
1.1 kristaps 630:
1.3 kristaps 631: if (node->next)
1.12 kristaps 632: body(p, ppair, meta, node->next);
1.3 kristaps 633: }
634:
635:
636: static void
637: footer(struct termp *p, const struct mdoc_meta *meta)
638: {
639: struct tm *tm;
640: char *buf, *os;
641:
642: if (NULL == (buf = malloc(p->rmargin)))
643: err(1, "malloc");
644: if (NULL == (os = malloc(p->rmargin)))
645: err(1, "malloc");
646:
647: tm = localtime(&meta->date);
648:
1.7 kristaps 649: #ifdef __OpenBSD__
650: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
651: #else
1.3 kristaps 652: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
653: #endif
654: err(1, "strftime");
655:
1.15 kristaps 656: (void)strlcpy(os, meta->os, p->rmargin);
1.3 kristaps 657:
1.16 kristaps 658: /*
659: * This is /slightly/ different from regular groff output
660: * because we don't have page numbers. Print the following:
661: *
662: * OS MDOCDATE
663: */
664:
1.15 kristaps 665: vspace(p);
1.3 kristaps 666:
1.15 kristaps 667: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
668: p->rmargin = p->maxrmargin - strlen(buf);
669: p->offset = 0;
1.3 kristaps 670:
1.15 kristaps 671: word(p, os);
672: flushln(p);
1.3 kristaps 673:
1.15 kristaps 674: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
675: p->offset = p->rmargin;
676: p->rmargin = p->maxrmargin;
677: p->flags &= ~TERMP_NOBREAK;
678:
679: word(p, buf);
680: flushln(p);
1.1 kristaps 681:
1.3 kristaps 682: free(buf);
683: free(os);
1.1 kristaps 684: }
685:
686:
1.3 kristaps 687: static void
688: header(struct termp *p, const struct mdoc_meta *meta)
689: {
1.18 kristaps 690: char *buf, *title, *bufp, *vbuf;
1.13 kristaps 691: const char *pp;
1.18 kristaps 692: struct utsname uts;
693:
694: p->rmargin = p->maxrmargin;
695: p->offset = 0;
1.3 kristaps 696:
697: if (NULL == (buf = malloc(p->rmargin)))
698: err(1, "malloc");
699: if (NULL == (title = malloc(p->rmargin)))
700: err(1, "malloc");
1.18 kristaps 701: if (NULL == (vbuf = malloc(p->rmargin)))
702: err(1, "malloc");
1.3 kristaps 703:
1.18 kristaps 704: if (NULL == (pp = mdoc_vol2a(meta->vol))) {
1.3 kristaps 705: switch (meta->msec) {
706: case (MSEC_1):
707: /* FALLTHROUGH */
708: case (MSEC_6):
709: /* FALLTHROUGH */
710: case (MSEC_7):
711: pp = mdoc_vol2a(VOL_URM);
712: break;
713: case (MSEC_8):
714: pp = mdoc_vol2a(VOL_SMM);
715: break;
716: case (MSEC_2):
717: /* FALLTHROUGH */
718: case (MSEC_3):
719: /* FALLTHROUGH */
720: case (MSEC_4):
721: /* FALLTHROUGH */
722: case (MSEC_5):
723: pp = mdoc_vol2a(VOL_PRM);
724: break;
725: case (MSEC_9):
726: pp = mdoc_vol2a(VOL_KM);
727: break;
728: default:
729: break;
730: }
1.18 kristaps 731: }
732: vbuf[0] = 0;
733:
734: if (pp) {
735: if (-1 == uname(&uts))
736: err(1, "uname");
737: (void)strlcat(vbuf, uts.sysname, p->rmargin);
738: (void)strlcat(vbuf, " ", p->rmargin);
739: } else if (NULL == (pp = mdoc_msec2a(meta->msec)))
740: pp = mdoc_msec2a(MSEC_local);
741:
742: (void)strlcat(vbuf, pp, p->rmargin);
1.3 kristaps 743:
1.16 kristaps 744: /*
745: * The header is strange. It has three components, which are
746: * really two with the first duplicated. It goes like this:
747: *
748: * IDENTIFIER TITLE IDENTIFIER
749: *
750: * The IDENTIFIER is NAME(SECTION), which is the command-name
751: * (if given, or "unknown" if not) followed by the manual page
752: * section. These are given in `Dt'. The TITLE is a free-form
753: * string depending on the manual volume. If not specified, it
754: * switches on the manual section.
755: */
756:
1.13 kristaps 757: if (mdoc_arch2a(meta->arch))
1.16 kristaps 758: (void)snprintf(buf, p->rmargin, "%s (%s)",
1.18 kristaps 759: vbuf, mdoc_arch2a(meta->arch));
1.13 kristaps 760: else
1.18 kristaps 761: (void)strlcpy(buf, vbuf, p->rmargin);
1.13 kristaps 762:
763: pp = mdoc_msec2a(meta->msec);
764:
765: (void)snprintf(title, p->rmargin, "%s(%s)",
766: meta->title, pp ? pp : "");
767:
1.16 kristaps 768: for (bufp = title; *bufp; bufp++)
769: *bufp = toupper(*bufp);
770:
1.13 kristaps 771: p->offset = 0;
772: p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15 kristaps 773: p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3 kristaps 774:
1.13 kristaps 775: word(p, title);
776: flushln(p);
1.3 kristaps 777:
1.15 kristaps 778: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13 kristaps 779: p->offset = p->rmargin;
1.15 kristaps 780: p->rmargin = p->maxrmargin - strlen(title);
1.3 kristaps 781:
1.13 kristaps 782: word(p, buf);
783: flushln(p);
1.3 kristaps 784:
1.13 kristaps 785: p->offset = p->rmargin;
786: p->rmargin = p->maxrmargin;
787: p->flags &= ~TERMP_NOBREAK;
1.15 kristaps 788: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3 kristaps 789:
1.13 kristaps 790: word(p, title);
791: flushln(p);
1.3 kristaps 792:
1.13 kristaps 793: p->rmargin = p->maxrmargin;
794: p->offset = 0;
795: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 796:
797: free(title);
1.18 kristaps 798: free(vbuf);
1.3 kristaps 799: free(buf);
800: }
CVSweb