[BACK]Return to pod2mdoc.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / pod2mdoc

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