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