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