#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "str.h" #include "arena.h" #include "args.h" #define ARENA(n, sz) Arena n; { static char arena_backarr[sz];\ n.beg = arena_backarr; n.end = arena_backarr + sizeof(arena_backarr);\ __asm("":"+r"(n.beg)); __asm("":"+r"(n.end)); } int read_all(FILE *f, Str *buf, Arena *a) { if (!f) return -1; buf->s = a->beg; buf->n = fread(a->beg, 1, (uintptr_t)(a->end - a->beg) - 1, f); a->beg += buf->n; return ferror(f) ? -1 : 0; } int next_line(Str *src, Str *line) { if (src->n < 1) return 0; line->s = src->s; char *newln = memchr(src->s, '\n', src->n); line->n = newln ? newln - src->s : src->n; src->s += line->n + 1; src->n -= line->n + 1; return 1; } void str_putf(Str s, FILE *f) { fwrite(s.s, 1, s.n, f); } void str_put(Str s) { str_putf(s, stdout); } char to_xdigit(int x) { if (x > 9) { return 'A' + (x - 10); } else { return '0' + x; } } void str_cat_uri(Str *s, Str uri, Arena *a) { str_catc(s, '\'', a); for (isize i = 0; i < uri.n; i++) { char c = uri.s[i]; if (c == '\'' || c == '%') { str_catc(s, '%', a); str_catc(s, to_xdigit((c & 0xff) >> 4), a); str_catc(s, to_xdigit(c & 0xf), a); } else { str_catc(s, c, a); } } str_catc(s, '\'', a); } void str_cat_html(Str *s, Str uri, Arena *a) { for (isize i = 0; i < uri.n; i++) { char c = uri.s[i]; switch (c) { case '&': str_cat(s, S("&"), a); break; case '<': str_cat(s, S("<"), a); break; case '>': str_cat(s, S(">"), a); break; default: str_catc(s, c, a); break; } } } typedef struct Doc Doc; struct Doc { Str html; Str title; Doc *prev, *next; }; typedef enum { LN_PAR, LN_CODE, LN_LINK, LN_BQUOT, LN_ULIST, LN_OLIST, LN_HDR1, LN_HDR2, LN_HDR3, LN_NONE } LineType; typedef struct Line Line; struct Line { Str txt; Line *next; }; typedef struct { LineType type; Line *lines; } Block; typedef struct { Block *data; size_t len, cap; } BlockList; int is_ol_item(Str s) { Str h = str_cut(s, '.').head; if (h.n < 1) return 0; for (isize i = 0; i < h.n; i++) { if (!(h.s[i] >= '0' && h.s[i] <= '9')) return 0; } return 1; } LineType classify_line(Str line, LineType prev) { if (line.n == 0) { return LN_NONE; } else if (str_starts(line, S("###"))) { return LN_HDR3; } else if (str_starts(line, S("##"))) { return LN_HDR2; } else if (str_starts(line, S("#"))) { return LN_HDR1; } else if (str_starts(line, S("=>"))) { return LN_LINK; } else if (str_starts(line, S(">"))) { return LN_BQUOT; } else if (str_starts(line, S("* ")) || (prev == LN_ULIST && str_starts(line, S(" ")))) { return LN_ULIST; } else if (is_ol_item(line)) { return LN_OLIST; } else if (str_starts(line, S("```"))) { return LN_CODE; } else { return LN_PAR; } } BlockList blk_gather(Str src, Arena *perm) { Str line; LineType last = LN_NONE; BlockList blk = { 0 }; Line *lptr = NULL; while (next_line(&src, &line)) { LineType t = classify_line(line, last); if (last == LN_CODE) { if (t == LN_CODE) last = LN_NONE; } else if (t == LN_CODE) { last = LN_CODE; } else { if (blk.len < 1 || t != blk.data[blk.len-1].type) { if (blk.cap <= blk.len) { size_t c = blk.cap; if (!c) c = 16; while (c <= blk.len) c <<= 1; blk.data = resize(perm, blk.data, blk.cap, c); blk.cap = c; } Block *b = &blk.data[blk.len++]; b->type = t; b->lines = NULL; lptr = NULL; } Line *l = new(perm, Line); l->txt = line; if (lptr) lptr->next = l; lptr = l; Block *b = &blk.data[blk.len-1]; if (!b->lines) b->lines = lptr; } last = t; } for (size_t i = 0; i < blk.len; i++) { if (blk.data[i].type == LN_NONE) { blk.len--; memcpy(&blk.data[i], &blk.data[i+1], (blk.len - i) * sizeof(*blk.data)); i--; } } return blk; } #define O(s) str_cat_html(out, s, perm) #define Os(s) str_cat(out, S(s), perm) #define Ot(a, s, b) Os(a), O(s), Os(b) #define Otl(a, f, b) for (Line *l = blk->lines; l; l = l->next) Ot(a, f, b) void str_cat_blk(Str *out, Block *blk, Arena *perm, Arena *scratch) { switch (blk->type) { case LN_CODE: Os("
");
		for (Line *l = blk->lines; l; l = l->next) {
			O(l->txt);
			Os("\n");
		}
		Os("
"); break; case LN_LINK: Os("
    \n"); for (Line *l = blk->lines; l; l = l->next) { Cut c = str_cut(str_trim(str_skip(l->txt, 2)), ' '); Str url = c.head, txt = c.tail.n > 0 ? c.tail : c.head; Os("
  • ", txt, "
  • \n"); } Os("
"); break; case LN_BQUOT: Os("
"); for (Line *l = blk->lines; l; l = l->next) { O(str_trim(str_skip(l->txt, 1))); if (l->next) Os("
\n"); } Os("
"); break; case LN_ULIST: Os("
    \n"); Otl("
  • ", str_skip(l->txt, 2), "
  • \n"); Os("
      "); break; case LN_OLIST: Os("
        \n"); Otl("
      1. ", str_trim(str_cut(l->txt, '.').tail), "
      2. \n"); Os("
          "); break; case LN_HDR1: Otl("

          ", str_trim(str_skip(l->txt,1)), "

          "); break; case LN_HDR2: Otl("

          ", str_trim(str_skip(l->txt,2)), "

          "); break; case LN_HDR3: Otl("

          ", str_trim(str_skip(l->txt,3)), "

          "); break; default: case LN_PAR: Os("

          "); for (Line *l = blk->lines; l; l = l->next) { O(l->txt); if (l->next) Os("
          \n"); } Os("

          "); break; } Os("\n"); } int has_image_ext(Str url) { return str_ends(url, S(".png")) || str_ends(url, S(".jpg")) || str_ends(url, S(".jpeg")) || str_ends(url, S(".webp")); } int wdoc(FILE *f, Doc **dp, Arena *perm, Arena *scratch) { Str buf; if (read_all(f, &buf, scratch)) return -1; Doc *d = new(perm, Doc); BlockList blk = blk_gather(buf, scratch); for (size_t i = 0; i < blk.len; i++) { if (blk.data[i].type == LN_HDR1 && !d->title.s) { d->title = str_trim(str_skip(blk.data[i].lines->txt, 1)); } str_cat_blk(&d->html, &blk.data[i], perm, scratch); } *dp = d; return 0; } uint64_t str_hash(Str s) { uint64_t h = 14695981039346656037LU; for (isize i = 0; i < s.n; i++) h = (h ^ (s.s[i] & 0xff)) * 1099511628211LU; return h; } /* --hvar bgcolor:'#fcc,#cfc,#ccf,#cff,#ffc,#fcf' */ int hvar_calc(Str param, Str *name, Str *val, Str filename) { Cut c = str_cut(param, ':'); if (!c.head.n || !c.tail.n) return -1; *name = c.head; usize n = 0; for (Str h = c.tail; h.n > 0; h = str_cut(h, ',').tail) n++; if (!n) return -1; srand(str_hash(filename)); usize j = rand() % n; usize i = 0; for (Str h = c.tail; h.n > 0; h = str_cut(h, ',').tail) { if (i == j) { *val = str_cut(h, ',').head; return 0; } i++; } return -1; } void usage(const char *cmd) { fprintf(stderr, "usage: %s -?\n" " %s [-s] [-c FILE] [-t TITLE] [-h NAME:ARG1[,ARG2,ARG3...]] [FILES...]\n" "\n" " -? --help show this help text\n" " -s --standalone prefix with html metadata\n" " -h --hvar define a css variable (--name) with a random value,\n" " selected by a hash of the document's title\n" " -c --css embed the given file within a \n"), m); } return h; } #define countof(x) (sizeof(x) / sizeof(*x)) int main(int argc, const char **argv) { (void)argc; ARENA(perm, 1 << 20) ARENA(scratch, 1 << 20) Doc *doc = 0; Options opts = { 0 }; int r; ArgsState a = args_begin(argv); Str param = { 0 }; opts.from_stdin = 1; while ((r = arg_get(&a, "?sc:h:t:", ¶m, "help", '?', "standalone", 's', ":title", 't', ":css", 'c', ":hvar", 'h')) >= ARG_OK) { Arena reset = scratch; FILE *f; switch (r) { case '?': usage(argv[0]); return 0; case 's': opts.standalone = 1; break; case 'c': opts.stylesheet = param; break; case 't': opts.title = param; break; case 'h': if (opts.hvarc == countof(opts.hvarv)) { fprintf(stderr, "too many hash variables!\n"); return 1; } opts.hvarv[opts.hvarc++] = param; break; default: if (str_eql(param, S("-"))) { if (wdoc(stdin, &doc, &perm, &scratch)) { fprintf(stderr, "error reading document" " from stdin: %s\n", strerror(errno)); return 1; } } else { opts.from_stdin = 0; f = fopen(str_to_cstr(param, &scratch), "r/o"); if (wdoc(f, &doc, &perm, &scratch)) { fwrite(param.s, 1, param.n, stderr); fprintf(stderr, ": %s\n", strerror(errno)); return 1; } fclose(f); break; } } scratch = reset; } switch (r) { case ARG_BAD: fprintf(stderr, "unknown option '"); fwrite(param.s, 1, param.n, stderr); fprintf(stderr, "' (see %s --help)\n", argv[0]); return 1; case ARG_EMPTY: fprintf(stderr, "'"); fwrite(param.s, 1, param.n, stderr); fprintf(stderr, "' option expected an argument (see %s --help)\n", argv[0]); return 1; } if (opts.from_stdin) { if (wdoc(stdin, &doc, &perm, &scratch)) { fprintf(stderr, "error reading document from stdin: " "%s\n", strerror(errno)); return 1; } } if (doc && !opts.title.s) opts.title = doc->title; while (doc && doc->prev) { doc = doc->prev; if (doc->title.s && !opts.title.s) opts.title = doc->title; } if (opts.standalone) { str_put(html_head(&opts, &perm, &scratch)); } while (doc) { str_put(doc->html); doc = doc->next; } return 0; }