Annotation of mandoc/mdocterm.c, Revision 1.8
1.8 ! kristaps 1: /* $Id: mdocterm.c,v 1.7 2009/02/23 15:34:53 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) {
203: for ( ; vis <= maxvis; vis++)
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);
224: }
225:
226:
227: void
228: vspace(struct termp *p)
229: {
230:
231: /*
232: * Asserts a vertical space (a full, empty line-break between
233: * lines).
234: */
235: newln(p);
236: putchar('\n');
237: }
238:
239:
240: static void
1.4 kristaps 241: stringa(struct termp *p, const char *s)
242: {
243:
244: /* XXX - speed up if not passing to chara. */
245: for ( ; *s; s++)
246: chara(p, *s);
247: }
248:
249:
250: static void
1.3 kristaps 251: chara(struct termp *p, char c)
252: {
253:
254: /* TODO: dynamically expand the buffer. */
255: if (p->col + 1 >= p->maxcols)
256: errx(1, "line overrun");
257: p->buf[(p->col)++] = c;
258: }
259:
260:
261: static void
262: style(struct termp *p, enum termstyle esc)
263: {
264:
265: if (p->col + 4 >= p->maxcols)
266: errx(1, "line overrun");
267:
268: p->buf[(p->col)++] = 27;
269: p->buf[(p->col)++] = '[';
270: switch (esc) {
271: case (STYLE_CLEAR):
272: p->buf[(p->col)++] = '0';
273: break;
274: case (STYLE_BOLD):
275: p->buf[(p->col)++] = '1';
276: break;
277: case (STYLE_UNDERLINE):
278: p->buf[(p->col)++] = '4';
279: break;
280: default:
281: abort();
282: /* NOTREACHED */
283: }
284: p->buf[(p->col)++] = 'm';
285: }
286:
287:
288: static void
289: pescape(struct termp *p, const char *word, size_t *i, size_t len)
290: {
291:
292: (*i)++;
293: assert(*i < len);
294:
295: if ('(' == word[*i]) {
296: /* Two-character escapes. */
297: (*i)++;
298: assert(*i + 1 < len);
299:
300: if ('r' == word[*i] && 'B' == word[*i + 1])
301: chara(p, ']');
302: else if ('l' == word[*i] && 'B' == word[*i + 1])
303: chara(p, '[');
1.4 kristaps 304: else if ('<' == word[*i] && '-' == word[*i + 1])
305: stringa(p, "<-");
306: else if ('-' == word[*i] && '>' == word[*i + 1])
307: stringa(p, "->");
1.8 ! kristaps 308: else if ('l' == word[*i] && 'q' == word[*i + 1])
! 309: chara(p, '\"');
! 310: else if ('r' == word[*i] && 'q' == word[*i + 1])
! 311: chara(p, '\"');
1.3 kristaps 312:
313: (*i)++;
314: return;
315:
316: } else if ('[' != word[*i]) {
317: /* One-character escapes. */
318: switch (word[*i]) {
319: case ('\\'):
320: /* FALLTHROUGH */
321: case ('\''):
322: /* FALLTHROUGH */
323: case ('`'):
324: /* FALLTHROUGH */
325: case ('-'):
326: /* FALLTHROUGH */
327: case ('.'):
328: chara(p, word[*i]);
1.1 kristaps 329: default:
1.3 kristaps 330: break;
331: }
332: return;
333: }
334: /* n-character escapes. */
335: }
336:
337:
338: static void
339: pword(struct termp *p, const char *word, size_t len)
340: {
341: size_t i;
342:
343: /*assert(len > 0);*/ /* Can be, if literal. */
344:
345: if ( ! (p->flags & TERMP_NOSPACE) &&
346: ! (p->flags & TERMP_LITERAL))
347: chara(p, ' ');
348:
349: p->flags &= ~TERMP_NOSPACE;
350:
351: if (p->flags & TERMP_BOLD)
352: style(p, STYLE_BOLD);
353: if (p->flags & TERMP_UNDERLINE)
354: style(p, STYLE_UNDERLINE);
355:
356: for (i = 0; i < len; i++) {
357: if ('\\' == word[i]) {
358: pescape(p, word, &i, len);
359: continue;
360: }
361: chara(p, word[i]);
362: }
363:
364: if (p->flags & TERMP_BOLD ||
365: p->flags & TERMP_UNDERLINE)
366: style(p, STYLE_CLEAR);
367: }
368:
369:
370: void
371: word(struct termp *p, const char *word)
372: {
373: size_t i, j, len;
374:
375: if (p->flags & TERMP_LITERAL) {
376: pword(p, word, strlen(word));
377: return;
378: }
379:
380: len = strlen(word);
381: assert(len > 0);
382:
383: if (mdoc_isdelim(word)) {
384: if ( ! (p->flags & TERMP_IGNDELIM))
385: p->flags |= TERMP_NOSPACE;
386: p->flags &= ~TERMP_IGNDELIM;
387: }
388:
389: /* LINTED */
390: for (j = i = 0; i < len; i++) {
1.7 kristaps 391: if ( ! xisspace(word[i])) {
1.3 kristaps 392: j++;
393: continue;
1.1 kristaps 394: }
1.3 kristaps 395: if (0 == j)
396: continue;
397: assert(i >= j);
398: pword(p, &word[i - j], j);
399: j = 0;
400: }
401: if (j > 0) {
402: assert(i >= j);
403: pword(p, &word[i - j], j);
404: }
405: }
406:
407:
408: static void
409: body(struct termp *p, const struct mdoc_meta *meta,
410: const struct mdoc_node *node)
411: {
412: int dochild;
413:
414: /* Pre-processing. */
415:
416: dochild = 1;
417:
418: if (MDOC_TEXT != node->type) {
419: if (termacts[node->tok].pre)
420: if ( ! (*termacts[node->tok].pre)(p, meta, node))
421: dochild = 0;
422: } else /* MDOC_TEXT == node->type */
423: word(p, node->data.text.string);
424:
425: /* Children. */
426:
427: if (dochild && node->child)
428: body(p, meta, node->child);
429:
430: /* Post-processing. */
431:
432: if (MDOC_TEXT != node->type)
433: if (termacts[node->tok].post)
434: (*termacts[node->tok].post)(p, meta, node);
435:
436: /* Siblings. */
1.1 kristaps 437:
1.3 kristaps 438: if (node->next)
439: body(p, meta, node->next);
440: }
441:
442:
443: static void
444: footer(struct termp *p, const struct mdoc_meta *meta)
445: {
446: struct tm *tm;
447: char *buf, *os;
448: size_t sz, osz, ssz, i;
449:
450: if (NULL == (buf = malloc(p->rmargin)))
451: err(1, "malloc");
452: if (NULL == (os = malloc(p->rmargin)))
453: err(1, "malloc");
454:
455: tm = localtime(&meta->date);
456:
1.7 kristaps 457: #ifdef __OpenBSD__
458: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
459: #else
1.3 kristaps 460: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
461: #endif
462: err(1, "strftime");
463:
464: osz = strlcpy(os, meta->os, p->rmargin);
465:
466: sz = strlen(buf);
467: ssz = sz + osz + 1;
468:
469: if (ssz > p->rmargin) {
470: ssz -= p->rmargin;
471: assert(ssz <= osz);
472: os[osz - ssz] = 0;
473: ssz = 1;
474: } else
475: ssz = p->rmargin - ssz + 1;
476:
477: printf("\n");
478: printf("%s", os);
479: for (i = 0; i < ssz; i++)
480: printf(" ");
481:
482: printf("%s\n", buf);
483: fflush(stdout);
1.1 kristaps 484:
1.3 kristaps 485: free(buf);
486: free(os);
1.1 kristaps 487: }
488:
489:
1.3 kristaps 490: static void
491: header(struct termp *p, const struct mdoc_meta *meta)
492: {
493: char *buf, *title;
494: const char *pp, *msec;
495: size_t ssz, tsz, ttsz, i;;
496:
497: if (NULL == (buf = malloc(p->rmargin)))
498: err(1, "malloc");
499: if (NULL == (title = malloc(p->rmargin)))
500: err(1, "malloc");
501:
502: if (NULL == (pp = mdoc_vol2a(meta->vol)))
503: switch (meta->msec) {
504: case (MSEC_1):
505: /* FALLTHROUGH */
506: case (MSEC_6):
507: /* FALLTHROUGH */
508: case (MSEC_7):
509: pp = mdoc_vol2a(VOL_URM);
510: break;
511: case (MSEC_8):
512: pp = mdoc_vol2a(VOL_SMM);
513: break;
514: case (MSEC_2):
515: /* FALLTHROUGH */
516: case (MSEC_3):
517: /* FALLTHROUGH */
518: case (MSEC_4):
519: /* FALLTHROUGH */
520: case (MSEC_5):
521: pp = mdoc_vol2a(VOL_PRM);
522: break;
523: case (MSEC_9):
524: pp = mdoc_vol2a(VOL_KM);
525: break;
526: default:
527: /* FIXME: capitalise. */
528: if (NULL == (pp = mdoc_msec2a(meta->msec)))
529: pp = mdoc_msec2a(MSEC_local);
530: break;
531: }
532: assert(pp);
533:
534: tsz = strlcpy(buf, pp, p->rmargin);
535: assert(tsz < p->rmargin);
536:
537: if ((pp = mdoc_arch2a(meta->arch))) {
538: tsz = strlcat(buf, " (", p->rmargin);
539: assert(tsz < p->rmargin);
540: tsz = strlcat(buf, pp, p->rmargin);
541: assert(tsz < p->rmargin);
542: tsz = strlcat(buf, ")", p->rmargin);
543: assert(tsz < p->rmargin);
544: }
545:
546: ttsz = strlcpy(title, meta->title, p->rmargin);
547:
548: if (NULL == (msec = mdoc_msec2a(meta->msec)))
549: msec = "";
550:
551: ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2;
552:
553: if (ssz > p->rmargin) {
554: if ((ssz -= p->rmargin) % 2)
555: ssz++;
556: ssz /= 2;
557:
558: assert(ssz <= ttsz);
559: title[ttsz - ssz] = 0;
560: ssz = 1;
561: } else
562: ssz = ((p->rmargin - ssz) / 2) + 1;
563:
564: printf("%s(%s)", title, msec);
565:
566: for (i = 0; i < ssz; i++)
567: printf(" ");
568:
569: printf("%s", buf);
570:
571: for (i = 0; i < ssz; i++)
572: printf(" ");
573:
574: printf("%s(%s)\n", title, msec);
575: fflush(stdout);
576:
577: free(title);
578: free(buf);
579: }
CVSweb