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