Annotation of mandoc/mdocterm.c, Revision 1.12
1.12 ! kristaps 1: /* $Id: mdocterm.c,v 1.11 2009/02/25 13:30:53 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 */
348: case ('.'):
349: chara(p, word[*i]);
1.1 kristaps 350: default:
1.3 kristaps 351: break;
352: }
353: return;
354: }
1.12 ! kristaps 355:
! 356: (*i)++;
! 357: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
! 358: /* Loop... */ ;
! 359:
! 360: nescape(p, &word[*i - j], j);
1.3 kristaps 361: }
362:
363:
364: static void
365: pword(struct termp *p, const char *word, size_t len)
366: {
367: size_t i;
368:
369: /*assert(len > 0);*/ /* Can be, if literal. */
370:
371: if ( ! (p->flags & TERMP_NOSPACE) &&
372: ! (p->flags & TERMP_LITERAL))
373: chara(p, ' ');
374:
375: p->flags &= ~TERMP_NOSPACE;
376:
377: if (p->flags & TERMP_BOLD)
378: style(p, STYLE_BOLD);
379: if (p->flags & TERMP_UNDERLINE)
380: style(p, STYLE_UNDERLINE);
381:
382: for (i = 0; i < len; i++) {
383: if ('\\' == word[i]) {
384: pescape(p, word, &i, len);
385: continue;
386: }
387: chara(p, word[i]);
388: }
389:
390: if (p->flags & TERMP_BOLD ||
391: p->flags & TERMP_UNDERLINE)
392: style(p, STYLE_CLEAR);
393: }
394:
395:
396: void
397: word(struct termp *p, const char *word)
398: {
399: size_t i, j, len;
400:
401: if (p->flags & TERMP_LITERAL) {
402: pword(p, word, strlen(word));
403: return;
404: }
405:
406: len = strlen(word);
407: assert(len > 0);
408:
409: if (mdoc_isdelim(word)) {
410: if ( ! (p->flags & TERMP_IGNDELIM))
411: p->flags |= TERMP_NOSPACE;
412: p->flags &= ~TERMP_IGNDELIM;
413: }
414:
415: /* LINTED */
416: for (j = i = 0; i < len; i++) {
1.7 kristaps 417: if ( ! xisspace(word[i])) {
1.3 kristaps 418: j++;
419: continue;
1.1 kristaps 420: }
1.3 kristaps 421: if (0 == j)
422: continue;
423: assert(i >= j);
424: pword(p, &word[i - j], j);
425: j = 0;
426: }
427: if (j > 0) {
428: assert(i >= j);
429: pword(p, &word[i - j], j);
430: }
431: }
432:
433:
434: static void
1.12 ! kristaps 435: body(struct termp *p, struct termpair *ppair,
! 436: const struct mdoc_meta *meta,
1.3 kristaps 437: const struct mdoc_node *node)
438: {
439: int dochild;
1.9 kristaps 440: struct termpair pair;
1.3 kristaps 441:
442: /* Pre-processing. */
443:
444: dochild = 1;
1.12 ! kristaps 445: pair.ppair = ppair;
1.9 kristaps 446: pair.type = 0;
1.11 kristaps 447: pair.offset = pair.rmargin = 0;
1.10 kristaps 448: pair.flag = 0;
1.12 ! kristaps 449: pair.count = 0;
1.3 kristaps 450:
451: if (MDOC_TEXT != node->type) {
452: if (termacts[node->tok].pre)
1.9 kristaps 453: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 454: dochild = 0;
455: } else /* MDOC_TEXT == node->type */
456: word(p, node->data.text.string);
457:
458: /* Children. */
459:
1.10 kristaps 460: if (TERMPAIR_FLAG & pair.type)
461: p->flags |= pair.flag;
1.9 kristaps 462:
1.3 kristaps 463: if (dochild && node->child)
1.12 ! kristaps 464: body(p, &pair, meta, node->child);
1.3 kristaps 465:
1.10 kristaps 466: if (TERMPAIR_FLAG & pair.type)
467: p->flags &= ~pair.flag;
1.9 kristaps 468:
1.3 kristaps 469: /* Post-processing. */
470:
471: if (MDOC_TEXT != node->type)
472: if (termacts[node->tok].post)
1.9 kristaps 473: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 474:
475: /* Siblings. */
1.1 kristaps 476:
1.3 kristaps 477: if (node->next)
1.12 ! kristaps 478: body(p, ppair, meta, node->next);
1.3 kristaps 479: }
480:
481:
482: static void
483: footer(struct termp *p, const struct mdoc_meta *meta)
484: {
485: struct tm *tm;
486: char *buf, *os;
487: size_t sz, osz, ssz, i;
488:
489: if (NULL == (buf = malloc(p->rmargin)))
490: err(1, "malloc");
491: if (NULL == (os = malloc(p->rmargin)))
492: err(1, "malloc");
493:
494: tm = localtime(&meta->date);
495:
1.7 kristaps 496: #ifdef __OpenBSD__
497: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
498: #else
1.3 kristaps 499: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
500: #endif
501: err(1, "strftime");
502:
503: osz = strlcpy(os, meta->os, p->rmargin);
504:
505: sz = strlen(buf);
506: ssz = sz + osz + 1;
507:
508: if (ssz > p->rmargin) {
509: ssz -= p->rmargin;
510: assert(ssz <= osz);
511: os[osz - ssz] = 0;
512: ssz = 1;
513: } else
514: ssz = p->rmargin - ssz + 1;
515:
516: printf("\n");
517: printf("%s", os);
518: for (i = 0; i < ssz; i++)
519: printf(" ");
520:
521: printf("%s\n", buf);
522: fflush(stdout);
1.1 kristaps 523:
1.3 kristaps 524: free(buf);
525: free(os);
1.1 kristaps 526: }
527:
528:
1.3 kristaps 529: static void
530: header(struct termp *p, const struct mdoc_meta *meta)
531: {
532: char *buf, *title;
533: const char *pp, *msec;
534: size_t ssz, tsz, ttsz, i;;
535:
536: if (NULL == (buf = malloc(p->rmargin)))
537: err(1, "malloc");
538: if (NULL == (title = malloc(p->rmargin)))
539: err(1, "malloc");
540:
541: if (NULL == (pp = mdoc_vol2a(meta->vol)))
542: switch (meta->msec) {
543: case (MSEC_1):
544: /* FALLTHROUGH */
545: case (MSEC_6):
546: /* FALLTHROUGH */
547: case (MSEC_7):
548: pp = mdoc_vol2a(VOL_URM);
549: break;
550: case (MSEC_8):
551: pp = mdoc_vol2a(VOL_SMM);
552: break;
553: case (MSEC_2):
554: /* FALLTHROUGH */
555: case (MSEC_3):
556: /* FALLTHROUGH */
557: case (MSEC_4):
558: /* FALLTHROUGH */
559: case (MSEC_5):
560: pp = mdoc_vol2a(VOL_PRM);
561: break;
562: case (MSEC_9):
563: pp = mdoc_vol2a(VOL_KM);
564: break;
565: default:
566: /* FIXME: capitalise. */
567: if (NULL == (pp = mdoc_msec2a(meta->msec)))
568: pp = mdoc_msec2a(MSEC_local);
569: break;
570: }
571: assert(pp);
572:
573: tsz = strlcpy(buf, pp, p->rmargin);
574: assert(tsz < p->rmargin);
575:
576: if ((pp = mdoc_arch2a(meta->arch))) {
577: tsz = strlcat(buf, " (", p->rmargin);
578: assert(tsz < p->rmargin);
579: tsz = strlcat(buf, pp, p->rmargin);
580: assert(tsz < p->rmargin);
581: tsz = strlcat(buf, ")", p->rmargin);
582: assert(tsz < p->rmargin);
583: }
584:
585: ttsz = strlcpy(title, meta->title, p->rmargin);
586:
587: if (NULL == (msec = mdoc_msec2a(meta->msec)))
588: msec = "";
589:
590: ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2;
591:
592: if (ssz > p->rmargin) {
593: if ((ssz -= p->rmargin) % 2)
594: ssz++;
595: ssz /= 2;
596:
597: assert(ssz <= ttsz);
598: title[ttsz - ssz] = 0;
599: ssz = 1;
600: } else
601: ssz = ((p->rmargin - ssz) / 2) + 1;
602:
603: printf("%s(%s)", title, msec);
604:
605: for (i = 0; i < ssz; i++)
606: printf(" ");
607:
608: printf("%s", buf);
609:
610: for (i = 0; i < ssz; i++)
611: printf(" ");
612:
613: printf("%s(%s)\n", title, msec);
614: fflush(stdout);
615:
616: free(title);
617: free(buf);
618: }
CVSweb