Mercurial > sci
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(""", 6, fp) < 0) + return -1; + break; + case '&': + if (mch_out_verbatim("&", 5, fp) < 0) + return -1; + break; + case '<': + if (mch_out_verbatim("<", 4, fp) < 0) + return -1; + break; + case '>': + if (mch_out_verbatim(">", 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