author | Olivier Brunel
<jjk@jjacky.com> 2023-05-27 11:26:19 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2023-07-05 07:56:22 UTC |
parent | 173e3b06293409f2b714019b4a6b1687b3bf6e6b |
src/include/qmdoc.h | +0 | -11 |
src/qmdoc/qmdoc.c | +499 | -427 |
diff --git a/src/include/qmdoc.h b/src/include/qmdoc.h index b4af1af..4629e78 100644 --- a/src/include/qmdoc.h +++ b/src/include/qmdoc.h @@ -1,17 +1,6 @@ #ifndef QMDOC_H #define QMDOC_H -enum { - ERR_NONE = 0, - ERR_USAGE = -1, - ERR_IO = -5, - ERR_PARSER = -6, - ERR_MEM = -7, - ERR_NOTSUPP = -8, - ERR_INVALID = -9, - ERR_MISC = -88, -}; - const char metas[] = "<meta charset=\"utf-8\">" "<meta name=\"generator\" content=\"qmdoc\">" diff --git a/src/qmdoc/qmdoc.c b/src/qmdoc/qmdoc.c index 38863f1..8a88f7f 100644 --- a/src/qmdoc/qmdoc.c +++ b/src/qmdoc/qmdoc.c @@ -1,18 +1,14 @@ -#define _GNU_SOURCE /* memmem() */ -#include <stdio.h> -#include <stdarg.h> #include <stdlib.h> -#include <string.h> -#include <fcntl.h> #include <unistd.h> -#include <sys/stat.h> #include <time.h> -#include <getopt.h> -#include <errno.h> #include <limb/buffer.h> +#include <limb/bytestr.h> #include <limb/djbunix.h> #include <limb/exitcode.h> +#include <limb/loadopt.h> #include <limb/output.h> +#include <limb/posixplz.h> +#include <limb/samisc.h> #include <limb/u32.h> #include <limb/unix-transactional.h> #include "md4c.h" @@ -44,24 +40,16 @@ enum { CSS_NO_TOC, NB_CSS }; -static struct css { - const char *file; - size_t offset; -} css[NB_CSS] = { - { "qmdoc.css" }, - { NULL }, - { "no-toc.css" } -}; struct page { - const char *sce; + size_t sceoff; size_t fileoff; size_t titleoff; size_t nameoff; size_t veroff; size_t dateoff; - int fd; size_t size; + int fd; }; enum { @@ -72,26 +60,22 @@ enum { DOC_BUFFERED_A = (1 << 4), }; -struct ctx { - int options; +struct qmdoc { stralloc sa; - size_t otoc; /* where to include to page's TOC */ stralloc sa_out; - int toc_lvl; - struct css *css; - struct page *pages; - int nb_pages; - int cur_page; + size_t opages; + size_t otoc; /* where to include to page's TOC */ + size_t css[NB_CSS]; struct { - const char *title; - const char *subtitle; - const char *author; const char *lang; + size_t otitle; + size_t osubtitle; + size_t oauthor; size_t oheader; size_t ofooter; int flags; } doc; - const char *manurl; + size_t omanurl; struct { stralloc sa; size_t salen; @@ -104,8 +88,14 @@ struct ctx { int flags; int from; } code; + int toc_lvl; + int nb_pages; + int cur_page; + int options; }; +#define PAGE(ctx, n) ((struct page *) ((ctx)->sa.s + (ctx)->opages))[n] + #define BUFFERING_ON() \ ctx->doc.flags |= DOC_BUFFERING; \ ctx->buf.salen = ctx->buf.sa.len @@ -117,17 +107,19 @@ struct ctx { ctx->doc.flags &= ~DOC_BUFFERING enum { - ERR_PARSER_ENTER_BLOCK = -100, - ERR_PARSER_LEAVE_BLOCK = -101, - ERR_PARSER_ENTER_SPAN = -102, - ERR_PARSER_LEAVE_SPAN = -103, - ERR_PARSER_TEXT = -104, - ERR_PARSER_BUFFERED = -105, - ERR_PARSER_TOC = -106, + ERR_INVALID = -100, + ERR_NOTSUPP = -101, + ERR_ENTER_BLOCK = -100, + ERR_LEAVE_BLOCK = -101, + ERR_ENTER_SPAN = -102, + ERR_LEAVE_SPAN = -103, + ERR_TEXT = -104, + ERR_BUFFERED = -105, + ERR_TOC = -106, }; static int -raw_text(struct ctx *ctx, const char *text, size_t size) +raw_text(struct qmdoc *ctx, const char *text, size_t size) { stralloc *sa = (ctx->doc.flags & DOC_BUFFERING) ? &ctx->buf.sa : &ctx->sa_out; return stralloc_catb(sa, text, size); @@ -136,7 +128,7 @@ raw_text(struct ctx *ctx, const char *text, size_t size) #define raw_str(ctx,s) raw_text(ctx, s, strlen(s)) static int -escape_text(struct ctx *ctx, const char *text, size_t size) +escape_text(struct qmdoc *ctx, const char *text, size_t size) { size_t last = 0; for (size_t n = 0; n < size; ++n) { @@ -166,7 +158,7 @@ escape_text(struct ctx *ctx, const char *text, size_t size) } static int -anchor(struct ctx *ctx, const char *text, size_t size) +anchor(struct qmdoc *ctx, const char *text, size_t size) { char s[size]; size_t skipped = 0; @@ -195,9 +187,9 @@ anchor(struct ctx *ctx, const char *text, size_t size) } static int -strip_tags(struct ctx *ctx, const char *text, size_t size, int no_escaping) +strip_tags(struct qmdoc *ctx, const char *text, size_t size, int no_escaping) { - int (*do_text) (struct ctx *, const char *, size_t) = (no_escaping) ? raw_text : escape_text; + int (*do_text) (struct qmdoc *, const char *, size_t) = (no_escaping) ? raw_text : escape_text; const char *s; for(;;) { s = memchr(text, '<', size); @@ -216,7 +208,7 @@ strip_tags(struct ctx *ctx, const char *text, size_t size, int no_escaping) } static int -highlight_escape_text(struct ctx *ctx, const char *text, size_t size) +highlight_escape_text(struct qmdoc *ctx, const char *text, size_t size) { struct { const char *open; @@ -257,7 +249,7 @@ highlight_escape_text(struct ctx *ctx, const char *text, size_t size) } static int -attribute(struct ctx *ctx, MD_ATTRIBUTE *attr) +attribute(struct qmdoc *ctx, MD_ATTRIBUTE *attr) { int n = 0; MD_SIZE l, cur = 0, size = attr->size; @@ -336,76 +328,78 @@ get_section(MD_ATTRIBUTE *attr) static int enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) { - struct ctx *ctx = ctx_; + struct qmdoc *ctx = ctx_; switch (type) { case MD_BLOCK_DOC: { - struct page *p = &ctx->pages[ctx->cur_page]; - -#define offset(i) ((ctx->pages[i].nameoff) ? ctx->pages[i].nameoff : ctx->pages[i].titleoff) +#define offset(i) ((PAGE(ctx, i).nameoff) ? PAGE(ctx, i).nameoff : PAGE(ctx, i).titleoff) #define str_title(i) ctx->sa.s + offset(i) -#define str_file(i) ctx->sa.s + ctx->pages[i].fileoff - size_t authlen = strlen(ctx->doc.author); +#define str_file(i) ctx->sa.s + PAGE(ctx, i).fileoff if (!raw_str(ctx, "<!DOCTYPE html>\n<html lang=\"") || !escape_text(ctx, ctx->doc.lang, strlen(ctx->doc.lang)) || !raw_str(ctx, "\"><head>") || !raw_str(ctx, metas) - || (authlen && (!raw_str(ctx, "<meta name=\"author\" content=\"") - || !escape_text(ctx, ctx->doc.author, authlen) - || !raw_str(ctx, "\">"))) + || (ctx->doc.oauthor != (size_t) -1 + && (!raw_str(ctx, "<meta name=\"author\" content=\"") + || !escape_text(ctx, ctx->sa.s + ctx->doc.oauthor, + strlen(ctx->sa.s + ctx->doc.oauthor)) + || !raw_str(ctx, "\">"))) || !raw_str(ctx, "<title>")) - return ERR_PARSER_ENTER_BLOCK; - if (p->nameoff) { - if (!escape_text(ctx, ctx->sa.s + p->nameoff, - strlen(ctx->sa.s + p->nameoff)) + return ERR_ENTER_BLOCK; + if (PAGE(ctx, ctx->cur_page).nameoff) { + if (!escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff)) || !raw_str(ctx, " - ")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } - if (!escape_text(ctx, ctx->sa.s + p->titleoff, - strlen(ctx->sa.s + p->titleoff)) + if (!escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).titleoff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).titleoff)) || !raw_str(ctx, "</title>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; if (ctx->options & OPT_INLINE_CSS) { for (int i = 0; i < NB_CSS; ++i) { - if (ctx->css[i].file && + if (ctx->css[i] != (size_t) -1 && ( !raw_str(ctx, "<style>") - || !raw_str(ctx, ctx->sa.s + ctx->css[i].offset) + || !raw_str(ctx, ctx->sa.s + ctx->css[i]) || !raw_str(ctx, "</style>") )) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } } else { for (int i = 0; i < NB_CSS; ++i) { if ((i == CSS_CUSTOM || !(ctx->options & OPT_NO_CSS)) - && ctx->css[i].file && + && ctx->css[i] != (size_t) -1 && ( !raw_str(ctx, "<link rel=\"stylesheet\" href=\"") - || !escape_text(ctx, ctx->css[i].file, strlen(ctx->css[i].file)) + || !escape_text(ctx, ctx->sa.s + ctx->css[i], + strlen(ctx->sa.s + ctx->css[i])) || !raw_str(ctx, "\">") )) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } } if (!raw_str(ctx, "</head><body>") || (ctx->doc.oheader && (ctx->options & OPT_WIDE_INC) && !raw_str(ctx, ctx->sa.s + ctx->doc.oheader)) || !raw_str(ctx, "<main>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; if (!(ctx->options & OPT_NO_TOC)) { if (!raw_str(ctx, "<header class=\"toc\"><section><h1>") - || !escape_text(ctx, ctx->doc.title, strlen(ctx->doc.title)) + || !escape_text(ctx, ctx->sa.s + ctx->doc.otitle, + strlen(ctx->sa.s + ctx->doc.otitle)) || !raw_str(ctx, "</h1>") - || (ctx->doc.subtitle && (!raw_str(ctx, "<h2>") - || !escape_text(ctx, - ctx->doc.subtitle, - strlen(ctx->doc.subtitle)) - || !raw_str(ctx, "</h2>")) + || (ctx->doc.osubtitle != (size_t) -1 + && (!raw_str(ctx, "<h2>") + || !escape_text(ctx, + ctx->sa.s + ctx->doc.osubtitle, + strlen(ctx->sa.s + ctx->doc.osubtitle)) + || !raw_str(ctx, "</h2>")) ) || !raw_str(ctx, "</section><nav><ul class=\"toc page\">")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; for (int i = 0; i < ctx->nb_pages; ++i) { if (!raw_str(ctx, "<li><a href=\"") @@ -415,7 +409,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !raw_str(ctx, "\">") || !escape_text(ctx, str_title(i), strlen(str_title(i))) || !raw_str(ctx, "</a>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; /* remember positions for TOC */ if (i == ctx->cur_page) { @@ -425,7 +419,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) /* starting it? */ if (i == 1) { if (!raw_str(ctx, "<ul class=\"toc page\">")) - return ERR_PARSER_TOC; + return ERR_TOC; } /* adding page title */ if (i > 0 && @@ -436,7 +430,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !raw_str(ctx, "\">") || !escape_text(ctx, str_title(i), strlen(str_title(i))) || !raw_str(ctx, "</a>"))) - return ERR_PARSER_TOC; + return ERR_TOC; } /* where it begins */ @@ -446,33 +440,33 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) /* open it */ if (!raw_str(ctx, "<ul class=\"toc h1\">")) - return ERR_PARSER_TOC; + return ERR_TOC; ctx->toc_lvl = 1; ctx->doc.flags &= ~DOC_BUFFERING; } } if (!raw_str(ctx, "</ul></nav></header>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } if (!raw_str(ctx, "<section class=\"content\">") || (ctx->doc.oheader && !(ctx->options & OPT_WIDE_INC) && !raw_str(ctx, ctx->sa.s + ctx->doc.oheader))) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; - if (p->nameoff) { + if (PAGE(ctx, ctx->cur_page).nameoff) { if (!raw_str(ctx, "<header class=\"manpage\"><div class=\"left\">") - || !escape_text(ctx, ctx->sa.s + p->nameoff, - strlen(ctx->sa.s + p->nameoff)) + || !escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff)) || !raw_str(ctx, "</div><div class=\"middle\">") - || !escape_text(ctx, ctx->sa.s + p->titleoff, - strlen(ctx->sa.s + p->titleoff)) + || !escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).titleoff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).titleoff)) || !raw_str(ctx, "</div><div class=\"right\">") - || !escape_text(ctx, ctx->sa.s + p->nameoff, - strlen(ctx->sa.s + p->nameoff)) + || !escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff)) || !raw_str(ctx, "</div></header>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } #undef str_title #undef str_file @@ -481,12 +475,12 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) case MD_BLOCK_QUOTE: if (!raw_str(ctx, "<blockquote>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; break; case MD_BLOCK_INDENT: if (!raw_str(ctx, "<div class=\"indent\">")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; break; case MD_BLOCK_BOX: @@ -497,22 +491,22 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) if (!raw_str(ctx, "<div class=\"box ") || !raw_str(ctx, box_types[idx].class) || !raw_str(ctx, "\"><span>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; if (d->title.substr_offsets[0] + d->title.substr_offsets[1] == 0) { if (!raw_str(ctx, box_types[idx].title)) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } else { if (!attribute(ctx, &d->title)) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } if (!raw_str(ctx, "</span>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; break; case MD_BLOCK_UL: if (!raw_str(ctx, "<ul>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; break; case MD_BLOCK_OL: @@ -526,7 +520,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !raw_str(ctx, buf)) ) || !raw_str(ctx, ">")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } break; @@ -540,13 +534,13 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !raw_str(ctx, "\"") )) || !raw_str(ctx, ">")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; } break; case MD_BLOCK_HR: if (!raw_str(ctx, "<hr>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; break; case MD_BLOCK_H: @@ -555,7 +549,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) ctx->title.level = d->level; if ((ctx->doc.flags & DOC_HAS_TITLE) && !raw_str(ctx, "</section>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; ctx->doc.flags |= DOC_HAS_TITLE | DOC_BUFFERING; if (!(ctx->options & OPT_NO_TOC)) { @@ -565,11 +559,11 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) if (!raw_str(ctx, "<ul class=\"toc h") || !raw_text(ctx, &n, 1) || !raw_str(ctx, "\">")) - return ERR_PARSER_TOC; + return ERR_TOC; } for ( ; ctx->toc_lvl > d->level; --ctx->toc_lvl) { if (!raw_str(ctx, "</ul>")) - return ERR_PARSER_TOC; + return ERR_TOC; } ctx->buf.salen = ctx->buf.sa.len; } @@ -583,7 +577,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) ctx->code.flags = 0; if (!d->info.text || !strncmp(d->info.text, "pre\n", 4)) { - return (raw_str(ctx, "<pre>")) ? 0 : ERR_PARSER_ENTER_BLOCK; + return (raw_str(ctx, "<pre>")) ? 0 : ERR_ENTER_BLOCK; } else { const char *t = d->info.text; size_t l = d->info.size; @@ -629,7 +623,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) if (ctx->code.flags & CODE_BUFFERED) { BUFFERING_ON(); } else { - return (raw_str(ctx, "<pre>")) ? 0 : ERR_PARSER_ENTER_BLOCK; + return (raw_str(ctx, "<pre>")) ? 0 : ERR_ENTER_BLOCK; } } } @@ -640,7 +634,7 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) case MD_BLOCK_P: if (!raw_str(ctx, "<p>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_ENTER_BLOCK; break; case MD_BLOCK_TABLE: @@ -658,13 +652,11 @@ enter_block(MD_BLOCKTYPE type, void *details, void *ctx_) static int leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) { - struct ctx *ctx = ctx_; + struct qmdoc *ctx = ctx_; switch (type) { case MD_BLOCK_DOC: ; - struct page *p = &ctx->pages[ctx->cur_page]; - char year[U32_FMT]; struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -672,25 +664,25 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) localtime_r(&ts.tv_sec, &tm); year[u32_fmt(year, (u32) 1900 + tm.tm_year)] = '\0'; if ((ctx->doc.flags & DOC_HAS_TITLE) && !raw_str(ctx, "</section>")) - return ERR_PARSER_LEAVE_BLOCK; - if (p->nameoff) { + return ERR_LEAVE_BLOCK; + if (PAGE(ctx, ctx->cur_page).nameoff) { if (!raw_str(ctx, "<footer class=\"manpage\"><div class=\"left\">") - || !escape_text(ctx, ctx->sa.s + p->veroff, - strlen(ctx->sa.s + p->veroff)) + || !escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).veroff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).veroff)) || !raw_str(ctx, "</div><div class=\"middle\">") - || !escape_text(ctx, ctx->sa.s + p->dateoff, - strlen(ctx->sa.s + p->dateoff)) + || !escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).dateoff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).dateoff)) || !raw_str(ctx, "</div><div class=\"right\">") - || !escape_text(ctx, ctx->sa.s + p->nameoff, - strlen(ctx->sa.s + p->nameoff)) + || !escape_text(ctx, ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff, + strlen(ctx->sa.s + PAGE(ctx, ctx->cur_page).nameoff)) || !raw_str(ctx, "</div></footer>")) - return ERR_PARSER_ENTER_BLOCK; + return ERR_LEAVE_BLOCK; } if (ctx->options & OPT_BUTTONS) { -#define str_title(i) ctx->sa.s + ctx->pages[i].titleoff -#define str_file(i) ctx->sa.s + ctx->pages[i].fileoff +#define str_title(i) ctx->sa.s + PAGE(ctx, i).titleoff +#define str_file(i) ctx->sa.s + PAGE(ctx, i).fileoff if (!raw_str(ctx, "<section id=\"navbuttons\">")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; if (ctx->cur_page > 0 && (!raw_str(ctx, "<a class=\"prev\" href=\"") || !escape_text(ctx, str_file(ctx->cur_page - 1), @@ -699,7 +691,7 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !escape_text(ctx, str_title(ctx->cur_page - 1), strlen(str_title(ctx->cur_page - 1))) || !raw_str(ctx, "\">Previous</a>"))) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; if (ctx->cur_page < ctx->nb_pages - 1 && (!raw_str(ctx, "<a class=\"next\" href=\"") || !escape_text(ctx, str_file(ctx->cur_page + 1), @@ -708,16 +700,18 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !escape_text(ctx, str_title(ctx->cur_page + 1), strlen(str_title(ctx->cur_page + 1))) || !raw_str(ctx, "\">Next</a>"))) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; if (!raw_str(ctx, "</section>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; #undef str_file #undef str_title } if (!raw_str(ctx, "<footer class=\"page\">Copyright © ") || !raw_str(ctx, year) - || !raw_str(ctx, " ") - || !escape_text(ctx, ctx->doc.author, strlen(ctx->doc.author)) + || (ctx->doc.oauthor != (size_t) -1 + && !raw_str(ctx, " ") + && !escape_text(ctx, ctx->sa.s + ctx->doc.oauthor, + strlen(ctx->sa.s + ctx->doc.oauthor))) || !raw_str(ctx, "<br>" "<span class=\"generated\">Generated with qmdoc</span>" "</footer>") @@ -727,37 +721,37 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) || (ctx->doc.ofooter && (ctx->options & OPT_WIDE_INC) && !raw_str(ctx, ctx->sa.s + ctx->doc.ofooter)) || !raw_str(ctx, "</body></html>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_QUOTE: if (!raw_str(ctx, "</blockquote>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_INDENT: if (!raw_str(ctx, "</div>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_BOX: if (!raw_str(ctx, "</div>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_UL: if (!raw_str(ctx, "</ul>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_OL: if (!raw_str(ctx, "</ol>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_LI: if (!raw_str(ctx, "</li>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_HR: @@ -786,13 +780,13 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !raw_str(ctx, "\"></a></h") || !raw_str(ctx, buf) || !raw_str(ctx, ">")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; if (!(ctx->options & OPT_NO_TOC)) { /* TOC */ char toc[l]; memcpy(toc, s, l); - const char *file = ctx->sa.s + ctx->pages[ctx->cur_page].fileoff; + const char *file = ctx->sa.s + PAGE(ctx, ctx->cur_page).fileoff; ctx->doc.flags |= DOC_BUFFERING; if (!raw_str(ctx, "<li><a href=\"") || !escape_text(ctx, file, strlen(file)) @@ -803,7 +797,7 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) || !raw_str(ctx, "\">") || !strip_tags(ctx, toc, l, 1) || !raw_str(ctx, "</a>")) - return ERR_PARSER_TOC; + return ERR_TOC; ctx->doc.flags &= ~DOC_BUFFERING; } } @@ -811,10 +805,10 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) case MD_BLOCK_CODE: { - struct ctx *ctx = ctx_; + struct qmdoc *ctx = ctx_; if (!(ctx->code.flags & CODE_BUFFERED)) { - return (raw_str(ctx, "</pre>")) ? 0 : ERR_PARSER_LEAVE_BLOCK; + return (raw_str(ctx, "</pre>")) ? 0 : ERR_LEAVE_BLOCK; } else { BUFFERING_OFF(buf, blen); @@ -830,7 +824,7 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) codelen = (e) ? e - d->info.text : d->info.size; if (!raw_str(ctx, "<div class=\"code\"><pre class=\"lineno\">")) - return ERR_PARSER_BUFFERED; + return ERR_BUFFERED; for (int n = ctx->code.from; n > 0 && s; ++n) { const char *e = memchr(s, '\n', l); if (!e) break; @@ -839,17 +833,17 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) char buf[U32_FMT]; buf[u32_fmt(buf, (u32) n)] = '\0'; if (!raw_str(ctx, buf) || !raw_str(ctx, "\n")) - return ERR_PARSER_BUFFERED; + return ERR_BUFFERED; } if (!raw_str(ctx, "</pre>") || !raw_str(ctx, "<pre>") || !raw_str(ctx, "<span>") || !escape_text(ctx, d->info.text, codelen) || !raw_str(ctx, "</span>") || !raw_str(ctx, "<code>")) - return ERR_PARSER_BUFFERED; + return ERR_BUFFERED; } else { if (!raw_str(ctx, "<pre>")) - return ERR_PARSER_BUFFERED; + return ERR_BUFFERED; } int r; @@ -857,14 +851,14 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) r = highlight_escape_text(ctx, buf, blen); else r = escape_text(ctx, buf, blen); - if (!r) return ERR_PARSER_BUFFERED; + if (!r) return ERR_BUFFERED; if (ctx->code.flags & CODE_LINES) { if (!raw_str(ctx, "</code></pre></div>")) - return ERR_PARSER_BUFFERED; + return ERR_BUFFERED; } else { if (!raw_str(ctx, "</pre>")) - return ERR_PARSER_BUFFERED; + return ERR_BUFFERED; } ctx->code.flags = 0; @@ -877,7 +871,7 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) case MD_BLOCK_P: if (!raw_str(ctx, "</p>")) - return ERR_PARSER_LEAVE_BLOCK; + return ERR_LEAVE_BLOCK; break; case MD_BLOCK_TABLE: @@ -895,17 +889,17 @@ leave_block(MD_BLOCKTYPE type, void *details, void *ctx_) static int enter_span(MD_SPANTYPE type, void *details, void *ctx_) { - struct ctx *ctx = ctx_; + struct qmdoc *ctx = ctx_; switch (type) { case MD_SPAN_EM: if (!raw_str(ctx, "<em>")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; break; case MD_SPAN_STRONG: if (!raw_str(ctx, "<strong>")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; break; case MD_SPAN_A: @@ -917,17 +911,17 @@ enter_span(MD_SPANTYPE type, void *details, void *ctx_) ctx->doc.flags |= DOC_BUFFERED_A; } else if (!d->href.size) { warn("Link without target"); - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; } else { if (!raw_str(ctx, "<a href=\"") || !attribute(ctx, &d->href)) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; if (d->title.text && (!raw_str(ctx, "\" title=\"") || !attribute(ctx, &d->title))) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; if (!raw_str(ctx, "\">")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; } } break; @@ -938,17 +932,17 @@ enter_span(MD_SPANTYPE type, void *details, void *ctx_) case MD_SPAN_CODE: if (!raw_str(ctx, "<code>")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; break; case MD_SPAN_DEL: if (!raw_str(ctx, "<s>")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; break; case MD_SPAN_HIGHLIGHT: if (!raw_str(ctx, "<span class=\"highlighted\">")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; break; case MD_SPAN_WIKILINK: @@ -964,8 +958,8 @@ enter_span(MD_SPANTYPE type, void *details, void *ctx_) * link to its anchor */ for (int i = 0; i < ctx->nb_pages; ++i) { - if (!memcmp(s, ctx->sa.s + ctx->pages[i].fileoff, l) - && !strcmp(".html", ctx->sa.s + ctx->pages[i].fileoff + l)) { + if (!memcmp(s, ctx->sa.s + PAGE(ctx, i).fileoff, l) + && !strcmp(".html", ctx->sa.s + PAGE(ctx, i).fileoff + l)) { page = i; break; } @@ -973,14 +967,14 @@ enter_span(MD_SPANTYPE type, void *details, void *ctx_) if (!raw_str(ctx, "<a href=\"") || (page >= 0 && ( - !raw_str(ctx, ctx->sa.s + ctx->pages[page].fileoff) + !raw_str(ctx, ctx->sa.s + PAGE(ctx, page).fileoff) )) || (page < 0 && ( !raw_text(ctx, "#", 1) || !anchor(ctx, s, l) )) || !raw_str(ctx, "\">")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; } break; @@ -990,7 +984,7 @@ enter_span(MD_SPANTYPE type, void *details, void *ctx_) case MD_SPAN_U: if (!raw_str(ctx, "<u>")) - return ERR_PARSER_ENTER_SPAN; + return ERR_ENTER_SPAN; break; } @@ -1000,17 +994,17 @@ enter_span(MD_SPANTYPE type, void *details, void *ctx_) static int leave_span(MD_SPANTYPE type, void *details, void *ctx_) { - struct ctx *ctx = ctx_; + struct qmdoc *ctx = ctx_; switch (type) { case MD_SPAN_EM: if (!raw_str(ctx, "</em>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; break; case MD_SPAN_STRONG: if (!raw_str(ctx, "</strong>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; break; case MD_SPAN_A: @@ -1023,7 +1017,7 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) ctx->doc.flags &= ~DOC_BUFFERED_A; if (section < 0) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; char buf[U32_FMT + 2]; int e; @@ -1034,7 +1028,7 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) const char *file; for (int i = 0; i < ctx->nb_pages; ++i) { - file = ctx->sa.s + ctx->pages[i].fileoff; + file = ctx->sa.s + PAGE(ctx, i).fileoff; if (!memcmp(file, s, l) && file[l] == '.' && file[l + 1] == section + '0' @@ -1043,9 +1037,9 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) file = NULL; } - if (file || ctx->manurl) { + if (file || ctx->omanurl != (size_t) -1) { if (!raw_str(ctx, "<a href=\"") - || (!file && !raw_str(ctx, ctx->manurl)) + || (!file && !raw_str(ctx, ctx->sa.s + ctx->omanurl)) || !raw_text(ctx, s, l) || !raw_text(ctx, ".", 1) || !raw_text(ctx, buf + 1, 1) @@ -1056,17 +1050,17 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) || !raw_str(ctx, "</strong>") || !raw_str(ctx, buf) || !raw_str(ctx, "</a>")) - return ERR_PARSER_ENTER_SPAN; + return ERR_LEAVE_SPAN; } else { if (!raw_str(ctx, "<strong>") || !raw_text(ctx, s, l) || !raw_str(ctx, "</strong>") || !raw_str(ctx, buf)) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; } } else { if (!raw_str(ctx, "</a>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; } } break; @@ -1077,22 +1071,22 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) case MD_SPAN_CODE: if (!raw_str(ctx, "</code>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; break; case MD_SPAN_DEL: if (!raw_str(ctx, "</s>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; break; case MD_SPAN_HIGHLIGHT: if (!raw_str(ctx, "</span>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; break; case MD_SPAN_WIKILINK: if (!raw_str(ctx, "</a>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; break; case MD_SPAN_LATEXMATH: @@ -1101,7 +1095,7 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) case MD_SPAN_U: if (!raw_str(ctx, "</u>")) - return ERR_PARSER_LEAVE_SPAN; + return ERR_LEAVE_SPAN; break; } @@ -1111,12 +1105,12 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) static int text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) { - struct ctx *ctx = ctx_; + struct qmdoc *ctx = ctx_; switch (type) { case MD_TEXT_NORMAL: if (!escape_text(ctx, text, size)) - return ERR_PARSER_TEXT; + return ERR_TEXT; break; case MD_TEXT_NULLCHAR: @@ -1124,25 +1118,25 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) case MD_TEXT_BR: if (!raw_str(ctx, "<br>")) - return ERR_PARSER_TEXT; + return ERR_TEXT; break; case MD_TEXT_SOFTBR: if (!raw_str(ctx, "\n")) - return ERR_PARSER_TEXT; + return ERR_TEXT; break; case MD_TEXT_ENTITY: if (!escape_text(ctx, text, size)) - return ERR_PARSER_TEXT; + return ERR_TEXT; break; case MD_TEXT_CODE: if (ctx->code.flags & CODE_BUFFERED) { if (!raw_text(ctx, text, size)) - return ERR_PARSER_TEXT; + return ERR_TEXT; } else if (!escape_text(ctx, text, size)) { - return ERR_PARSER_TEXT; + return ERR_TEXT; } break; @@ -1171,7 +1165,7 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) /* buf.otoc is where our current page's toc begins, which we * don't want to include in the full toc */ if (!raw_text(ctx, ctx->buf.sa.s, ctx->buf.otoc)) - return ERR_PARSER_TOC; + return ERR_TOC; return 0; } @@ -1179,12 +1173,12 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) for (i = 0, n = sizeof(tags) / sizeof(*tags); i < n; ++i) { if (size == tags[i].len && !strncmp(text, tags[i].name, tags[i].len)) { if (!raw_str(ctx, (tags[i].repl) ? tags[i].repl : tags[i].name)) - return ERR_PARSER_TEXT; + return ERR_TEXT; break; } } if (i == n && !escape_text(ctx, text, size)) - return ERR_PARSER_TEXT; + return ERR_TEXT; break; case MD_TEXT_LATEXMATH: @@ -1195,45 +1189,41 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) } static int -load_source(struct ctx *ctx, size_t *salen) +load_source(struct qmdoc *ctx, size_t *salen) { - struct page *p = &ctx->pages[ctx->cur_page]; - *salen = ctx->sa.len; - if (!stralloc_readyplus(&ctx->sa, p->size + 1)) - return ERR_MEM; + if (!stralloc_readyplus(&ctx->sa, PAGE(ctx, ctx->cur_page).size + 1)) + return EX_TEMPFAIL; - if (allread(p->fd, ctx->sa.s + *salen, p->size) != p->size) - retwusys(ERR_IO, "read source file"); - ctx->sa.len += p->size; + if (allread(PAGE(ctx, ctx->cur_page).fd, ctx->sa.s + *salen, + PAGE(ctx, ctx->cur_page).size) != PAGE(ctx, ctx->cur_page).size) + retwusys(EX_NOINPUT, "read source file"); + ctx->sa.len += PAGE(ctx, ctx->cur_page).size; /* ending on a new line allows parser optimization */ - if (ctx->sa.s[ctx->sa.len - 1] != '\n') { + if (ctx->sa.s[ctx->sa.len - 1] != '\n') stralloc_catb(&ctx->sa, "\n", 1); - } return 0; } static int -convert_page(struct ctx *ctx, int fddest) +convert_page(struct qmdoc *ctx, int fddest) { - struct page *p = &ctx->pages[ctx->cur_page]; - - const char *dst = ctx->sa.s + p->fileoff; + const char *dst = ctx->sa.s + PAGE(ctx, ctx->cur_page).fileoff; int fd; if (ctx->options & OPT_OVERWRITE) fd = open_truncat(fddest, dst); else fd = open_exclat(fddest, dst); - if (fd < 0) retwusys(ERR_IO, "create destination"); + if (fd < 0) retwusys(EX_IOERR, "create destination"); size_t salen; const char *sce; - if (p->fd >= 0) { + if (PAGE(ctx, ctx->cur_page).fd >= 0) { int r = load_source(ctx, &salen); - if (r < 0) return r; + if (r) return r; sce = ctx->sa.s + salen; } else { salen = ctx->sa.len; @@ -1255,22 +1245,16 @@ convert_page(struct ctx *ctx, int fddest) .leave_span = leave_span, .text = text, }; - int r = md_parse(sce, p->size, &parser, ctx); - if (r != 0) { - char buf[U32_FMT]; - buf[u32_fmt(buf, (u32) (r < 0) ? -r : r)] = '\0'; - retw(ERR_PARSER, (r < 0) ? "parser internal error " : "parser error ", buf); - } + int r = md_parse(sce, PAGE(ctx, ctx->cur_page).size, &parser, ctx); + if (r) + retw(EX_DATA_ERR, "parser error ", PMINT(r)); if (!(ctx->options & OPT_NO_TOC)) { /* close TOC */ ctx->doc.flags |= DOC_BUFFERING; for ( ; ctx->toc_lvl > 0; --ctx->toc_lvl) { - if (!raw_str(ctx, "</ul>")) { - char buf[U32_FMT]; - buf[u32_fmt(buf, (u32) ERR_PARSER_TOC)] = '\0'; - retw(ERR_PARSER, "parser internal error ", buf); - } + if (!raw_str(ctx, "</ul>")) + retw(EX_DATA_ERR, "parser error ", PMINT(ERR_TOC)); } ctx->doc.flags &= ~DOC_BUFFERING; } @@ -1285,7 +1269,7 @@ convert_page(struct ctx *ctx, int fddest) || allwrite(fd, ctx->sa_out.s + ctx->otoc, ctx->sa_out.len - ctx->otoc) != ctx->sa_out.len - ctx->otoc ) - retwusys(ERR_IO, "write destination"); + retwusys(EX_IOERR, "write destination"); fd_close(fd); /* reset sa_out position */ @@ -1294,7 +1278,7 @@ convert_page(struct ctx *ctx, int fddest) if (!(ctx->doc.flags & DOC_FULL_TOC)) ctx->buf.sa.len = 0; - fd_close(p->fd); + fd_close(PAGE(ctx, ctx->cur_page).fd); ctx->sa.len = salen; return 0; @@ -1309,29 +1293,33 @@ empty(const char *s) } static int -load_page_from_file(const char *file, struct page *page, stralloc *sa) +load_page_from_file(stralloc *sa, size_t fileoff, size_t flen, int pgn, struct qmdoc *ctx) { - page->fd = open_read(file); - if (page->fd < 0) retwusys(ERR_IO, "open '", file, "'"); - - page->sce = strrchr(file, '/'); - if (!page->sce) page->sce = file; - else ++page->sce; - - size_t l = strlen(page->sce); - page->fileoff = sa->len; - if (!stralloc_catb(sa, page->sce, l - 2) - || !stralloc_catb(sa, "html", 5)) - retwusys(EX_TEMPFAIL, "load page title from '", file, "'"); +#define file() (sa->s + fileoff) + PAGE(ctx, pgn).fd = open_read(file()); + if (PAGE(ctx, pgn).fd < 0) retwusys(EX_NOINPUT, "open ", ESC, file(), ESC); + + PAGE(ctx, pgn).sceoff = byte_rchr(file(), flen, '/'); + if (PAGE(ctx, pgn).sceoff == flen) PAGE(ctx, pgn).sceoff = 0; + else ++PAGE(ctx, pgn).sceoff; + + /* l = strlen(file() + PAGE(ctx, pgn).sceoff) */ + size_t l = flen - PAGE(ctx, pgn).sceoff; + PAGE(ctx, pgn).sceoff += fileoff; + PAGE(ctx, pgn).fileoff = sa->len; + if (!stralloc_readyplus(sa, l - 2 + 5)) + retwusys(EX_TEMPFAIL, "load page title from ", ESC, file(), ESC); + stralloc_catb(sa, sa->s + PAGE(ctx, pgn).sceoff, l - 2); + stralloc_catb(sa, "html", 5); char buf_[256], buf[sizeof(buf_)]; - buffer buffer = BUFFER_INIT(&fd_readv, page->fd, buf_, sizeof(buf_)); + buffer buffer = BUFFER_INIT(&fd_readv, PAGE(ctx, pgn).fd, buf_, sizeof(buf_)); ssize_t left = buffer_get(&buffer, buf, sizeof(buf)); if (left <= 0) - retwu(EX_IOERR, "load page title from '", file, "'"); + retwu(EX_NOINPUT, "load page title from ", ESC, file(), ESC); - page->titleoff = page->fileoff; + PAGE(ctx, pgn).titleoff = PAGE(ctx, pgn).fileoff; char *b = buf; int line = 1, begin = 1, is_hdr = 0; @@ -1344,20 +1332,21 @@ load_page_from_file(const char *file, struct page *page, stralloc *sa) size_t *offset = NULL; switch (line) { case 1: - offset = &page->titleoff; + offset = &PAGE(ctx, pgn).titleoff; break; case 2: - offset = &page->nameoff; + offset = &PAGE(ctx, pgn).nameoff; break; case 3: - offset = &page->veroff; + offset = &PAGE(ctx, pgn).veroff; break; case 4: - offset = &page->dateoff; + offset = &PAGE(ctx, pgn).dateoff; break; default: if (begin && is_hdr && !warned) { - err("warning: header too long in '", file, "': Only 4 lines supported"); + err("warning: header too long in ", ESC, file(), ESC, ": ", + "Only 4 lines supported"); warned = 1; } } @@ -1367,7 +1356,7 @@ load_page_from_file(const char *file, struct page *page, stralloc *sa) && (!stralloc_catb(sa, b + ((is_hdr) ? 2 : 0), ((e) ? e - b : left) - ((is_hdr) ? 2 : 0)) || (e && !stralloc_0(sa)))) - retwusys(EX_TEMPFAIL, "load page title from '", file, "'"); + retwusys(EX_TEMPFAIL, "load page title from ", ESC, file(), ESC); } if (e) { int l = e - b + 1; @@ -1383,204 +1372,250 @@ load_page_from_file(const char *file, struct page *page, stralloc *sa) b = buf; left = buffer_get(&buffer, buf, sizeof(buf)); if (left <= 0) - retwusys(EX_IOERR, "load page title from '", file, "'"); + retwusys(EX_DATA_ERR, "load page title from ", ESC, file(), ESC); if (!e) begin = 0; } } - if (empty(sa->s + page->titleoff)) - page->titleoff = page->fileoff; + if (empty(sa->s + PAGE(ctx, pgn).titleoff)) + PAGE(ctx, pgn).titleoff = PAGE(ctx, pgn).fileoff; - if (page->nameoff && empty(sa->s + page->nameoff)) - page->nameoff = 0; + if (PAGE(ctx, pgn).nameoff && empty(sa->s + PAGE(ctx, pgn).nameoff)) + PAGE(ctx, pgn).nameoff = 0; - if (page->nameoff) { - if (!page->veroff || empty(sa->s + page->veroff)) - page->veroff = page->nameoff; - if (!page->dateoff || empty(sa->s + page->dateoff)) - page->dateoff = page->titleoff; + if (PAGE(ctx, pgn).nameoff) { + if (!PAGE(ctx, pgn).veroff || empty(sa->s + PAGE(ctx, pgn).veroff)) + PAGE(ctx, pgn).veroff = PAGE(ctx, pgn).nameoff; + if (!PAGE(ctx, pgn).dateoff || empty(sa->s + PAGE(ctx, pgn).dateoff)) + PAGE(ctx, pgn).dateoff = PAGE(ctx, pgn).titleoff; } - page->size = lseek(page->fd, 0, SEEK_END); - if (page->size == (off_t) -1 || lseek(page->fd, done, SEEK_SET) < 0) - retwusys(EX_IOERR, "seek into '", file, "'"); - page->size -= done; + PAGE(ctx, pgn).size = lseek(PAGE(ctx, pgn).fd, 0, SEEK_END); + if (PAGE(ctx, pgn).size == (off_t) -1 || lseek(PAGE(ctx, pgn).fd, done, SEEK_SET) < 0) + retwusys(EX_IOERR, "seek into ", ESC, file(), ESC); + PAGE(ctx, pgn).size -= done; return 0; +#undef file } -static void -help(void) -{ - diehelp(0, "[OPTION..] FILE...", - " -a, --author AUTHOR Set AUTHOR as author (in meta in footer)\n" - " -b, --buttons Put Previous & Next buttons on pages\n" - " -C, --no-css Do not use CSS (still process --css if any)\n" - " -c, --css FILE Add FILE as additional CSS\n" - " -d, --destdir DIR Write files into DIR\n" - " -F, --footer FILE Insert FILE as common footer\n" - " -H, --header FILE Insert FILE as common header\n" - " -h, --help Show this help screen and exit\n" - " -I, --inline-css Use inline CSS instead of external files\n" - " -i, --index Force index mode\n" - " -l, --lang LNG Set LNG as language attribute\n" - " -M, --man-url URL Use URL as prefix for external man-page links\n" - " -o, --overwrite Overwrite destination files if already exist\n" - " -s, --subtitle TEXT Set TEXT as general subtitle\n" - " -T, --no-toc Don't write a TOC on each page. Implies --no-index\n" - " -t, --title TITLE Set TITLE as general (across all pages) title\n" - " -V, --version Show version screen and exit\n" - " -W, --wide-include Include header/footer right within <body>\n" - " -X, --no-index Disable index mode\n" - ); -} +struct parse { + struct qmdoc *qmdoc; + size_t destdir; + size_t footer; + size_t header; + size_t ffile; +}; -static void -usage(int err) -{ - dieusage((err) ? -ERR_USAGE : -ERR_NONE, "[OPTION..] FILE..."); -} +enum { + OPTID_VERSION = OPTID_FIRST, + ARGID_FILE +}; -int -main (int argc, char *argv[]) +static int +parse_cmdline(int argc, const char *argv[], const char usage[], struct parse *ctx) { - PROG = strrchr(argv[0], '/'); - if (PROG) ++PROG; - else PROG = argv[0]; - - char *destdir = "."; - const char *footer = NULL; - const char *header = NULL; - struct ctx ctx = { - .options = 0, - .sa = STRALLOC_ZERO, - .sa_out = STRALLOC_ZERO, - .css = css, - .doc.title = "Documentation", - .doc.subtitle = NULL, - .doc.author = "", - .doc.lang = "en", - .buf.sa = STRALLOC_ZERO, + const struct option options[] = { + OPTION_ARG_REQ( 'a', "author", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_NONE('b', "buttons", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('C', "no-css", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ( 'c', "css", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_REQ( 'd', "destdir", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_REQ( 'F', "footer", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_REQ( 'H', "header", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_NONE('h', "help", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('I', "inline-css", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('i', "index", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ( 'l', "lang", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ( 'M', "man-url", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_NONE('o', "overwrite", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ( 's', "subtitle", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_NONE('T', "no-toc", 0, OPTID_SHORTOPT), + OPTION_ARG_REQ( 't', "title", OPT_PATH, OPTID_SHORTOPT), + OPTION_ARG_NONE( 0 , "version", 0, OPTID_VERSION), + OPTION_ARG_NONE('W', "wide-includes", 0, OPTID_SHORTOPT), + OPTION_ARG_NONE('X', "no-index", 0, OPTID_SHORTOPT), + LOADOPT_ARGUMENTS, + ARGUMENT_REQ( "file", OPT_PATH | OPT_RPT, ARGID_FILE), + LOADOPT_DONE }; + struct loadopt lo = LOADOPT_ZERO; + int nfile = 0; int c; - struct option opts[] = { - { "author", required_argument, NULL, 'a' }, - { "buttons", no_argument, NULL, 'b' }, - { "no-css", no_argument, NULL, 'C' }, - { "css", required_argument, NULL, 'c' }, - { "destdir", required_argument, NULL, 'd' }, - { "footer", required_argument, NULL, 'F' }, - { "header", required_argument, NULL, 'H' }, - { "help", no_argument, NULL, 'h' }, - { "inline-css", no_argument, NULL, 'I' }, - { "index", no_argument, NULL, 'i' }, - { "lang", no_argument, NULL, 'l' }, - { "man-url", required_argument, NULL, 'M' }, - { "overwrite", no_argument, NULL, 'o' }, - { "subtitle", required_argument, NULL, 's' }, - { "no-toc", no_argument, NULL, 'T' }, - { "title", required_argument, NULL, 't' }, - { "version", no_argument, NULL, 'V' }, - { "wide-includes", no_argument, NULL, 'W' }, - { "no-index", no_argument, NULL, 'X' }, - { NULL, 0, NULL, 0 }, - }; - while ((c = getopt_long(argc, argv, "a:bCc:d:F:H:hIil:M:os:Tt:VWX", opts, NULL)) != -1) switch (c) { + while ((c = loadopt(&ctx->qmdoc->sa, argc, argv, options, 0, NULL, 0, &lo))) switch (c) { case 'a': - ctx.doc.author = optarg; + ctx->qmdoc->doc.oauthor = LO_OFF(&lo); break; case 'b': - ctx.options |= OPT_BUTTONS; + ctx->qmdoc->options |= OPT_BUTTONS; break; case 'C': - ctx.options |= OPT_NO_CSS; + ctx->qmdoc->options |= OPT_NO_CSS; break; case 'c': - ctx.css[CSS_CUSTOM].file = optarg; + ctx->qmdoc->css[CSS_CUSTOM] = LO_OFF(&lo); break; case 'd': - destdir = optarg; + ctx->destdir = LO_OFF(&lo); break; case 'F': - footer = optarg; + ctx->footer = LO_OFF(&lo); break; case 'H': - header = optarg; + ctx->header = LO_OFF(&lo); break; case 'h': - help(); + diehelp(0, usage, +" -a, --author AUTHOR Set AUTHOR as author (in meta in footer)\n" +" -b, --buttons Put Previous & Next buttons on pages\n" +" -C, --no-css Do not use CSS (still process --css if any)\n" +" -c, --css FILE Add FILE as additional CSS\n" +" -d, --destdir DIR Write files into DIR\n" +" -F, --footer FILE Insert FILE as common footer\n" +" -H, --header FILE Insert FILE as common header\n" +" -h, --help Show this help screen and exit\n" +" -I, --inline-css Use inline CSS instead of external files\n" +" -i, --index Force index mode\n" +" -l, --lang LNG Set LNG as language attribute\n" +" -M, --man-url URL Use URL as prefix for external man-page links\n" +" -o, --overwrite Overwrite destination files if already exist\n" +" -s, --subtitle TEXT Set TEXT as general subtitle\n" +" -T, --no-toc Don't write a TOC on each page. Implies --no-index\n" +" -t, --title TITLE Set TITLE as general (across all pages) title\n" +" -V, --version Show version screen and exit\n" +" -W, --wide-include Include header/footer right within <body>\n" +" -X, --no-index Disable index mode\n" +); case 'I': - ctx.options |= OPT_INLINE_CSS; + ctx->qmdoc->options |= OPT_INLINE_CSS; break; case 'i': - ctx.options |= OPT_INDEX; + ctx->qmdoc->options |= OPT_INDEX; break; case 'l': - ctx.doc.lang = optarg; + ctx->qmdoc->doc.lang = LO_ARG(&lo); break; case 'M': - ctx.manurl = optarg; + ctx->qmdoc->omanurl = LO_OFF(&lo); break; case 'o': - ctx.options |= OPT_OVERWRITE; + ctx->qmdoc->options |= OPT_OVERWRITE; break; case 's': - ctx.doc.subtitle = optarg; + ctx->qmdoc->doc.osubtitle = LO_OFF(&lo); break; case 'T': - ctx.options |= OPT_NO_TOC | OPT_NO_INDEX; + ctx->qmdoc->options |= OPT_NO_TOC | OPT_NO_INDEX; break; case 't': - ctx.doc.title = optarg; - break; - case 'V': - dieversion(QMDOC_VERSION, "2023", QMDOC_CURYEAR, QMDOC_AUTHOR, QMDOC_URL, NULL); + ctx->qmdoc->doc.otitle = LO_OFF(&lo); break; case 'W': - ctx.options |= OPT_WIDE_INC; + ctx->qmdoc->options |= OPT_WIDE_INC; break; case 'X': - ctx.options |= OPT_NO_INDEX; + ctx->qmdoc->options |= OPT_NO_INDEX; break; - case '?': - usage(1); + case OPTID_VERSION: + dieversion(QMDOC_VERSION, "2023", QMDOC_CURYEAR, QMDOC_AUTHOR, QMDOC_URL, NULL); + break; + + case ARGID_FILE: + if (nfile++ == 0) + ctx->ffile = LO_OFF(&lo); + break; + + case -1: + dieusage(EX_USAGE, usage); + default: + die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt"); + } + + return nfile; +} + +int +main (int argc, const char *argv[]) +{ + PROG = strrchr(argv[0], '/'); + if (PROG) ++PROG; + else PROG = argv[0]; + + struct qmdoc ctx = { + .options = 0, + .sa = STRALLOC_ZERO, + .sa_out = STRALLOC_ZERO, + .css[CSS_CUSTOM] = (size_t) -1, + .doc.otitle = (size_t) -1, + .doc.oauthor = (size_t) -1, + .doc.osubtitle = (size_t) -1, + .doc.lang = "en", + .omanurl = (size_t) -1, + .buf.sa = STRALLOC_ZERO, + }; + struct parse parse = { .qmdoc = &ctx, .destdir = (size_t) -1, + .header = (size_t) -1, .footer = (size_t) -1, .ffile = (size_t) -1 }; + const char usage[] = "[OPTION..] FILE.."; + + ctx.nb_pages = parse_cmdline(argc, argv, usage, &parse); + size_t lfile = ctx.sa.len; + + if (ctx.doc.otitle == (size_t) -1) { + ctx.doc.otitle = ctx.sa.len; + if (!stralloc_cats0(&ctx.sa, "Documentation")) + diefusys(EX_TEMPFAIL, "initialize"); + } + + if (parse.destdir == (size_t) -1) { + parse.destdir = ctx.sa.len; + if (!stralloc_cats0(&ctx.sa, ".")) + diefusys(EX_TEMPFAIL, "initialize"); } - if (optind == argc) usage(1); + if ((ctx.options & (OPT_NO_CSS | OPT_INLINE_CSS)) == (OPT_NO_CSS | OPT_INLINE_CSS)) - dief(EX_USAGE, "cannot use '", "--no-css", "' and '", "--inline-css", "' together"); + dief(EX_USAGE, "cannot use ", "--no-css", " and ", "--inline-css", " together"); if ((ctx.options & (OPT_INDEX | OPT_NO_INDEX)) == (OPT_INDEX | OPT_NO_INDEX)) - dief(EX_USAGE, "cannot use '", "--index", "' and '", "--no-index", "' together"); + dief(EX_USAGE, "cannot use ", "--index", " and ", "--no-index", " together"); + + if (!(ctx.options & OPT_NO_TOC)) + ctx.css[CSS_NO_TOC] = (size_t) -1; - int fddest = open(destdir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); - if (fddest < 0) diefusys(EX_IOERR, "open '", destdir, "'"); + int fddest = open(ctx.sa.s + parse.destdir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (fddest < 0) diefusys(EX_IOERR, "open ", ESC, ctx.sa.s + parse.destdir, ESC); - if (!(ctx.options & OPT_NO_TOC)) css[CSS_NO_TOC].file = NULL; + { + /* +1 in case we'll need to add our internal index */ + size_t len = (ctx.nb_pages + 1) * sizeof(struct page); - /* +1 in case we'll need to add our internal index */ - struct page pages[argc - optind + 1]; - ctx.pages = pages; - ctx.nb_pages = sizeof(pages) / sizeof(*pages) - 1; - memset(pages, 0, ctx.nb_pages * sizeof(*pages)); + ctx.opages = ctx.sa.len; + if (!stralloc_readyplus(&ctx.sa, len)) + diefusys(EX_TEMPFAIL, "initialize"); + ctx.sa.len += len; + memset(ctx.sa.s + ctx.opages, 0, len); + } int err = 0; int idx_page = -1; out("Scanning pages..."); - for (int i = optind; i < argc; ++i) { - const char *file = argv[i]; - size_t len = strlen(file); - - if (strcmp(file + len - 3, ".md")) { - warn("File '", file, "' not a markdown file (*.md)"); - pages[i - optind].fd = -1; + size_t off = ctx.sa.len; + ctx.nb_pages = sacoloff(&ctx.sa, parse.ffile, lfile); + for (int i = 0; i < ctx.nb_pages; ++i) { + size_t *fileoff = (size_t *) (ctx.sa.s + off); + const char *file = ctx.sa.s + fileoff[i]; + size_t flen = strlen(file); + + if (strcmp(file + flen - 3, ".md")) { + warn("File ", ESC, file, ESC, " not a markdown file (*.md)"); + PAGE(&ctx, i).fd = -1; err = EX_DATA_ERR; continue; } - int r = load_page_from_file(file, &pages[i - optind], &ctx.sa); + int r = load_page_from_file(&ctx.sa, fileoff[i], flen, i, &ctx); if (r) err = r; if (!(ctx.options & OPT_NO_INDEX) - && !strcmp(ctx.sa.s + pages[i - optind].fileoff, "index.html")) + && !strcmp(ctx.sa.s + PAGE(&ctx, i).fileoff, "index.html")) idx_page = i - optind; } @@ -1595,105 +1630,142 @@ main (int argc, char *argv[]) if (idx_page < 0) { /* no index, add our internal page first */ /* move all pages up by one */ - memmove(&pages[1], &pages[0], ctx.nb_pages * sizeof(*pages)); + memmove(&PAGE(&ctx, 1), &PAGE(&ctx, 0), ctx.nb_pages * sizeof(struct page)); /* add our internal index */ - pages[0].sce = "<internal index>"; - pages[0].fileoff = ctx.sa.len; + PAGE(&ctx, 0).sceoff = ctx.sa.len; + if (!stralloc_cats0(&ctx.sa, "<internal index>")) + diefusys(EX_TEMPFAIL, "set internal page title"); + + PAGE(&ctx, 0).fileoff = ctx.sa.len; if (!stralloc_cats0(&ctx.sa, "index.html")) - diefusys(EX_TEMPFAIL, "load page title from '", pages[0].sce, "'"); + diefusys(EX_TEMPFAIL, "load page title from ", + ESC, ctx.sa.s + PAGE(&ctx, 0).sceoff, ESC); - pages[0].titleoff = ctx.sa.len; + PAGE(&ctx, 0).titleoff = ctx.sa.len; if (!stralloc_cats0(&ctx.sa, index_title)) - diefusys(EX_TEMPFAIL, "load page title from '", pages[0].sce, "'"); + diefusys(EX_TEMPFAIL, "load page title from ", + ESC, ctx.sa.s + PAGE(&ctx, 0).sceoff, ESC); /* fd == -1 means use index_md instead of reading from fd */ - pages[0].fd = -1; - pages[0].size = strlen(index_md); - pages[0].nameoff = pages[0].veroff = pages[0].dateoff = 0; + PAGE(&ctx, 0).fd = -1; + PAGE(&ctx, 0).size = strlen(index_md); + PAGE(&ctx, 0).nameoff = PAGE(&ctx, 0).veroff = PAGE(&ctx, 0).dateoff = 0; ++ctx.nb_pages; } else if (idx_page > 0) { /* move index's page to first */ - /* move index's page into last slot */ - memmove(&pages[ctx.nb_pages], &pages[idx_page], sizeof(*pages)); + struct page pg; + /* "extract" the index page */ + pg = PAGE(&ctx, idx_page); /* move everything before up by one */ - memmove(&pages[1], &pages[0], idx_page * sizeof(*pages)); - /* put index back in first */ - memmove(&pages[0], &pages[ctx.nb_pages], sizeof(*pages)); + memmove(&PAGE(&ctx, 1), &PAGE(&ctx, 0), idx_page * sizeof(struct page)); + /* put index page first */ + PAGE(&ctx, 0) = pg; } } - if (header || footer) { + if (parse.header != (size_t) -1 || parse.footer != (size_t) -1) { out("Loading files..."); - if (header) { + if (parse.header != (size_t) -1) { ctx.doc.oheader = ctx.sa.len; - if (!openreadfileclose(header, &ctx.sa, 0) || !stralloc_0(&ctx.sa)) - diefusys(EX_IOERR, "load data from '", header, "'"); + if (!open_slurp_close(&ctx.sa, ctx.sa.s + parse.header) || !stralloc_0(&ctx.sa)) + diefusys((errno == ENOMEM) ? EX_TEMPFAIL : EX_NOINPUT, + "load data from '", ctx.sa.s + parse.header, "'"); } - if (footer) { + if (parse.footer != (size_t) -1) { ctx.doc.ofooter = ctx.sa.len; - if (!openreadfileclose(footer, &ctx.sa, 0) || !stralloc_0(&ctx.sa)) - diefusys(EX_IOERR, "load data from '", footer, "'"); + if (!open_slurp_close(&ctx.sa, ctx.sa.s + parse.footer) || !stralloc_0(&ctx.sa)) + diefusys((errno == ENOMEM) ? EX_TEMPFAIL : EX_NOINPUT, + "load data from '", ctx.sa.s + parse.footer, "'"); } } - if (!(ctx.options & OPT_NO_CSS) || ctx.css[CSS_CUSTOM].file) { + if (!(ctx.options & OPT_NO_CSS) || ctx.css[CSS_CUSTOM] != (size_t) -1) { out((ctx.options & OPT_INLINE_CSS) ? "Loading" : "Copying", " CSS files..."); + int dirfd; + if (!(ctx.options & OPT_NO_CSS)) { + dirfd = open2(QMDOC_SHAREDIR, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) + diefusys(EX_IOERR, "open ", ESC, QMDOC_SHAREDIR, ESC); + } else { + dirfd = AT_FDCWD; + } + for (int i = 0; i < NB_CSS; ++i) { - if (!css[i].file) continue; - size_t flen = strlen(css[i].file); - char file[strlen(QMDOC_SHAREDIR) + 1 + flen + 1]; - if (i != CSS_CUSTOM) { - memcpy(file, QMDOC_SHAREDIR, strlen(QMDOC_SHAREDIR)); - file[strlen(QMDOC_SHAREDIR)] = '/'; - memcpy(file + strlen(QMDOC_SHAREDIR) + 1, css[i].file, flen + 1); - } else { - memcpy(file, css[i].file, flen + 1); + if ((ctx.options & OPT_NO_CSS) || ctx.css[i] == (size_t) -1) + continue; + + const char *file; + size_t off = ctx.css[CSS_CUSTOM]; + switch (i) { + case CSS_QMDOC: + file = "qmdoc.css"; + break; + case CSS_CUSTOM: + file = ctx.sa.s + off; + break; + case CSS_NO_TOC: + file = "no-toc.css"; + break; } + + int from; + from = open_readat((i == CSS_CUSTOM) ? AT_FDCWD : dirfd, file); + if (from < 0) + diefusys(EX_IOERR, "open ", ESC, file, ESC); + if (ctx.options & OPT_INLINE_CSS) { - css[i].offset = ctx.sa.len; - if (!openreadfileclose(file, &ctx.sa, 0) - || !stralloc_0(&ctx.sa)) - diefusys((errno == ENOMEM) ? EX_TEMPFAIL : EX_IOERR, - "load CSS from '", file, "'"); + ctx.css[i] = ctx.sa.len; + if (!slurp(&ctx.sa, from) || !stralloc_0(&ctx.sa)) + diefusys((errno == ENOMEM) ? EX_TEMPFAIL : EX_NOINPUT, + "load CSS from ", ESC, + (i == CSS_CUSTOM) ? ctx.sa.s + off : file, + ESC); } else { - int from, to; - from = open_read(file); - if (from < 0) diefusys(-ERR_IO, "open '", file, "'"); + int to; if (ctx.options & OPT_OVERWRITE) - to = open_truncat(fddest, css[i].file); + to = open_truncat(fddest, file); else - to = open_exclat(fddest, css[i].file); - if (to < 0) diefusys(-ERR_IO, "create '", destdir, "/", css[i].file, "'"); - if (fd_cat(from, to) < 0) - diefusys(EX_IOERR, "copy CSS to '", destdir, "/", css[i].file, "'"); - fd_close(from); + to = open_exclat(fddest, file); + if (to < 0 || fd_cat(from, to) < 0) + diefusys(EX_IOERR, "copy CSS to ", + ESC, ctx.sa.s + parse.destdir, "/", file, ESC); fd_close(to); + + if (i != CSS_CUSTOM) { + ctx.css[i] = ctx.sa.len; + if (!stralloc_cats0(&ctx.sa, file)) + diefusys(EX_TEMPFAIL, "copy CSS to ", + ESC, ctx.sa.s + parse.destdir, "/", file, ESC); + } } + fd_close(from); } + if (dirfd != AT_FDCWD) fd_close(dirfd); } - for (ctx.cur_page = 0; ctx.cur_page < ctx.nb_pages; ++ctx.cur_page) { - if ((ctx.doc.flags & DOC_FULL_TOC) && ctx.cur_page == 0) - continue; + ctx.cur_page = 0; + for (;;) { + if ((ctx.doc.flags & (DOC_FULL_TOC | DOC_IS_INDEX)) == DOC_FULL_TOC + && ctx.cur_page == 0) + ++ctx.cur_page; - out("Converting ", pages[ctx.cur_page].sce, "..."); + out("Converting ", ESC, ctx.sa.s + PAGE(&ctx, ctx.cur_page).sceoff, ESC, "..."); int r = convert_page(&ctx, fddest); - if (r < 0) diefu(-r, "convert '", pages[ctx.cur_page].sce, "' to '", - destdir, "/", ctx.sa.s + pages[ctx.cur_page].fileoff, "'"); - } + if (r) + diefu(r, "convert ", ESC, ctx.sa.s + PAGE(&ctx, ctx.cur_page).sceoff, ESC, + " to ", ESC, ctx.sa.s + parse.destdir, "/", + ctx.sa.s + PAGE(&ctx, ctx.cur_page).fileoff, ESC); - if (ctx.doc.flags & DOC_FULL_TOC) { - ctx.cur_page = 0; - - out("Converting ", pages[ctx.cur_page].sce, "..."); - - ctx.doc.flags |= DOC_IS_INDEX; - int r = convert_page(&ctx, fddest); - if (r < 0) diefu(-r, "convert '", pages[ctx.cur_page].sce, "' to '", - destdir, "/", ctx.sa.s + pages[ctx.cur_page].fileoff, "'"); + if (++ctx.cur_page == ctx.nb_pages) { + if (!(ctx.doc.flags & DOC_FULL_TOC)) + break; + ctx.cur_page = 0; + ctx.doc.flags |= DOC_IS_INDEX; + } else if (ctx.doc.flags & DOC_IS_INDEX) + break; } stralloc_free(&ctx.buf.sa);