Annotation of mandoc/term.c, Revision 1.9
1.9 ! kristaps 1: /* $Id: term.c,v 1.8 2009/02/22 15:50:45 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.9 ! kristaps 322: if (mdoc_isdelim(word)) {
! 323: if ( ! (p->flags & TERMP_IGNDELIM))
! 324: p->flags |= TERMP_NOSPACE;
! 325: p->flags &= ~TERMP_IGNDELIM;
! 326: }
1.8 kristaps 327:
1.3 kristaps 328: /* LINTED */
1.2 kristaps 329: for (j = i = 0; i < len; i++) {
330: if ( ! isspace(word[i])) {
331: j++;
332: continue;
333: }
334: if (0 == j)
335: continue;
336: assert(i >= j);
337: pword(p, &word[i - j], j);
338: j = 0;
339: }
340: if (j > 0) {
341: assert(i >= j);
342: pword(p, &word[i - j], j);
343: }
344: }
345:
346:
1.1 kristaps 347: static void
1.2 kristaps 348: termprint_r(struct termp *p, const struct mdoc_meta *meta,
349: const struct mdoc_node *node)
350: {
1.4 kristaps 351: int dochild;
1.2 kristaps 352:
1.5 kristaps 353: /* Pre-processing. */
1.2 kristaps 354:
1.4 kristaps 355: dochild = 1;
356:
1.2 kristaps 357: if (MDOC_TEXT != node->type) {
358: if (termacts[node->tok].pre)
359: if ( ! (*termacts[node->tok].pre)(p, meta, node))
1.4 kristaps 360: dochild = 0;
1.2 kristaps 361: } else /* MDOC_TEXT == node->type */
362: word(p, node->data.text.string);
363:
1.5 kristaps 364: /* Children. */
1.2 kristaps 365:
1.5 kristaps 366: if (dochild && node->child)
1.2 kristaps 367: termprint_r(p, meta, node->child);
368:
1.5 kristaps 369: /* Post-processing. */
1.2 kristaps 370:
1.6 kristaps 371: if (MDOC_TEXT != node->type)
1.2 kristaps 372: if (termacts[node->tok].post)
1.6 kristaps 373: (*termacts[node->tok].post)(p, meta, node);
1.2 kristaps 374:
1.5 kristaps 375: /* Siblings. */
1.2 kristaps 376:
377: if (node->next)
378: termprint_r(p, meta, node->next);
379: }
380:
381:
382: static void
383: termprint_footer(struct termp *p, const struct mdoc_meta *meta)
1.1 kristaps 384: {
385: struct tm *tm;
386: char *buf, *os;
387: size_t sz, osz, ssz, i;
388:
1.3 kristaps 389: if (NULL == (buf = malloc(p->rmargin)))
1.1 kristaps 390: err(1, "malloc");
1.3 kristaps 391: if (NULL == (os = malloc(p->rmargin)))
1.1 kristaps 392: err(1, "malloc");
393:
394: tm = localtime(&meta->date);
1.7 kristaps 395:
396: #ifdef __linux__
397: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
398: #else
1.3 kristaps 399: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
1.7 kristaps 400: #endif
1.1 kristaps 401: err(1, "strftime");
402:
1.3 kristaps 403: osz = strlcpy(os, meta->os, p->rmargin);
1.1 kristaps 404:
405: sz = strlen(buf);
406: ssz = sz + osz + 1;
407:
1.3 kristaps 408: if (ssz > p->rmargin) {
409: ssz -= p->rmargin;
1.1 kristaps 410: assert(ssz <= osz);
411: os[osz - ssz] = 0;
412: ssz = 1;
413: } else
1.3 kristaps 414: ssz = p->rmargin - ssz + 1;
1.1 kristaps 415:
1.2 kristaps 416: printf("\n");
1.1 kristaps 417: printf("%s", os);
418: for (i = 0; i < ssz; i++)
419: printf(" ");
420:
421: printf("%s\n", buf);
1.2 kristaps 422: fflush(stdout);
1.1 kristaps 423:
424: free(buf);
425: free(os);
426: }
427:
428:
429: static void
1.2 kristaps 430: termprint_header(struct termp *p, const struct mdoc_meta *meta)
1.1 kristaps 431: {
1.6 kristaps 432: char *buf, *title;
433: const char *pp, *msec;
1.2 kristaps 434: size_t ssz, tsz, ttsz, i;;
1.1 kristaps 435:
1.3 kristaps 436: if (NULL == (buf = malloc(p->rmargin)))
1.1 kristaps 437: err(1, "malloc");
1.3 kristaps 438: if (NULL == (title = malloc(p->rmargin)))
1.1 kristaps 439: err(1, "malloc");
440:
1.2 kristaps 441: if (NULL == (pp = mdoc_vol2a(meta->vol)))
442: switch (meta->msec) {
443: case (MSEC_1):
444: /* FALLTHROUGH */
445: case (MSEC_6):
446: /* FALLTHROUGH */
447: case (MSEC_7):
448: pp = mdoc_vol2a(VOL_URM);
449: break;
450: case (MSEC_8):
451: pp = mdoc_vol2a(VOL_SMM);
452: break;
453: case (MSEC_2):
454: /* FALLTHROUGH */
455: case (MSEC_3):
456: /* FALLTHROUGH */
457: case (MSEC_4):
458: /* FALLTHROUGH */
459: case (MSEC_5):
460: pp = mdoc_vol2a(VOL_PRM);
461: break;
462: case (MSEC_9):
463: pp = mdoc_vol2a(VOL_KM);
464: break;
465: default:
466: /* FIXME: capitalise. */
467: if (NULL == (pp = mdoc_msec2a(meta->msec)))
468: pp = mdoc_msec2a(MSEC_local);
469: break;
470: }
471: assert(pp);
1.1 kristaps 472:
1.3 kristaps 473: tsz = strlcpy(buf, pp, p->rmargin);
474: assert(tsz < p->rmargin);
1.1 kristaps 475:
1.2 kristaps 476: if ((pp = mdoc_arch2a(meta->arch))) {
1.3 kristaps 477: tsz = strlcat(buf, " (", p->rmargin);
478: assert(tsz < p->rmargin);
479: tsz = strlcat(buf, pp, p->rmargin);
480: assert(tsz < p->rmargin);
481: tsz = strlcat(buf, ")", p->rmargin);
482: assert(tsz < p->rmargin);
1.2 kristaps 483: }
484:
1.3 kristaps 485: ttsz = strlcpy(title, meta->title, p->rmargin);
1.2 kristaps 486:
487: if (NULL == (msec = mdoc_msec2a(meta->msec)))
1.1 kristaps 488: msec = "";
489:
490: ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2;
491:
1.3 kristaps 492: if (ssz > p->rmargin) {
493: if ((ssz -= p->rmargin) % 2)
1.1 kristaps 494: ssz++;
495: ssz /= 2;
496:
497: assert(ssz <= ttsz);
498: title[ttsz - ssz] = 0;
499: ssz = 1;
500: } else
1.3 kristaps 501: ssz = ((p->rmargin - ssz) / 2) + 1;
1.1 kristaps 502:
503: printf("%s(%s)", title, msec);
504:
505: for (i = 0; i < ssz; i++)
506: printf(" ");
507:
508: printf("%s", buf);
509:
510: for (i = 0; i < ssz; i++)
511: printf(" ");
512:
1.2 kristaps 513: printf("%s(%s)\n", title, msec);
514: fflush(stdout);
1.1 kristaps 515:
516: free(title);
517: free(buf);
518: }
519:
520:
1.6 kristaps 521: void
1.1 kristaps 522: termprint(const struct mdoc_node *node,
523: const struct mdoc_meta *meta)
524: {
1.2 kristaps 525: struct termp p;
1.1 kristaps 526:
1.5 kristaps 527: p.maxrmargin = 80; /* XXX */
1.3 kristaps 528: p.rmargin = p.maxrmargin;
1.2 kristaps 529: p.maxcols = 1024;
1.3 kristaps 530: p.offset = p.col = 0;
1.2 kristaps 531: p.flags = TERMP_NOSPACE;
532:
533: if (NULL == (p.buf = malloc(p.maxcols)))
534: err(1, "malloc");
535:
536: termprint_header(&p, meta);
537: termprint_r(&p, meta, node);
538: termprint_footer(&p, meta);
539:
540: free(p.buf);
1.1 kristaps 541: }
542:
543:
CVSweb