summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-04-15 19:06:42 -0400
committerJune McEnroe <june@causal.agency>2020-04-15 19:06:42 -0400
commitd72dcfd4665f937eec2d325c10d35a644e9c06ad (patch)
tree81617185483e24ab19a0beefa4c69405a69e596e
parentTweak fetchParts naming (diff)
downloadbubger-d72dcfd4665f937eec2d325c10d35a644e9c06ad.tar.gz
bubger-d72dcfd4665f937eec2d325c10d35a644e9c06ad.zip
Rewrite HTML rendering
-rw-r--r--archive.c9
-rw-r--r--archive.h12
-rw-r--r--concat.c37
-rw-r--r--export.c51
-rw-r--r--html.c218
5 files changed, 217 insertions, 110 deletions
diff --git a/archive.c b/archive.c
index 992b592..7edc6d6 100644
--- a/archive.c
+++ b/archive.c
@@ -62,9 +62,6 @@ int main(int argc, char *argv[]) {
 	const char *algo = "REFERENCES";
 	const char *search = "ALL";
 
-	const char *title = NULL;
-	const char *headPath = NULL;
-
 	for (int opt; 0 < (opt = getopt(argc, argv, "C:a:h:p:s:t:u:vw:"));) {
 		switch (opt) {
 			break; case 'C': {
@@ -72,10 +69,10 @@ int main(int argc, char *argv[]) {
 				if (error) err(EX_NOINPUT, "%s", optarg);
 			}
 			break; case 'a': algo = optarg;
-			break; case 'h': headPath = optarg;
+			break; case 'h': concatHead = optarg;
 			break; case 'p': port = optarg;
 			break; case 's': search = optarg;
-			break; case 't': title = optarg;
+			break; case 't': htmlTitle = optarg;
 			break; case 'u': atomBaseURL = optarg;
 			break; case 'v': imapVerbose = true;
 			break; case 'w': passPath = optarg;
@@ -87,7 +84,7 @@ int main(int argc, char *argv[]) {
 
 	if (!host) errx(EX_USAGE, "host required");
 	if (!user) errx(EX_USAGE, "user required");
-	if (!title) title = mailbox;
+	if (!htmlTitle) htmlTitle = mailbox;
 
 	char *pass = NULL;
 	if (passPath) {
diff --git a/archive.h b/archive.h
index c48d8e3..94db1a6 100644
--- a/archive.h
+++ b/archive.h
@@ -130,6 +130,7 @@ void parseBodyPart(struct BodyPart *part, struct List list);
 bool exportFetch(FILE *imap, enum Atom tag, struct List threads);
 bool exportData(FILE *imap, enum Atom tag, struct List items);
 
+extern const char *concatHead;
 void concatFetch(FILE *imap, enum Atom tag, struct List threads);
 void concatData(struct List threads, struct List items);
 
@@ -203,10 +204,11 @@ int atomEntryClose(FILE *file);
 int atomFeedOpen(FILE *file, const struct Envelope *envelope);
 int atomFeedClose(FILE *file);
 
-int htmlMessageHead(FILE *file, const struct Envelope *envelope);
-int htmlMessageTail(FILE *file);
+extern const char *htmlTitle;
+int htmlMessageOpen(FILE *file, const struct Envelope *envelope);
+int htmlMessageClose(FILE *file);
 int htmlThreadHead(FILE *file, const struct Envelope *envelope);
-int htmlThreadHeader(FILE *file, const struct Envelope *envelope);
-int htmlThreadOpen(FILE *file);
+int htmlThreadOpen(FILE *file, const struct Envelope *envelope);
+int htmlSubthreadOpen(FILE *file);
+int htmlSubthreadClose(FILE *file);
 int htmlThreadClose(FILE *file);
-int htmlThreadTail(FILE *file);
diff --git a/concat.c b/concat.c
index a0f451e..04d2e27 100644
--- a/concat.c
+++ b/concat.c
@@ -80,22 +80,21 @@ static int concatHTML(FILE *file, struct List thread) {
 	int error;
 	for (size_t i = 0; i < thread.len; ++i) {
 		if (thread.ptr[i].type == List) {
-			error = concatHTML(file, thread.ptr[i].list);
+			error = 0
+				|| htmlSubthreadOpen(file)
+				|| concatHTML(file, thread.ptr[i].list)
+				|| htmlSubthreadClose(file);
 		} else {
 			uint32_t uid = dataCheck(thread.ptr[i], Number).number;
-			error = htmlThreadOpen(file)
-				|| concatFile(file, pathUID(uid, "html"));
+			error = concatFile(file, pathUID(uid, "html"));
 		}
 		if (error) return error;
 	}
-	for (size_t i = 0; i < thread.len; ++i) {
-		if (thread.ptr[i].type == List) continue;
-		error = htmlThreadClose(file);
-		if (error) return error;
-	}
 	return 0;
 }
 
+const char *concatHead;
+
 void concatData(struct List threads, struct List items) {
 	uint32_t uid = 0;
 	struct Envelope envelope = {0};
@@ -157,5 +156,27 @@ void concatData(struct List threads, struct List items) {
 		if (error) err(EX_IOERR, "%s", path);
 	}
 
+	path = pathThread(envelope.messageID, "html");
+	error = stat(path, &status);
+	if (error || status.st_mtime < uidNewest(flat, "html")) {
+		FILE *file = fopen(path, "w");
+		if (!file) err(EX_CANTCREAT, "%s", path);
+
+		error = htmlThreadHead(file, &envelope);
+		if (error) err(EX_IOERR, "%s", path);
+
+		if (concatHead) {
+			error = concatFile(file, concatHead);
+			if (error) err(EX_IOERR, "%s", path);
+		}
+
+		error = 0
+			|| htmlThreadOpen(file, &envelope)
+			|| concatHTML(file, thread)
+			|| htmlThreadClose(file)
+			|| fclose(file);
+		if (error) err(EX_IOERR, "%s", path);
+	}
+
 	listFree(flat);
 }
diff --git a/export.c b/export.c
index fe166f8..3c6a31f 100644
--- a/export.c
+++ b/export.c
@@ -111,6 +111,55 @@ static void exportAtom(
 	if (error) err(EX_IOERR, "%s", path);
 }
 
+static int exportHTMLBody(
+	FILE *file, struct List *section,
+	const struct BodyPart *structure, struct Data body
+) {
+	int error;
+	if (structure->multipart) {
+		// TODO: Choose a part from multipart/alternative.
+		for (size_t i = 0; i < structure->parts.len; ++i) {
+			struct Data part = { .type = Number, .number = 1 + i };
+			listPush(section, part);
+			error = exportHTMLBody(
+				file, section,
+				&structure->parts.ptr[i], dataCheck(body, List).list.ptr[i]
+			);
+			if (error) return error;
+			section->len--;
+		}
+	} else if (structure->message.envelope) {
+		error = 0
+			|| htmlMessageOpen(file, structure->message.envelope)
+			|| exportHTMLBody(file, section, structure->message.structure, body)
+			|| htmlMessageClose(file);
+	} else {
+		// TODO: Content.
+		error = 0;
+	}
+	return error;
+}
+
+static void exportHTML(
+	uint32_t uid, const struct Envelope *envelope,
+	const struct BodyPart *structure, struct Data body
+) {
+	const char *path = pathUID(uid, "html");
+	FILE *file = fopen(path, "w");
+	if (!file) err(EX_CANTCREAT, "%s", path);
+
+	int error = htmlMessageOpen(file, envelope);
+	if (error) err(EX_IOERR, "%s", path);
+
+	struct List section = {0};
+	error = exportHTMLBody(file, &section, structure, body);
+	if (error) err(EX_IOERR, "%s", path);
+	listFree(section);
+
+	error = htmlMessageClose(file) || fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+}
+
 static void fetchParts(
 	FILE *imap, struct List *section, const struct BodyPart *structure
 ) {
@@ -232,8 +281,10 @@ bool exportData(FILE *imap, enum Atom tag, struct List items) {
 	bool fetch = false;
 	if (!structure.multipart) {
 		exportAtom(uid, &envelope, &structure, bodyText);
+		exportHTML(uid, &envelope, &structure, bodyText);
 	} else if (bodyParts.type == List) {
 		exportAtom(uid, &envelope, &structure, bodyParts);
+		exportHTML(uid, &envelope, &structure, bodyParts);
 	} else {
 		fetch = true;
 		fprintf(
diff --git a/html.c b/html.c
index d6bf723..090bfab 100644
--- a/html.c
+++ b/html.c
@@ -24,161 +24,197 @@
 #include "archive.h"
 
 static int htmlAddress(FILE *file, const char *class, struct Address addr) {
-	struct Variable vars[] = {
-		{ "class", class },
-		{ "name", addressName(addr) },
-		{ "mailbox", addr.mailbox },
-		{0},
-	};
+	const char *template;
 	if (addr.host) {
-		return templateRender(
-			file,
-			TEMPLATE(<li><address class="[class]">[name]</address></li>),
-			vars,
-			escapeXML
+		template = TEMPLATE(
+			<li><address class="[class]">[name]</address></li>
 		);
 	} else if (addr.mailbox) {
-		return templateRender(
-			file,
-			TEMPLATE(<li><address class="[class] group">[mailbox]<ul>),
-			vars,
-			escapeXML
+		template = TEMPLATE(
+			<li>
+				<address class="[class] group">[mailbox]</address>
+				<ul class="group">
 		);
 	} else {
-		return templateRender(
-			file,
-			TEMPLATE(</ul></address></li>),
-			vars,
-			escapeXML
+		template = TEMPLATE(
+				</ul>
+			</li>
 		);
 	}
+	struct Variable vars[] = {
+		{ "class", class },
+		{ "name", addressName(addr) },
+		{ "mailbox", addr.mailbox },
+		{0},
+	};
+	return templateRender(file, template, vars, escapeXML);
 }
 
 static int
 htmlAddressList(FILE *file, const char *class, struct AddressList list) {
 	if (!list.len) return 0;
+	const char *template = TEMPLATE(
+		<ul class="[class]">
+	);
 	struct Variable vars[] = {
 		{ "class", class },
 		{0},
 	};
-	int error = templateRender(
-		file, TEMPLATE(<ul class="[class]">), vars, escapeXML
-	);
+	int error = templateRender(file, template, vars, escapeXML);
 	if (error) return error;
 	for (size_t i = 0; i < list.len; ++i) {
 		error = htmlAddress(file, class, list.addrs[i]);
 		if (error) return error;
 	}
-	return templateRender(file, TEMPLATE(</ul>), vars, escapeXML);
+	return templateRender(file, TEMPLATE(</ul>), NULL, NULL);
 }
 
-int htmlMessageHead(FILE *file, const struct Envelope *envelope) {
-	struct Variable urlVars[] = {
-		{ "mailbox", envelope->replyTo.mailbox },
-		{ "host", envelope->replyTo.host },
+static char *htmlMailto(const struct Envelope *envelope) {
+	const char *template = {
+		"mailto:[mailbox]@[host]?subject=[re][subject]&In-Reply-To=[messageID]"
+	};
+	struct Variable vars[] = {
+		{ "mailbox", envelope->from.mailbox },
+		{ "host", envelope->from.host },
 		{ "re", (strncmp(envelope->subject, "Re: ", 4) ? "Re: " : "") },
 		{ "subject", envelope->subject },
 		{ "messageID", envelope->messageID },
-		{ "pathID", pathSafe(envelope->messageID) },
 		{0},
 	};
-	char *fragment = templateURL("#[messageID]", urlVars);
-	char *mailto = templateURL(
-		"mailto:[mailbox]@[host]?subject=[re][subject]&In-Reply-To=[messageID]",
-		urlVars
-	);
-	char *mbox = templateURL("../message/[pathID].mbox", urlVars);
+	return templateURL(template, vars);
+}
+
+static char *htmlFragment(const struct Envelope *envelope) {
+	struct Variable vars[] = {
+		{ "messageID", envelope->messageID },
+		{0},
+	};
+	return templateURL("#[messageID]", vars);
+}
 
+static char *htmlMbox(const struct Envelope *envelope) {
+	struct Variable vars[] = {
+		{ "name", pathSafe(envelope->messageID) },
+		{0},
+	};
+	return templateURL("../message/[name].mbox", vars);
+}
+
+int htmlMessageOpen(FILE *file, const struct Envelope *envelope) {
+	// TODO: Conditionally include mailto: link.
+	const char *template1 = TEMPLATE(
+		<article class="message" id="[messageID]">
+		<header>
+			<h2>[subject]</h2>
+			<address class="from">
+				<a href="[mailto]">[from]</a>
+			</address>
+			<time datetime="[utc]">[date]</time>
+	);
+	const char *template2 = TEMPLATE(
+			<nav>
+				<ul>
+					<li><a href="[fragment]">permalink</a></li>
+					<li><a href="[mbox]">mbox</a></li>
+				</ul>
+			</nav>
+		</header>
+	);
+	char *mailto = htmlMailto(envelope);
 	char utc[sizeof("0000-00-00T00:00:00Z")];
 	strftime(utc, sizeof(utc), "%FT%TZ", gmtime(&envelope->time));
+	char *fragment = htmlFragment(envelope);
+	char *mbox = htmlMbox(envelope);
 	struct Variable vars[] = {
 		{ "messageID", envelope->messageID },
-		{ "fragment", fragment },
 		{ "subject", envelope->subject },
 		{ "mailto", mailto },
 		{ "from", addressName(envelope->from) },
-		{ "date", envelope->date },
 		{ "utc", utc },
+		{ "date", envelope->date },
+		{ "fragment", fragment },
 		{ "mbox", mbox },
 		{0},
 	};
-	const char *Summary = TEMPLATE(
-		<details class="message" id="[messageID]">
-		<summary>
-			<a class="subject" href="[fragment]">[subject]</a>
-			<address class="from"><a href="[mailto]">[from]</a></address>
-			<time datetime="[utc]">[date]</time>
-			<a class="mbox" href="[mbox]">mbox</a>
-		</summary>
-	);
-	int error = templateRender(file, Summary, vars, escapeXML);
-	free(fragment);
+	int error = 0
+		|| templateRender(file, template1, vars, escapeXML)
+		|| htmlAddressList(file, "to", envelope->to)
+		|| htmlAddressList(file, "cc", envelope->cc)
+		|| templateRender(file, template2, vars, escapeXML);
 	free(mailto);
+	free(fragment);
 	free(mbox);
-	if (error) return error;
-
-	return 0
-		|| htmlAddressList(file, "to", envelope->to)
-		|| htmlAddressList(file, "cc", envelope->cc);
+	return error;
 }
 
-int htmlMessageTail(FILE *file) {
-	int n = fprintf(file, "</details>\n");
-	return (n < 0 ? n : 0);
+int htmlMessageClose(FILE *file) {
+	return templateRender(file, TEMPLATE(</article>), NULL, NULL);
 }
 
-int htmlThreadHead(FILE *file, const struct Envelope *envelope) {
-	struct Variable urlVars[] = {
-		{ "pathID", pathSafe(envelope->messageID) },
-		{0},
-	};
-	char *path = templateURL("[pathID]", urlVars);
+const char *htmlTitle;
 
+static char *htmlThreadURL(const struct Envelope *envelope) {
 	struct Variable vars[] = {
-		{ "subject", envelope->subject },
-		{ "path", path },
+		{ "name", pathSafe(envelope->messageID) },
 		{0},
 	};
-	const char *Head = TEMPLATE(
+	return templateURL("[name]", vars);
+}
+
+int htmlThreadHead(FILE *file, const struct Envelope *envelope) {
+	const char *template = TEMPLATE(
 		<!DOCTYPE html>
 		<meta charset="utf-8">
-		<title>[subject]</title>
-		<link rel="alternate" type="application/atom+xml" href="[path].atom">
-		<link rel="alternate" type="application/mbox" href="[path].mbox">
-		<style>
-		address { display: inline; }
-		section.thread section.thread:not(:first-child) {
-			border-left: 1px solid gray;
-			padding-left: 2ch;
-		}
-		</style>
+		<title>[subject] &middot; [title]</title>
+		<link rel="alternate" type="application/atom+xml" href="[url].atom">
+		<link rel="alternate" type="application/mbox" href="[url].mbox">
 	);
-	int error = templateRender(file, Head, vars, escapeXML);
-	free(path);
+	char *url = htmlThreadURL(envelope);
+	struct Variable vars[] = {
+		{ "subject", envelope->subject },
+		{ "title", htmlTitle },
+		{ "url", url },
+		{0},
+	};
+	int error = templateRender(file, template, vars, escapeXML);
+	free(url);
 	return error;
 }
 
-int htmlThreadHeader(FILE *file, const struct Envelope *envelope) {
+int htmlThreadOpen(FILE *file, const struct Envelope *envelope) {
+	const char *template = TEMPLATE(
+		<header class="thread">
+			<h1>[subject]</h1>
+			<nav>
+				<ul>
+					<li><a href="[url].atom">Atom</a></li>
+					<li><a href="[url].mbox">mbox</a></li>
+				</ul>
+			</nav>
+		</header>
+		<main class="thread">
+	);
+	char *url = htmlThreadURL(envelope);
 	struct Variable vars[] = {
 		{ "subject", envelope->subject },
+		{ "url", url },
 		{0},
 	};
-	const char *Header = TEMPLATE(
-		<h1>[subject]</h1>
-	);
-	return templateRender(file, Header, vars, escapeXML);
+	int error = templateRender(file, template, vars, escapeXML);
+	free(url);
+	return error;
 }
 
-int htmlThreadOpen(FILE *file) {
-	int n = fprintf(file, TEMPLATE(<section class="thread">));
-	return (n < 0 ? n : 0);
+int htmlSubthreadOpen(FILE *file) {
+	return templateRender(
+		file, TEMPLATE(<section class="subthread">), NULL, NULL
+	);
 }
 
-int htmlThreadClose(FILE *file) {
-	int n = fprintf(file, TEMPLATE(</section>));
-	return (n < 0 ? n : 0);
+int htmlSubthreadClose(FILE *file) {
+	return templateRender(file, TEMPLATE(</section>), NULL, NULL);
 }
 
-int htmlThreadTail(FILE *file) {
-	return 0;
+int htmlThreadClose(FILE *file) {
+	return templateRender(file, TEMPLATE(</main>), NULL, NULL);
 }