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