exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit b8ec6db023d99022003bbd17094c043bfe74d4b5
parent 490ab7ea22887a2316fa287f00bedbe2ddddc26e
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue, 30 Jun 2026 22:38:26 +0200

update mustach copylib to latest version

Diffstat:
Msrc/templating/AUTHORS | 1+
Msrc/templating/CHANGELOG.md | 27+++++++++++++++++++++++++++
Msrc/templating/LICENSE.txt | 12+++++-------
Msrc/templating/ORIGIN | 2++
Msrc/templating/README.md | 10++++++++--
Msrc/templating/dotest.sh | 7++++++-
Msrc/templating/meson.build | 17+++++++++++++++++
Msrc/templating/mustach-cjson.c | 10+++++-----
Msrc/templating/mustach-cjson.h | 2+-
Msrc/templating/mustach-jansson.c | 472+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/templating/mustach-jansson.h | 2+-
Msrc/templating/mustach-json-c.c | 8++++----
Msrc/templating/mustach-json-c.h | 2+-
Msrc/templating/mustach-original-Makefile | 19+++++++++++++++----
Msrc/templating/mustach-tool.c | 10+++++-----
Msrc/templating/mustach-wrap.c | 8++++----
Msrc/templating/mustach-wrap.h | 2+-
Msrc/templating/mustach.c | 42++++++++++++++++++++++++++++++++++++++----
Msrc/templating/mustach.h | 4+++-
Asrc/templating/test_templating_api.c | 378+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
20 files changed, 731 insertions(+), 304 deletions(-)

diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS @@ -13,6 +13,7 @@ Contributors: Lailton Fernando Mariano Lucas Ramage Paramtamtam + qaqland RekGRpth Ryan Fox Sami Kerola diff --git a/src/templating/CHANGELOG.md b/src/templating/CHANGELOG.md @@ -1,3 +1,30 @@ +1.2.10 (2024-10-03) +------------------- + +Minor: + - fix compiler warnings + +1.2.9 (2024-06-04) +------------------ + +Change: + - meson.build updated + +1.2.8 (2024-04-16) +------------------ + +New: + - test scripts now return failing status + - explicit version of mustache specs for test (#49) + - handling of CR (iOS) and CRLF (windows) end of line styles + +Change: + - License of use is now Zero-Clause BSD + +Fix: + - improved management and detection of valgrind (#56, #57) + - Headers without implicit dependency + 1.2.7 (2024-03-21) ------------------ diff --git a/src/templating/LICENSE.txt b/src/templating/LICENSE.txt @@ -1,13 +1,11 @@ - -Copyright (c) 2017-2020 by José Bollo +Copyright (c) 2017-2024 by José Bollo 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. +purpose with or without fee is hereby granted. -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, +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 diff --git a/src/templating/ORIGIN b/src/templating/ORIGIN @@ -1,5 +1,7 @@ Cloned originally from https://gitlab.com/jobol/mustach/ +Currently tracking upstream release 1.2.10 (2024-10-03). + Changes: ======== diff --git a/src/templating/README.md b/src/templating/README.md @@ -1,7 +1,7 @@ # Introduction to Mustach 1.2 `mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache") -template specification. +template specification, version 1.4.1, only the mandatory part. The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach). @@ -154,12 +154,18 @@ The libraries that can be produced are: There is no dependencies of a library to an other. This is intended and doesn't hurt today because the code is small. +### Compilation Using Meson + +Thanks to *qaqland* work, meson build is available. Please refer to **README.meson**. + ### Testing The makefile offers the way to execute basic tests. Just type `make test`. By default, if valgrind is available, tests are using it. It can be disabled -by typing `make test valgrind=no` or `NOVALGRIND=1 make test`. +by typing `make test valgrind=no` or `NOVALGRIND=1 make test`. Conversely, +use of valgrind can be enforced by typing `make test valgrind=yes` or +`VALGRIND=1 make test`. ## Extensions diff --git a/src/templating/dotest.sh b/src/templating/dotest.sh @@ -6,9 +6,14 @@ exit_fail() { exit 1 } +if ! valgrind --version > /dev/null 2>&1 +then + [ "$VALGRIND" = 1 ] && exit_fail "no valgrind" + NOVALGRIND=1 +fi mustach="${mustach:-../mustach}" echo "starting test" -if ! valgrind --version 2> /dev/null +if [ "$NOVALGRIND" = 1 ] then $mustach "$@" > resu.last || exit_fail "ERROR! mustach command failed ($?)!" else diff --git a/src/templating/meson.build b/src/templating/meson.build @@ -63,3 +63,20 @@ test( workdir: meson.current_build_dir(), suite: ['templating'], ) + + +test_templating_api = executable( + 'test_templating_api', + ['test_templating_api.c'], + dependencies: [gnunetutil_dep, json_dep, libmustach_dep], + include_directories: [incdir, configuration_inc], + build_by_default: false, + install: false, +) + +test( + 'test_templating_api', + test_templating_api, + workdir: meson.current_build_dir(), + suite: ['templating'], +) diff --git a/src/templating/mustach-cjson.c b/src/templating/mustach-cjson.c @@ -3,21 +3,21 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include <stdio.h> -#include <string.h> -#include <stdlib.h> - #include "mustach.h" #include "mustach-wrap.h" #include "mustach-cjson.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + struct expl { cJSON null; cJSON *root; diff --git a/src/templating/mustach-cjson.h b/src/templating/mustach-cjson.h @@ -3,7 +3,7 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _mustach_cJSON_h_included_ diff --git a/src/templating/mustach-jansson.c b/src/templating/mustach-jansson.c @@ -3,323 +3,269 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include <stdio.h> -#include <string.h> - #include "mustach.h" #include "mustach-wrap.h" #include "mustach-jansson.h" -struct expl -{ - json_t *root; - json_t *selection; - int depth; - struct - { - json_t *cont; - json_t *obj; - void *iter; - int is_objiter; - size_t index, count; - } stack[MUSTACH_MAX_DEPTH]; +#include <stdio.h> +#include <string.h> + +struct expl { + json_t *root; + json_t *selection; + int depth; + struct { + json_t *cont; + json_t *obj; + void *iter; + int is_objiter; + size_t index, count; + } stack[MUSTACH_MAX_DEPTH]; }; -static int -start (void *closure) +static int start(void *closure) { - struct expl *e = closure; - e->depth = 0; - e->selection = json_null (); - e->stack[0].cont = NULL; - e->stack[0].obj = e->root; - e->stack[0].index = 0; - e->stack[0].count = 1; - return MUSTACH_OK; + struct expl *e = closure; + e->depth = 0; + e->selection = json_null(); + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + e->stack[0].index = 0; + e->stack[0].count = 1; + return MUSTACH_OK; } - -static int -compare (void *closure, const char *value) +static int compare(void *closure, const char *value) { - struct expl *e = closure; - json_t *o = e->selection; - double d; - json_int_t i; - - switch (json_typeof (o)) - { - case JSON_REAL: - d = json_number_value (o) - atof (value); - return d < 0 ? -1 : d > 0 ? 1 : 0; - case JSON_INTEGER: - i = (json_int_t) json_integer_value (o) - (json_int_t) atoll (value); - return i < 0 ? -1 : i > 0 ? 1 : 0; - case JSON_STRING: - return strcmp (json_string_value (o), value); - case JSON_TRUE: - return strcmp ("true", value); - case JSON_FALSE: - return strcmp ("false", value); - case JSON_NULL: - return strcmp ("null", value); - default: - return 1; - } + struct expl *e = closure; + json_t *o = e->selection; + double d; + json_int_t i; + + switch (json_typeof(o)) { + case JSON_REAL: + d = json_number_value(o) - atof(value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + case JSON_INTEGER: + i = (json_int_t)json_integer_value(o) - (json_int_t)atoll(value); + return i < 0 ? -1 : i > 0 ? 1 : 0; + case JSON_STRING: + return strcmp(json_string_value(o), value); + case JSON_TRUE: + return strcmp("true", value); + case JSON_FALSE: + return strcmp("false", value); + case JSON_NULL: + return strcmp("null", value); + default: + return 1; + } } - -static int -sel (void *closure, const char *name) +static int sel(void *closure, const char *name) { - struct expl *e = closure; - json_t *o; - int i, r; - - if (name == NULL) - { - o = e->stack[e->depth].obj; - r = 1; - } - else - { - i = e->depth; - while (i >= 0 && ! (o = json_object_get (e->stack[i].obj, name))) - i--; - if (i >= 0) - r = 1; - else - { - o = json_null (); - r = 0; - } - } - e->selection = o; - return r; + struct expl *e = closure; + json_t *o; + int i, r; + + if (name == NULL) { + o = e->stack[e->depth].obj; + r = 1; + } else { + i = e->depth; + while (i >= 0 && !(o = json_object_get(e->stack[i].obj, name))) + i--; + if (i >= 0) + r = 1; + else { + o = json_null(); + r = 0; + } + } + e->selection = o; + return r; } - -static int -subsel (void *closure, const char *name) +static int subsel(void *closure, const char *name) { - struct expl *e = closure; - json_t *o = NULL; - int r = 0; - - if (json_is_object (e->selection)) - { - o = json_object_get (e->selection, name); - r = o != NULL; - } - else if (json_is_array (e->selection)) - { - char *end; - size_t idx = (size_t) strtol (name, &end, 10); - if (! *end && idx < json_array_size (e->selection)) - { - o = json_array_get (e->selection, idx); - r = 1; - } - } - if (r) - e->selection = o; - return r; + struct expl *e = closure; + json_t *o = NULL; + int r = 0; + + if (json_is_object(e->selection)) { + o = json_object_get(e->selection, name); + r = o != NULL; + } + else if (json_is_array(e->selection)) { + char *end; + size_t idx = (size_t)strtol(name, &end, 10); + if (!*end && idx < json_array_size(e->selection)) { + o = json_array_get(e->selection, idx); + r = 1; + } + } + if (r) + e->selection = o; + return r; } - -static int -enter (void *closure, int objiter) +static int enter(void *closure, int objiter) { - struct expl *e = closure; - json_t *o; - - if (++e->depth >= MUSTACH_MAX_DEPTH) - return MUSTACH_ERROR_TOO_DEEP; - - o = e->selection; - e->stack[e->depth].is_objiter = 0; - /* Squash warning for comparing float to 0.0 */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wfloat-equal" - if (objiter) - { - if (! json_is_object (o)) - goto not_entering; - e->stack[e->depth].iter = json_object_iter (o); - if (e->stack[e->depth].iter == NULL) - goto not_entering; - e->stack[e->depth].obj = json_object_iter_value (e->stack[e->depth].iter); - e->stack[e->depth].cont = o; - e->stack[e->depth].is_objiter = 1; - } - else if (json_is_array (o)) - { - e->stack[e->depth].count = json_array_size (o); - if (e->stack[e->depth].count == 0) - goto not_entering; - e->stack[e->depth].cont = o; - e->stack[e->depth].obj = json_array_get (o, 0); - e->stack[e->depth].index = 0; - } - else if ((json_is_object (o) && json_object_size (o)) - || json_is_true (o) - || (json_is_string (o) && json_string_length (o) > 0) - || (json_is_integer (o) && json_integer_value (o) != 0) - || (json_is_real (o) && json_real_value (o) != 0)) - { - e->stack[e->depth].count = 1; - e->stack[e->depth].cont = NULL; - e->stack[e->depth].obj = o; - e->stack[e->depth].index = 0; - } - else - goto not_entering; -#pragma GCC diagnostic pop - return 1; + struct expl *e = closure; + json_t *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + if (objiter) { + if (!json_is_object(o)) + goto not_entering; + e->stack[e->depth].iter = json_object_iter(o); + if (e->stack[e->depth].iter == NULL) + goto not_entering; + e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } else if (json_is_array(o)) { + e->stack[e->depth].count = json_array_size(o); + if (e->stack[e->depth].count == 0) + goto not_entering; + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_array_get(o, 0); + e->stack[e->depth].index = 0; + } else if ((json_is_object(o) && json_object_size(o)) + || json_is_true(o) + || (json_is_string(o) && json_string_length(o) > 0) + || (json_is_integer(o) && json_integer_value(o) != 0) + || (json_is_real(o) && json_real_value(o) != 0)) { + e->stack[e->depth].count = 1; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].obj = o; + e->stack[e->depth].index = 0; + } else + goto not_entering; + return 1; not_entering: - e->depth--; - return 0; + e->depth--; + return 0; } - -static int -next (void *closure) +static int next(void *closure) { - struct expl *e = closure; - - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - - if (e->stack[e->depth].is_objiter) - { - e->stack[e->depth].iter = json_object_iter_next (e->stack[e->depth].cont, e->stack[e->depth]. - iter); - if (e->stack[e->depth].iter == NULL) - return 0; - e->stack[e->depth].obj = json_object_iter_value (e->stack[e->depth].iter); - return 1; - } - - e->stack[e->depth].index++; - if (e->stack[e->depth].index >= e->stack[e->depth].count) - return 0; - - e->stack[e->depth].obj = json_array_get (e->stack[e->depth].cont, e->stack[e->depth].index); - return 1; -} + struct expl *e = closure; + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; -static int -leave (void *closure) -{ - struct expl *e = closure; + if (e->stack[e->depth].is_objiter) { + e->stack[e->depth].iter = json_object_iter_next(e->stack[e->depth].cont, e->stack[e->depth].iter); + if (e->stack[e->depth].iter == NULL) + return 0; + e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); + return 1; + } - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; + e->stack[e->depth].index++; + if (e->stack[e->depth].index >= e->stack[e->depth].count) + return 0; - e->depth--; - return 0; + e->stack[e->depth].obj = json_array_get(e->stack[e->depth].cont, e->stack[e->depth].index); + return 1; } - -static int -get (void *closure, struct mustach_sbuf *sbuf, int key) +static int leave(void *closure) { - struct expl *e = closure; - const char *s; - int d; - - if (key) - { - s = ""; - for (d = e->depth ; d >= 0 ; d--) - if (e->stack[d].is_objiter) - { - s = json_object_iter_key (e->stack[d].iter); - break; - } - } - else if (json_is_string (e->selection)) - s = json_string_value (e->selection); - else if (json_is_null (e->selection)) - s = ""; - else - { - s = json_dumps (e->selection, JSON_ENCODE_ANY | JSON_COMPACT); - if (s == NULL) - return MUSTACH_ERROR_SYSTEM; - sbuf->freecb = free; - } - sbuf->value = s; - return 1; + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + e->depth--; + return 0; } +static int get(void *closure, struct mustach_sbuf *sbuf, int key) +{ + struct expl *e = closure; + const char *s; + int d; + + if (key) { + s = ""; + for (d = e->depth ; d >= 0 ; d--) + if (e->stack[d].is_objiter) { + s = json_object_iter_key(e->stack[d].iter); + break; + } + } + else if (json_is_string(e->selection)) + s = json_string_value(e->selection); + else if (json_is_null(e->selection)) + s = ""; + else { + s = json_dumps(e->selection, JSON_ENCODE_ANY | JSON_COMPACT); + if (s == NULL) + return MUSTACH_ERROR_SYSTEM; + sbuf->freecb = free; + } + sbuf->value = s; + return 1; +} const struct mustach_wrap_itf mustach_jansson_wrap_itf = { - .start = start, - .stop = NULL, - .compare = compare, - .sel = sel, - .subsel = subsel, - .enter = enter, - .next = next, - .leave = leave, - .get = get + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get }; -int -mustach_jansson_file (const char *template, size_t length, json_t *root, int flags, FILE *file) +int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file) { - struct expl e; - e.root = root; - return mustach_wrap_file (template, length, &mustach_jansson_wrap_itf, &e, flags, file); + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_jansson_wrap_itf, &e, flags, file); } - -int -mustach_jansson_fd (const char *template, size_t length, json_t *root, int flags, int fd) +int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd) { - struct expl e; - e.root = root; - return mustach_wrap_fd (template, length, &mustach_jansson_wrap_itf, &e, flags, fd); + struct expl e; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_jansson_wrap_itf, &e, flags, fd); } - -int -mustach_jansson_mem (const char *template, size_t length, json_t *root, int flags, char **result, - size_t *size) +int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size) { - struct expl e; - e.root = root; - return mustach_wrap_mem (template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); + struct expl e; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); } - -int -mustach_jansson_write (const char *template, size_t length, json_t *root, int flags, - mustach_write_cb_t *writecb, void *closure) +int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure) { - struct expl e; - e.root = root; - return mustach_wrap_write (template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, - closure); + struct expl e; + e.root = root; + return mustach_wrap_write(template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, closure); } - -int -mustach_jansson_emit (const char *template, size_t length, json_t *root, int flags, - mustach_emit_cb_t *emitcb, void *closure) +int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure) { - struct expl e; - e.root = root; - return mustach_wrap_emit (template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure) - ; + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure); } + diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h @@ -3,7 +3,7 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _mustach_jansson_h_included_ diff --git a/src/templating/mustach-json-c.c b/src/templating/mustach-json-c.c @@ -3,20 +3,20 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif -#include <stdio.h> -#include <string.h> - #include "mustach.h" #include "mustach-wrap.h" #include "mustach-json-c.h" +#include <stdio.h> +#include <string.h> + struct expl { struct json_object *root; struct json_object *selection; diff --git a/src/templating/mustach-json-c.h b/src/templating/mustach-json-c.h @@ -3,7 +3,7 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _mustach_json_c_h_included_ diff --git a/src/templating/mustach-original-Makefile b/src/templating/mustach-original-Makefile @@ -1,7 +1,10 @@ # version MAJOR := 1 MINOR := 2 -REVIS := 7 +REVIS := 10 + +# Mustache spec +VSPEC := v1.4.1 # installation settings DESTDIR ?= @@ -237,6 +240,15 @@ uninstall: rm -f $(DESTDIR)$(LIBDIR)/libmustach*.so* rm -rf $(DESTDIR)$(INCLUDEDIR)/mustach +# testing +ifeq ($(valgrind),no) + NOVALGRIND := 1 +else ifeq ($(valgrind),yes) + VALGRIND := 1 +endif +export NOVALGRIND +export VALGRIND + .PHONY: test test-basic test-specs test: basic-tests spec-tests @@ -276,11 +288,10 @@ test-specs/jansson-test-specs: test-specs/jansson-test-specs.o mustach-jansson.o .PHONY: test-specs/specs test-specs/specs: - if test -d test-specs/spec; then \ - git -C test-specs/spec pull; \ - else \ + if ! test -d test-specs/spec; then \ git -C test-specs clone https://github.com/mustache/spec.git; \ fi + git -C test-specs/spec checkout $(VSPEC) -B test; #cleaning .PHONY: clean diff --git a/src/templating/mustach-tool.c b/src/templating/mustach-tool.c @@ -3,13 +3,15 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif +#include "mustach-wrap.h" + #include <stdlib.h> #include <stdio.h> #include <sys/stat.h> @@ -18,8 +20,6 @@ #include <string.h> #include <libgen.h> -#include "mustach-wrap.h" - static const size_t BLOCKSIZE = 8192; static const char *errors[] = { @@ -133,8 +133,8 @@ static void close_json(void); int main(int ac, char **av) { - char *t; - const char *f; + char *t; + const char *f; char *prog = *av; int s; size_t length; diff --git a/src/templating/mustach-wrap.c b/src/templating/mustach-wrap.c @@ -3,13 +3,16 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif +#include "mustach.h" +#include "mustach-wrap.h" + #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -17,9 +20,6 @@ #include <malloc.h> #endif -#include "mustach.h" -#include "mustach-wrap.h" - /* * It was stated that allowing to include files * through template is not safe when the mustache diff --git a/src/templating/mustach-wrap.h b/src/templating/mustach-wrap.h @@ -3,7 +3,7 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _mustach_wrap_h_included_ diff --git a/src/templating/mustach.c b/src/templating/mustach.c @@ -3,13 +3,15 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif +#include "mustach.h" + #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -19,8 +21,6 @@ #include <malloc.h> #endif -#include "mustach.h" - 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 */ @@ -43,6 +43,13 @@ struct prefix { struct prefix *prefix; }; +/********************************************************* +* This section wraps implementation details of memory files. +* It also takes care of adding a terminating zero to the +* produced text. +* When the function open_memstream exists, use it. +* Otherwise emulate it using a temporary file. +**********************************************************/ #if !defined(NO_OPEN_MEMSTREAM) static FILE *memfile_open(char **buffer, size_t *size) { @@ -120,6 +127,10 @@ static int memfile_close(FILE *file, char **buffer, size_t *size) } #endif +/********************************************************* +* This section has functions for managing instances of +* struct mustach_sbuf. +*********************************************************/ static inline void sbuf_reset(struct mustach_sbuf *sbuf) { sbuf->value = NULL; @@ -142,6 +153,10 @@ static inline size_t sbuf_length(struct mustach_sbuf *sbuf) return length; } +/********************************************************* +* This section is for default implementations of +* optional callbacks that are although required +*********************************************************/ static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) { size_t i, j, r; @@ -227,6 +242,9 @@ static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *s return rc; } +/********************************************************* +* Function for writing the indentation prefix +*********************************************************/ static int emitprefix(struct iwrap *iwrap, struct prefix *prefix) { if (prefix->prefix) { @@ -237,6 +255,10 @@ static int emitprefix(struct iwrap *iwrap, struct prefix *prefix) return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, iwrap->file) : 0; } +/********************************************************* +* This section is for default implementations of +* optional callbacks that are although required +*********************************************************/ static int process(const char *template, size_t length, struct iwrap *iwrap, struct prefix *prefix) { struct mustach_sbuf sbuf; @@ -258,7 +280,16 @@ static int process(const char *template, size_t length, struct iwrap *iwrap, str for (;;) { /* search next openning delimiter */ for (beg = template ; ; beg++) { - c = beg == end ? '\n' : *beg; + if (beg == end) + c = '\n'; + else { + c = *beg; + if (c == '\r') { + if ((beg + 1) != end && beg[1] == '\n') + beg++; + c = '\n'; + } + } if (c == '\n') { l = (beg != end) + (size_t)(beg - template); if (stdalone != 2 && enabled) { @@ -460,6 +491,9 @@ get_name: } } +/********************************************************* +* This section is for the public interface functions +*********************************************************/ int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file) { int rc; diff --git a/src/templating/mustach.h b/src/templating/mustach.h @@ -3,12 +3,14 @@ https://gitlab.com/jobol/mustach - SPDX-License-Identifier: ISC + SPDX-License-Identifier: 0BSD */ #ifndef _mustach_h_included_ #define _mustach_h_included_ +#include <stdio.h> + struct mustach_sbuf; /* see below */ /** diff --git a/src/templating/test_templating_api.c b/src/templating/test_templating_api.c @@ -0,0 +1,378 @@ +/* + This file is part of TALER + Copyright (C) 2026 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, or + (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ + +/** + * @file test_templating_api.c + * @brief tests for the templating engine behind the TALER_TEMPLATING_* + * API. + * + * TALER_TEMPLATING_fill() and TALER_TEMPLATING_fill2() are thin + * passthroughs to mustach_jansson_mem() with Mustach_With_AllExtensions; + * this test exercises exactly that code path (the bundled mustach + * copylib) without dragging in the full libtaler* link stack. It pins + * down the stable behaviour of the templating engine (interpolation, + * escaping, sections, inverted sections, dotted access, iteration, + * comments, scalar rendering, length handling, ...) so that updates of + * the bundled mustach copylib do not silently introduce regressions. + * + * @author Christian Grothoff + */ +#include "mustach-jansson.h" +#include <gnunet/gnunet_util_lib.h> +#include <string.h> + + +/** + * Global test result: 0 on success, 1 on failure. + */ +static int global_ret; + + +/** + * Render @a tmpl with @a root, mirroring TALER_TEMPLATING_fill2() + * (which calls mustach_jansson_mem with Mustach_With_AllExtensions), + * and check that the result equals @a expected. + * + * @param name human readable name of the subtest + * @param tmpl the mustach template + * @param tmpl_len length of @a tmpl, or 0 to use strlen() + * (matching the TALER_TEMPLATING_fill convention) + * @param root JSON context (ownership stays with caller) + * @param expected expected rendering + */ +static void +check_len (const char *name, + const char *tmpl, + size_t tmpl_len, + json_t *root, + const char *expected) +{ + char *result; + size_t result_size; + int eno; + + result = NULL; + result_size = 0; + eno = mustach_jansson_mem (tmpl, + tmpl_len, + root, + Mustach_With_AllExtensions, + &result, + &result_size); + if (0 != eno) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Subtest `%s' FAILED: mustach returned error %d\n", + name, + eno); + global_ret = 1; + return; + } + if ( (result_size != strlen (expected)) || + (0 != memcmp (result, + expected, + result_size)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Subtest `%s' FAILED: expected `%s', got `%.*s'\n", + name, + expected, + (int) result_size, + (NULL == result) ? "" : result); + global_ret = 1; + } + GNUNET_free (result); +} + + +/** + * Render 0-terminated @a tmpl with @a root (mirrors + * TALER_TEMPLATING_fill()) and check against @a expected. + */ +static void +check (const char *name, + const char *tmpl, + json_t *root, + const char *expected) +{ + check_len (name, + tmpl, + 0, + root, + expected); +} + + +/** + * Test that the length argument is honored: trailing data beyond the + * given length must be ignored (this is what TALER_TEMPLATING_fill2 + * relies on for non-0-terminated buffers). + */ +static void +check_fill2_length (void) +{ + json_t *root; + + root = json_pack ("{s:s}", + "x", "VALUE"); + GNUNET_assert (NULL != root); + /* Only the first strlen("abc{{x}}def") bytes are part of the + template; the trailing garbage must not be rendered. */ + check_len ("fill2-length", + "abc{{x}}def_THIS_MUST_BE_IGNORED", + strlen ("abc{{x}}def"), + root, + "abcVALUEdef"); + json_decref (root); +} + + +int +main (int argc, + char *const *argv) +{ + (void) argc; + (void) argv; + GNUNET_log_setup ("test-templating-api", + "WARNING", + NULL); + + /* --- plain text, no tags --- */ + { + json_t *root = json_object (); + + check ("literal", + "hello world", + root, + "hello world"); + check ("empty-template", + "", + root, + ""); + json_decref (root); + } + + /* --- simple interpolation --- */ + { + json_t *root = json_pack ("{s:s}", + "a", "X"); + + check ("interpolate", + "a={{a}}", + root, + "a=X"); + check ("interpolate-spaces", + "a={{ a }}", + root, + "a=X"); + check ("interpolate-missing", + "a=[{{nope}}]", + root, + "a=[]"); + json_decref (root); + } + + /* --- HTML escaping --- */ + { + json_t *root = json_pack ("{s:s}", + "h", "<b>&\"x"); + + check ("escape-default", + "{{h}}", + root, + "&lt;b&gt;&amp;&quot;x"); + check ("escape-triple", + "{{{h}}}", + root, + "<b>&\"x"); + check ("escape-ampersand", + "{{&h}}", + root, + "<b>&\"x"); + json_decref (root); + } + + /* --- dotted / nested access --- */ + { + json_t *root = json_pack ("{s:{s:s}}", + "o", + "x", "deep"); + + check ("dotted", + "{{o.x}}", + root, + "deep"); + check ("section-object", + "{{#o}}{{x}}{{/o}}", + root, + "deep"); + json_decref (root); + } + + /* --- sections over arrays --- */ + { + json_t *root = json_pack ("{s:[s,s,s]}", + "arr", + "a", "b", "c"); + + check ("section-array-scalars", + "{{#arr}}[{{.}}]{{/arr}}", + root, + "[a][b][c]"); + json_decref (root); + } + { + json_t *root = json_pack ("{s:[{s:s},{s:s}]}", + "people", + "name", "Alice", + "name", "Bob"); + + check ("section-array-objects", + "{{#people}}{{name}};{{/people}}", + root, + "Alice;Bob;"); + json_decref (root); + } + + /* --- empty array: section skipped, inverted shown --- */ + { + json_t *root = json_pack ("{s:[]}", + "empty"); + + check ("section-empty-array", + "{{#empty}}X{{/empty}}", + root, + ""); + check ("inverted-empty-array", + "{{^empty}}none{{/empty}}", + root, + "none"); + json_decref (root); + } + + /* --- inverted sections on missing / present keys --- */ + { + json_t *root = json_pack ("{s:s}", + "present", "v"); + + check ("inverted-missing", + "{{^missing}}fallback{{/missing}}", + root, + "fallback"); + check ("inverted-present", + "{{^present}}fallback{{/present}}", + root, + ""); + json_decref (root); + } + + /* --- boolean truthiness --- */ + { + json_t *root = json_pack ("{s:b, s:b}", + "t", 1, + "f", 0); + + check ("section-true", + "{{#t}}yes{{/t}}", + root, + "yes"); + check ("section-false", + "{{#f}}yes{{/f}}", + root, + ""); + check ("inverted-false", + "{{^f}}no{{/f}}", + root, + "no"); + json_decref (root); + } + + /* --- null renders as empty, does not enter section --- */ + { + json_t *root = json_pack ("{s:n}", + "z"); + + check ("null-interpolate", + "[{{z}}]", + root, + "[]"); + check ("null-section", + "{{#z}}X{{/z}}", + root, + ""); + check ("null-inverted", + "{{^z}}empty{{/z}}", + root, + "empty"); + json_decref (root); + } + + /* --- scalar rendering: integer and real --- */ + { + json_t *root = json_pack ("{s:I, s:f}", + "n", (json_int_t) 42, + "r", 1.5); + + check ("integer", + "{{n}}", + root, + "42"); + check ("real", + "{{r}}", + root, + "1.5"); + json_decref (root); + } + + /* --- comments are stripped --- */ + { + json_t *root = json_object (); + + check ("comment", + "x{{! this is a comment }}y", + root, + "xy"); + json_decref (root); + } + + /* --- nested sections --- */ + { + json_t *root = json_pack ("{s:{s:b, s:s}}", + "outer", + "inner", 1, + "val", "Z"); + + check ("nested-sections", + "{{#outer}}{{#inner}}{{val}}{{/inner}}{{/outer}}", + root, + "Z"); + json_decref (root); + } + + /* --- length-respecting rendering (TALER_TEMPLATING_fill2) --- */ + check_fill2_length (); + + if (0 == global_ret) + fprintf (stderr, + "All templating API tests passed.\n"); + return global_ret; +} + + +/* end of test_templating_api.c */