summary refs log tree commit diff
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;
+}