Mercurial > sci
view scid/theme.c @ 85:cf49ab595e2e default tip @
sciworkerd: avoid spawning several tasks
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 09 Mar 2023 10:43:48 +0100 |
parents | 71cd8447e3a4 |
children |
line wrap: on
line source
/* * theme.c -- theme management * * Copyright (c) 2021-2023 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #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 "theme.h" #include "util.h" #define SIGNATURE DUK_HIDDEN_SYMBOL("File") static duk_context *context; static char base[PATH_MAX]; /* {{{ 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 (output && 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(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 inline void push_object(duk_context *, json_t *); static inline void push_array(duk_context *, json_t *); static inline void push_value(duk_context *ctx, json_t *val) { switch (json_typeof(val)) { case JSON_STRING: duk_push_string(ctx, json_string_value(val)); break; case JSON_INTEGER: duk_push_number(ctx, json_integer_value(val)); break; case JSON_REAL: duk_push_number(ctx, json_real_value(val)); break; case JSON_TRUE: duk_push_true(ctx); break; case JSON_FALSE: duk_push_false(ctx); break; case JSON_OBJECT: push_object(ctx, val); break; case JSON_ARRAY: push_array(ctx, val); break; default: duk_push_null(ctx); break; } } static inline void push_object(duk_context *ctx, json_t *object) { assert(json_is_object(object)); json_t *val; const char *key; duk_push_object(ctx); json_object_foreach(object, key, val) { push_value(ctx, val); duk_put_prop_string(ctx, -2, key); } } static inline void push_array(duk_context *ctx, json_t *array) { assert(json_is_array(array)); json_t *val; size_t i; duk_push_array(ctx); json_array_foreach(array, i, val) { push_value(ctx, val); duk_put_prop_index(ctx, -2, i); } } static char * call(json_t *json, const char *function) { char *out = NULL; size_t outsz = 0; FILE *fp; duk_get_global_string(context, function); if (duk_is_callable(context, -1)) { fp = util_open_memstream(&out, &outsz); duk_push_pointer(context, fp); push_value(context, json); if (duk_pcall(context, 2) != 0) log_warn("theme: %s", duk_safe_to_string(context, -1)); duk_pop(context); fclose(fp); } else duk_pop(context); if (!out) out = util_strdup(""); if (json) json_decref(json); return out; } void theme_open(const char *directory) { assert(directory); const char *path; char *data; util_strlcpy(base, directory, sizeof (base)); context = duk_create_heap_default(); path = theme_path("theme.js"); if (!(data = util_read(path))) log_warn("theme: %s: %s", path, strerror(errno)); else { if (duk_peval_string(context, data) != 0) log_warn("theme: %s", duk_safe_to_string(context, -1)); duk_pop(context); duk_push_object(context); duk_put_function_list(context, -1, functions); duk_put_global_string(context, "Scid"); free(data); } } const char * theme_path(const char *filename) { assert(filename); /* Build path to the template file. */ static _Thread_local char path[PATH_MAX]; snprintf(path, sizeof (path), "%s/%s", base, filename); return path; } char * theme_render(const char *function, json_t *json) { assert(function); return call(json, function); } char * theme_status(int status, const char *message) { return theme_render("onPageStatus", util_json_pack("{si ss}", "status", status, "message", message )); } void theme_finish(void) { duk_destroy_heap(context); }