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