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