Annotation of pod2mdoc/pod2mdoc.c, Revision 1.1
1.1 ! schwarze 1: /* $Id: pod2mdoc.c,v 1.7 2014/03/20 00:55:35 kristaps Exp $ */
! 2: /*
! 3: * Copyright (c) 2014 Kristaps Dzonsons <kristaps@bsd.lv>
! 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 above
! 7: * copyright notice and this permission notice appear in all copies.
! 8: *
! 9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
! 10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
! 12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
! 16: */
! 17: #include <sys/stat.h>
! 18: #include <sys/time.h>
! 19:
! 20: #include <assert.h>
! 21: #include <ctype.h>
! 22: #include <fcntl.h>
! 23: #include <getopt.h>
! 24: #include <stdio.h>
! 25: #include <stdlib.h>
! 26: #include <string.h>
! 27: #include <unistd.h>
! 28:
! 29: struct args {
! 30: const char *title; /* override "Dt" title */
! 31: const char *date; /* override "Dd" date */
! 32: const char *section; /* override "Dt" section */
! 33: };
! 34:
! 35: struct state {
! 36: int parsing; /* after =cut of before command */
! 37: int paused; /* in =begin and before =end */
! 38: int haspar; /* in paragraph: do we need Pp? */
! 39: int isname; /* are we the NAME section? */
! 40: const char *fname; /* file being parsed */
! 41: };
! 42:
! 43: enum fmt {
! 44: FMT_ITALIC,
! 45: FMT_BOLD,
! 46: FMT_CODE,
! 47: FMT_LINK,
! 48: FMT_ESCAPE,
! 49: FMT_FILE,
! 50: FMT_NBSP,
! 51: FMT_INDEX,
! 52: FMT_NULL,
! 53: FMT__MAX
! 54: };
! 55:
! 56: enum cmd {
! 57: CMD_POD = 0,
! 58: CMD_HEAD1,
! 59: CMD_HEAD2,
! 60: CMD_HEAD3,
! 61: CMD_HEAD4,
! 62: CMD_OVER,
! 63: CMD_ITEM,
! 64: CMD_BACK,
! 65: CMD_BEGIN,
! 66: CMD_END,
! 67: CMD_FOR,
! 68: CMD_ENCODING,
! 69: CMD_CUT,
! 70: CMD__MAX
! 71: };
! 72:
! 73: static const char *const cmds[CMD__MAX] = {
! 74: "pod", /* CMD_POD */
! 75: "head1", /* CMD_HEAD1 */
! 76: "head2", /* CMD_HEAD2 */
! 77: "head3", /* CMD_HEAD3 */
! 78: "head4", /* CMD_HEAD4 */
! 79: "over", /* CMD_OVER */
! 80: "item", /* CMD_ITEM */
! 81: "back", /* CMD_BACK */
! 82: "begin", /* CMD_BEGIN */
! 83: "end", /* CMD_END */
! 84: "for", /* CMD_FOR */
! 85: "encoding", /* CMD_ENCODING */
! 86: "cut" /* CMD_CUT */
! 87: };
! 88:
! 89: static const char fmts[FMT__MAX] = {
! 90: 'I', /* FMT_ITALIC */
! 91: 'B', /* FMT_BOLD */
! 92: 'C', /* FMT_CODE */
! 93: 'L', /* FMT_LINK */
! 94: 'E', /* FMT_ESCAPE */
! 95: 'F', /* FMT_FILE */
! 96: 'S', /* FMT_NBSP */
! 97: 'X', /* FMT_INDEX */
! 98: 'Z' /* FMT_NULL */
! 99: };
! 100:
! 101: /*
! 102: * Given buf[*start] is at the start of an escape name, read til the end
! 103: * of the escape ('>') then try to do something with it.
! 104: * Sets start to be one after the '>'.
! 105: */
! 106: static void
! 107: formatescape(const char *buf, size_t *start, size_t end)
! 108: {
! 109: char esc[16]; /* no more needed */
! 110: size_t i, max;
! 111:
! 112: max = sizeof(esc) - 1;
! 113: i = 0;
! 114: /* Read til our buffer is full. */
! 115: while (*start < end && '>' != buf[*start] && i < max)
! 116: esc[i++] = buf[(*start)++];
! 117: esc[i] = '\0';
! 118:
! 119: if (i == max) {
! 120: /* Too long... skip til we end. */
! 121: while (*start < end && '>' != buf[*start])
! 122: (*start)++;
! 123: return;
! 124: } else if (*start >= end)
! 125: return;
! 126:
! 127: assert('>' == buf[*start]);
! 128: (*start)++;
! 129:
! 130: /*
! 131: * TODO: right now, we only recognise the named escapes.
! 132: * Just let the rest of them go.
! 133: */
! 134: if (0 == strcmp(esc, "lt"))
! 135: printf("\\(la");
! 136: else if (0 == strcmp(esc, "gt"))
! 137: printf("\\(ra");
! 138: else if (0 == strcmp(esc, "vb"))
! 139: printf("\\(ba");
! 140: else if (0 == strcmp(esc, "sol"))
! 141: printf("\\(sl");
! 142: }
! 143:
! 144: /*
! 145: * Skip space characters.
! 146: */
! 147: static void
! 148: skipspace(const char *buf, size_t *start, size_t end)
! 149: {
! 150:
! 151: while (*start < end && ' ' == buf[*start])
! 152: (*start)++;
! 153: }
! 154:
! 155: /*
! 156: * We're at the character in front of a format code, which is structured
! 157: * like X<...> and can contain nested format codes.
! 158: * This consumes the whole format code, and any nested format codes, til
! 159: * the end of matched production.
! 160: * If "reentrant", then we're being called after a macro has already
! 161: * been printed to the current line.
! 162: * "last" is set to the last read character: this is used to determine
! 163: * whether we should buffer with space or not.
! 164: * If "nomacro", then we don't print any macros, just contained data.
! 165: */
! 166: static int
! 167: formatcode(const char *buf, size_t *start,
! 168: size_t end, int reentrant, int last, int nomacro)
! 169: {
! 170: enum fmt fmt;
! 171:
! 172: assert(*start + 1 < end);
! 173: assert('<' == buf[*start + 1]);
! 174:
! 175: for (fmt = 0; fmt < FMT__MAX; fmt++)
! 176: if (buf[*start] == fmts[fmt])
! 177: break;
! 178:
! 179: /* Invalid macros are just regular text. */
! 180:
! 181: if (FMT__MAX == fmt) {
! 182: putchar(buf[*start]);
! 183: (*start)++;
! 184: return(0);
! 185: }
! 186:
! 187: *start += 2;
! 188:
! 189: /*
! 190: * Escapes don't print macro sequences, so just output them like
! 191: * normal text before processing for macros.
! 192: */
! 193: if (FMT_ESCAPE == fmt) {
! 194: formatescape(buf, start, end);
! 195: return(0);
! 196: } else if (FMT_NULL == fmt || FMT_INDEX == fmt) {
! 197: /* For indices and nulls, just consume. */
! 198: while (*start < end && '>' != buf[*start])
! 199: (*start)++;
! 200: if (*start < end)
! 201: (*start)++;
! 202: return(0);
! 203: }
! 204:
! 205: if ( ! nomacro) {
! 206: /*
! 207: * Print out the macro describing this format code.
! 208: * If we're not "reentrant" (not yet on a macro line)
! 209: * then print a newline, if necessary, and the macro
! 210: * indicator.
! 211: * Otherwise, offset us with a space.
! 212: */
! 213: if ( ! reentrant && last != '\n')
! 214: putchar('\n');
! 215: if ( ! reentrant)
! 216: putchar('.');
! 217: else
! 218: putchar(' ');
! 219:
! 220: /*
! 221: * If we don't have whitespace before us, then suppress
! 222: * macro whitespace with Ns.
! 223: */
! 224: if (' ' != last)
! 225: printf("Ns ");
! 226: switch (fmt) {
! 227: case (FMT_ITALIC):
! 228: printf("Em ");
! 229: break;
! 230: case (FMT_BOLD):
! 231: printf("Sy ");
! 232: break;
! 233: case (FMT_CODE):
! 234: printf("Li ");
! 235: break;
! 236: case (FMT_LINK):
! 237: printf("Lk ");
! 238: break;
! 239: case (FMT_FILE):
! 240: printf("Pa ");
! 241: break;
! 242: case (FMT_NBSP):
! 243: /* TODO. */
! 244: printf("No ");
! 245: break;
! 246: default:
! 247: abort();
! 248: }
! 249: }
! 250:
! 251: /*
! 252: * Read until we reach the end market ('>') or until we find a
! 253: * nested format code.
! 254: * Don't emit any newlines: since we're on a macro line, we
! 255: * don't want to break the line.
! 256: */
! 257: while (*start < end) {
! 258: if ('>' == buf[*start]) {
! 259: (*start)++;
! 260: break;
! 261: }
! 262: if (*start + 1 < end && '<' == buf[*start + 1]) {
! 263: formatcode(buf, start, end, 1, last, nomacro);
! 264: continue;
! 265: }
! 266: if ('\n' != buf[*start]) {
! 267: /*
! 268: * Make sure that any macro-like words (or
! 269: * really any word starting with a capital
! 270: * letter) is assumed to be a macro that must be
! 271: * escaped.
! 272: * XXX: should this be isalpha()?
! 273: */
! 274: if ((' ' == last || '\n' == last) &&
! 275: isupper(buf[*start]))
! 276: printf("\\&");
! 277: putchar(last = buf[*start]);
! 278: }
! 279: (*start)++;
! 280: }
! 281:
! 282: if (reentrant)
! 283: return(1);
! 284:
! 285: /*
! 286: * If we're not reentrant, we want to put ending punctuation on
! 287: * the macro line so that it's properly handled by being
! 288: * smooshed against the terminal word.
! 289: */
! 290: skipspace(buf, start, end);
! 291: if (',' != buf[*start] && '.' != buf[*start] &&
! 292: '!' != buf[*start] && '?' != buf[*start] &&
! 293: ')' != buf[*start])
! 294: return(1);
! 295: while (*start < end) {
! 296: if (',' != buf[*start] &&
! 297: '.' != buf[*start] &&
! 298: '!' != buf[*start] &&
! 299: '?' != buf[*start] &&
! 300: ')' != buf[*start])
! 301: break;
! 302: putchar(' ');
! 303: putchar(buf[*start]);
! 304: (*start)++;
! 305: }
! 306: skipspace(buf, start, end);
! 307: return(1);
! 308: }
! 309:
! 310: /*
! 311: * Calls formatcode() til the end of a paragraph.
! 312: */
! 313: static void
! 314: formatcodeln(const char *buf, size_t *start, size_t end, int nomacro)
! 315: {
! 316: int last;
! 317:
! 318: last = '\n';
! 319: while (*start < end) {
! 320: if (*start + 1 < end && '<' == buf[*start + 1]) {
! 321: formatcode(buf, start, end, 1, last, nomacro);
! 322: continue;
! 323: }
! 324: if ('\n' != buf[*start])
! 325: putchar(last = buf[*start]);
! 326: (*start)++;
! 327: }
! 328: }
! 329:
! 330: /*
! 331: * A command paragraph, as noted in the perlpod manual, just indicates
! 332: * that we should do something, optionally with some text to print as
! 333: * well.
! 334: */
! 335: static void
! 336: command(struct state *st, const char *buf, size_t start, size_t end)
! 337: {
! 338: size_t len, csz;
! 339: enum cmd cmd;
! 340:
! 341: assert('=' == buf[start]);
! 342: start++;
! 343: len = end - start;
! 344:
! 345: for (cmd = 0; cmd < CMD__MAX; cmd++) {
! 346: csz = strlen(cmds[cmd]);
! 347: if (len < csz)
! 348: continue;
! 349: if (0 == memcmp(&buf[start], cmd[cmds], csz))
! 350: break;
! 351: }
! 352:
! 353: /* Ignore bogus commands. */
! 354:
! 355: if (CMD__MAX == cmd)
! 356: return;
! 357:
! 358: start += csz;
! 359: skipspace(buf, &start, end);
! 360: len = end - start;
! 361:
! 362: if (st->paused) {
! 363: st->paused = CMD_END != cmd;
! 364: return;
! 365: }
! 366:
! 367: switch (cmd) {
! 368: case (CMD_POD):
! 369: break;
! 370: case (CMD_HEAD1):
! 371: /*
! 372: * The behaviour of head= follows from a quick glance at
! 373: * how pod2man handles it.
! 374: */
! 375: printf(".Sh ");
! 376: st->isname = 0;
! 377: if (end - start == 4)
! 378: if (0 == memcmp(&buf[start], "NAME", 4))
! 379: st->isname = 1;
! 380: formatcodeln(buf, &start, end, 1);
! 381: putchar('\n');
! 382: st->haspar = 1;
! 383: break;
! 384: case (CMD_HEAD2):
! 385: printf(".Ss ");
! 386: formatcodeln(buf, &start, end, 1);
! 387: putchar('\n');
! 388: st->haspar = 1;
! 389: break;
! 390: case (CMD_HEAD3):
! 391: puts(".Pp");
! 392: printf(".Em ");
! 393: formatcodeln(buf, &start, end, 0);
! 394: putchar('\n');
! 395: puts(".Pp");
! 396: st->haspar = 1;
! 397: break;
! 398: case (CMD_HEAD4):
! 399: puts(".Pp");
! 400: printf(".No ");
! 401: formatcodeln(buf, &start, end, 0);
! 402: putchar('\n');
! 403: puts(".Pp");
! 404: st->haspar = 1;
! 405: break;
! 406: case (CMD_OVER):
! 407: /*
! 408: * TODO: we should be doing this after we process the
! 409: * first =item to see whether we'll do an -enum,
! 410: * -bullet, or something else.
! 411: */
! 412: puts(".Bl -tag -width Ds");
! 413: break;
! 414: case (CMD_ITEM):
! 415: printf(".It ");
! 416: formatcodeln(buf, &start, end, 0);
! 417: putchar('\n');
! 418: st->haspar = 1;
! 419: break;
! 420: case (CMD_BACK):
! 421: puts(".El");
! 422: break;
! 423: case (CMD_BEGIN):
! 424: /*
! 425: * We disregard all types for now.
! 426: * TODO: process at least "text" in a -literal block.
! 427: */
! 428: st->paused = 1;
! 429: break;
! 430: case (CMD_FOR):
! 431: /*
! 432: * We ignore all types of encodings and formats
! 433: * unilaterally.
! 434: */
! 435: break;
! 436: case (CMD_ENCODING):
! 437: break;
! 438: case (CMD_CUT):
! 439: st->parsing = 0;
! 440: return;
! 441: default:
! 442: abort();
! 443: }
! 444:
! 445: /* Any command (but =cut) makes us start parsing. */
! 446: st->parsing = 1;
! 447: }
! 448:
! 449: /*
! 450: * Just pump out the line in a verbatim block.
! 451: */
! 452: static void
! 453: verbatim(struct state *st, const char *buf, size_t start, size_t end)
! 454: {
! 455:
! 456: if ( ! st->parsing || st->paused)
! 457: return;
! 458:
! 459: puts(".Bd -literal");
! 460: printf("%.*s\n", (int)(end - start), &buf[start]);
! 461: puts(".Ed");
! 462: }
! 463:
! 464: /*
! 465: * Ordinary paragraph.
! 466: * Well, this is really the hardest--POD seems to assume that, for
! 467: * example, a leading space implies a newline, and so on.
! 468: * Lots of other snakes in the grass: escaping a newline followed by a
! 469: * period (accidental mdoc(7) control), double-newlines after macro
! 470: * passages, etc.
! 471: */
! 472: static void
! 473: ordinary(struct state *st, const char *buf, size_t start, size_t end)
! 474: {
! 475: int last;
! 476: size_t i, j;
! 477:
! 478: if ( ! st->parsing || st->paused)
! 479: return;
! 480:
! 481: /*
! 482: * Special-case: the NAME section.
! 483: * If we find a "-" when searching from the end, assume that
! 484: * we're in "name - description" format.
! 485: * To wit, print out a "Nm" and "Nd" in that format.
! 486: */
! 487: if (st->isname) {
! 488: for (i = end - 1; i > start; i--)
! 489: if ('-' == buf[i])
! 490: break;
! 491: if ('-' == buf[i]) {
! 492: j = i;
! 493: /* Roll over multiple "-". */
! 494: for ( ; i > start; i--)
! 495: if ('-' != buf[i])
! 496: break;
! 497: printf(".Nm %.*s\n",
! 498: (int)((i + 1) - start), &buf[start]);
! 499: printf(".Nd %.*s\n",
! 500: (int)(end - (j + 1)), &buf[j + 1]);
! 501: return;
! 502: }
! 503: }
! 504:
! 505: if ( ! st->haspar)
! 506: puts(".Pp");
! 507:
! 508: st->haspar = 0;
! 509: last = '\n';
! 510:
! 511: while (start < end) {
! 512: /*
! 513: * Loop til we get either to a newline or escape.
! 514: * Escape initial control characters.
! 515: */
! 516: while (start < end) {
! 517: if (start < end - 1 && '<' == buf[start + 1])
! 518: break;
! 519: else if ('\n' == buf[start])
! 520: break;
! 521: else if ('\n' == last && '.' == buf[start])
! 522: printf("\\&");
! 523: else if ('\n' == last && '\'' == buf[start])
! 524: printf("\\&");
! 525: putchar(last = buf[start++]);
! 526: }
! 527:
! 528: if (start < end - 1 && '<' == buf[start + 1]) {
! 529: /*
! 530: * We've encountered a format code.
! 531: * This is going to trigger a macro no matter
! 532: * what, so print a newline now.
! 533: * Then print the (possibly nested) macros and
! 534: * following that, a newline.
! 535: */
! 536: if (formatcode(buf, &start, end, 0, last, 0))
! 537: putchar(last = '\n');
! 538: } else if (start < end && '\n' == buf[start]) {
! 539: /*
! 540: * Print the newline only if we haven't already
! 541: * printed a newline.
! 542: */
! 543: if (last != '\n')
! 544: putchar(last = buf[start]);
! 545: if (++start >= end)
! 546: continue;
! 547: /*
! 548: * If we have whitespace next, eat it to prevent
! 549: * mdoc(7) from thinking that it's meant for
! 550: * verbatim text.
! 551: * It is--but if we start with that, we can't
! 552: * have a macro subsequent it, which may be
! 553: * possible if we have an escape next.
! 554: */
! 555: if (' ' == buf[start] || '\t' == buf[start]) {
! 556: puts(".br");
! 557: last = '\n';
! 558: }
! 559: for ( ; start < end; start++)
! 560: if (' ' != buf[start] && '\t' != buf[start])
! 561: break;
! 562: } else if (start < end) {
! 563: /*
! 564: * Default: print the character.
! 565: * Escape initial control characters.
! 566: */
! 567: if ('\n' == last && '.' == buf[start])
! 568: printf("\\&");
! 569: else if ('\n' == last && '\'' == buf[start])
! 570: printf("\\&");
! 571: putchar(last = buf[start++]);
! 572: }
! 573: }
! 574:
! 575: if (last != '\n')
! 576: putchar('\n');
! 577: }
! 578:
! 579: /*
! 580: * There are three kinds of paragraphs: verbatim (starts with whitespace
! 581: * of some sort), ordinary (starts without "=" marker), or a command
! 582: * (default: starts with "=").
! 583: */
! 584: static void
! 585: dopar(struct state *st, const char *buf, size_t start, size_t end)
! 586: {
! 587:
! 588: if (end == start)
! 589: return;
! 590: if (' ' == buf[start] || '\t' == buf[start])
! 591: verbatim(st, buf, start, end);
! 592: else if ('=' != buf[start])
! 593: ordinary(st, buf, start, end);
! 594: else
! 595: command(st, buf, start, end);
! 596: }
! 597:
! 598: /*
! 599: * Loop around paragraphs within a document, processing each one in the
! 600: * POD way.
! 601: */
! 602: static void
! 603: dofile(const struct args *args, const char *fname,
! 604: const struct tm *tm, const char *buf, size_t sz)
! 605: {
! 606: size_t sup, end, i, cur = 0;
! 607: struct state st;
! 608: const char *section, *date;
! 609: char datebuf[64];
! 610: char *title, *cp;
! 611:
! 612: if (0 == sz)
! 613: return;
! 614:
! 615: /* Title is last path component of the filename. */
! 616:
! 617: if (NULL != args->title)
! 618: title = strdup(args->title);
! 619: else if (NULL != (cp = strrchr(fname, '/')))
! 620: title = strdup(cp + 1);
! 621: else
! 622: title = strdup(fname);
! 623:
! 624: if (NULL == title) {
! 625: perror(NULL);
! 626: exit(EXIT_FAILURE);
! 627: }
! 628:
! 629: /* Section is 1 unless suffix is "pm". */
! 630:
! 631: if (NULL == (section = args->section)) {
! 632: section = "1";
! 633: if (NULL != (cp = strrchr(title, '.'))) {
! 634: *cp++ = '\0';
! 635: if (0 == strcmp(cp, "pm"))
! 636: section = "3p";
! 637: }
! 638: }
! 639:
! 640: /* Date. Or the given "tm" if not supplied. */
! 641:
! 642: if (NULL == (date = args->date)) {
! 643: strftime(datebuf, sizeof(datebuf), "%B %d, %Y", tm);
! 644: date = datebuf;
! 645: }
! 646:
! 647: for (cp = title; '\0' != *cp; cp++)
! 648: *cp = toupper((int)*cp);
! 649:
! 650: /* The usual mdoc(7) preamble. */
! 651:
! 652: printf(".Dd %s\n", date);
! 653: printf(".Dt %s %s\n", title, section);
! 654: puts(".Os");
! 655:
! 656: free(title);
! 657:
! 658: memset(&st, 0, sizeof(struct state));
! 659: assert(sz > 0);
! 660:
! 661: /* Main loop over file contents. */
! 662:
! 663: while (cur < sz) {
! 664: /* Read until next paragraph. */
! 665: for (i = cur + 1; i < sz; i++)
! 666: if ('\n' == buf[i] && '\n' == buf[i - 1]) {
! 667: /* Consume blank paragraphs. */
! 668: while (i + 1 < sz && '\n' == buf[i + 1])
! 669: i++;
! 670: break;
! 671: }
! 672:
! 673: /* Adjust end marker for EOF. */
! 674: end = i < sz ? i - 1 :
! 675: ('\n' == buf[sz - 1] ? sz - 1 : sz);
! 676: sup = i < sz ? end + 2 : sz;
! 677:
! 678: /* Process paragraph and adjust start. */
! 679: dopar(&st, buf, cur, end);
! 680: cur = sup;
! 681: }
! 682: }
! 683:
! 684: /*
! 685: * Read a single file fully into memory.
! 686: * If the file is "-", do it from stdin.
! 687: * If successfully read, send the input buffer to dofile() for further
! 688: * processing.
! 689: */
! 690: static int
! 691: readfile(const struct args *args, const char *fname)
! 692: {
! 693: int fd;
! 694: char *buf;
! 695: size_t bufsz, cur;
! 696: ssize_t ssz;
! 697: struct tm *tm;
! 698: time_t ttm;
! 699: struct stat st;
! 700:
! 701: assert(NULL != fname);
! 702:
! 703: fd = 0 != strcmp("-", fname) ?
! 704: open(fname, O_RDONLY, 0) : STDIN_FILENO;
! 705:
! 706: if (-1 == fd) {
! 707: perror(fname);
! 708: return(0);
! 709: }
! 710:
! 711: if (STDIN_FILENO == fd || -1 == fstat(fd, &st)) {
! 712: ttm = time(NULL);
! 713: tm = localtime(&ttm);
! 714: } else
! 715: tm = localtime(&st.st_mtime);
! 716:
! 717: /*
! 718: * Arbitrarily-sized initial buffer.
! 719: * Should be big enough for most files...
! 720: */
! 721: cur = 0;
! 722: bufsz = 1 << 14;
! 723: if (NULL == (buf = malloc(bufsz))) {
! 724: perror(NULL);
! 725: exit(EXIT_FAILURE);
! 726: }
! 727:
! 728: while ((ssz = read(fd, buf + cur, bufsz - cur)) > 0) {
! 729: /* Double buffer size on fill. */
! 730: if ((size_t)ssz == bufsz - cur) {
! 731: bufsz *= 2;
! 732: if (NULL == (buf = realloc(buf, bufsz))) {
! 733: perror(NULL);
! 734: exit(EXIT_FAILURE);
! 735: }
! 736: }
! 737: cur += (size_t)ssz;
! 738: }
! 739: if (ssz < 0) {
! 740: perror(fname);
! 741: free(buf);
! 742: return(0);
! 743: }
! 744:
! 745: dofile(args, STDIN_FILENO == fd ?
! 746: "STDIN" : fname, tm, buf, cur);
! 747: free(buf);
! 748: if (STDIN_FILENO != fd)
! 749: close(fd);
! 750: return(1);
! 751: }
! 752:
! 753: int
! 754: main(int argc, char *argv[])
! 755: {
! 756: const char *fname, *name;
! 757: struct args args;
! 758: int c;
! 759:
! 760: name = strrchr(argv[0], '/');
! 761: if (name == NULL)
! 762: name = argv[0];
! 763: else
! 764: ++name;
! 765:
! 766: memset(&args, 0, sizeof(struct args));
! 767: fname = "-";
! 768:
! 769: /* Accept no arguments for now. */
! 770:
! 771: while (-1 != (c = getopt(argc, argv, "c:d:hln:oq:rs:uv")))
! 772: switch (c) {
! 773: case ('h'):
! 774: /* FALLTHROUGH */
! 775: case ('l'):
! 776: /* FALLTHROUGH */
! 777: case ('c'):
! 778: /* FALLTHROUGH */
! 779: case ('o'):
! 780: /* FALLTHROUGH */
! 781: case ('q'):
! 782: /* FALLTHROUGH */
! 783: case ('r'):
! 784: /* FALLTHROUGH */
! 785: case ('u'):
! 786: /* FALLTHROUGH */
! 787: case ('v'):
! 788: /* Ignore these. */
! 789: break;
! 790: case ('d'):
! 791: args.date = optarg;
! 792: break;
! 793: case ('n'):
! 794: args.title = optarg;
! 795: break;
! 796: case ('s'):
! 797: args.section = optarg;
! 798: break;
! 799: default:
! 800: goto usage;
! 801: }
! 802:
! 803: argc -= optind;
! 804: argv += optind;
! 805:
! 806: /* Accept only a single input file. */
! 807:
! 808: if (argc > 2)
! 809: return(EXIT_FAILURE);
! 810: else if (1 == argc)
! 811: fname = *argv;
! 812:
! 813: return(readfile(&args, fname) ?
! 814: EXIT_SUCCESS : EXIT_FAILURE);
! 815:
! 816: usage:
! 817: fprintf(stderr, "usage: %s [-d date] "
! 818: "[-n title] [-s section]\n", name);
! 819:
! 820: return(EXIT_FAILURE);
! 821: }
CVSweb