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