Mercurial > sci
view 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 source
#include <assert.h> #include <errno.h> #include <limits.h> #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 * call(struct theme *t, json_t *json, const char *function) { 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)) 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++; } if (duk_pcall(t->ctx, nargs) != 0) log_warn("theme: %s", duk_safe_to_string(t->ctx, -1)); over: duk_pop(t->ctx); /* * For convenience, otherwise all callers have to check for non-NULL * after calling the function. */ free(dump); if (fp) fclose(fp); if (!out) out = util_strdup(""); return out; } const char * theme_path(struct theme *t, const char *filename) { assert(filename); /* Build path to the template file. */ static _Thread_local char path[PATH_MAX]; snprintf(path, sizeof (path), "%s/%s", t->base, filename); return path; } struct theme * theme_open(const char *directory) { assert(directory); struct theme *t; char themefile[PATH_MAX], *data; t = util_calloc(1, sizeof (*t)); t->ctx = duk_create_heap_default(); util_strlcpy(t->base, directory, sizeof (t->base)); /* Open theme.js in the directory. */ snprintf(themefile, sizeof (themefile), "%s/theme.js", t->base); if (!(data = util_read(themefile))) log_warn("theme: %s: %s", themefile, strerror(errno)); else { if (duk_peval_string(t->ctx, data) != 0) 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_page_index(struct theme *t, json_t *json) { assert(t); return call(t, json, "onPageIndex"); } 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 theme_free(struct theme *t) { assert(t); duk_destroy_heap(t->ctx); }