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