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