diff options
Diffstat (limited to 'src/templating/mustach.c')
-rw-r--r-- | src/templating/mustach.c | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/src/templating/mustach.c b/src/templating/mustach.c new file mode 100644 index 000000000..caa80dcc9 --- /dev/null +++ b/src/templating/mustach.c @@ -0,0 +1,472 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#ifdef _WIN32 +#include <malloc.h> +#endif +#ifdef __sun +# include <alloca.h> +#endif + +#include "mustach.h" + +#if defined(NO_EXTENSION_FOR_MUSTACH) +# undef NO_COLON_EXTENSION_FOR_MUSTACH +# define NO_COLON_EXTENSION_FOR_MUSTACH +# undef NO_ALLOW_EMPTY_TAG +# define NO_ALLOW_EMPTY_TAG +#endif + +struct iwrap { + int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); + void *closure; /* closure for: enter, next, leave, emit, get */ + int (*put)(void *closure, const char *name, int escape, FILE *file); + void *closure_put; /* closure for put */ + int (*enter)(void *closure, const char *name); + int (*next)(void *closure); + int (*leave)(void *closure); + int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); + int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); + void *closure_partial; /* closure for partial */ +}; + +#if !defined(NO_OPEN_MEMSTREAM) +static FILE *memfile_open(char **buffer, size_t *size) +{ + return open_memstream(buffer, size); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ + fclose(file); + free(*buffer); + *buffer = NULL; + *size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ + int rc; + + /* adds terminating null */ + rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0; + fclose(file); + if (rc == 0) + /* removes terminating null of the length */ + (*size)--; + else { + free(*buffer); + *buffer = NULL; + *size = 0; + } + return rc; +} +#else +static FILE *memfile_open(char **buffer, size_t *size) +{ + /* + * We can't provide *buffer and *size as open_memstream does but + * at least clear them so the caller won't get bad data. + */ + *buffer = NULL; + *size = 0; + + return tmpfile(); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ + fclose(file); + *buffer = NULL; + *size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ + int rc; + size_t s; + char *b; + + s = (size_t)ftell(file); + b = malloc(s + 1); + if (b == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + s = 0; + } else { + rewind(file); + if (1 == fread(b, s, 1, file)) { + rc = 0; + b[s] = 0; + } else { + rc = MUSTACH_ERROR_SYSTEM; + free(b); + b = NULL; + s = 0; + } + } + *buffer = b; + *size = s; + return rc; +} +#endif + +static inline void sbuf_reset(struct mustach_sbuf *sbuf) +{ + sbuf->value = NULL; + sbuf->freecb = NULL; + sbuf->closure = NULL; +} + +static inline void sbuf_release(struct mustach_sbuf *sbuf) +{ + if (sbuf->releasecb) + sbuf->releasecb(sbuf->value, sbuf->closure); +} + +static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) +{ + size_t i, j; + + (void)closure; /* unused */ + + if (!escape) + return fwrite(buffer, size, 1, file) != 1 ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; + + i = 0; + while (i < size) { + j = i; + while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&') + j++; + if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + if (j < size) { + switch(buffer[j++]) { + case '<': + if (fwrite("<", 4, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + break; + case '>': + if (fwrite(">", 4, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + break; + case '&': + if (fwrite("&", 5, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + break; + default: break; + } + } + i = j; + } + return MUSTACH_OK; +} + +static int iwrap_put(void *closure, const char *name, int escape, FILE *file) +{ + struct iwrap *iwrap = closure; + int rc; + struct mustach_sbuf sbuf; + size_t length; + + sbuf_reset(&sbuf); + rc = iwrap->get(iwrap->closure, name, &sbuf); + if (rc >= 0) { + length = strlen(sbuf.value); + if (length) + rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file); + sbuf_release(&sbuf); + } + return rc; +} + +static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ + struct iwrap *iwrap = closure; + int rc; + FILE *file; + size_t size; + char *result; + + result = NULL; + file = memfile_open(&result, &size); + if (file == NULL) + rc = MUSTACH_ERROR_SYSTEM; + else { + rc = iwrap->put(iwrap->closure_put, name, 0, file); + if (rc < 0) + memfile_abort(file, &result, &size); + else { + rc = memfile_close(file, &result, &size); + if (rc == 0) { + sbuf->value = result; + sbuf->freecb = free; + } + } + } + return rc; +} + +static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr) +{ + struct mustach_sbuf sbuf; + char name[MUSTACH_MAX_LENGTH + 1], c, *tmp; + const char *beg, *term; + struct { const char *name, *again; size_t length; int enabled, entered; } stack[MUSTACH_MAX_DEPTH]; + size_t oplen, cllen, len, l; + int depth, rc, enabled; + + enabled = 1; + oplen = strlen(opstr); + cllen = strlen(clstr); + depth = 0; + for(;;) { + beg = strstr(template, opstr); + if (beg == NULL) { + /* no more mustach */ + if (enabled && template[0]) { + rc = iwrap->emit(iwrap->closure, template, strlen(template), 0, file); + if (rc < 0) + return rc; + } + return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK; + } + if (enabled && beg != template) { + rc = iwrap->emit(iwrap->closure, template, (size_t)(beg - template), 0, file); + if (rc < 0) + return rc; + } + beg += oplen; + term = strstr(beg, clstr); + if (term == NULL) + return MUSTACH_ERROR_UNEXPECTED_END; + template = term + cllen; + len = (size_t)(term - beg); + c = *beg; + switch(c) { + case '!': + case '=': + break; + case '{': + for (l = 0 ; clstr[l] == '}' ; l++); + if (clstr[l]) { + if (!len || beg[len-1] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + len--; + } else { + if (term[l] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + template++; + } + c = '&'; + /*@fallthrough@*/ + case '^': + case '#': + case '/': + case '&': + case '>': +#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH) + case ':': +#endif + beg++; len--; + default: + while (len && isspace(beg[0])) { beg++; len--; } + while (len && isspace(beg[len-1])) len--; +#if !defined(NO_ALLOW_EMPTY_TAG) + if (len == 0) + return MUSTACH_ERROR_EMPTY_TAG; +#endif + if (len > MUSTACH_MAX_LENGTH) + return MUSTACH_ERROR_TAG_TOO_LONG; + memcpy(name, beg, len); + name[len] = 0; + break; + } + switch(c) { + case '!': + /* comment */ + /* nothing to do */ + break; + case '=': + /* defines separators */ + if (len < 5 || beg[len - 1] != '=') + return MUSTACH_ERROR_BAD_SEPARATORS; + beg++; + len -= 2; + for (l = 0; l < len && !isspace(beg[l]) ; l++); + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + oplen = l; + tmp = alloca(oplen + 1); + memcpy(tmp, beg, oplen); + tmp[oplen] = 0; + opstr = tmp; + while (l < len && isspace(beg[l])) l++; + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + cllen = len - l; + tmp = alloca(cllen + 1); + memcpy(tmp, beg + l, cllen); + tmp[cllen] = 0; + clstr = tmp; + break; + case '^': + case '#': + /* begin section */ + if (depth == MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + rc = enabled; + if (rc) { + rc = iwrap->enter(iwrap->closure, name); + if (rc < 0) + return rc; + } + stack[depth].name = beg; + stack[depth].again = template; + stack[depth].length = len; + stack[depth].enabled = enabled; + stack[depth].entered = rc; + if ((c == '#') == (rc == 0)) + enabled = 0; + depth++; + break; + case '/': + /* end section */ + if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len)) + return MUSTACH_ERROR_CLOSING; + rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0; + if (rc < 0) + return rc; + if (rc) { + template = stack[depth++].again; + } else { + enabled = stack[depth].enabled; + if (enabled && stack[depth].entered) + iwrap->leave(iwrap->closure); + } + break; + case '>': + /* partials */ + if (enabled) { + sbuf_reset(&sbuf); + rc = iwrap->partial(iwrap->closure_partial, name, &sbuf); + if (rc >= 0) { + rc = process(sbuf.value, iwrap, file, opstr, clstr); + sbuf_release(&sbuf); + } + if (rc < 0) + return rc; + } + break; + default: + /* replacement */ + if (enabled) { + rc = iwrap->put(iwrap->closure_put, name, c != '&', file); + if (rc < 0) + return rc; + } + break; + } + } +} + +int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file) +{ + int rc; + struct iwrap iwrap; + + /* check validity */ + if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get)) + return MUSTACH_ERROR_INVALID_ITF; + + /* init wrap structure */ + iwrap.closure = closure; + if (itf->put) { + iwrap.put = itf->put; + iwrap.closure_put = closure; + } else { + iwrap.put = iwrap_put; + iwrap.closure_put = &iwrap; + } + if (itf->partial) { + iwrap.partial = itf->partial; + iwrap.closure_partial = closure; + } else if (itf->get) { + iwrap.partial = itf->get; + iwrap.closure_partial = closure; + } else { + iwrap.partial = iwrap_partial; + iwrap.closure_partial = &iwrap; + } + iwrap.emit = itf->emit ? itf->emit : iwrap_emit; + iwrap.enter = itf->enter; + iwrap.next = itf->next; + iwrap.leave = itf->leave; + iwrap.get = itf->get; + + /* process */ + rc = itf->start ? itf->start(closure) : 0; + if (rc == 0) + rc = process(template, &iwrap, file, "{{", "}}"); + if (itf->stop) + itf->stop(closure, rc); + return rc; +} + +int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd) +{ + int rc; + FILE *file; + + file = fdopen(fd, "w"); + if (file == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + } else { + rc = fmustach(template, itf, closure, file); + fclose(file); + } + return rc; +} + +int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size) +{ + int rc; + FILE *file; + size_t s; + + *result = NULL; + if (size == NULL) + size = &s; + file = memfile_open(result, size); + if (file == NULL) + rc = MUSTACH_ERROR_SYSTEM; + else { + rc = fmustach(template, itf, closure, file); + if (rc < 0) + memfile_abort(file, result, size); + else + rc = memfile_close(file, result, size); + } + return rc; +} + |