summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorWormHeamer2025-03-08 16:45:17 -0500
committerWormHeamer2025-03-08 16:45:17 -0500
commit4af753d591e61a7380735e03a8658fb8949e0448 (patch)
treeae92949580512dadecd26b8c3a359f2bd7755d58 /main.c
initial commit
Diffstat (limited to 'main.c')
-rw-r--r--main.c409
1 files changed, 409 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..8dc8a6e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,409 @@
+#define _POSIX_C_SOURCE 200809L
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#define ARENA_IMPL
+#include "str.h"
+#include "arena.h"
+#define ARGS_IMPL
+#include "args.h"
+
+int read_all(FILE *f, Str *buf, Arena *a) {
+ if (!f) return -1;
+ if (isatty(fileno(f))) {
+ buf->s = a->beg;
+ buf->n = fread(a->beg, 1, (uintptr_t)(a->end - a->beg) - 1, f);
+ a->beg += buf->n;
+ } else {
+ fseek(f, 0, SEEK_END);
+ long ofs = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ buf->n = ofs;
+ buf->s = new_arr(a, char, buf->n);
+ if ((isize)fread(buf->s, 1, buf->n, f) != buf->n) return -1;
+ }
+ 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("&amp;"), a); break;
+ case '<': str_cat(s, S("&lt;"), a); break;
+ case '>': str_cat(s, S("&gt;"), 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) {
+ static Str op[] = {
+ [LINE_BLANK] = S(""),
+ [LINE_PARA] = S("<p>"),
+ [LINE_LINK] = S("<ul>\n<li>"),
+ [LINE_FIGURE] = S("<figure>"),
+ [LINE_UL] = S("<ul>\n<li>"),
+ [LINE_OL] = S("<ol>\n<li>"),
+ [LINE_HDR1] = S("<h1>"),
+ [LINE_HDR2] = S("<h2>"),
+ [LINE_HDR3] = S("<h3>"),
+ [LINE_CODE] = S("<pre><code>"),
+ [LINE_BQUOT] = S("<blockquote>"),
+ };
+ static Str cl[] = {
+ [LINE_BLANK] = S(""),
+ [LINE_PARA] = S("</p>"),
+ [LINE_LINK] = S("</li>\n</ul>"),
+ [LINE_FIGURE] = S("</figure>"),
+ [LINE_UL] = S("</li>\n</ul>"),
+ [LINE_OL] = S("</li>\n</ol>"),
+ [LINE_HDR1] = S("</h1>"),
+ [LINE_HDR2] = S("</h2>"),
+ [LINE_HDR3] = S("</h3>"),
+ [LINE_CODE] = S("</code></pre>"),
+ [LINE_BQUOT] = S("</blockquote>"),
+ };
+ static Str cont[] = {
+ [LINE_BLANK] = S(""),
+ [LINE_PARA] = S("<br>\n"),
+ [LINE_FIGURE] = S("</figure>\n<figure>"),
+ [LINE_LINK] = S("</li>\n<li>"),
+ [LINE_UL] = S("</li>\n<li>"),
+ [LINE_OL] = S("</li>\n<li>"),
+ [LINE_HDR1] = S("</h1>\n<h1>"),
+ [LINE_HDR2] = S("</h2>\n<h2>"),
+ [LINE_HDR3] = S("</h3>\n<h3>"),
+ [LINE_CODE] = S("\n"),
+ [LINE_BQUOT] = S("<br>\n"),
+ };
+ 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("<img src="), a);
+ str_cat_uri(&out, url, a);
+ str_catc(&out, '>', a);
+ if (line.n > 0) {
+ str_cat(&out, S("<figcaption>"), a);
+ str_cat_html(&out, line, a);
+ str_cat(&out, S("</figcaption>"), a);
+ }
+ } else {
+ Str display = line.n > 0 ? line : url;
+ lm = lm_chg(lm, LINE_LINK, &out, a);
+ str_cat(&out, S("<a href="), a);
+ str_cat_uri(&out, url, a);
+ str_catc(&out, '>', a);
+ str_cat_html(&out, display, a);
+ str_cat(&out, S("</a>"), 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;
+}
+
+#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)); }
+
+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, ':');
+ *name = c.head;
+ usize n = 0;
+ for (Str h = c.tail; h.n > 0; h = str_cut(h, ',').tail) n++;
+ 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;
+}
+
+#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;
+ struct {
+ int standalone;
+ int from_stdin;
+ Str stylesheet;
+ Str hvarv[1024];
+ int hvarc;
+ } opts = { 0 };
+ int r;
+
+ ArgsState a = args_begin(argv);
+ Str param = { 0 };
+ opts.from_stdin = 1;
+
+ while ((r = arg_get(&a, "sc:h:", &param, ":css", 'c', ":hvar", 'h')) >= ARG_OK) {
+ Arena reset = scratch;
+ FILE *f;
+ switch (r) {
+ case 's':
+ opts.standalone = 1;
+ break;
+ case 'c':
+ opts.stylesheet = 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:
+ 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, "'\n");
+ return 1;
+ case ARG_EMPTY:
+ fprintf(stderr, "'");
+ fwrite(param.s, 1, param.n, stderr);
+ fprintf(stderr, "' option expected an argument\n");
+ return 1;
+ }
+
+ if (opts.from_stdin) {
+ wdoc(stdin, &doc, &perm, &scratch);
+ }
+
+ if (doc && opts.standalone) {
+ Str title = doc->title;
+ while (doc->prev) {
+ doc = doc->prev;
+ if (doc->title.s) title = doc->title;
+ }
+ Str thtml = S("<!DOCTYPE html>"
+ "<meta charset=utf-8>"
+ "<meta name=viewport content='width=device-width,initial-scale=1'>");
+ if (title.s) {
+ str_cat(&thtml, S("<title>"), &scratch);
+ str_cat_html(&thtml, title, &scratch);
+ str_cat(&thtml, S("</title>"), &scratch);
+ }
+
+ if (opts.stylesheet.s) {
+ FILE *f = fopen(str_to_cstr(opts.stylesheet, &scratch), "r/o");
+ if (!f) {
+ str_putf(opts.stylesheet, stderr);
+ fprintf(stderr, ": %s\n", strerror(errno));
+ return 1;
+ }
+ Str css;
+ Arena p = perm;
+ if (read_all(f, &css, &perm)) {
+ fprintf(stderr, "failed to read stylesheet: %s\n", strerror(errno));
+ return 1;
+ }
+ str_cat(&thtml, S("<style>"), &scratch);
+ if (opts.hvarc > 0) {
+ str_cat(&thtml, S(":root{"), &scratch);
+ for (int i = 0; i < opts.hvarc; i++) {
+ Str name, val;
+ if (hvar_calc(opts.hvarv[i], &name, &val, title)) {
+ fprintf(stderr, "failed to caluclate hashvar!\n");
+ return 1;
+ }
+ str_cat(&thtml, S("--"), &scratch);
+ str_cat(&thtml, name, &scratch);
+ str_catc(&thtml, ':', &scratch);
+ str_cat(&thtml, val, &scratch);
+ str_catc(&thtml, ';', &scratch);
+ }
+ str_catc(&thtml, '}', &scratch);
+ }
+ str_cat(&thtml, css, &scratch);
+ str_cat(&thtml, S("</style>"), &scratch);
+ perm = p;
+ }
+
+ str_put(thtml);
+ }
+
+ while (doc) {
+ str_put(doc->html);
+ doc = doc->next;
+ }
+
+ return 0;
+}