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