Annotation of mandoc/mdocterm.c, Revision 1.7
1.7 ! kristaps 1: /* $Id: mdocterm.c,v 1.6 2009/02/23 15:19: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 *,
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.3 kristaps 308:
309: (*i)++;
310: return;
311:
312: } else if ('[' != word[*i]) {
313: /* One-character escapes. */
314: switch (word[*i]) {
315: case ('\\'):
316: /* FALLTHROUGH */
317: case ('\''):
318: /* FALLTHROUGH */
319: case ('`'):
320: /* FALLTHROUGH */
321: case ('-'):
322: /* FALLTHROUGH */
323: case ('.'):
324: chara(p, word[*i]);
1.1 kristaps 325: default:
1.3 kristaps 326: break;
327: }
328: return;
329: }
330: /* n-character escapes. */
331: }
332:
333:
334: static void
335: pword(struct termp *p, const char *word, size_t len)
336: {
337: size_t i;
338:
339: /*assert(len > 0);*/ /* Can be, if literal. */
340:
341: if ( ! (p->flags & TERMP_NOSPACE) &&
342: ! (p->flags & TERMP_LITERAL))
343: chara(p, ' ');
344:
345: p->flags &= ~TERMP_NOSPACE;
346:
347: if (p->flags & TERMP_BOLD)
348: style(p, STYLE_BOLD);
349: if (p->flags & TERMP_UNDERLINE)
350: style(p, STYLE_UNDERLINE);
351:
352: for (i = 0; i < len; i++) {
353: if ('\\' == word[i]) {
354: pescape(p, word, &i, len);
355: continue;
356: }
357: chara(p, word[i]);
358: }
359:
360: if (p->flags & TERMP_BOLD ||
361: p->flags & TERMP_UNDERLINE)
362: style(p, STYLE_CLEAR);
363: }
364:
365:
366: void
367: word(struct termp *p, const char *word)
368: {
369: size_t i, j, len;
370:
371: if (p->flags & TERMP_LITERAL) {
372: pword(p, word, strlen(word));
373: return;
374: }
375:
376: len = strlen(word);
377: assert(len > 0);
378:
379: if (mdoc_isdelim(word)) {
380: if ( ! (p->flags & TERMP_IGNDELIM))
381: p->flags |= TERMP_NOSPACE;
382: p->flags &= ~TERMP_IGNDELIM;
383: }
384:
385: /* LINTED */
386: for (j = i = 0; i < len; i++) {
1.7 ! kristaps 387: if ( ! xisspace(word[i])) {
1.3 kristaps 388: j++;
389: continue;
1.1 kristaps 390: }
1.3 kristaps 391: if (0 == j)
392: continue;
393: assert(i >= j);
394: pword(p, &word[i - j], j);
395: j = 0;
396: }
397: if (j > 0) {
398: assert(i >= j);
399: pword(p, &word[i - j], j);
400: }
401: }
402:
403:
404: static void
405: body(struct termp *p, const struct mdoc_meta *meta,
406: const struct mdoc_node *node)
407: {
408: int dochild;
409:
410: /* Pre-processing. */
411:
412: dochild = 1;
413:
414: if (MDOC_TEXT != node->type) {
415: if (termacts[node->tok].pre)
416: if ( ! (*termacts[node->tok].pre)(p, meta, node))
417: dochild = 0;
418: } else /* MDOC_TEXT == node->type */
419: word(p, node->data.text.string);
420:
421: /* Children. */
422:
423: if (dochild && node->child)
424: body(p, meta, node->child);
425:
426: /* Post-processing. */
427:
428: if (MDOC_TEXT != node->type)
429: if (termacts[node->tok].post)
430: (*termacts[node->tok].post)(p, meta, node);
431:
432: /* Siblings. */
1.1 kristaps 433:
1.3 kristaps 434: if (node->next)
435: body(p, meta, node->next);
436: }
437:
438:
439: static void
440: footer(struct termp *p, const struct mdoc_meta *meta)
441: {
442: struct tm *tm;
443: char *buf, *os;
444: size_t sz, osz, ssz, i;
445:
446: if (NULL == (buf = malloc(p->rmargin)))
447: err(1, "malloc");
448: if (NULL == (os = malloc(p->rmargin)))
449: err(1, "malloc");
450:
451: tm = localtime(&meta->date);
452:
1.7 ! kristaps 453: #ifdef __OpenBSD__
! 454: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
! 455: #else
1.3 kristaps 456: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
457: #endif
458: err(1, "strftime");
459:
460: osz = strlcpy(os, meta->os, p->rmargin);
461:
462: sz = strlen(buf);
463: ssz = sz + osz + 1;
464:
465: if (ssz > p->rmargin) {
466: ssz -= p->rmargin;
467: assert(ssz <= osz);
468: os[osz - ssz] = 0;
469: ssz = 1;
470: } else
471: ssz = p->rmargin - ssz + 1;
472:
473: printf("\n");
474: printf("%s", os);
475: for (i = 0; i < ssz; i++)
476: printf(" ");
477:
478: printf("%s\n", buf);
479: fflush(stdout);
1.1 kristaps 480:
1.3 kristaps 481: free(buf);
482: free(os);
1.1 kristaps 483: }
484:
485:
1.3 kristaps 486: static void
487: header(struct termp *p, const struct mdoc_meta *meta)
488: {
489: char *buf, *title;
490: const char *pp, *msec;
491: size_t ssz, tsz, ttsz, i;;
492:
493: if (NULL == (buf = malloc(p->rmargin)))
494: err(1, "malloc");
495: if (NULL == (title = malloc(p->rmargin)))
496: err(1, "malloc");
497:
498: if (NULL == (pp = mdoc_vol2a(meta->vol)))
499: switch (meta->msec) {
500: case (MSEC_1):
501: /* FALLTHROUGH */
502: case (MSEC_6):
503: /* FALLTHROUGH */
504: case (MSEC_7):
505: pp = mdoc_vol2a(VOL_URM);
506: break;
507: case (MSEC_8):
508: pp = mdoc_vol2a(VOL_SMM);
509: break;
510: case (MSEC_2):
511: /* FALLTHROUGH */
512: case (MSEC_3):
513: /* FALLTHROUGH */
514: case (MSEC_4):
515: /* FALLTHROUGH */
516: case (MSEC_5):
517: pp = mdoc_vol2a(VOL_PRM);
518: break;
519: case (MSEC_9):
520: pp = mdoc_vol2a(VOL_KM);
521: break;
522: default:
523: /* FIXME: capitalise. */
524: if (NULL == (pp = mdoc_msec2a(meta->msec)))
525: pp = mdoc_msec2a(MSEC_local);
526: break;
527: }
528: assert(pp);
529:
530: tsz = strlcpy(buf, pp, p->rmargin);
531: assert(tsz < p->rmargin);
532:
533: if ((pp = mdoc_arch2a(meta->arch))) {
534: tsz = strlcat(buf, " (", p->rmargin);
535: assert(tsz < p->rmargin);
536: tsz = strlcat(buf, pp, p->rmargin);
537: assert(tsz < p->rmargin);
538: tsz = strlcat(buf, ")", p->rmargin);
539: assert(tsz < p->rmargin);
540: }
541:
542: ttsz = strlcpy(title, meta->title, p->rmargin);
543:
544: if (NULL == (msec = mdoc_msec2a(meta->msec)))
545: msec = "";
546:
547: ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2;
548:
549: if (ssz > p->rmargin) {
550: if ((ssz -= p->rmargin) % 2)
551: ssz++;
552: ssz /= 2;
553:
554: assert(ssz <= ttsz);
555: title[ttsz - ssz] = 0;
556: ssz = 1;
557: } else
558: ssz = ((p->rmargin - ssz) / 2) + 1;
559:
560: printf("%s(%s)", title, msec);
561:
562: for (i = 0; i < ssz; i++)
563: printf(" ");
564:
565: printf("%s", buf);
566:
567: for (i = 0; i < ssz; i++)
568: printf(" ");
569:
570: printf("%s(%s)\n", title, msec);
571: fflush(stdout);
572:
573: free(title);
574: free(buf);
575: }
CVSweb