Mercurial > sci
view 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 source
/* * page.c -- page renderer * * Copyright (c) 2021 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 <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, enum khttp status, enum kmime mime, 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 (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); }