Annotation of pod2mdoc/pod2mdoc.c, Revision 1.62
1.62 ! schwarze 1: /* $Id: pod2mdoc.c,v 1.61 2015/05/19 19:22:14 schwarze Exp $ */
1.1 schwarze 2: /*
3: * Copyright (c) 2014 Kristaps Dzonsons <kristaps@bsd.lv>
1.37 schwarze 4: * Copyright (c) 2014, 2015 Ingo Schwarze <schwarze@openbsd.org>
1.1 schwarze 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #include <sys/stat.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>
1.61 schwarze 27: #include <time.h>
1.1 schwarze 28: #include <unistd.h>
29:
1.37 schwarze 30: #include "dict.h"
31:
1.10 kristaps 32: /*
1.19 kristaps 33: * In what section can we find Perl module manuals?
34: * Sometimes (Mac OS X) it's 3pm, sometimes (OpenBSD, etc.) 3p.
35: * XXX IF YOU CHANGE THIS, CHANGE POD2MDOC.1 AS WELL.
1.10 kristaps 36: */
37: #define PERL_SECTION "3p"
38:
1.1 schwarze 39: struct args {
40: const char *title; /* override "Dt" title */
41: const char *date; /* override "Dd" date */
42: const char *section; /* override "Dt" section */
43: };
44:
1.4 schwarze 45: enum list {
46: LIST_BULLET = 0,
47: LIST_ENUM,
48: LIST_TAG,
49: LIST__MAX
50: };
51:
1.11 kristaps 52: enum sect {
53: SECT_NONE = 0,
54: SECT_NAME, /* NAME section */
55: SECT_SYNOPSIS, /* SYNOPSIS section */
56: };
57:
1.32 schwarze 58: enum outstate {
59: OUST_NL = 0, /* just started a new output line */
60: OUST_TXT, /* text line output in progress */
61: OUST_MAC /* macro line output in progress */
62: };
63:
1.1 schwarze 64: struct state {
1.31 schwarze 65: const char *fname; /* file being parsed */
1.1 schwarze 66: int parsing; /* after =cut of before command */
67: int paused; /* in =begin and before =end */
1.11 kristaps 68: enum sect sect; /* which section are we in? */
1.4 schwarze 69: #define LIST_STACKSZ 128
70: enum list lstack[LIST_STACKSZ]; /* open lists */
71: size_t lpos; /* where in list stack */
1.31 schwarze 72: int haspar; /* in paragraph: do we need Pp? */
1.32 schwarze 73: enum outstate oust; /* state of the mdoc output stream */
74: int wantws; /* let mdoc(7) output whitespace here */
1.31 schwarze 75: char *outbuf; /* text buffered for output */
76: size_t outbufsz; /* allocated size of outbuf */
77: size_t outbuflen; /* current length of outbuf */
1.58 schwarze 78: size_t outlnlen; /* chars so far on this output line */
1.1 schwarze 79: };
80:
81: enum fmt {
82: FMT_ITALIC,
83: FMT_BOLD,
84: FMT_CODE,
85: FMT_LINK,
86: FMT_ESCAPE,
87: FMT_FILE,
88: FMT_NBSP,
89: FMT_INDEX,
90: FMT_NULL,
91: FMT__MAX
92: };
93:
94: enum cmd {
95: CMD_POD = 0,
96: CMD_HEAD1,
97: CMD_HEAD2,
98: CMD_HEAD3,
99: CMD_HEAD4,
100: CMD_OVER,
101: CMD_ITEM,
102: CMD_BACK,
103: CMD_BEGIN,
104: CMD_END,
105: CMD_FOR,
106: CMD_ENCODING,
107: CMD_CUT,
108: CMD__MAX
109: };
1.55 schwarze 110:
111: static void command(struct state *, const char *, size_t, size_t);
112: static void dofile(const struct args *, const char *,
113: const struct tm *, char *, size_t);
114: static void donamenm(struct state *, const char *, size_t *, size_t);
115: static void dopar(struct state *, char *, size_t, size_t);
116: static void dosynopsisfl(const char *, size_t *, size_t);
117: static int dosynopsisop(struct state *, const char *, size_t *,
118: size_t, size_t *);
119: static int formatcode(struct state *, const char *, size_t *,
120: size_t, int, int);
121: static void formatcodeln(struct state *, const char *, const char *,
122: size_t *, size_t, int);
123: static void formatescape(struct state *, const char *, size_t *, size_t);
124: static int hasmatch(const char *, size_t, size_t);
125: static void ordinary(struct state *, const char *, size_t, size_t);
126: static void outbuf_addchar(struct state *);
127: static void outbuf_addstr(struct state *, const char *);
128: static void outbuf_flush(struct state *);
129: static void outbuf_grow(struct state *, size_t);
130: static enum list listguess(const char *, size_t, size_t);
131: static void mdoc_newln(struct state *);
132: static int readfile(const struct args *, const char *);
133: static void register_type(const char *);
134: static int trylink(const char *, size_t *, size_t, size_t);
135: static void verbatim(struct state *, char *, size_t, size_t);
1.1 schwarze 136:
137: static const char *const cmds[CMD__MAX] = {
1.60 schwarze 138: "pod", /* CMD_POD */
1.1 schwarze 139: "head1", /* CMD_HEAD1 */
140: "head2", /* CMD_HEAD2 */
141: "head3", /* CMD_HEAD3 */
142: "head4", /* CMD_HEAD4 */
143: "over", /* CMD_OVER */
144: "item", /* CMD_ITEM */
145: "back", /* CMD_BACK */
146: "begin", /* CMD_BEGIN */
147: "end", /* CMD_END */
148: "for", /* CMD_FOR */
149: "encoding", /* CMD_ENCODING */
150: "cut" /* CMD_CUT */
151: };
152:
153: static const char fmts[FMT__MAX] = {
154: 'I', /* FMT_ITALIC */
155: 'B', /* FMT_BOLD */
156: 'C', /* FMT_CODE */
157: 'L', /* FMT_LINK */
158: 'E', /* FMT_ESCAPE */
159: 'F', /* FMT_FILE */
160: 'S', /* FMT_NBSP */
161: 'X', /* FMT_INDEX */
162: 'Z' /* FMT_NULL */
163: };
164:
1.60 schwarze 165: static unsigned char last;
1.6 kristaps 166:
1.31 schwarze 167:
168: static void
169: outbuf_grow(struct state *st, size_t by)
170: {
171:
172: st->outbufsz += (by / 128 + 1) * 128;
173: st->outbuf = realloc(st->outbuf, st->outbufsz);
174: if (NULL == st->outbuf) {
175: perror(NULL);
176: exit(EXIT_FAILURE);
177: }
178: }
179:
180: static void
181: outbuf_addchar(struct state *st)
182: {
183:
184: if (st->outbuflen + 2 >= st->outbufsz)
185: outbuf_grow(st, 1);
186: st->outbuf[st->outbuflen++] = last;
187: if ('\\' == last)
188: st->outbuf[st->outbuflen++] = 'e';
189: st->outbuf[st->outbuflen] = '\0';
190: }
191:
192: static void
193: outbuf_addstr(struct state *st, const char *str)
194: {
195: size_t slen;
196:
197: slen = strlen(str);
198: if (st->outbuflen + slen >= st->outbufsz)
199: outbuf_grow(st, slen);
200: memcpy(st->outbuf + st->outbuflen, str, slen+1);
1.33 schwarze 201: st->outbuflen += slen;
1.31 schwarze 202: last = str[slen - 1];
203: }
204:
205: static void
206: outbuf_flush(struct state *st)
207: {
208:
209: if (0 == st->outbuflen)
210: return;
211:
1.58 schwarze 212: st->outlnlen += st->outbuflen;
213: if (OUST_TXT == st->oust && st->wantws) {
214: if (++st->outlnlen > 72) {
215: putchar('\n');
216: st->oust = OUST_NL;
217: st->outlnlen = st->outbuflen;
218: }
219: }
1.56 schwarze 220: if (OUST_NL != st->oust && st->wantws)
1.40 schwarze 221: putchar(' ');
222:
1.54 schwarze 223: if (OUST_MAC == st->oust && '"' == *st->outbuf)
224: printf("\\(dq%s", st->outbuf + 1);
225: else
226: fputs(st->outbuf, stdout);
227:
1.31 schwarze 228: *st->outbuf = '\0';
229: st->outbuflen = 0;
1.32 schwarze 230:
231: if (OUST_NL == st->oust)
232: st->oust = OUST_TXT;
1.31 schwarze 233: }
234:
235: static void
1.32 schwarze 236: mdoc_newln(struct state *st)
1.31 schwarze 237: {
238:
1.32 schwarze 239: if (OUST_NL == st->oust)
1.31 schwarze 240: return;
1.32 schwarze 241:
1.31 schwarze 242: putchar('\n');
243: last = '\n';
1.32 schwarze 244: st->oust = OUST_NL;
1.58 schwarze 245: st->outlnlen = 0;
1.32 schwarze 246: st->wantws = 1;
1.31 schwarze 247: }
248:
1.1 schwarze 249: /*
250: * Given buf[*start] is at the start of an escape name, read til the end
251: * of the escape ('>') then try to do something with it.
252: * Sets start to be one after the '>'.
1.32 schwarze 253: *
254: * This function does not care about output modes,
255: * it merely appends text to the output buffer,
256: * which can then be used in any mode.
1.1 schwarze 257: */
258: static void
1.31 schwarze 259: formatescape(struct state *st, const char *buf, size_t *start, size_t end)
1.1 schwarze 260: {
261: char esc[16]; /* no more needed */
262: size_t i, max;
263:
264: max = sizeof(esc) - 1;
265: i = 0;
266: /* Read til our buffer is full. */
267: while (*start < end && '>' != buf[*start] && i < max)
268: esc[i++] = buf[(*start)++];
269: esc[i] = '\0';
270:
271: if (i == max) {
272: /* Too long... skip til we end. */
273: while (*start < end && '>' != buf[*start])
274: (*start)++;
275: return;
276: } else if (*start >= end)
277: return;
278:
279: assert('>' == buf[*start]);
280: (*start)++;
281:
282: /*
283: * TODO: right now, we only recognise the named escapes.
1.60 schwarze 284: * Just let the rest of them go.
1.1 schwarze 285: */
1.60 schwarze 286: if (0 == strcmp(esc, "lt"))
1.31 schwarze 287: outbuf_addstr(st, "\\(la");
1.1 schwarze 288: else if (0 == strcmp(esc, "gt"))
1.31 schwarze 289: outbuf_addstr(st, "\\(ra");
1.33 schwarze 290: else if (0 == strcmp(esc, "verbar"))
1.31 schwarze 291: outbuf_addstr(st, "\\(ba");
1.1 schwarze 292: else if (0 == strcmp(esc, "sol"))
1.31 schwarze 293: outbuf_addstr(st, "\\(sl");
1.1 schwarze 294: }
295:
296: /*
1.9 kristaps 297: * Run some heuristics to intuit a link format.
1.19 kristaps 298: * I set "start" to be the end of the sequence (last right-carrot) so
1.9 kristaps 299: * that the caller can safely just continue processing.
1.19 kristaps 300: * If this is just an empty tag, I'll return 0.
1.32 schwarze 301: *
302: * Always operates in OUST_MAC mode.
303: * Mode handling is done by the caller.
1.9 kristaps 304: */
305: static int
306: trylink(const char *buf, size_t *start, size_t end, size_t dsz)
307: {
1.60 schwarze 308: size_t linkstart, realend, linkend,
1.21 kristaps 309: i, j, textsz, stack;
1.9 kristaps 310:
1.60 schwarze 311: /*
312: * Scan to the start of the terminus.
1.9 kristaps 313: * This function is more or less replicated in the formatcode()
314: * for null or index formatting codes.
1.23 kristaps 315: * However, we're slightly different because we might have
316: * nested escapes we need to ignore.
1.9 kristaps 317: */
1.21 kristaps 318: stack = 0;
1.19 kristaps 319: for (linkstart = realend = *start; realend < end; realend++) {
1.23 kristaps 320: if ('<' == buf[realend])
321: stack++;
1.19 kristaps 322: if ('>' != buf[realend])
1.9 kristaps 323: continue;
1.23 kristaps 324: else if (stack-- > 0)
325: continue;
326: if (dsz == 1)
1.9 kristaps 327: break;
1.19 kristaps 328: assert(realend > 0);
329: if (' ' != buf[realend - 1])
1.9 kristaps 330: continue;
1.60 schwarze 331: for (i = realend, j = 0; i < end && j < dsz; j++)
1.9 kristaps 332: if ('>' != buf[i++])
333: break;
1.60 schwarze 334: if (dsz == j)
1.9 kristaps 335: break;
336: }
1.19 kristaps 337:
338: /* Ignore stubs. */
339: if (realend == end || realend == *start)
1.9 kristaps 340: return(0);
341:
1.19 kristaps 342: /* Set linkend to the end of content. */
343: linkend = dsz > 1 ? realend - 1 : realend;
1.18 kristaps 344:
1.19 kristaps 345: /* Re-scan to see if we have a title or section. */
346: for (textsz = *start; textsz < linkend; textsz++)
347: if ('|' == buf[textsz] || '/' == buf[textsz])
1.18 kristaps 348: break;
349:
1.19 kristaps 350: if (textsz < linkend && '|' == buf[textsz]) {
1.20 kristaps 351: /* With title: set start, then end at section. */
1.19 kristaps 352: linkstart = textsz + 1;
1.18 kristaps 353: textsz = textsz - *start;
1.19 kristaps 354: for (i = linkstart; i < linkend; i++)
355: if ('/' == buf[i])
356: break;
357: if (i < linkend)
358: linkend = i;
1.20 kristaps 359: } else if (textsz < linkend && '/' == buf[textsz]) {
360: /* With section: set end at section. */
361: linkend = textsz;
362: textsz = 0;
363: } else
364: /* No title, no section. */
1.18 kristaps 365: textsz = 0;
1.19 kristaps 366:
367: *start = realend;
368: j = linkend - linkstart;
369:
1.20 kristaps 370: /* Do we have only subsection material? */
371: if (0 == j && '/' == buf[linkend]) {
372: linkstart = linkend + 1;
373: linkend = dsz > 1 ? realend - 1 : realend;
374: if (0 == (j = linkend - linkstart))
375: return(0);
376: printf("Sx %.*s", (int)j, &buf[linkstart]);
377: return(1);
378: } else if (0 == j)
1.19 kristaps 379: return(0);
380:
381: /* See if we qualify as being a link or not. */
1.20 kristaps 382: if ((j > 4 && 0 == memcmp("http:", &buf[linkstart], j)) ||
383: (j > 5 && 0 == memcmp("https:", &buf[linkstart], j)) ||
384: (j > 3 && 0 == memcmp("ftp:", &buf[linkstart], j)) ||
385: (j > 4 && 0 == memcmp("sftp:", &buf[linkstart], j)) ||
386: (j > 3 && 0 == memcmp("smb:", &buf[linkstart], j)) ||
387: (j > 3 && 0 == memcmp("afs:", &buf[linkstart], j))) {
388: /* Gross. */
1.60 schwarze 389: printf("Lk %.*s", (int)((dsz > 1 ? realend - 1 :
1.20 kristaps 390: realend) - linkstart), &buf[linkstart]);
1.19 kristaps 391: return(1);
1.60 schwarze 392: }
393:
1.19 kristaps 394: /* See if we qualify as a mailto. */
1.20 kristaps 395: if (j > 6 && 0 == memcmp("mailto:", &buf[linkstart], j)) {
1.19 kristaps 396: printf("Mt %.*s", (int)j, &buf[linkstart]);
397: return(1);
398: }
399:
400: /* See if we're a foo(5), foo(5x), or foo(5xx) manpage. */
1.60 schwarze 401: if ((j > 3 && ')' == buf[linkend - 1]) &&
1.19 kristaps 402: ('(' == buf[linkend - 3])) {
1.60 schwarze 403: printf("Xr %.*s %c", (int)(j - 3),
1.19 kristaps 404: &buf[linkstart], buf[linkend - 2]);
405: return(1);
406: } else if ((j > 4 && ')' == buf[linkend - 1]) &&
407: ('(' == buf[linkend - 4])) {
1.60 schwarze 408: printf("Xr %.*s %.*s", (int)(j - 4),
1.19 kristaps 409: &buf[linkstart], 2, &buf[linkend - 3]);
410: return(1);
411: } else if ((j > 5 && ')' == buf[linkend - 1]) &&
412: ('(' == buf[linkend - 5])) {
1.60 schwarze 413: printf("Xr %.*s %.*s", (int)(j - 5),
1.19 kristaps 414: &buf[linkstart], 3, &buf[linkend - 4]);
415: return(1);
416: }
417:
418: /* Last try: do we have a double-colon? */
419: for (i = linkstart + 1; i < linkend; i++)
420: if (':' == buf[i] && ':' == buf[i - 1])
1.18 kristaps 421: break;
1.9 kristaps 422:
1.19 kristaps 423: if (i < linkend)
1.60 schwarze 424: printf("Xr %.*s " PERL_SECTION,
1.19 kristaps 425: (int)j, &buf[linkstart]);
1.9 kristaps 426: else
1.19 kristaps 427: printf("Xr %.*s 1", (int)j, &buf[linkstart]);
1.9 kristaps 428:
429: return(1);
430: }
431:
1.13 kristaps 432: /*
433: * Doclifting: if we're a bold "-xx" and we're in the SYNOPSIS section,
434: * then it's likely that we're a flag.
435: * Our flag might be followed by an argument, so make sure that we're
436: * accounting for that, too.
437: * If we don't have a flag at all, however, then assume we're an "Ar".
1.32 schwarze 438: *
439: * Always operates in OUST_MAC mode.
440: * Mode handlinf is done by the caller.
1.13 kristaps 441: */
442: static void
443: dosynopsisfl(const char *buf, size_t *start, size_t end)
444: {
445: size_t i;
446: again:
1.14 kristaps 447: assert(*start + 1 < end);
448: assert('-' == buf[*start]);
449:
450: if ( ! isalnum((int)buf[*start + 1]) &&
451: '?' != buf[*start + 1] &&
452: '-' != buf[*start + 1]) {
453: (*start)--;
1.56 schwarze 454: fputs("Ar", stdout);
1.14 kristaps 455: return;
456: }
457:
1.13 kristaps 458: (*start)++;
459: for (i = *start; i < end; i++)
460: if (isalnum((int)buf[i]))
461: continue;
1.14 kristaps 462: else if ('?' == buf[i])
463: continue;
1.13 kristaps 464: else if ('-' == buf[i])
465: continue;
466: else if ('_' == buf[i])
467: continue;
468: else
469: break;
470:
471: assert(i < end);
472:
473: if ( ! (' ' == buf[i] || '>' == buf[i])) {
1.56 schwarze 474: fputs("Ar", stdout);
1.13 kristaps 475: return;
476: }
477:
478: printf("Fl ");
1.60 schwarze 479: if (end - *start > 1 &&
1.13 kristaps 480: isupper((int)buf[*start]) &&
481: islower((int)buf[*start + 1]) &&
482: (end - *start == 2 ||
483: ' ' == buf[*start + 2]))
484: printf("\\&");
1.56 schwarze 485: printf("%.*s", (int)(i - *start), &buf[*start]);
1.13 kristaps 486: *start = i;
487:
488: if (' ' == buf[i]) {
489: while (i < end && ' ' == buf[i])
490: i++;
491: assert(i < end);
492: if ('-' == buf[i]) {
493: *start = i;
494: goto again;
495: }
1.56 schwarze 496: fputs("Ar", stdout);
1.13 kristaps 497: *start = i;
498: }
499: }
500:
1.9 kristaps 501: /*
1.1 schwarze 502: * We're at the character in front of a format code, which is structured
503: * like X<...> and can contain nested format codes.
504: * This consumes the whole format code, and any nested format codes, til
505: * the end of matched production.
1.6 kristaps 506: * If "nomacro", then we don't print any macros, just contained data
507: * (e.g., following "Sh" or "Nm").
1.15 kristaps 508: * "pos" is only significant in SYNOPSIS, and should be 0 when invoked
509: * as the first format code on a line (for decoration as an "Nm"),
510: * non-zero otherwise.
1.32 schwarze 511: *
512: * Output mode handling is most complicated here.
513: * We may enter in any mode.
514: * We usually exit in OUST_MAC mode, except when
515: * entering without OUST_MAC and the code is invalid.
1.1 schwarze 516: */
1.33 schwarze 517: static int
1.60 schwarze 518: formatcode(struct state *st, const char *buf, size_t *start,
1.32 schwarze 519: size_t end, int nomacro, int pos)
1.1 schwarze 520: {
1.40 schwarze 521: size_t i, j, dsz;
1.1 schwarze 522: enum fmt fmt;
1.39 schwarze 523: unsigned char uc;
1.56 schwarze 524: int gotmacro, wantws;
1.1 schwarze 525:
526: assert(*start + 1 < end);
527: assert('<' == buf[*start + 1]);
528:
1.60 schwarze 529: /*
530: * First, look up the format code.
1.30 schwarze 531: * If it's not valid, treat it as a NOOP.
1.6 kristaps 532: */
1.60 schwarze 533: for (fmt = 0; fmt < FMT__MAX; fmt++)
1.6 kristaps 534: if (buf[*start] == fmts[fmt])
535: break;
536:
1.5 kristaps 537: /*
538: * Determine whether we're overriding our delimiter.
539: * According to POD, if we have more than one '<' followed by a
540: * space, then we need a space followed by matching '>' to close
541: * the expression.
542: * Otherwise we use the usual '<' and '>' matched pair.
543: */
544: i = *start + 1;
545: while (i < end && '<' == buf[i])
546: i++;
547: assert(i > *start + 1);
548: dsz = i - (*start + 1);
549: if (dsz > 1 && (i >= end || ' ' != buf[i]))
550: dsz = 1;
551:
552: /* Remember, if dsz>1, to jump the trailing space. */
553: *start += dsz + 1 + (dsz > 1 ? 1 : 0);
1.1 schwarze 554:
555: /*
1.6 kristaps 556: * Escapes and ignored codes (NULL and INDEX) don't print macro
557: * sequences, so just output them like normal text before
558: * processing for real macros.
1.1 schwarze 559: */
560: if (FMT_ESCAPE == fmt) {
1.31 schwarze 561: formatescape(st, buf, start, end);
1.33 schwarze 562: return(0);
1.1 schwarze 563: } else if (FMT_NULL == fmt || FMT_INDEX == fmt) {
1.60 schwarze 564: /*
1.6 kristaps 565: * Just consume til the end delimiter, accounting for
566: * whether it's a custom one.
1.5 kristaps 567: */
568: for ( ; *start < end; (*start)++) {
569: if ('>' != buf[*start])
570: continue;
571: else if (dsz == 1)
572: break;
573: assert(*start > 0);
574: if (' ' != buf[*start - 1])
575: continue;
576: i = *start;
1.60 schwarze 577: for (j = 0; i < end && j < dsz; j++)
1.5 kristaps 578: if ('>' != buf[i++])
579: break;
1.60 schwarze 580: if (dsz != j)
1.5 kristaps 581: continue;
582: (*start) += dsz;
583: break;
584: }
1.24 kristaps 585: if (*start < end) {
586: assert('>' == buf[*start]);
587: (*start)++;
588: }
589: if (isspace(last))
590: while (*start < end && isspace((int)buf[*start]))
591: (*start)++;
1.33 schwarze 592: return(0);
1.1 schwarze 593: }
594:
1.6 kristaps 595: /*
596: * Check whether we're supposed to print macro stuff (this is
597: * suppressed in, e.g., "Nm" and "Sh" macros).
598: */
1.30 schwarze 599: if (FMT__MAX != fmt && !nomacro) {
1.32 schwarze 600:
601: /*
1.56 schwarze 602: * Do we need spacing before the upcoming macro,
603: * after any pending text already in the outbuf?
604: * We may already have wantws if there was whitespace
605: * before the code ("text B<text"), or there may be
606: * whitespace inside our scope ("textB< text").
607: */
608:
609: wantws = ' ' == buf[*start] ||
610: (st->wantws && ! st->outbuflen);
611:
612: /*
1.31 schwarze 613: * If we are on a text line and there is no
614: * whitespace before our content, we have to make
615: * the previous word a prefix to the macro line.
1.1 schwarze 616: */
1.31 schwarze 617:
1.56 schwarze 618: if (OUST_MAC != st->oust && ! wantws) {
1.32 schwarze 619: if (OUST_NL != st->oust)
1.54 schwarze 620: mdoc_newln(st);
1.56 schwarze 621: fputs(".Pf", stdout);
1.54 schwarze 622: st->oust = OUST_MAC;
1.56 schwarze 623: st->wantws = wantws = 1;
1.31 schwarze 624: }
625:
626: outbuf_flush(st);
627:
1.56 schwarze 628: /* Whitespace is easier to suppress on macro lines. */
1.31 schwarze 629:
1.56 schwarze 630: if (OUST_MAC == st->oust && ! wantws)
1.54 schwarze 631: printf(" Ns");
1.31 schwarze 632:
633: /* Unless we are on a macro line, start one. */
634:
1.54 schwarze 635: if (OUST_MAC != st->oust) {
1.32 schwarze 636: if (OUST_NL != st->oust)
1.54 schwarze 637: mdoc_newln(st);
1.1 schwarze 638: putchar('.');
1.54 schwarze 639: st->oust = OUST_MAC;
1.31 schwarze 640: } else
1.1 schwarze 641: putchar(' ');
1.54 schwarze 642: st->wantws = 1;
1.31 schwarze 643:
1.32 schwarze 644: /*
645: * Print the macro corresponding to this format code,
646: * and update the output state afterwards.
647: */
1.6 kristaps 648:
1.1 schwarze 649: switch (fmt) {
650: case (FMT_BOLD):
1.60 schwarze 651: if (SECT_SYNOPSIS == st->sect) {
1.14 kristaps 652: if (1 == dsz && '-' == buf[*start])
653: dosynopsisfl(buf, start, end);
1.15 kristaps 654: else if (0 == pos)
1.56 schwarze 655: fputs("Nm", stdout);
1.14 kristaps 656: else
1.56 schwarze 657: fputs("Ar", stdout);
1.14 kristaps 658: break;
1.39 schwarze 659: }
1.59 schwarze 660: /* FALLTHROUGH */
661: case (FMT_ITALIC):
1.39 schwarze 662: i = 0;
663: uc = buf[*start];
664: while (isalnum(uc) || '_' == uc || ' ' == uc)
665: uc = buf[*start + ++i];
666: if ('=' != uc && '>' != uc)
667: i = 0;
668: if (4 == i && ! strncmp(buf + *start, "NULL", 4)) {
1.56 schwarze 669: fputs("Dv", stdout);
1.38 schwarze 670: break;
671: }
1.39 schwarze 672: switch (i ? dict_get(buf + *start, i) : MDOC_MAX) {
673: case MDOC_Fa:
1.56 schwarze 674: fputs("Fa", stdout);
1.39 schwarze 675: break;
676: case MDOC_Vt:
1.56 schwarze 677: fputs("Vt", stdout);
1.39 schwarze 678: break;
679: default:
1.59 schwarze 680: fputs(FMT_BOLD == fmt ? "Sy" : "Em", stdout);
1.39 schwarze 681: break;
682: }
1.1 schwarze 683: break;
684: case (FMT_CODE):
1.56 schwarze 685: fputs("Qo Li", stdout);
1.1 schwarze 686: break;
687: case (FMT_LINK):
1.19 kristaps 688: /* Try to link; use "No" if it's empty. */
1.9 kristaps 689: if ( ! trylink(buf, start, end, dsz))
1.56 schwarze 690: fputs("No", stdout);
1.1 schwarze 691: break;
692: case (FMT_FILE):
1.56 schwarze 693: fputs("Pa", stdout);
1.1 schwarze 694: break;
695: case (FMT_NBSP):
1.56 schwarze 696: fputs("No", stdout);
1.1 schwarze 697: break;
698: default:
699: abort();
700: }
1.56 schwarze 701: } else {
1.31 schwarze 702: outbuf_flush(st);
1.56 schwarze 703: st->wantws = 0;
704: }
1.1 schwarze 705:
706: /*
1.6 kristaps 707: * Process until we reach the end marker (e.g., '>') or until we
1.5 kristaps 708: * find a nested format code.
1.1 schwarze 709: * Don't emit any newlines: since we're on a macro line, we
710: * don't want to break the line.
711: */
1.56 schwarze 712:
713: gotmacro = 0;
1.1 schwarze 714: while (*start < end) {
1.5 kristaps 715: if ('>' == buf[*start] && 1 == dsz) {
1.1 schwarze 716: (*start)++;
717: break;
1.60 schwarze 718: } else if ('>' == buf[*start] &&
1.5 kristaps 719: ' ' == buf[*start - 1]) {
720: /*
721: * Handle custom delimiters.
722: * These require a certain number of
723: * space-preceded carrots before we're really at
724: * the end.
725: */
726: i = *start;
727: for (j = 0; i < end && j < dsz; j++)
728: if ('>' != buf[i++])
729: break;
730: if (dsz == j) {
731: *start += dsz;
732: break;
733: }
1.1 schwarze 734: }
1.34 schwarze 735: if (*start + 1 < end && '<' == buf[*start + 1] &&
736: 'A' <= buf[*start] && 'Z' >= buf[*start]) {
1.56 schwarze 737: gotmacro = formatcode(st, buf,
738: start, end, nomacro, 1);
1.1 schwarze 739: continue;
740: }
1.3 schwarze 741:
1.32 schwarze 742: /* Suppress newlines and multiple spaces. */
743:
744: last = buf[(*start)++];
1.56 schwarze 745: if (isspace(last)) {
746: outbuf_flush(st);
747: st->wantws = 1;
748: gotmacro = 0;
749: while (*start < end &&
750: isspace((unsigned char)buf[*start]))
1.32 schwarze 751: (*start)++;
752: continue;
753: }
754:
1.33 schwarze 755: if (OUST_MAC == st->oust && FMT__MAX != fmt) {
1.56 schwarze 756: if (gotmacro && ! st->wantws) {
757: printf(" Ns");
1.32 schwarze 758: st->wantws = 1;
759: }
1.56 schwarze 760: gotmacro = 0;
1.32 schwarze 761:
762: /*
763: * Escape macro-like words.
764: * This matches "Xx " and "XxEOLN".
765: */
766:
1.56 schwarze 767: if (*start < end && ! st->outbuflen &&
768: isupper(last) &&
1.32 schwarze 769: islower((unsigned char)buf[*start]) &&
770: (end - *start == 1 ||
771: ' ' == buf[*start + 1] ||
772: '>' == buf[*start + 1]))
1.56 schwarze 773: outbuf_addstr(st, "\\&");
774: last = buf[*start - 1];
1.32 schwarze 775: }
1.56 schwarze 776: outbuf_addchar(st);
777: }
1.3 schwarze 778:
1.56 schwarze 779: if (FMT__MAX == fmt)
780: return(0);
1.4 schwarze 781:
1.56 schwarze 782: outbuf_flush(st);
1.2 schwarze 783:
784: if ( ! nomacro && FMT_CODE == fmt)
1.56 schwarze 785: fputs(" Qc", stdout);
1.1 schwarze 786:
1.33 schwarze 787: st->wantws = ' ' == last;
1.56 schwarze 788: return(1);
1.1 schwarze 789: }
790:
791: /*
792: * Calls formatcode() til the end of a paragraph.
1.32 schwarze 793: * Goes to OUST_MAC mode and stays there when returning,
794: * such that the caller can add arguments to the macro line
795: * before closing it out.
1.1 schwarze 796: */
797: static void
1.32 schwarze 798: formatcodeln(struct state *st, const char *linemac,
799: const char *buf, size_t *start, size_t end, int nomacro)
1.1 schwarze 800: {
1.56 schwarze 801: int gotmacro;
1.1 schwarze 802:
1.32 schwarze 803: assert(OUST_NL == st->oust);
804: assert(st->wantws);
1.56 schwarze 805: printf(".%s", linemac);
1.32 schwarze 806: st->oust = OUST_MAC;
807:
1.33 schwarze 808: gotmacro = 0;
1.1 schwarze 809: while (*start < end) {
1.34 schwarze 810: if (*start + 1 < end && '<' == buf[*start + 1] &&
811: 'A' <= buf[*start] && 'Z' >= buf[*start]) {
1.33 schwarze 812: gotmacro = formatcode(st, buf,
813: start, end, nomacro, 1);
1.1 schwarze 814: continue;
815: }
1.32 schwarze 816:
1.56 schwarze 817: /* Suppress newlines and multiple spaces. */
818:
819: last = buf[(*start)++];
820: if (isspace(last)) {
821: outbuf_flush(st);
822: st->wantws = 1;
823: while (*start < end &&
824: isspace((unsigned char)buf[*start]))
825: (*start)++;
826: continue;
827: }
828:
1.33 schwarze 829: if (gotmacro) {
1.56 schwarze 830: if (*start < end) {
831: if (st->wantws)
832: printf(" No");
1.33 schwarze 833: else
1.56 schwarze 834: printf(" Ns");
1.33 schwarze 835: }
1.56 schwarze 836: st->wantws = 1;
1.33 schwarze 837: gotmacro = 0;
838: }
1.32 schwarze 839:
1.4 schwarze 840: /*
841: * Since we're already on a macro line, we want to make
842: * sure that we don't inadvertently invoke a macro.
843: * We need to do this carefully because section names
844: * are used in troff and we don't want to escape
845: * something that needn't be escaped.
846: */
1.56 schwarze 847: if (*start < end && ! st->outbuflen && isupper(last) &&
848: islower((unsigned char)buf[*start]) &&
849: (end - *start == 1 || ' ' == buf[*start + 1])) {
850: outbuf_addstr(st, "\\&");
851: last = buf[*start - 1];
852: }
853: outbuf_addchar(st);
1.1 schwarze 854: }
1.56 schwarze 855: outbuf_flush(st);
856: st->wantws = 1;
1.1 schwarze 857: }
858:
859: /*
1.4 schwarze 860: * Guess at what kind of list we are.
861: * These are taken straight from the POD manual.
862: * I don't know what people do in real life.
863: */
864: static enum list
865: listguess(const char *buf, size_t start, size_t end)
866: {
867: size_t len = end - start;
868:
869: assert(end >= start);
870:
871: if (len == 1 && '*' == buf[start])
872: return(LIST_BULLET);
873: if (len == 2 && '1' == buf[start] && '.' == buf[start + 1])
874: return(LIST_ENUM);
875: else if (len == 1 && '1' == buf[start])
876: return(LIST_ENUM);
877: else
878: return(LIST_TAG);
879: }
880:
881: /*
1.1 schwarze 882: * A command paragraph, as noted in the perlpod manual, just indicates
883: * that we should do something, optionally with some text to print as
884: * well.
1.32 schwarze 885: * From the perspective of external callers,
886: * always stays in OUST_NL/wantws mode,
887: * but its children do use OUST_MAC.
1.1 schwarze 888: */
889: static void
890: command(struct state *st, const char *buf, size_t start, size_t end)
891: {
892: size_t len, csz;
893: enum cmd cmd;
894:
895: assert('=' == buf[start]);
896: start++;
897: len = end - start;
898:
899: for (cmd = 0; cmd < CMD__MAX; cmd++) {
900: csz = strlen(cmds[cmd]);
901: if (len < csz)
902: continue;
903: if (0 == memcmp(&buf[start], cmd[cmds], csz))
904: break;
905: }
906:
907: /* Ignore bogus commands. */
908:
909: if (CMD__MAX == cmd)
910: return;
911:
912: start += csz;
1.8 kristaps 913: while (start < end && ' ' == buf[start])
914: start++;
915:
1.1 schwarze 916: len = end - start;
917:
918: if (st->paused) {
919: st->paused = CMD_END != cmd;
920: return;
921: }
922:
923: switch (cmd) {
924: case (CMD_POD):
925: break;
926: case (CMD_HEAD1):
927: /*
928: * The behaviour of head= follows from a quick glance at
929: * how pod2man handles it.
930: */
1.11 kristaps 931: st->sect = SECT_NONE;
932: if (end - start == 4) {
1.1 schwarze 933: if (0 == memcmp(&buf[start], "NAME", 4))
1.11 kristaps 934: st->sect = SECT_NAME;
935: } else if (end - start == 8) {
936: if (0 == memcmp(&buf[start], "SYNOPSIS", 8))
937: st->sect = SECT_SYNOPSIS;
1.60 schwarze 938: }
1.32 schwarze 939: formatcodeln(st, "Sh", buf, &start, end, 1);
940: mdoc_newln(st);
1.1 schwarze 941: st->haspar = 1;
942: break;
943: case (CMD_HEAD2):
1.32 schwarze 944: formatcodeln(st, "Ss", buf, &start, end, 1);
945: mdoc_newln(st);
1.1 schwarze 946: st->haspar = 1;
947: break;
948: case (CMD_HEAD3):
949: puts(".Pp");
1.32 schwarze 950: formatcodeln(st, "Em", buf, &start, end, 0);
951: mdoc_newln(st);
1.1 schwarze 952: puts(".Pp");
953: st->haspar = 1;
954: break;
955: case (CMD_HEAD4):
956: puts(".Pp");
1.32 schwarze 957: formatcodeln(st, "No", buf, &start, end, 0);
958: mdoc_newln(st);
1.1 schwarze 959: puts(".Pp");
960: st->haspar = 1;
961: break;
962: case (CMD_OVER):
1.60 schwarze 963: /*
1.4 schwarze 964: * If we have an existing list that hasn't had an =item
965: * yet, then make sure that we open it now.
966: * We use the default list type, but that can't be
967: * helped (we haven't seen any items yet).
1.1 schwarze 968: */
1.4 schwarze 969: if (st->lpos > 0)
970: if (LIST__MAX == st->lstack[st->lpos - 1]) {
971: st->lstack[st->lpos - 1] = LIST_TAG;
972: puts(".Bl -tag -width Ds");
973: }
974: st->lpos++;
975: assert(st->lpos < LIST_STACKSZ);
976: st->lstack[st->lpos - 1] = LIST__MAX;
1.1 schwarze 977: break;
978: case (CMD_ITEM):
1.6 kristaps 979: if (0 == st->lpos) {
1.60 schwarze 980: /*
1.6 kristaps 981: * Bad markup.
982: * Try to compensate.
983: */
984: st->lstack[st->lpos] = LIST__MAX;
985: st->lpos++;
986: }
1.4 schwarze 987: assert(st->lpos > 0);
988: /*
989: * If we're the first =item, guess at what our content
990: * will be: "*" is a bullet list, "1." is a numbered
991: * list, and everything is tagged.
992: */
993: if (LIST__MAX == st->lstack[st->lpos - 1]) {
1.60 schwarze 994: st->lstack[st->lpos - 1] =
1.4 schwarze 995: listguess(buf, start, end);
996: switch (st->lstack[st->lpos - 1]) {
997: case (LIST_BULLET):
998: puts(".Bl -bullet");
999: break;
1000: case (LIST_ENUM):
1001: puts(".Bl -enum");
1002: break;
1003: default:
1004: puts(".Bl -tag -width Ds");
1005: break;
1006: }
1007: }
1008: switch (st->lstack[st->lpos - 1]) {
1009: case (LIST_TAG):
1.32 schwarze 1010: formatcodeln(st, "It", buf, &start, end, 0);
1011: mdoc_newln(st);
1.4 schwarze 1012: break;
1013: case (LIST_ENUM):
1014: /* FALLTHROUGH */
1015: case (LIST_BULLET):
1016: /*
1017: * Abandon the remainder of the paragraph
1018: * because we're going to be a bulletted or
1019: * numbered list.
1020: */
1021: puts(".It");
1022: break;
1023: default:
1024: abort();
1025: }
1.1 schwarze 1026: st->haspar = 1;
1027: break;
1028: case (CMD_BACK):
1.4 schwarze 1029: /* Make sure we don't back over the stack. */
1030: if (st->lpos > 0) {
1031: st->lpos--;
1032: puts(".El");
1033: }
1.1 schwarze 1034: break;
1035: case (CMD_BEGIN):
1.60 schwarze 1036: /*
1.1 schwarze 1037: * We disregard all types for now.
1038: * TODO: process at least "text" in a -literal block.
1039: */
1040: st->paused = 1;
1041: break;
1042: case (CMD_FOR):
1.60 schwarze 1043: /*
1.1 schwarze 1044: * We ignore all types of encodings and formats
1045: * unilaterally.
1046: */
1047: break;
1048: case (CMD_ENCODING):
1049: break;
1050: case (CMD_CUT):
1051: st->parsing = 0;
1052: return;
1053: default:
1054: abort();
1055: }
1056:
1057: /* Any command (but =cut) makes us start parsing. */
1058: st->parsing = 1;
1059: }
1060:
1061: /*
1.39 schwarze 1062: * Put the type provided as an argument into the dictionary.
1063: */
1064: static void
1065: register_type(const char *ptype)
1066: {
1067: const char *pname, *pend;
1068:
1069: pname = ptype;
1070: while (isalnum((unsigned char)*pname) || '_' == *pname)
1071: pname++;
1072: if ((pname - ptype == 6 && ! strncmp(ptype, "struct", 6)) ||
1073: (pname - ptype == 4 && ! strncmp(ptype, "enum", 4))) {
1074: while (' ' == *pname)
1075: pname++;
1076: pend = pname;
1077: while (isalnum((unsigned char)*pend) || '_' == *pend)
1078: pend++;
1079: if (pend > pname)
1080: dict_put(pname, pend - pname, MDOC_Vt);
1081: } else
1082: pend = pname;
1083: if (pend > ptype)
1084: dict_put(ptype, pend - ptype, MDOC_Vt);
1085: }
1086:
1087: /*
1.1 schwarze 1088: * Just pump out the line in a verbatim block.
1.32 schwarze 1089: * From the perspective of external callers,
1090: * always stays in OUST_NL/wantws mode.
1.1 schwarze 1091: */
1092: static void
1.35 schwarze 1093: verbatim(struct state *st, char *buf, size_t start, size_t end)
1.1 schwarze 1094: {
1.36 schwarze 1095: size_t i, ift, ifo, ifa, ifc, inl;
1.38 schwarze 1096: char *cp, *cp2;
1.53 schwarze 1097: int indisplay, nopen, wantsp;
1.1 schwarze 1098:
1.53 schwarze 1099: if (st->paused || ! st->parsing)
1.1 schwarze 1100: return;
1.53 schwarze 1101:
1102: indisplay = wantsp = 0;
1103:
1.22 kristaps 1104: again:
1.53 schwarze 1105: if (start == end) {
1106: if (indisplay)
1107: puts(".Ed");
1108: return;
1109: }
1110:
1111: if ('\n' == buf[start]) {
1112: wantsp = 1;
1113: start++;
1114: goto again;
1115: }
1116:
1.60 schwarze 1117: /*
1.22 kristaps 1118: * If we're in the SYNOPSIS, see if we're an #include block.
1119: * If we are, then print the "In" macro and re-loop.
1120: * This handles any number of inclusions, but only when they
1121: * come before the remaining parts...
1122: */
1123: if (SECT_SYNOPSIS == st->sect) {
1124: i = start;
1.35 schwarze 1125: while (i < end && buf[i] == ' ')
1126: i++;
1.22 kristaps 1127: if (i == end)
1.53 schwarze 1128: goto again;
1.35 schwarze 1129:
1.22 kristaps 1130: /* We're an include block! */
1.60 schwarze 1131: if (end - i > 10 &&
1.22 kristaps 1132: 0 == memcmp(&buf[i], "#include <", 10)) {
1133: start = i + 10;
1134: while (start < end && ' ' == buf[start])
1135: start++;
1.53 schwarze 1136: if (indisplay)
1137: puts(".Ed");
1138: indisplay = wantsp = 0;
1.22 kristaps 1139: fputs(".In ", stdout);
1140: /* Stop til the '>' marker or we hit eoln. */
1.60 schwarze 1141: while (start < end &&
1.22 kristaps 1142: '>' != buf[start] && '\n' != buf[start])
1143: putchar(buf[start++]);
1144: putchar('\n');
1145: if (start < end && '>' == buf[start])
1146: start++;
1147: if (start < end && '\n' == buf[start])
1148: start++;
1.41 schwarze 1149: goto again;
1150: }
1151:
1152: /* Other preprocessor directives. */
1153: if ('#' == buf[i]) {
1.53 schwarze 1154: if (indisplay)
1155: puts(".Ed");
1156: indisplay = wantsp = 0;
1.41 schwarze 1157: fputs(".Fd ", stdout);
1158: start = i;
1159: while(start < end && '\n' != buf[start])
1160: putchar(buf[start++]);
1161: putchar('\n');
1162: if (start < end && '\n' == buf[start])
1163: start++;
1.49 schwarze 1164:
1165: /* Remember #define for Dv or Fn. */
1166:
1167: if (strncmp(buf + i + 1, "define", 6) ||
1168: ! isspace((unsigned char)buf[i + 7]))
1169: goto again;
1170:
1171: ifo = i + 7;
1172: while (ifo < start &&
1173: isspace((unsigned char)buf[ifo]))
1174: ifo++;
1175: ifa = ifo;
1176: while ('_' == buf[ifa] ||
1177: isalnum((unsigned char)buf[ifa]))
1178: ifa++;
1179: dict_put(buf + ifo, ifa - ifo,
1180: '(' == buf[ifa] ? MDOC_Fo : MDOC_Dv);
1181:
1.41 schwarze 1182: goto again;
1.22 kristaps 1183: }
1.35 schwarze 1184:
1185: /* Parse function declaration. */
1186: ifo = ifa = ifc = 0;
1.36 schwarze 1187: inl = end;
1188: nopen = 0;
1189: for (ift = i; i < end; i++) {
1190: if (ifc) {
1191: if (buf[i] != '\n')
1192: continue;
1193: inl = i;
1194: break;
1195: }
1196: switch (buf[i]) {
1.45 schwarze 1197: case '\t':
1198: /* FALLTHROUGH */
1.36 schwarze 1199: case ' ':
1200: if ( ! ifa)
1201: ifo = i;
1202: break;
1203: case '(':
1204: if (ifo) {
1205: nopen++;
1206: if ( ! ifa)
1207: ifa = i;
1208: } else
1209: i = end;
1210: break;
1211: case ')':
1212: switch (nopen) {
1213: case 0:
1214: i = end;
1215: break;
1216: case 1:
1.35 schwarze 1217: ifc = i;
1.36 schwarze 1218: break;
1219: default:
1220: nopen--;
1221: break;
1222: }
1223: break;
1224: default:
1225: break;
1226: }
1.35 schwarze 1227: }
1228:
1229: /* Encode function declaration. */
1230: if (ifc) {
1.36 schwarze 1231: for (i = ifa; i < ifc; i++)
1232: if (buf[i] == '\n')
1233: buf[i] = ' ';
1.35 schwarze 1234: buf[ifo++] = '\0';
1.39 schwarze 1235: register_type(buf + ift);
1.53 schwarze 1236: if (indisplay)
1237: puts(".Ed");
1238: indisplay = wantsp = 0;
1.35 schwarze 1239: printf(".Ft %s", buf + ift);
1240: if (buf[ifo] == '*') {
1241: fputs(" *", stdout);
1242: ifo++;
1243: }
1244: putchar('\n');
1245: buf[ifa++] = '\0';
1.39 schwarze 1246: dict_put(buf + ifo, 0, MDOC_Fo);
1.35 schwarze 1247: buf[ifc++] = '\0';
1.62 ! schwarze 1248: if (strcmp(buf + ifa, "void")) {
! 1249: printf(".Fo %s\n", buf + ifo);
! 1250: for (;;) {
! 1251: cp = strchr(buf + ifa, ',');
! 1252: if (cp != NULL) {
! 1253: cp2 = cp;
! 1254: *cp++ = '\0';
! 1255: } else
! 1256: cp2 = strchr(buf + ifa, '\0');
! 1257: while (isalnum((unsigned char)cp2[-1])
! 1258: || '_' == cp2[-1])
! 1259: cp2--;
! 1260: if ('\0' != *cp2)
! 1261: dict_put(cp2, 0, MDOC_Fa);
! 1262: register_type(buf + ifa);
! 1263: if (strchr(buf + ifa, ' ') == NULL)
! 1264: printf(".Fa %s\n", buf + ifa);
! 1265: else
! 1266: printf(".Fa \"%s\"\n",
! 1267: buf + ifa);
! 1268: if (cp == NULL)
! 1269: break;
! 1270: while (*cp == ' ' || *cp == '\t')
! 1271: cp++;
! 1272: ifa = cp - buf;
! 1273: }
! 1274: puts(".Fc");
! 1275: } else
! 1276: printf(".Fn %s void\n", buf + ifo);
1.35 schwarze 1277: if (buf[ifc] == ';')
1278: ifc++;
1.36 schwarze 1279: if (ifc < inl) {
1280: buf[inl] = '\0';
1.35 schwarze 1281: puts(buf + ifc);
1282: }
1.53 schwarze 1283: start = inl < end ? inl + 1 : end;
1284: goto again;
1.35 schwarze 1285: }
1.22 kristaps 1286: }
1.53 schwarze 1287:
1288: if ( ! indisplay)
1289: puts(".Bd -literal");
1290: else if (wantsp)
1291: putchar('\n');
1292: indisplay = 1;
1293: wantsp = 0;
1294:
1295: for (last = '\n'; start < end; start++) {
1.8 kristaps 1296: /*
1297: * Handle accidental macros (newline starting with
1298: * control character) and escapes.
1299: */
1.53 schwarze 1300: if ('\n' == last) {
1301: if ('\n' == buf[start])
1302: goto again;
1.7 kristaps 1303: if ('.' == buf[start] || '\'' == buf[start])
1304: printf("\\&");
1.53 schwarze 1305: }
1.8 kristaps 1306: putchar(last = buf[start]);
1307: if ('\\' == buf[start])
1308: printf("e");
1.7 kristaps 1309: }
1.53 schwarze 1310: if ('\n' != last)
1311: putchar('\n');
1312: if (indisplay)
1313: puts(".Ed");
1.1 schwarze 1314: }
1315:
1316: /*
1.13 kristaps 1317: * See dosynopsisop().
1318: */
1319: static int
1320: hasmatch(const char *buf, size_t start, size_t end)
1321: {
1322: size_t stack;
1323:
1.60 schwarze 1324: for (stack = 0; start < end; start++)
1.13 kristaps 1325: if (buf[start] == '[')
1326: stack++;
1327: else if (buf[start] == ']' && 0 == stack)
1328: return(1);
1329: else if (buf[start] == ']')
1330: stack--;
1331: return(0);
1332: }
1333:
1334: /*
1335: * If we're in the SYNOPSIS section and we've encounter braces in an
1336: * ordinary paragraph, then try to see whether we're an [-option].
1337: * Do this, if we're an opening bracket, by first seeing if we have a
1338: * matching end via hasmatch().
1339: * If we're an ending bracket, see if we have a stack already.
1340: */
1341: static int
1.32 schwarze 1342: dosynopsisop(struct state *st, const char *buf,
1343: size_t *start, size_t end, size_t *opstack)
1.13 kristaps 1344: {
1345:
1346: assert('[' == buf[*start] || ']' == buf[*start]);
1347:
1348: if ('[' == buf[*start] && hasmatch(buf, *start + 1, end)) {
1.32 schwarze 1349: mdoc_newln(st);
1.13 kristaps 1350: puts(".Oo");
1351: (*opstack)++;
1352: } else if ('[' == buf[*start])
1353: return(0);
1354:
1355: if (']' == buf[*start] && *opstack > 0) {
1.32 schwarze 1356: mdoc_newln(st);
1.13 kristaps 1357: puts(".Oc");
1358: (*opstack)--;
1359: } else if (']' == buf[*start])
1360: return(0);
1361:
1362: (*start)++;
1.31 schwarze 1363: last = '\n';
1.13 kristaps 1364: while (' ' == buf[*start])
1365: (*start)++;
1366: return(1);
1367: }
1368:
1369: /*
1.17 kristaps 1370: * Format multiple "Nm" manpage names in the NAME section.
1.32 schwarze 1371: * From the perspective of external callers,
1372: * always stays in OUST_NL/wantws mode,
1373: * but its children do use OUST_MAC.
1.17 kristaps 1374: */
1375: static void
1376: donamenm(struct state *st, const char *buf, size_t *start, size_t end)
1377: {
1378: size_t word;
1379:
1.32 schwarze 1380: assert(OUST_NL == st->oust);
1381: assert(st->wantws);
1382:
1.47 schwarze 1383: while (*start < end && isspace((unsigned char)buf[*start]))
1.17 kristaps 1384: (*start)++;
1385:
1386: if (end == *start) {
1387: puts(".Nm unknown");
1388: return;
1389: }
1390:
1391: while (*start < end) {
1392: for (word = *start; word < end; word++)
1393: if (',' == buf[word])
1394: break;
1.32 schwarze 1395: formatcodeln(st, "Nm", buf, start, word, 1);
1.17 kristaps 1396: if (*start == end) {
1.32 schwarze 1397: mdoc_newln(st);
1398: break;
1.17 kristaps 1399: }
1400: assert(',' == buf[*start]);
1.32 schwarze 1401: printf(" ,");
1402: mdoc_newln(st);
1.17 kristaps 1403: (*start)++;
1.47 schwarze 1404: while (*start < end && isspace((unsigned char)buf[*start]))
1.17 kristaps 1405: (*start)++;
1406: }
1407: }
1408:
1409: /*
1.1 schwarze 1410: * Ordinary paragraph.
1411: * Well, this is really the hardest--POD seems to assume that, for
1412: * example, a leading space implies a newline, and so on.
1413: * Lots of other snakes in the grass: escaping a newline followed by a
1414: * period (accidental mdoc(7) control), double-newlines after macro
1415: * passages, etc.
1.32 schwarze 1416: *
1417: * Uses formatcode() to go to OUST_MAC mode
1418: * and outbuf_flush() to go to OUST_TXT mode.
1.40 schwarze 1419: * In text mode, wantws requests white space before the text
1420: * currently contained in the outbuf, not before upcoming text.
1.32 schwarze 1421: * Must make sure to go back to OUST_NL/wantws mode before returning.
1.1 schwarze 1422: */
1423: static void
1424: ordinary(struct state *st, const char *buf, size_t start, size_t end)
1425: {
1.44 schwarze 1426: size_t i, j, opstack, wend;
1.43 schwarze 1427: enum mdoc_type mtype;
1.44 schwarze 1428: int eos, noeos, seq;
1.49 schwarze 1429: char savechar;
1.1 schwarze 1430:
1431: if ( ! st->parsing || st->paused)
1432: return;
1433:
1434: /*
1435: * Special-case: the NAME section.
1436: * If we find a "-" when searching from the end, assume that
1437: * we're in "name - description" format.
1438: * To wit, print out a "Nm" and "Nd" in that format.
1439: */
1.11 kristaps 1440: if (SECT_NAME == st->sect) {
1.15 kristaps 1441: for (i = end - 2; i > start; i--)
1.47 schwarze 1442: if ('-' == buf[i] &&
1443: isspace((unsigned char)buf[i + 1]))
1.1 schwarze 1444: break;
1445: if ('-' == buf[i]) {
1446: j = i;
1447: /* Roll over multiple "-". */
1448: for ( ; i > start; i--)
1449: if ('-' != buf[i])
1450: break;
1.17 kristaps 1451: donamenm(st, buf, &start, i + 1);
1.5 kristaps 1452: start = j + 1;
1.47 schwarze 1453: while (start < end &&
1454: isspace((unsigned char)buf[start]))
1.17 kristaps 1455: start++;
1.57 schwarze 1456: while (start < end && '.' == buf[end - 1])
1457: end--;
1.32 schwarze 1458: formatcodeln(st, "Nd", buf, &start, end, 1);
1459: mdoc_newln(st);
1.1 schwarze 1460: return;
1461: }
1462: }
1463:
1464: if ( ! st->haspar)
1465: puts(".Pp");
1466:
1467: st->haspar = 0;
1468: last = '\n';
1.13 kristaps 1469: opstack = 0;
1.1 schwarze 1470:
1.15 kristaps 1471: for (seq = 0; start < end; seq++) {
1.60 schwarze 1472: /*
1473: * Loop til we get either to a newline or escape.
1.1 schwarze 1474: * Escape initial control characters.
1475: */
1476: while (start < end) {
1.34 schwarze 1477: if (start < end - 1 && '<' == buf[start + 1] &&
1478: 'A' <= buf[start] && 'Z' >= buf[start])
1.1 schwarze 1479: break;
1480: else if ('\n' == buf[start])
1481: break;
1482: else if ('\n' == last && '.' == buf[start])
1.31 schwarze 1483: outbuf_addstr(st, "\\&");
1.1 schwarze 1484: else if ('\n' == last && '\'' == buf[start])
1.31 schwarze 1485: outbuf_addstr(st, "\\&");
1.12 kristaps 1486: /*
1487: * If we're in the SYNOPSIS, have square
1488: * brackets indicate that we're opening and
1489: * closing an optional context.
1490: */
1.32 schwarze 1491:
1.13 kristaps 1492: if (SECT_SYNOPSIS == st->sect &&
1.60 schwarze 1493: ('[' == buf[start] ||
1.13 kristaps 1494: ']' == buf[start]) &&
1.32 schwarze 1495: dosynopsisop(st, buf,
1496: &start, end, &opstack))
1.13 kristaps 1497: continue;
1.32 schwarze 1498:
1.42 schwarze 1499: /* Merely buffer non-whitespace. */
1.32 schwarze 1500:
1.31 schwarze 1501: last = buf[start++];
1.44 schwarze 1502: if ( ! isspace(last))
1.37 schwarze 1503: outbuf_addchar(st);
1.44 schwarze 1504: if (start < end &&
1.52 schwarze 1505: ! isspace((unsigned char)buf[start - 1]) &&
1.44 schwarze 1506: ! isspace((unsigned char)buf[start]))
1.37 schwarze 1507: continue;
1508:
1.44 schwarze 1509: /*
1510: * Found the end of a word.
1511: * Rewind trailing delimiters.
1512: */
1513:
1514: eos = noeos = 0;
1515: for (wend = st->outbuflen; wend; wend--)
1516: if ('.' == st->outbuf[wend - 1] ||
1517: '!' == st->outbuf[wend - 1] ||
1518: '?' == st->outbuf[wend - 1])
1519: eos = 1;
1520: else if ('|' == st->outbuf[wend - 1] ||
1521: ',' == st->outbuf[wend - 1] ||
1522: ';' == st->outbuf[wend - 1] ||
1523: ':' == st->outbuf[wend - 1])
1524: noeos = 1;
1525: else if ('\'' != st->outbuf[wend - 1] &&
1526: '"' != st->outbuf[wend - 1] &&
1527: ')' != st->outbuf[wend - 1] &&
1528: ']' != st->outbuf[wend - 1])
1529: break;
1530: eos &= ! noeos;
1531:
1532: /*
1533: * Detect function names.
1534: */
1.42 schwarze 1535:
1.43 schwarze 1536: mtype = MDOC_Fa;
1.49 schwarze 1537: savechar = '\0';
1.44 schwarze 1538: if (wend && ')' == st->outbuf[wend] &&
1539: '(' == st->outbuf[wend - 1]) {
1540: mtype = dict_get(st->outbuf, --wend);
1.49 schwarze 1541: if (MDOC_Dv == mtype)
1542: mtype = MDOC_Fo;
1.43 schwarze 1543: if (MDOC_Fo == mtype || MDOC_MAX == mtype) {
1.44 schwarze 1544: st->outbuflen = wend;
1545: st->outbuf[wend] = '\0';
1.43 schwarze 1546: mdoc_newln(st);
1547: if (MDOC_Fo == mtype)
1.56 schwarze 1548: fputs(".Fn", stdout);
1.43 schwarze 1549: else
1.56 schwarze 1550: fputs(".Xr", stdout);
1.43 schwarze 1551: st->oust = OUST_MAC;
1552: }
1.49 schwarze 1553: } else {
1554: mtype = dict_get(st->outbuf, wend);
1555: if (MDOC_Dv == mtype) {
1556: savechar = st->outbuf[wend];
1557: st->outbuf[wend] = '\0';
1558: mdoc_newln(st);
1.56 schwarze 1559: fputs(".Dv", stdout);
1.49 schwarze 1560: st->oust = OUST_MAC;
1561: } else
1562: mtype = MDOC_Fa;
1.37 schwarze 1563: }
1564:
1.42 schwarze 1565: /*
1566: * On whitespace, flush the output buffer
1567: * and allow breaking to a macro line.
1568: */
1569:
1.37 schwarze 1570: outbuf_flush(st);
1.42 schwarze 1571:
1572: /*
1573: * End macro lines, and
1574: * end text lines at the end of sentences.
1575: */
1576:
1.44 schwarze 1577: if (OUST_MAC == st->oust || (eos && wend > 1 &&
1578: islower((unsigned char)st->outbuf[wend - 1]))) {
1.43 schwarze 1579: if (MDOC_MAX == mtype)
1580: fputs(" 3", stdout);
1.49 schwarze 1581: if (MDOC_Fa != mtype) {
1582: if (MDOC_Dv == mtype)
1583: st->outbuf[wend] = savechar;
1584: else
1585: wend += 2;
1586: while ('\0' != st->outbuf[wend])
1.44 schwarze 1587: printf(" %c",
1.49 schwarze 1588: st->outbuf[wend++]);
1589: }
1.40 schwarze 1590: mdoc_newln(st);
1.43 schwarze 1591: }
1.42 schwarze 1592:
1593: /* Advance to the next word. */
1594:
1.44 schwarze 1595: while ('\n' != buf[start] &&
1596: isspace((unsigned char)buf[start]))
1.42 schwarze 1597: start++;
1598: st->wantws = 1;
1.1 schwarze 1599: }
1600:
1.34 schwarze 1601: if (start < end - 1 && '<' == buf[start + 1] &&
1602: 'A' <= buf[start] && 'Z' >= buf[start]) {
1.32 schwarze 1603: formatcode(st, buf, &start, end, 0, seq);
1604: if (OUST_MAC == st->oust) {
1.30 schwarze 1605: /*
1606: * Let mdoc(7) handle trailing punctuation.
1607: * XXX Some punctuation characters
1608: * are not handled yet.
1609: */
1.51 schwarze 1610: if ((start == end - 1 ||
1611: (start < end - 1 &&
1612: (' ' == buf[start + 1] ||
1613: '\n' == buf[start + 1]))) &&
1614: NULL != strchr("|.,;:?!)]", buf[start])) {
1.16 kristaps 1615: putchar(' ');
1616: putchar(buf[start++]);
1617: }
1.32 schwarze 1618:
1619: if (st->wantws ||
1620: ' ' == buf[start] ||
1621: '\n' == buf[start])
1622: mdoc_newln(st);
1623:
1.30 schwarze 1624: /*
1625: * Consume all whitespace
1626: * so we don't accidentally start
1627: * an implicit literal line.
1628: */
1.32 schwarze 1629:
1.6 kristaps 1630: while (start < end && ' ' == buf[start])
1631: start++;
1.32 schwarze 1632:
1633: /*
1634: * Some text is following.
1635: * Implement requested spacing.
1636: */
1637:
1638: if ( ! st->wantws && start < end &&
1.34 schwarze 1639: ('<' != buf[start + 1] ||
1640: 'A' > buf[start] ||
1641: 'Z' < buf[start])) {
1.56 schwarze 1642: fputs(" Ns", stdout);
1.32 schwarze 1643: st->wantws = 1;
1644: }
1.6 kristaps 1645: }
1.1 schwarze 1646: } else if (start < end && '\n' == buf[start]) {
1.32 schwarze 1647: outbuf_flush(st);
1.58 schwarze 1648: st->wantws = 1;
1.1 schwarze 1649: if (++start >= end)
1650: continue;
1651: /*
1652: * If we have whitespace next, eat it to prevent
1653: * mdoc(7) from thinking that it's meant for
1654: * verbatim text.
1655: * It is--but if we start with that, we can't
1656: * have a macro subsequent it, which may be
1657: * possible if we have an escape next.
1658: */
1.58 schwarze 1659: if (' ' == buf[start] || '\t' == buf[start]) {
1660: mdoc_newln(st);
1.1 schwarze 1661: puts(".br");
1.58 schwarze 1662: }
1.1 schwarze 1663: for ( ; start < end; start++)
1664: if (' ' != buf[start] && '\t' != buf[start])
1665: break;
1.60 schwarze 1666: }
1.1 schwarze 1667: }
1.32 schwarze 1668: outbuf_flush(st);
1669: mdoc_newln(st);
1.1 schwarze 1670: }
1671:
1672: /*
1673: * There are three kinds of paragraphs: verbatim (starts with whitespace
1674: * of some sort), ordinary (starts without "=" marker), or a command
1675: * (default: starts with "=").
1676: */
1677: static void
1.35 schwarze 1678: dopar(struct state *st, char *buf, size_t start, size_t end)
1.1 schwarze 1679: {
1680:
1.32 schwarze 1681: assert(OUST_NL == st->oust);
1682: assert(st->wantws);
1683:
1.1 schwarze 1684: if (end == start)
1685: return;
1686: if (' ' == buf[start] || '\t' == buf[start])
1687: verbatim(st, buf, start, end);
1688: else if ('=' != buf[start])
1689: ordinary(st, buf, start, end);
1690: else
1691: command(st, buf, start, end);
1692: }
1693:
1694: /*
1695: * Loop around paragraphs within a document, processing each one in the
1696: * POD way.
1697: */
1698: static void
1.60 schwarze 1699: dofile(const struct args *args, const char *fname,
1.35 schwarze 1700: const struct tm *tm, char *buf, size_t sz)
1.1 schwarze 1701: {
1.29 schwarze 1702: char datebuf[64];
1.1 schwarze 1703: struct state st;
1.46 schwarze 1704: const char *fbase, *fext, *section, *date, *format;
1.1 schwarze 1705: char *title, *cp;
1.53 schwarze 1706: size_t cur, end;
1707: int verb;
1.1 schwarze 1708:
1709: if (0 == sz)
1710: return;
1711:
1.29 schwarze 1712: /*
1713: * Parsing the filename is almost always required,
1714: * except when both the title and the section
1715: * are provided on the command line.
1716: */
1717:
1718: if (NULL == args->title || NULL == args->section) {
1719: fbase = strrchr(fname, '/');
1720: if (NULL == fbase)
1721: fbase = fname;
1722: else
1723: fbase++;
1724: fext = strrchr(fbase, '.');
1725: } else
1726: fext = NULL;
1727:
1728: /*
1729: * The title will be converted to uppercase,
1730: * so it needs to be copied.
1731: */
1732:
1733: title = (NULL != args->title) ? strdup(args->title) :
1734: (NULL != fext) ? strndup(fbase, fext - fbase) :
1735: strdup(fbase);
1.1 schwarze 1736:
1737: if (NULL == title) {
1738: perror(NULL);
1739: exit(EXIT_FAILURE);
1740: }
1741:
1742: /* Section is 1 unless suffix is "pm". */
1743:
1.29 schwarze 1744: section = (NULL != args->section) ? args->section :
1745: (NULL == fext || strcmp(fext + 1, "pm")) ? "1" :
1746: PERL_SECTION;
1.1 schwarze 1747:
1748: /* Date. Or the given "tm" if not supplied. */
1749:
1.46 schwarze 1750: date = args->date;
1751: format = (NULL == date) ? "%B %d, %Y" :
1.48 schwarze 1752: strcmp(date, "Mdocdate") ? NULL : "$" "Mdocdate: %B %d %Y $";
1.46 schwarze 1753:
1754: if (NULL != format) {
1755: strftime(datebuf, sizeof(datebuf), format, tm);
1.1 schwarze 1756: date = datebuf;
1757: }
1758:
1759: for (cp = title; '\0' != *cp; cp++)
1760: *cp = toupper((int)*cp);
1761:
1762: /* The usual mdoc(7) preamble. */
1763:
1764: printf(".Dd %s\n", date);
1765: printf(".Dt %s %s\n", title, section);
1766: puts(".Os");
1767:
1768: free(title);
1769:
1.37 schwarze 1770: dict_init();
1.1 schwarze 1771: memset(&st, 0, sizeof(struct state));
1.32 schwarze 1772: st.oust = OUST_NL;
1773: st.wantws = 1;
1774:
1.1 schwarze 1775: assert(sz > 0);
1776:
1777: /* Main loop over file contents. */
1778:
1.53 schwarze 1779: cur = 0;
1780: for (;;) {
1781: while (cur < sz && '\n' == buf[cur])
1782: cur++;
1783: if (cur >= sz)
1784: break;
1785:
1786: verb = isspace((unsigned char)buf[cur]);
1787:
1.1 schwarze 1788: /* Read until next paragraph. */
1.53 schwarze 1789:
1790: for (end = cur + 1; end + 1 < sz; end++)
1791: if ('\n' == buf[end] && '\n' == buf[end + 1] &&
1792: !(verb && end + 2 < sz &&
1793: isspace((unsigned char)buf[end + 2])))
1.1 schwarze 1794: break;
1.60 schwarze 1795:
1.1 schwarze 1796: /* Adjust end marker for EOF. */
1.53 schwarze 1797:
1798: if (end < sz && '\n' != buf[end])
1799: end++;
1.1 schwarze 1800:
1801: /* Process paragraph and adjust start. */
1.53 schwarze 1802:
1.1 schwarze 1803: dopar(&st, buf, cur, end);
1.53 schwarze 1804: cur = end + 2;
1.1 schwarze 1805: }
1.37 schwarze 1806: dict_destroy();
1.1 schwarze 1807: }
1808:
1809: /*
1810: * Read a single file fully into memory.
1811: * If the file is "-", do it from stdin.
1812: * If successfully read, send the input buffer to dofile() for further
1813: * processing.
1814: */
1815: static int
1816: readfile(const struct args *args, const char *fname)
1817: {
1818: int fd;
1819: char *buf;
1820: size_t bufsz, cur;
1821: ssize_t ssz;
1822: struct tm *tm;
1823: time_t ttm;
1.60 schwarze 1824: struct stat st;
1.1 schwarze 1825:
1.60 schwarze 1826: fd = 0 != strcmp("-", fname) ?
1.1 schwarze 1827: open(fname, O_RDONLY, 0) : STDIN_FILENO;
1828:
1829: if (-1 == fd) {
1830: perror(fname);
1831: return(0);
1832: }
1833:
1834: if (STDIN_FILENO == fd || -1 == fstat(fd, &st)) {
1835: ttm = time(NULL);
1836: tm = localtime(&ttm);
1837: } else
1838: tm = localtime(&st.st_mtime);
1839:
1.60 schwarze 1840: /*
1.1 schwarze 1841: * Arbitrarily-sized initial buffer.
1842: * Should be big enough for most files...
1843: */
1844: cur = 0;
1845: bufsz = 1 << 14;
1846: if (NULL == (buf = malloc(bufsz))) {
1847: perror(NULL);
1848: exit(EXIT_FAILURE);
1849: }
1850:
1851: while ((ssz = read(fd, buf + cur, bufsz - cur)) > 0) {
1852: /* Double buffer size on fill. */
1853: if ((size_t)ssz == bufsz - cur) {
1854: bufsz *= 2;
1855: if (NULL == (buf = realloc(buf, bufsz))) {
1856: perror(NULL);
1857: exit(EXIT_FAILURE);
1858: }
1859: }
1860: cur += (size_t)ssz;
1861: }
1862: if (ssz < 0) {
1863: perror(fname);
1864: free(buf);
1865: return(0);
1866: }
1867:
1.60 schwarze 1868: dofile(args, STDIN_FILENO == fd ?
1.1 schwarze 1869: "STDIN" : fname, tm, buf, cur);
1870: free(buf);
1871: if (STDIN_FILENO != fd)
1872: close(fd);
1873: return(1);
1874: }
1875:
1876: int
1877: main(int argc, char *argv[])
1878: {
1879: const char *fname, *name;
1880: struct args args;
1881: int c;
1882:
1883: name = strrchr(argv[0], '/');
1884: if (name == NULL)
1885: name = argv[0];
1886: else
1887: ++name;
1888:
1889: memset(&args, 0, sizeof(struct args));
1890: fname = "-";
1891:
1892: /* Accept no arguments for now. */
1893:
1894: while (-1 != (c = getopt(argc, argv, "c:d:hln:oq:rs:uv")))
1895: switch (c) {
1896: case ('h'):
1897: /* FALLTHROUGH */
1898: case ('l'):
1899: /* FALLTHROUGH */
1900: case ('c'):
1901: /* FALLTHROUGH */
1902: case ('o'):
1903: /* FALLTHROUGH */
1904: case ('q'):
1905: /* FALLTHROUGH */
1906: case ('r'):
1907: /* FALLTHROUGH */
1908: case ('u'):
1909: /* FALLTHROUGH */
1910: case ('v'):
1911: /* Ignore these. */
1912: break;
1913: case ('d'):
1914: args.date = optarg;
1915: break;
1916: case ('n'):
1917: args.title = optarg;
1918: break;
1919: case ('s'):
1920: args.section = optarg;
1921: break;
1922: default:
1923: goto usage;
1924: }
1925:
1926: argc -= optind;
1927: argv += optind;
1928:
1929: /* Accept only a single input file. */
1930:
1.25 schwarze 1931: if (argc > 1)
1932: goto usage;
1.1 schwarze 1933: else if (1 == argc)
1934: fname = *argv;
1935:
1.60 schwarze 1936: return(readfile(&args, fname) ?
1.1 schwarze 1937: EXIT_SUCCESS : EXIT_FAILURE);
1938:
1939: usage:
1.60 schwarze 1940: fprintf(stderr, "usage: %s [-d date] "
1.25 schwarze 1941: "[-n title] [-s section] [file]\n", name);
1.1 schwarze 1942:
1943: return(EXIT_FAILURE);
1944: }
CVSweb