summary refs log tree commit diff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c80
1 files changed, 73 insertions, 7 deletions
diff --git a/main.c b/main.c
index e65eb9d..8f5a784 100644
--- a/main.c
+++ b/main.c
@@ -24,6 +24,7 @@ typedef struct {
 	Str title;
 	int hvarc;
 	Str docroot;
+	Str header_file, footer_file;
 } Options;
 
 Options opts = { 0 };
@@ -66,12 +67,33 @@ char to_xdigit(int x) {
 	}
 }
 
+int uri_ch_reserved(char c) {
+	return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']';
+}
+
+int uri_ch_unreserved(char c) {
+	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+		|| (c >= '0' && c <= '9') || c == '-' || c == '.'
+		|| c == '_' || c == '~';
+}
+
+int uri_ch_sub_delim(char c) {
+	return c == '!' || c == '$' || c == '&' || c == '\'' || c == '('
+		|| c == ')' || c == '*' || c == '+' || c == ',' || c == ';'
+		|| c == '=';
+}
+
+int uri_ch_allowed(char c) {
+	return uri_ch_reserved(c) || uri_ch_unreserved(c) || uri_ch_sub_delim(c)
+		|| c == '%';
+}
+
 void str_cat_uri_internal(Str *s, Str uri, Arena *a) {
 	for (isize i = 0; i < uri.n; i++) {
 		char c = uri.s[i];
-		if (c == '\'' || c == '%') {
+		if (c == '\'' || !uri_ch_allowed(c)) {
 			str_catc(s, '%', a);
-			str_catc(s, to_xdigit((c & 0xff) >> 4), a);
+			str_catc(s, to_xdigit((c >> 4) & 0xf), a);
 			str_catc(s, to_xdigit(c & 0xf), a);
 		} else {
 			str_catc(s, c, a);
@@ -379,7 +401,7 @@ void markup_inline(InlineState *ms, Str *out, Str src, Arena *scratch, Arena *pe
 				if (!f.n) break;
 				i = f.s - src.s + 1;
 			} else if (str_starts(skp, S("---"))) {
-				str_cat(out, S("&mdash;"), perm);
+				str_cat(out, S("—"), perm);
 				i += 2;
 			} else {
 				text_inline(ms, c, out, perm);
@@ -498,6 +520,15 @@ uint64_t str_hash(Str s) {
 	return h;
 }
 
+uint64_t murmur64(uint64_t h) {
+	h ^= h >> 33;
+	h *= 0xff51afd7ed558ccdL;
+	h ^= h >> 33;
+	h *= 0xc4ceb9fe1a85ec53L;
+	h ^= h >> 33;
+	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, ':');
@@ -506,7 +537,7 @@ int hvar_calc(Str param, Str *name, Str *val, Str filename) {
 	usize n = 0;
 	for (Str h = c.tail; h.n > 0; h = str_cut(h, ',').tail) n++;
 	if (!n) return -1;
-	usize j = str_hash(filename) % n;
+	usize j = murmur64(str_hash(filename)) % n;
 	usize i = 0;
 	for (Str h = c.tail; h.n > 0; h = str_cut(h, ',').tail) {
 		if (i == j) {
@@ -520,12 +551,13 @@ int hvar_calc(Str param, Str *name, Str *val, Str filename) {
 
 void usage(const char *cmd) {
 	fprintf(stderr, "usage: %s -?\n"
-			"       %s [-silq] [-L LANG] [-c FILE] [-t TITLE] [-h NAME:ARG1[,ARG2,ARG3...]] [FILES...]\n"
+			"       %s [OPTIONS] [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"
+			"                   (format: NAME:ARG1[,ARG2,ARG3...])\n"
 			" -c --css          embed the given file within a <style> element\n"
 			" -l --link         when combined with --css, link to an external stylehseet\n"
 			"                   instead of reading from a file locally\n"
@@ -533,6 +565,8 @@ void usage(const char *cmd) {
 			" -q --smartq       enable smart quotes\n"
 			" -t --title        set the document title (does nothing without --standalone)\n"
 			" -R --root         set a root url prepended to absolute paths\n"
+			" -H --header       prepend the given file before the document\n"
+			" -F --footer       append the given file after the document\n"
 			" -L --lang         set the document's language\n",
 			cmd, cmd);
 }
@@ -609,6 +643,26 @@ Str html_tail(Arena *m, Arena *l) {
 	return h;
 }
 
+void put_file(Str filename, Arena *scratch) {
+	Arena t = *scratch;
+	const char *path = str_to_cstr(filename, scratch);
+	FILE *f = fopen(path, "r/o");
+	if (!f) {
+		fprintf(stderr, "failed to open file %s: %s\n", path,
+				strerror(errno));
+		return;
+	}
+	Str buf;
+	if (read_all(f, &buf, scratch)) {
+		fprintf(stderr, "failed to read file %s: %s\n", path,
+				strerror(errno));
+		return;
+	}
+	str_put(buf);
+	fclose(f);
+	*scratch = t;
+}
+
 #define countof(x) (sizeof(x) / sizeof(*x))
 int main(int argc, const char **argv) {
 	(void)argc;
@@ -623,7 +677,7 @@ int main(int argc, const char **argv) {
 	Str param = { 0 };
 	opts.from_stdin = 1;
 
-	while ((r = arg_get(&a, "?sliqc:h:t:L:R:", &param,
+	while ((r = arg_get(&a, "?sliqc:h:t:L:R:H:F:", &param,
 					"help", '?',
 					"standalone", 's',
 					"smartq", 'q',
@@ -633,7 +687,9 @@ int main(int argc, const char **argv) {
 					"link", 'l',
 					"lang", 'L',
 					"root", 'R',
-					":hvar", 'h')) >= ARG_OK) {
+					":hvar", 'h',
+					"header", 'H',
+					"footer", 'F')) >= ARG_OK) {
 		Arena reset = scratch;
 		FILE *f;
 		switch (r) {
@@ -671,6 +727,12 @@ int main(int argc, const char **argv) {
 			}
 			opts.hvarv[opts.hvarc++] = param;
 			break;
+		case 'H':
+			opts.header_file = param;
+			break;
+		case 'F':
+			opts.footer_file = param;
+			break;
 		default:
 			if (str_eql(param, S("-"))) {
 				if (wdoc(stdin, &doc, &perm, &scratch)) {
@@ -727,11 +789,15 @@ int main(int argc, const char **argv) {
 		str_put(S("\n"));
 	}
 
+	if (opts.header_file.n) put_file(opts.header_file, &scratch);
+
 	while (doc) {
 		str_put(doc->html);
 		doc = doc->next;
 	}
 
+	if (opts.footer_file.n) put_file(opts.footer_file, &scratch);
+
 	str_put(html_tail(&perm, &scratch));
 
 	return 0;