diff scid/page.c @ 26:7e10cace67a3

scid: add basic mustache support
author David Demelier <markand@malikania.fr>
date Tue, 02 Aug 2022 13:24:13 +0200
parents 600204c31bf0
children
line wrap: on
line diff
--- a/scid/page.c	Mon Jul 25 21:22:13 2022 +0200
+++ b/scid/page.c	Tue Aug 02 13:24:13 2022 +0200
@@ -16,24 +16,304 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <sys/stat.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <mustache.h>
+
+#include "log.h"
 #include "page.h"
+#include "scid.h"
 #include "util.h"
 
+static void
+parse_error(int code, const char *msg, unsigned int line, unsigned int col, void *data)
+{
+	(void)code;
+
+	const char *path = data;
+
+	log_warn("page: %s:%u:%u:%s", path, line, col, msg);
+}
+
+static int
+out_verbatim(const char *output, size_t size, void *data)
+{
+	FILE *fp = data;
+
+	if (fwrite(output, 1, size, fp) != size)
+		return -1;
+
+	return 0;
+}
+
+static int
+out_escaped(const char *output, size_t size, void *data)
+{
+	FILE *fp = data;
+
+	for (size_t i = 0; i < size; ++i) {
+		switch (output[i]) {
+		case '"':
+			if (out_verbatim("&quot;", 6, fp) < 0)
+				return -1;
+			break;
+		case '&':
+			if (out_verbatim("&amp;", 5, fp) < 0)
+				return -1;
+			break;
+		case '<':
+			if (out_verbatim("&lt;", 4, fp) < 0)
+				return -1;
+			break;
+		case '>':
+			if (out_verbatim("&gt;", 4, fp) < 0)
+				return -1;
+			break;
+		default:
+			if (out_verbatim(&output[i], 1, fp) <0)
+				return -1;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static inline int
+out_int(FILE *fp, intmax_t val)
+{
+	char buf[64] = {0};
+
+	snprintf(buf, sizeof (buf), "%jd", val);
+
+	return out_verbatim(buf, strlen(buf), fp);
+}
+
+static inline int
+out_double(FILE *fp, double val)
+{
+	char buf[64] = {0};
+
+	snprintf(buf, sizeof (buf), "%f", val);
+
+	return out_verbatim(buf, strlen(buf), fp);
+}
+
+static int
+dump(void *node, int (*outputter)(const char *, size_t, void *), void *rdata, void *pdata)
+{
+	(void)pdata;
+
+	FILE *fp = rdata;
+	const json_t *value = node;
+
+	switch (json_typeof(value)) {
+	case JSON_OBJECT:
+	case JSON_ARRAY:
+		/* This indicates a document construction error. */
+		// TODO: change to error log.
+		abort();
+		break;
+	case JSON_STRING:
+		return outputter(json_string_value(value), json_string_length(value), fp);
+	case JSON_INTEGER:
+		return out_int(fp, json_integer_value(value));
+	case JSON_REAL:
+		return out_double(fp, json_real_value(value));
+	case JSON_TRUE:
+		return out_verbatim("true", 4, fp);
+	case JSON_FALSE:
+		return out_verbatim("false", 5, fp);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void *
+get_root(void *pdata)
+{
+	return pdata;
+}
+
+static void *
+get_child_by_name(void *node, const char *name, size_t size, void *pdata)
+{
+	(void)pdata;
+
+	json_t *value = node;
+
+#if 1
+	printf("-> Seeking name '%.*s'\n", (int)size, name);
+	printf("-> Type of node: %d (%p)\n", json_typeof(value), value);
+#endif
+
+	if (json_is_object(value))
+		return json_object_getn(node, name, size);
+
+	return NULL;
+}
+
+static void *
+get_child_by_index(void *node, unsigned int index, void *pdata)
+{
+	(void)pdata;
+
+	json_t *value = node;
+
+#if 1
+	printf("-> Seeking index '%u'\n", index);
+	printf("-> Type of node: %d (%p)\n", json_typeof(value), value);
+#endif
+
+	if (json_is_array(value))
+		return json_array_get(node, index);
+
+	return NULL;
+}
+
+static MUSTACHE_TEMPLATE *
+get_partial(const char *name, size_t size, void *pdata)
+{
+	(void)name;
+	(void)size;
+	(void)pdata;
+
+	return NULL;
+}
+
+static char *
+readall(const char *file)
+{
+	int fd;
+	char *ret = NULL;
+	struct stat st;
+
+	if ((fd = open(file, O_RDONLY)) < 0)
+		return NULL;
+	if (fstat(fd, &st) < 0)
+		goto exit;
+	if (!(ret = calloc(1, st.st_size + 1)))
+		goto exit;
+	if (read(fd, ret, st.st_size) != st.st_size) {
+		free(ret);
+		ret = NULL;
+	}
+
+exit:
+	close(fd);
+
+	return ret;
+}
+
+static int
+process(const char *path, const char *input, FILE *fp, json_t *doc)
+{
+	MUSTACHE_PARSER parser = {
+		.parse_error = parse_error
+	};
+	MUSTACHE_RENDERER rdr = {
+		.out_verbatim = out_verbatim,
+		.out_escaped = out_escaped
+	};
+	MUSTACHE_DATAPROVIDER pv = {
+		.dump = dump,
+		.get_root = get_root,
+		.get_child_by_name = get_child_by_name,
+		.get_child_by_index = get_child_by_index,
+		.get_partial = get_partial
+	};
+	MUSTACHE_TEMPLATE *tmpl;
+	int status;
+	
+	if (!(tmpl = mustache_compile(input, strlen(input), &parser, (void *)path, 0)))
+		return -1;
+
+	status = mustache_process(tmpl, &rdr, fp, &pv, doc);
+	mustache_release(tmpl);
+
+	return status ? -1 : 0;
+}
+
+char *
+templatize(const char *path, json_t *doc)
+{
+	assert(path);
+
+	char *in = NULL, *out = NULL;
+	size_t outsz = 0;
+	FILE *fp;
+
+	if (!(fp = open_memstream(&out, &outsz)))
+		goto exit;
+	if (!(in = readall(path)))
+		goto exit;
+
+	/* Process template but don't return a partially written file. */
+	if (process(path, in, fp, doc) < 0) {
+		fclose(fp);
+		free(out);
+		fp = NULL;
+		out = NULL;
+		errno = EINVAL;
+	}
+
+exit:
+	if (!out)
+		log_warn("page: %s: %s", path, strerror(errno));
+	if (fp)
+		fclose(fp);
+
+	free(in);
+	json_decref(doc);
+
+	return out;
+}
+
+static void
+render(struct kreq *req, const char *path, json_t *doc)
+{
+	char *content;
+
+	if (!(content = templatize(path, doc)))
+		log_warn("page: unable to templatize: %s", strerror(errno));
+	if (doc)
+		json_decref(doc);
+
+	khttp_printf(req, "%s", content);
+
+	free(content);
+}
+
 void
 page(struct kreq *req,
-     const struct ktemplate *tmpl,
      enum khttp status,
      enum kmime mime,
-     const char *file)
+     const char *path,
+     json_t *doc)
 {
+	assert(req);
+	assert(!path || (path && doc));
+
 	khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]);
 	khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]);
 	khttp_body(req);
 
-	if (file) {
-		khttp_template(req, NULL, util_path("fragments/header.html"));
-		khttp_template(req, tmpl, util_path(file));
-		khttp_template(req, NULL, util_path("fragments/footer.html"));
+	if (path) {
+		printf("document is: [%s]\n", json_dumps(doc, JSON_INDENT(2)));
+		/* TODO: add title in the header. */
+		render(req, scid_theme_path("fragments/header.html"), NULL);
+		render(req, scid_theme_path(path), doc);
+		render(req, scid_theme_path("fragments/footer.html"), NULL);
 	}
 
 	khttp_free(req);