#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #define ARENA_IMPL #include "str.h" #include "arena.h" #define ARGS_IMPL #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; } } } 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; } typedef enum { LINE_BLANK, LINE_PARA, LINE_LINK, LINE_FIGURE, LINE_UL, LINE_OL, LINE_HDR1, LINE_HDR2, LINE_HDR3, LINE_CODE, LINE_BQUOT, } LineMode; LineMode lm_chg(LineMode from, LineMode to, Str *out, Arena *a) { #undef S #define S(s) {s,sizeof(s)-1} static Str op[] = { [LINE_BLANK] = S(""), [LINE_PARA] = S("

"), [LINE_LINK] = S("

    \n
  • "), [LINE_FIGURE] = S("
    "), [LINE_UL] = S("
      \n
    • "), [LINE_OL] = S("
        \n
      1. "), [LINE_HDR1] = S("

        "), [LINE_HDR2] = S("

        "), [LINE_HDR3] = S("

        "), [LINE_CODE] = S("
        "),
        		[LINE_BQUOT] = S("
        "), }; static Str cl[] = { [LINE_BLANK] = S(""), [LINE_PARA] = S("

        "), [LINE_LINK] = S("

      2. \n
    "), [LINE_FIGURE] = S("
    "), [LINE_UL] = S("
  • \n
"), [LINE_OL] = S("\n"), [LINE_HDR1] = S(""), [LINE_HDR2] = S(""), [LINE_HDR3] = S(""), [LINE_CODE] = S(""), [LINE_BQUOT] = S(""), }; static Str cont[] = { [LINE_BLANK] = S(""), [LINE_PARA] = S("
\n"), [LINE_FIGURE] = S("\n
"), [LINE_LINK] = S("\n
  • "), [LINE_UL] = S("
  • \n
  • "), [LINE_OL] = S("
  • \n
  • "), [LINE_HDR1] = S("\n

    "), [LINE_HDR2] = S("

    \n

    "), [LINE_HDR3] = S("

    \n

    "), [LINE_CODE] = S("\n"), [LINE_BQUOT] = S("
    \n"), }; #undef S #define S(s) (Str){s,sizeof(s)-1} if (from == to) { str_cat(out, cont[from], a); } else { str_cat(out, cl[from], a); str_catc(out, '\n', a); str_cat(out, op[to], a); } return to; } typedef struct Doc Doc; struct Doc { Str html; Str title; Doc *prev, *next; }; 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")); } Str str_replace_end(Str s, Str a, Str b, Arena *m) { if (!str_ends(s, a)) return s; char *p = new_arr(m, char, s.n + b.n - a.n); memcpy(p, s.s, s.n - a.n); memcpy(p + s.n - a.n, b.s, b.n); return (Str) { p, s.n + b.n - a.n }; } int wdoc(FILE *f, Doc **dp, Arena *a, Arena *scratch) { Str buf, line, out = {0}, title = {0}; if (read_all(f, &buf, scratch)) return -1; LineMode lm = LINE_BLANK; while (next_line(&buf, &line)) { if (str_starts(line, S("```"))) { lm = lm_chg(lm, lm == LINE_CODE ? LINE_BLANK : LINE_CODE, &out, a); continue; } else if (lm == LINE_CODE) { lm = lm_chg(lm, LINE_CODE, &out, a); str_cat(&out, line, a); continue; } else if (line.n == 0) { lm = lm_chg(lm, LINE_BLANK, &out, a); } else if (str_starts(line, S("=>"))) { line = str_trim(str_skip(line, 2)); isize i = 0; while (i < line.n && !is_space(line.s[i])) i++; Str url = { line.s, i }; line = str_trim(str_skip(line, i)); if (!str_starts(url, S("gemini://"))) { url = str_replace_end(url, S(".gmi"), S(".html"), scratch); } if (has_image_ext(url)) { lm = lm_chg(lm, LINE_FIGURE, &out, a); str_cat(&out, S(""), a); str_cat_html(&out, line, a); str_cat(&out, S(""), a); } } else { Str display = line.n > 0 ? line : url; lm = lm_chg(lm, LINE_LINK, &out, a); str_cat(&out, S(""), a); } } else if (str_starts(line, S("*"))) { lm = lm_chg(lm, LINE_UL, &out, a); str_cat_html(&out, str_trim(str_skip(line, 1)), a); } else if (is_ol_item(line)) { lm = lm_chg(lm, LINE_OL, &out, a); str_cat_html(&out, str_trim(str_cut(line, '.').tail), a); } else if (str_starts(line, S("###"))) { lm = lm_chg(lm, LINE_HDR3, &out, a); str_cat_html(&out, str_trim(str_skip(line, 3)), a); } else if (str_starts(line, S("##"))) { lm = lm_chg(lm, LINE_HDR2, &out, a); str_cat_html(&out, str_trim(str_skip(line, 2)), a); } else if (str_starts(line, S("#"))) { lm = lm_chg(lm, LINE_HDR1, &out, a); title = str_trim(str_skip(line, 1)); str_cat_html(&out, title, a); } else if (str_starts(line, S(">"))) { lm = lm_chg(lm, LINE_BQUOT, &out, a); str_cat_html(&out, str_trim(str_skip(line, 1)), a); } else { lm = lm_chg(lm, LINE_PARA, &out, a); str_cat_html(&out, line, a); } } lm = lm_chg(lm, LINE_BLANK, &out, a); Doc *d = new(a, Doc); if (title.s) d->title = str_dup(title, a); d->html = out; d->prev = (*dp); if (*dp) (*dp)->next = d; *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; }