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