diff scid/theme.c @ 29:695637f1d8a7

scid: first index page in javascript
author David Demelier <markand@malikania.fr>
date Thu, 04 Aug 2022 14:13:58 +0200
parents 4c16bb25e4f1
children 43333d18e4b8
line wrap: on
line diff
--- a/scid/theme.c	Thu Aug 04 06:09:54 2022 +0200
+++ b/scid/theme.c	Thu Aug 04 14:13:58 2022 +0200
@@ -4,46 +4,308 @@
 #include <string.h>
 
 #include <duktape.h>
+#include <jansson.h>
+#include <mustache.h>
 
 #include "log.h"
+#include "scid.h"
 #include "theme.h"
 #include "util.h"
 
+#define SIGNATURE DUK_HIDDEN_SYMBOL("File")
+
 struct theme {
 	char base[PATH_MAX];
 	duk_context *ctx;
 };
 
+/* {{{ mustache support */
+
+static void
+mch_parse_error(int code, const char *msg, unsigned int line, unsigned int col, void *data)
+{
+	(void)code;
+
+	const char *path = data;
+
+	log_warn("theme: %s:%u:%u:%s", path, line, col, msg);
+}
+
+static int
+mch_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
+mch_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 (mch_out_verbatim("&quot;", 6, fp) < 0)
+				return -1;
+			break;
+		case '&':
+			if (mch_out_verbatim("&amp;", 5, fp) < 0)
+				return -1;
+			break;
+		case '<':
+			if (mch_out_verbatim("&lt;", 4, fp) < 0)
+				return -1;
+			break;
+		case '>':
+			if (mch_out_verbatim("&gt;", 4, fp) < 0)
+				return -1;
+			break;
+		default:
+			if (mch_out_verbatim(&output[i], 1, fp) <0)
+				return -1;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static inline int
+mch_out_int(FILE *fp, intmax_t val)
+{
+	char buf[64] = {0};
+
+	snprintf(buf, sizeof (buf), "%jd", val);
+
+	return mch_out_verbatim(buf, strlen(buf), fp);
+}
+
+static inline int
+mch_out_double(FILE *fp, double val)
+{
+	char buf[64] = {0};
+
+	snprintf(buf, sizeof (buf), "%f", val);
+
+	return mch_out_verbatim(buf, strlen(buf), fp);
+}
+
+static int
+mch_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 mch_out_int(fp, json_integer_value(value));
+	case JSON_REAL:
+		return mch_out_double(fp, json_real_value(value));
+	case JSON_TRUE:
+		return mch_out_verbatim("true", 4, fp);
+	case JSON_FALSE:
+		return mch_out_verbatim("false", 5, fp);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void *
+mch_get_root(void *pdata)
+{
+	return pdata;
+}
+
+static void *
+mch_get_child_by_name(void *node, const char *name, size_t size, void *pdata)
+{
+	(void)pdata;
+
+	json_t *value = node;
+
+#if 0
+	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 *
+mch_get_child_by_index(void *node, unsigned int index, void *pdata)
+{
+	(void)pdata;
+
+	json_t *value = node;
+
+#if 0
+	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 *
+mch_get_partial(const char *name, size_t size, void *pdata)
+{
+	(void)name;
+	(void)size;
+	(void)pdata;
+
+	return NULL;
+}
+
+static int
+mch_process(const char *path, const char *input, FILE *fp, json_t *doc)
+{
+	MUSTACHE_PARSER parser = {
+		.parse_error = mch_parse_error
+	};
+	MUSTACHE_RENDERER rdr = {
+		.out_verbatim = mch_out_verbatim,
+		.out_escaped = mch_out_escaped
+	};
+	MUSTACHE_DATAPROVIDER pv = {
+		.dump = mch_dump,
+		.get_root = mch_get_root,
+		.get_child_by_name = mch_get_child_by_name,
+		.get_child_by_index = mch_get_child_by_index,
+		.get_partial = mch_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;
+}
+
+void
+mch_templatize(FILE *fp, const char *path, json_t *doc)
+{
+	assert(path);
+
+	char *in = NULL;
+
+	if ((in = util_read(path))) {
+		/* Process template but don't return a partially written file. */
+		if (mch_process(path, in, fp, doc) < 0)
+			log_warn("theme: mustache error %s", path);
+
+		free(in);
+	} else
+		log_warn("theme: %s: %s", path, strerror(errno));
+}
+
+/* }}} */
+
+/* {{{ Scid module */
+
+static duk_ret_t
+Scid_render(duk_context *ctx)
+{
+	FILE *fp = duk_require_pointer(ctx, 0);
+	const char *path = duk_require_string(ctx, 1);
+	const char *json = duk_json_encode(ctx, 2);
+	json_t *doc = NULL;
+	json_error_t err;
+
+	if (json && !(doc = json_loads(json, 0, &err)))
+		return duk_error(ctx, DUK_ERR_ERROR, "%d:%d:%s", err.line, err.column, err.text);
+
+	mch_templatize(fp, theme_path(scid.theme, path), doc);
+
+	if (doc)
+		json_decref(doc);
+
+	return 0;
+}
+
+static duk_ret_t
+Scid_print(duk_context *ctx)
+{
+	puts(duk_require_string(ctx, 0));
+
+	return 0;
+}
+
+static const duk_function_list_entry functions[] = {
+	{ "render",     Scid_render,    3 },
+	{ "print",      Scid_print,     1 },
+	{ NULL,         NULL,           0 }
+};
+
+/* }}} */
+
 static char *
-render(struct theme *t, const char *json, const char *function)
+call(struct theme *t, json_t *json, const char *function)
 {
-	char *ret = NULL;
+	char *out = NULL, *dump = NULL;
+	size_t outsz = 0;
+	FILE *fp = NULL;
+	int nargs = 1;
 
 	duk_get_global_string(t->ctx, function);
 
-	if (!duk_is_callable(t->ctx, -1)) {
-		duk_pop(t->ctx);
-		return NULL;
+	if (!duk_is_callable(t->ctx, -1))
+		goto over;
+	if (!(fp = open_memstream(&out, &outsz)))
+		goto over;
+
+	duk_push_pointer(t->ctx, fp);
+
+	if (json && (dump = json_dumps(json, JSON_COMPACT))) {
+		duk_push_string(t->ctx, dump);
+		duk_json_decode(t->ctx, -1);
+		nargs++;
 	}
 
-	duk_push_string(t->ctx, json);
-	duk_json_decode(t->ctx, -1);
-
-	if (duk_pcall(t->ctx, 1) != 0) {
+	if (duk_pcall(t->ctx, nargs) != 0)
 		log_warn("theme: %s", duk_safe_to_string(t->ctx, -1));
-	} else if (duk_is_string(t->ctx, -1))
-		ret = util_strdup(duk_get_string(t->ctx, -1));
 
-	duk_pop_n(t->ctx, 1);
+over:
+	duk_pop(t->ctx);
 
 	/*
 	 * For convenience, otherwise all callers have to check for non-NULL
 	 * after calling the function.
 	 */
-	if (!ret)
-		ret = util_strdup("");
+	free(dump);
 
-	return ret;
+	if (fp)
+		fclose(fp);
+	if (!out)
+		out = util_strdup("");
+
+	return out;
 }
 
 const char *
@@ -81,18 +343,35 @@
 			log_warn("theme: %s", duk_safe_to_string(t->ctx, -1));
 
 		duk_pop(t->ctx);
+		duk_push_object(t->ctx);
+		duk_put_function_list(t->ctx, -1, functions);
+		duk_put_global_string(t->ctx, "Scid");
+		free(data);
 	}
 
 	return t;
 }
 
 char *
-theme_render_index(struct theme *t, const char *json)
+theme_page_index(struct theme *t, json_t *json)
 {
 	assert(t);
-	assert(json);
+
+	return call(t, json, "onPageIndex");
+}
 
-	return render(t, json, "index");
+char *
+theme_page_status(struct theme *t, enum khttp status)
+{
+	assert(t);
+
+	(void)t;
+	(void)status;
+
+#if 0
+	return call(t, json, "onPageStatus");
+#endif
+	return "ERROR";
 }
 
 void