Annotation of mandoc/mdocterm.c, Revision 1.13
1.13 ! kristaps 1: /* $Id: mdocterm.c,v 1.12 2009/02/25 15:12:26 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: */
19: #include <assert.h>
1.3 kristaps 20: #include <ctype.h>
1.1 kristaps 21: #include <err.h>
22: #include <getopt.h>
1.3 kristaps 23: #include <stdio.h>
1.1 kristaps 24: #include <stdlib.h>
1.3 kristaps 25: #include <string.h>
1.1 kristaps 26:
1.7 kristaps 27: #ifndef __OpenBSD__
1.5 kristaps 28: #include <time.h>
29: #endif
30:
1.2 kristaps 31: #include "mmain.h"
1.1 kristaps 32: #include "term.h"
33:
1.7 kristaps 34: #ifdef __NetBSD__
35: #define xisspace(x) isspace((int)(x))
36: #else
37: #define xisspace(x) isspace((x))
38: #endif
39:
1.3 kristaps 40: enum termstyle {
41: STYLE_CLEAR,
42: STYLE_BOLD,
43: STYLE_UNDERLINE
44: };
45:
46: static void body(struct termp *,
1.12 kristaps 47: struct termpair *,
1.3 kristaps 48: const struct mdoc_meta *,
49: const struct mdoc_node *);
50: static void header(struct termp *,
51: const struct mdoc_meta *);
52: static void footer(struct termp *,
53: const struct mdoc_meta *);
54:
55: static void pword(struct termp *, const char *, size_t);
56: static void pescape(struct termp *,
57: const char *, size_t *, size_t);
1.12 kristaps 58: static void nescape(struct termp *,
59: const char *, size_t);
1.3 kristaps 60: static void chara(struct termp *, char);
1.4 kristaps 61: static void stringa(struct termp *, const char *);
1.3 kristaps 62: static void style(struct termp *, enum termstyle);
63:
64: #ifdef __linux__
65: extern size_t strlcat(char *, const char *, size_t);
66: extern size_t strlcpy(char *, const char *, size_t);
67: #endif
68:
69:
1.1 kristaps 70: int
71: main(int argc, char *argv[])
72: {
1.2 kristaps 73: struct mmain *p;
74: const struct mdoc *mdoc;
1.3 kristaps 75: struct termp termp;
1.2 kristaps 76:
77: p = mmain_alloc();
1.1 kristaps 78:
1.3 kristaps 79: if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
1.2 kristaps 80: mmain_exit(p, 1);
1.1 kristaps 81:
1.3 kristaps 82: if (NULL == (mdoc = mmain_mdoc(p)))
83: mmain_exit(p, 1);
84:
85: termp.maxrmargin = 80; /* XXX */
86: termp.rmargin = termp.maxrmargin;
87: termp.maxcols = 1024;
88: termp.offset = termp.col = 0;
89: termp.flags = TERMP_NOSPACE;
90:
91: if (NULL == (termp.buf = malloc(termp.maxcols)))
92: err(1, "malloc");
93:
94: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 95: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 96: footer(&termp, mdoc_meta(mdoc));
97:
98: free(termp.buf);
99:
100: mmain_exit(p, 0);
101: /* NOTREACHED */
102: }
103:
104:
105: void
106: flushln(struct termp *p)
107: {
108: size_t i, j, vsz, vis, maxvis;
109:
110: /*
111: * First, establish the maximum columns of "visible" content.
112: * This is usually the difference between the right-margin and
113: * an indentation, but can be, for tagged lists or columns, a
114: * small set of values.
115: */
116:
117: assert(p->offset < p->rmargin);
118: maxvis = p->rmargin - p->offset;
119: vis = 0;
120:
121: /*
122: * If in the standard case (left-justified), then begin with our
123: * indentation, otherwise (columns, etc.) just start spitting
124: * out text.
125: */
126:
127: if ( ! (p->flags & TERMP_NOLPAD))
128: /* LINTED */
129: for (j = 0; j < p->offset; j++)
130: putchar(' ');
131:
132: /*
133: * If we're literal, print out verbatim.
134: */
135: if (p->flags & TERMP_LITERAL) {
136: /* FIXME: count non-printing chars. */
137: for (i = 0; i < p->col; i++)
138: putchar(p->buf[i]);
139: putchar('\n');
140: p->col = 0;
141: return;
142: }
143:
144: for (i = 0; i < p->col; i++) {
145: /*
146: * Count up visible word characters. Control sequences
147: * (starting with the CSI) aren't counted.
148: */
1.7 kristaps 149: assert( ! xisspace(p->buf[i]));
1.3 kristaps 150:
151: /* LINTED */
152: for (j = i, vsz = 0; j < p->col; j++) {
1.7 kristaps 153: if (xisspace(p->buf[j]))
1.3 kristaps 154: break;
155: else if (27 == p->buf[j]) {
156: assert(j + 4 <= p->col);
157: j += 3;
158: } else
159: vsz++;
160: }
161: assert(vsz > 0);
162:
163: /*
164: * If a word is too long and we're within a line, put it
165: * on the next line. Puke if we're being asked to write
166: * something that will exceed the right margin (i.e.,
167: * from a fresh line or when we're not allowed to break
168: * the line with TERMP_NOBREAK).
169: */
170:
171: if (vis && vis + vsz >= maxvis) {
172: /* FIXME */
173: if (p->flags & TERMP_NOBREAK)
174: errx(1, "word breaks right margin");
175: putchar('\n');
176: for (j = 0; j < p->offset; j++)
177: putchar(' ');
178: vis = 0;
1.6 kristaps 179: } else if (vis + vsz >= maxvis)
1.3 kristaps 180: /* FIXME */
181: errx(1, "word breaks right margin");
182:
183: /*
184: * Write out the word and a trailing space. Omit the
185: * space if we're the last word in the line.
186: */
187:
188: for ( ; i < p->col; i++) {
1.7 kristaps 189: if (xisspace(p->buf[i]))
1.3 kristaps 190: break;
191: putchar(p->buf[i]);
192: }
193: vis += vsz;
194: if (i < p->col) {
195: putchar(' ');
196: vis++;
197: }
198: }
199:
200: /*
201: * If we're not to right-marginalise it (newline), then instead
202: * pad to the right margin and stay off.
203: */
204:
205: if (p->flags & TERMP_NOBREAK) {
1.11 kristaps 206: for ( ; vis < maxvis; vis++)
1.3 kristaps 207: putchar(' ');
208: } else
209: putchar('\n');
210:
211: p->col = 0;
212: }
213:
214:
215: void
216: newln(struct termp *p)
217: {
1.1 kristaps 218:
1.3 kristaps 219: /*
220: * A newline only breaks an existing line; it won't assert
221: * vertical space.
222: */
223: p->flags |= TERMP_NOSPACE;
1.12 kristaps 224: if (0 == p->col) {
225: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 226: return;
1.12 kristaps 227: }
1.3 kristaps 228: flushln(p);
1.11 kristaps 229: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 230: }
231:
232:
233: void
234: vspace(struct termp *p)
235: {
236:
237: /*
238: * Asserts a vertical space (a full, empty line-break between
239: * lines).
240: */
241: newln(p);
242: putchar('\n');
243: }
244:
245:
246: static void
1.4 kristaps 247: stringa(struct termp *p, const char *s)
248: {
249:
250: /* XXX - speed up if not passing to chara. */
251: for ( ; *s; s++)
252: chara(p, *s);
253: }
254:
255:
256: static void
1.3 kristaps 257: chara(struct termp *p, char c)
258: {
259:
260: /* TODO: dynamically expand the buffer. */
261: if (p->col + 1 >= p->maxcols)
262: errx(1, "line overrun");
263: p->buf[(p->col)++] = c;
264: }
265:
266:
267: static void
268: style(struct termp *p, enum termstyle esc)
269: {
270:
271: if (p->col + 4 >= p->maxcols)
272: errx(1, "line overrun");
273:
274: p->buf[(p->col)++] = 27;
275: p->buf[(p->col)++] = '[';
276: switch (esc) {
277: case (STYLE_CLEAR):
278: p->buf[(p->col)++] = '0';
279: break;
280: case (STYLE_BOLD):
281: p->buf[(p->col)++] = '1';
282: break;
283: case (STYLE_UNDERLINE):
284: p->buf[(p->col)++] = '4';
285: break;
286: default:
287: abort();
288: /* NOTREACHED */
289: }
290: p->buf[(p->col)++] = 'm';
291: }
292:
293:
294: static void
1.12 kristaps 295: nescape(struct termp *p, const char *word, size_t len)
296: {
297:
298: switch (len) {
299: case (2):
300: if ('r' == word[0] && 'B' == word[1])
301: chara(p, ']');
302: else if ('l' == word[0] && 'B' == word[1])
303: chara(p, '[');
304: else if ('<' == word[0] && '-' == word[1])
305: stringa(p, "<-");
306: else if ('-' == word[0] && '>' == word[1])
307: stringa(p, "->");
308: else if ('l' == word[0] && 'q' == word[1])
309: chara(p, '\"');
310: else if ('r' == word[0] && 'q' == word[1])
311: chara(p, '\"');
312: else if ('b' == word[0] && 'u' == word[1])
313: chara(p, 'o');
314: break;
315: default:
316: break;
317: }
318: }
319:
320:
321: static void
1.3 kristaps 322: pescape(struct termp *p, const char *word, size_t *i, size_t len)
323: {
1.12 kristaps 324: size_t j;
1.3 kristaps 325:
326: (*i)++;
327: assert(*i < len);
328:
329: if ('(' == word[*i]) {
330: /* Two-character escapes. */
331: (*i)++;
332: assert(*i + 1 < len);
1.12 kristaps 333: nescape(p, &word[*i], 2);
1.3 kristaps 334: (*i)++;
335: return;
336:
337: } else if ('[' != word[*i]) {
338: /* One-character escapes. */
339: switch (word[*i]) {
340: case ('\\'):
341: /* FALLTHROUGH */
342: case ('\''):
343: /* FALLTHROUGH */
344: case ('`'):
345: /* FALLTHROUGH */
346: case ('-'):
347: /* FALLTHROUGH */
1.13 ! kristaps 348: case (' '):
! 349: /* FALLTHROUGH */
1.3 kristaps 350: case ('.'):
351: chara(p, word[*i]);
1.1 kristaps 352: default:
1.3 kristaps 353: break;
354: }
355: return;
356: }
1.12 kristaps 357:
358: (*i)++;
359: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
360: /* Loop... */ ;
361:
362: nescape(p, &word[*i - j], j);
1.3 kristaps 363: }
364:
365:
366: static void
367: pword(struct termp *p, const char *word, size_t len)
368: {
369: size_t i;
370:
371: /*assert(len > 0);*/ /* Can be, if literal. */
372:
373: if ( ! (p->flags & TERMP_NOSPACE) &&
374: ! (p->flags & TERMP_LITERAL))
375: chara(p, ' ');
376:
1.13 ! kristaps 377: if ( ! (p->flags & TERMP_NONOSPACE))
! 378: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 379:
380: if (p->flags & TERMP_BOLD)
381: style(p, STYLE_BOLD);
382: if (p->flags & TERMP_UNDERLINE)
383: style(p, STYLE_UNDERLINE);
384:
385: for (i = 0; i < len; i++) {
386: if ('\\' == word[i]) {
387: pescape(p, word, &i, len);
388: continue;
389: }
390: chara(p, word[i]);
391: }
392:
393: if (p->flags & TERMP_BOLD ||
394: p->flags & TERMP_UNDERLINE)
395: style(p, STYLE_CLEAR);
396: }
397:
398:
399: void
400: word(struct termp *p, const char *word)
401: {
402: size_t i, j, len;
403:
404: if (p->flags & TERMP_LITERAL) {
405: pword(p, word, strlen(word));
406: return;
407: }
408:
409: len = strlen(word);
410: assert(len > 0);
411:
412: if (mdoc_isdelim(word)) {
413: if ( ! (p->flags & TERMP_IGNDELIM))
414: p->flags |= TERMP_NOSPACE;
415: p->flags &= ~TERMP_IGNDELIM;
416: }
417:
418: /* LINTED */
419: for (j = i = 0; i < len; i++) {
1.7 kristaps 420: if ( ! xisspace(word[i])) {
1.3 kristaps 421: j++;
422: continue;
1.1 kristaps 423: }
1.3 kristaps 424: if (0 == j)
425: continue;
426: assert(i >= j);
427: pword(p, &word[i - j], j);
428: j = 0;
429: }
430: if (j > 0) {
431: assert(i >= j);
432: pword(p, &word[i - j], j);
433: }
434: }
435:
436:
437: static void
1.12 kristaps 438: body(struct termp *p, struct termpair *ppair,
439: const struct mdoc_meta *meta,
1.3 kristaps 440: const struct mdoc_node *node)
441: {
442: int dochild;
1.9 kristaps 443: struct termpair pair;
1.3 kristaps 444:
445: /* Pre-processing. */
446:
447: dochild = 1;
1.12 kristaps 448: pair.ppair = ppair;
1.9 kristaps 449: pair.type = 0;
1.11 kristaps 450: pair.offset = pair.rmargin = 0;
1.10 kristaps 451: pair.flag = 0;
1.12 kristaps 452: pair.count = 0;
1.3 kristaps 453:
454: if (MDOC_TEXT != node->type) {
455: if (termacts[node->tok].pre)
1.9 kristaps 456: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 457: dochild = 0;
458: } else /* MDOC_TEXT == node->type */
459: word(p, node->data.text.string);
460:
461: /* Children. */
462:
1.10 kristaps 463: if (TERMPAIR_FLAG & pair.type)
464: p->flags |= pair.flag;
1.9 kristaps 465:
1.3 kristaps 466: if (dochild && node->child)
1.12 kristaps 467: body(p, &pair, meta, node->child);
1.3 kristaps 468:
1.10 kristaps 469: if (TERMPAIR_FLAG & pair.type)
470: p->flags &= ~pair.flag;
1.9 kristaps 471:
1.3 kristaps 472: /* Post-processing. */
473:
474: if (MDOC_TEXT != node->type)
475: if (termacts[node->tok].post)
1.9 kristaps 476: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 477:
478: /* Siblings. */
1.1 kristaps 479:
1.3 kristaps 480: if (node->next)
1.12 kristaps 481: body(p, ppair, meta, node->next);
1.3 kristaps 482: }
483:
484:
485: static void
486: footer(struct termp *p, const struct mdoc_meta *meta)
487: {
488: struct tm *tm;
489: char *buf, *os;
490: size_t sz, osz, ssz, i;
491:
492: if (NULL == (buf = malloc(p->rmargin)))
493: err(1, "malloc");
494: if (NULL == (os = malloc(p->rmargin)))
495: err(1, "malloc");
496:
497: tm = localtime(&meta->date);
498:
1.7 kristaps 499: #ifdef __OpenBSD__
500: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
501: #else
1.3 kristaps 502: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
503: #endif
504: err(1, "strftime");
505:
506: osz = strlcpy(os, meta->os, p->rmargin);
507:
508: sz = strlen(buf);
509: ssz = sz + osz + 1;
510:
511: if (ssz > p->rmargin) {
512: ssz -= p->rmargin;
513: assert(ssz <= osz);
514: os[osz - ssz] = 0;
515: ssz = 1;
516: } else
517: ssz = p->rmargin - ssz + 1;
518:
519: printf("\n");
520: printf("%s", os);
521: for (i = 0; i < ssz; i++)
522: printf(" ");
523:
524: printf("%s\n", buf);
525: fflush(stdout);
1.1 kristaps 526:
1.3 kristaps 527: free(buf);
528: free(os);
1.1 kristaps 529: }
530:
531:
1.3 kristaps 532: static void
533: header(struct termp *p, const struct mdoc_meta *meta)
534: {
535: char *buf, *title;
1.13 ! kristaps 536: const char *pp;
1.3 kristaps 537:
538: if (NULL == (buf = malloc(p->rmargin)))
539: err(1, "malloc");
540: if (NULL == (title = malloc(p->rmargin)))
541: err(1, "malloc");
542:
543: if (NULL == (pp = mdoc_vol2a(meta->vol)))
544: switch (meta->msec) {
545: case (MSEC_1):
546: /* FALLTHROUGH */
547: case (MSEC_6):
548: /* FALLTHROUGH */
549: case (MSEC_7):
550: pp = mdoc_vol2a(VOL_URM);
551: break;
552: case (MSEC_8):
553: pp = mdoc_vol2a(VOL_SMM);
554: break;
555: case (MSEC_2):
556: /* FALLTHROUGH */
557: case (MSEC_3):
558: /* FALLTHROUGH */
559: case (MSEC_4):
560: /* FALLTHROUGH */
561: case (MSEC_5):
562: pp = mdoc_vol2a(VOL_PRM);
563: break;
564: case (MSEC_9):
565: pp = mdoc_vol2a(VOL_KM);
566: break;
567: default:
568: /* FIXME: capitalise. */
569: if (NULL == (pp = mdoc_msec2a(meta->msec)))
570: pp = mdoc_msec2a(MSEC_local);
571: break;
572: }
573:
1.13 ! kristaps 574: if (mdoc_arch2a(meta->arch))
! 575: (void)snprintf(buf, p->rmargin, "%s(%s)",
! 576: pp, mdoc_arch2a(meta->arch));
! 577: else
! 578: (void)strlcpy(buf, pp, p->rmargin);
! 579:
! 580: pp = mdoc_msec2a(meta->msec);
! 581:
! 582: (void)snprintf(title, p->rmargin, "%s(%s)",
! 583: meta->title, pp ? pp : "");
! 584:
! 585: p->offset = 0;
! 586: p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
! 587: p->flags |= TERMP_NOBREAK;
! 588: p->flags |= TERMP_NOSPACE;
1.3 kristaps 589:
1.13 ! kristaps 590: word(p, title);
! 591: flushln(p);
1.3 kristaps 592:
1.13 ! kristaps 593: p->offset = p->rmargin;
! 594: p->rmargin += strlen(buf);
1.3 kristaps 595:
1.13 ! kristaps 596: word(p, buf);
! 597: flushln(p);
1.3 kristaps 598:
1.13 ! kristaps 599: exit(1);
1.3 kristaps 600:
1.13 ! kristaps 601: p->offset = p->rmargin;
! 602: p->rmargin = p->maxrmargin;
! 603: p->flags &= ~TERMP_NOBREAK;
1.3 kristaps 604:
1.13 ! kristaps 605: word(p, title);
! 606: flushln(p);
1.3 kristaps 607:
1.13 ! kristaps 608: p->rmargin = p->maxrmargin;
! 609: p->offset = 0;
! 610: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 611:
612: free(title);
613: free(buf);
614: }
CVSweb