summaryrefslogtreecommitdiff
path: root/src/templating
diff options
context:
space:
mode:
Diffstat (limited to 'src/templating')
-rw-r--r--src/templating/.gitignore3
-rw-r--r--src/templating/AUTHORS38
-rw-r--r--src/templating/CHANGELOG.md161
-rw-r--r--src/templating/LICENSE.txt14
-rw-r--r--src/templating/Makefile.am132
-rw-r--r--src/templating/ORIGIN11
-rw-r--r--src/templating/README.md320
-rwxr-xr-xsrc/templating/dotest.sh26
-rw-r--r--src/templating/meson.build12
-rw-r--r--src/templating/mustach-cjson.c258
-rw-r--r--src/templating/mustach-cjson.h96
-rw-r--r--src/templating/mustach-jansson.c271
-rw-r--r--src/templating/mustach-jansson.h96
-rw-r--r--src/templating/mustach-json-c.c284
-rw-r--r--src/templating/mustach-json-c.h160
-rw-r--r--src/templating/mustach-original-Makefile305
-rw-r--r--src/templating/mustach-tool.c258
-rw-r--r--src/templating/mustach-wrap.c482
-rw-r--r--src/templating/mustach-wrap.h235
-rw-r--r--src/templating/mustach.1.gzbin0 -> 742 bytes
-rw-r--r--src/templating/mustach.1.scd60
-rw-r--r--src/templating/mustach.c561
-rw-r--r--src/templating/mustach.h319
-rw-r--r--src/templating/pkgcfgs35
-rwxr-xr-xsrc/templating/run-original-tests.sh19
-rw-r--r--src/templating/templating_api.c524
-rw-r--r--src/templating/test-specs/test-specs-cjson.ref425
-rw-r--r--src/templating/test-specs/test-specs-jansson.ref429
-rw-r--r--src/templating/test-specs/test-specs-json-c.ref425
-rw-r--r--src/templating/test-specs/test-specs.c520
-rw-r--r--src/templating/test1/.gitignore2
-rw-r--r--src/templating/test1/Makefile8
-rw-r--r--src/templating/test1/json23
-rw-r--r--src/templating/test1/must49
-rw-r--r--src/templating/test1/resu.ref41
-rw-r--r--src/templating/test1/vg.ref14
-rw-r--r--src/templating/test2/.gitignore2
-rw-r--r--src/templating/test2/Makefile8
-rw-r--r--src/templating/test2/json9
-rw-r--r--src/templating/test2/must17
-rw-r--r--src/templating/test2/resu.ref7
-rw-r--r--src/templating/test2/vg.ref14
-rw-r--r--src/templating/test3/.gitignore2
-rw-r--r--src/templating/test3/Makefile8
-rw-r--r--src/templating/test3/json7
-rw-r--r--src/templating/test3/must15
-rw-r--r--src/templating/test3/resu.ref13
-rw-r--r--src/templating/test3/vg.ref14
-rw-r--r--src/templating/test4/.gitignore2
-rw-r--r--src/templating/test4/Makefile8
-rw-r--r--src/templating/test4/json13
-rw-r--r--src/templating/test4/must58
-rw-r--r--src/templating/test4/resu.ref50
-rw-r--r--src/templating/test4/vg.ref14
-rw-r--r--src/templating/test5/.gitignore2
-rw-r--r--src/templating/test5/Makefile8
-rw-r--r--src/templating/test5/json23
-rw-r--r--src/templating/test5/must23
-rw-r--r--src/templating/test5/must214
-rw-r--r--src/templating/test5/must2.mustache1
-rw-r--r--src/templating/test5/must3.mustache17
-rw-r--r--src/templating/test5/resu.ref38
-rw-r--r--src/templating/test5/vg.ref14
-rw-r--r--src/templating/test6/.gitignore3
-rw-r--r--src/templating/test6/Makefile12
-rw-r--r--src/templating/test6/json23
-rw-r--r--src/templating/test6/must43
-rw-r--r--src/templating/test6/resu.ref93
-rw-r--r--src/templating/test6/test-custom-write.c149
-rw-r--r--src/templating/test6/vg.ref14
-rw-r--r--src/templating/test7/Makefile8
-rw-r--r--src/templating/test7/base.mustache2
-rw-r--r--src/templating/test7/json8
-rw-r--r--src/templating/test7/node.mustache4
-rw-r--r--src/templating/test7/resu.ref7
-rw-r--r--src/templating/test7/vg.ref14
-rw-r--r--src/templating/test8/.gitignore2
-rw-r--r--src/templating/test8/Makefile8
-rw-r--r--src/templating/test8/json8
-rw-r--r--src/templating/test8/must6
-rw-r--r--src/templating/test8/resu.ref6
-rw-r--r--src/templating/test8/vg.ref14
-rw-r--r--src/templating/test_mustach_jansson.c125
83 files changed, 7566 insertions, 0 deletions
diff --git a/src/templating/.gitignore b/src/templating/.gitignore
new file mode 100644
index 000000000..9ed2f3ff9
--- /dev/null
+++ b/src/templating/.gitignore
@@ -0,0 +1,3 @@
+test_mustach_jansson
+taler-mustach-tool
+mustach
diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS
new file mode 100644
index 000000000..110b36981
--- /dev/null
+++ b/src/templating/AUTHORS
@@ -0,0 +1,38 @@
+Main author:
+ José Bollo <jobol@nonadev.net>
+
+Contributors:
+ Abhishek Mishra
+ Atlas
+ Ben Beasley
+ Christian Grothoff
+ Dominik Kummer
+ Gabriel Zachmann
+ Harold L Marzan
+ Kurt Jung
+ Lailton Fernando Mariano
+ Lucas Ramage
+ Paramtamtam
+ RekGRpth
+ Ryan Fox
+ Sami Kerola
+ Sijmen J. Mulder
+ Steve-Chavez
+ Tomasz Sieprawski
+
+Packagers:
+ pkgsrc: Sijmen J. Mulder
+ alpine linux: Lucas Ramage
+ RPM & DEB: Marcus Hardt
+
+Thanks to issue submitters:
+ Dante Torres
+ @fabbe
+ Felix von Leitner
+ Johann Oskarsson
+ Mark Bucciarelli
+ Nigel Hathaway
+ Paul Wisehart
+ Tarik @tr001
+ Thierry Fournier
+ SASU OKFT
diff --git a/src/templating/CHANGELOG.md b/src/templating/CHANGELOG.md
new file mode 100644
index 000000000..003652ebf
--- /dev/null
+++ b/src/templating/CHANGELOG.md
@@ -0,0 +1,161 @@
+1.2.7 (2024-03-21)
+------------------
+
+New:
+ - fallback to default when mustach_wrap_get_partial
+ returns MUSTACH_ERROR_PARTIAL_NOT_FOUND
+ - remove at compile time the load of files for templates
+ if MUSTACH_LOAD_TEMPLATE is defined as 0
+ - add compile time flag MUSTACH_SAFE for enforcing
+ safety behaviours
+
+Fix:
+ - selection of subitem by index (#47)
+ - get latest iterated key when getting key name (#52)
+ - allow tests without valgrind
+ - avoid recursive template expansion (#55)
+
+1.2.6 (2024-01-08)
+------------------
+
+Fix:
+ - improve naming (#42)
+ - magical spaces in recursive partials (#43)
+ - installation when tool isn't built
+ - correct detection of falsey values (#45)
+
+Minor:
+ - update to newer reference tests
+
+1.2.5 (2023-02-18)
+------------------
+
+Fix:
+ - Don't override CFLAGS in Makefile
+ - Use of $(INSTALL) in Makefile for setting options
+
+Minor:
+ - Orthograf of 'instantiate'
+
+1.2.4 (2023-01-02)
+------------------
+
+Fix:
+ - Latent SIGSEGV using cJSON
+
+1.2.3 (2022-12-21)
+------------------
+
+New:
+ - Flag Mustach_With_ErrorUndefined (and option --strict for the tool)
+ for returning a requested tag is not defined
+ - Test of specifications in separate directory
+
+Fix:
+ - Version printing is now okay
+ - Compiling libraries on Darwin (no soname but install_name)
+ - Compiling test6 with correct flags
+ - Update test from specifications
+ - Better use of valgrind reports
+
+1.2.2 (2021-10-28)
+------------------
+
+Fix:
+ - SONAME of libmustach-json-c.so
+
+1.2.1 (2021-10-19)
+------------------
+
+New:
+ - Add SONAME in libraries.
+ - Flag Mustach_With_PartialDataFirst to switch the
+ policy of resolving partials.
+
+Fix:
+ - Identification of types in cJSON
+
+1.2.0 (2021-08-24)
+------------------
+
+New:
+ - Add hook 'mustach_wrap_get_partial' for handling partials.
+ - Add test of mustache specifications https://github.com/mustache/spec.
+
+Changes:
+ - Mustach_With_SingleDot is always set.
+ - Mustach_With_IncPartial is always set.
+ - Mustach_With_AllExtensions is changed to use currently known extensions.
+ - Output of tests changed.
+ - Makefile improved.
+ - Partials are first searched as file then in current selection.
+ - Improved management of delimiters.
+
+Fixes:
+ - Improved output accordingly to https://github.com/mustache/spec:
+ - escaping of quote "
+ - interpolating null with empty string
+ - removal of empty lines with standalone tag
+ - don't enter section if null
+ - indentation of partials
+ - comment improved for get of mustach_wrap_itf.
+
+1.1.1 (2021-08-19)
+------------------
+Fixes:
+ - Avoid conflicting with getopt.
+ - Remove unexpected build artifact.
+ - Handle correctly a size of 0.
+
+1.1.0 (2021-05-01)
+------------------
+New:
+ - API refactored to take lengths to ease working with partial or
+ non-NULL-terminated strings. (ABI break)
+
+Fixes:
+ - Use correct int type for jansson (json_int_t instead of int64_t).
+ - JSON output of different backends is now the same.
+
+1.0 (2021-04-28, retracted)
+---------------------------
+Legal:
+ - License changed to ISC.
+
+Fixes:
+ - Possible data leak in memfile_open() by clearing buffers.
+ - Fix build on Solaris-likes by including alloca.h.
+ - Fix Windows build by including malloc.h, using size_t instead of
+ ssize_t, and using the standard ternary operator syntax.
+ - Fix JSON in test3 by using double quote characters.
+ - Fix installation in alternative directories such as
+ /opt/pkg/lib on macOS by setting install_name.
+ - Normalise return values in compare() implementations.
+
+New:
+ - Support for cJSON and jansson libraries.
+ - Version info now embedded at build time and shown with mustach(1)
+ usage.
+ - Versioned so-names (e.g. libxlsx.so.1.0).
+ - BINDIR, LIBDIR and INCLUDEDIR variables in Makefile.
+ - New mustach-wrap.{c,h} to ease implementation new libraries,
+ extracted and refactored from the existing implementations.
+ - Makefile now supports 3 modes: single libmustach (default), split
+ libmustache-core etc, and both.
+ - Any or all backends (json-c, jansson, etc) can be enabled at compile
+ time. By default, all available libraries are used.
+ - mustach(1) can use any JSON backend instead of only json-c.
+ - MUSTACH_COMPATIBLE_0_99 can be defined for backwards source
+ compatibility.
+ - 'No extensions' can now be set Mustach_With_NoExtensions instead of
+ passing 0.
+ - pkgconfig (.pc) file for library.
+ - Manual page for mustach(1).
+
+Changed:
+ - Many renames.
+ - Maximum tag length increased from 1024 to 4096.
+ - Other headers include json-c.h instead of using forward declarations.
+ - mustach(1) reads from /dev/stdin instead of fd 0.
+ - Several structures are now taken as const.
+ - New/changed Makefile targets.
diff --git a/src/templating/LICENSE.txt b/src/templating/LICENSE.txt
new file mode 100644
index 000000000..495aeefd5
--- /dev/null
+++ b/src/templating/LICENSE.txt
@@ -0,0 +1,14 @@
+
+Copyright (c) 2017-2020 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.
+
+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,
+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.
diff --git a/src/templating/Makefile.am b/src/templating/Makefile.am
new file mode 100644
index 000000000..a79b109d1
--- /dev/null
+++ b/src/templating/Makefile.am
@@ -0,0 +1,132 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+noinst_PROGRAMS = \
+ taler-mustach-tool
+
+taler_mustach_tool_SOURCES = \
+ mustach-tool.c \
+ mustach-jansson.h
+taler_mustach_tool_LDADD = \
+ libmustach.la \
+ -ljansson
+taler_mustach_tool_CFLAGS = \
+ -DTOOL=MUSTACH_TOOL_JANSSON \
+ -DMUSTACH_SAFE=1 \
+ -DMUSTACH_LOAD_TEMPLATE=0
+
+lib_LTLIBRARIES = \
+ libtalertemplating.la
+
+noinst_LTLIBRARIES = \
+ libmustach.la
+
+libtalertemplating_la_SOURCES = \
+ mustach.c mustach.h \
+ mustach-wrap.c mustach-wrap.h \
+ mustach-jansson.c mustach-jansson.h \
+ templating_api.c
+libtalertemplating_la_LIBADD = \
+ $(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lmicrohttpd \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
+libtalertemplating_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libtalertemplating_la_CFLAGS = \
+ -DMUSTACH_SAFE=1 \
+ -DMUSTACH_LOAD_TEMPLATE=0
+
+libmustach_la_SOURCES = \
+ mustach.c mustach.h \
+ mustach-wrap.c mustach-wrap.h \
+ mustach-jansson.c mustach-jansson.h
+
+test_mustach_jansson_SOURCES = \
+ test_mustach_jansson.c
+test_mustach_jansson_LDADD = \
+ -lgnunetutil \
+ -ljansson \
+ -lmustach \
+ $(XLIB)
+
+check_PROGRAMS = \
+ test_mustach_jansson
+
+TESTS = $(check_PROGRAMS)
+
+EXTRA_DIST = \
+ $(check_SCRIPTS) \
+ mustach-original-Makefile \
+ mustach.1.gz \
+ mustach.1.scd \
+ meson.build \
+ LICENSE.txt \
+ ORIGIN \
+ pkgcfgs \
+ README.md \
+ dotest.sh \
+ AUTHORS \
+ CHANGELOG.md \
+ mustach-json-c.h \
+ mustach-json-c.c \
+ mustach-cjson.h \
+ mustach-cjson.c \
+ test1/json \
+ test1/Makefile \
+ test1/must \
+ test1/resu.ref \
+ test1/vg.ref \
+ test2/json \
+ test2/Makefile \
+ test2/must \
+ test2/resu.ref \
+ test2/vg.ref \
+ test3/json \
+ test3/Makefile \
+ test3/must \
+ test3/resu.ref \
+ test3/vg.ref \
+ test4/json \
+ test4/Makefile \
+ test4/must \
+ test4/resu.ref \
+ test4/vg.ref \
+ test5/json \
+ test5/Makefile \
+ test5/must \
+ test5/must2 \
+ test5/must2.mustache \
+ test5/must3.mustache \
+ test5/resu.ref \
+ test5/vg.ref \
+ test6/json \
+ test6/Makefile \
+ test6/must \
+ test6/resu.ref \
+ test6/test-custom-write.c \
+ test6/vg.ref \
+ test7/base.mustache \
+ test7/json \
+ test7/Makefile \
+ test7/node.mustache \
+ test7/resu.ref \
+ test7/vg.ref \
+ test8/json \
+ test8/Makefile \
+ test8/must \
+ test8/resu.ref \
+ test8/vg.ref \
+ test-specs/test-specs.c \
+ test-specs/test-specs-cjson.ref \
+ test-specs/test-specs-jansson.ref \
+ test-specs/test-specs-json-c.ref
diff --git a/src/templating/ORIGIN b/src/templating/ORIGIN
new file mode 100644
index 000000000..14902983a
--- /dev/null
+++ b/src/templating/ORIGIN
@@ -0,0 +1,11 @@
+Cloned originally from https://gitlab.com/jobol/mustach/
+
+Changes:
+========
+
+Renamed original Makefile to mustach-original-Makefile
+and wrote Makefile.am for us.
+
+Added run-original-tests.sh shell script as a wrapper around
+mustach-original-Makefile to use the original build process for the test
+suite.
diff --git a/src/templating/README.md b/src/templating/README.md
new file mode 100644
index 000000000..6e7a6c956
--- /dev/null
+++ b/src/templating/README.md
@@ -0,0 +1,320 @@
+# Introduction to Mustach 1.2
+
+`mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache")
+template specification.
+
+The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach).
+
+The simplest way to use mustach is to copy the files **mustach.h** and **mustach.c**
+directly into your project and use it.
+
+If you are using one of the JSON libraries listed below, you can get extended feature
+by also including **mustach-wrap.h**, **mustach-wrap.c**, **mustach-XXX.h** and
+**mustach-XXX.c** in your project (see below for **XXX**)
+
+- [json-c](https://github.com/json-c/json-c): use **XXX** = **json-c**
+- [jansson](http://www.digip.org/jansson/): use **XXX** = **jansson**
+- [cJSON](https://github.com/DaveGamble/cJSON): use **XXX** = **cjson**
+
+Alternatively, make and meson files are provided for building `mustach` and
+`libmustach.so` shared library.
+
+Since version 1.0, the makefile allows to compile and install different
+flavours. See below for details.
+
+## Distributions offering mustach package
+
+### Alpine Linux
+
+```sh
+apk add mustach
+apk add mustach-lib
+apk add mustach-dev
+```
+
+### NetBSD
+
+```sh
+cd devel/mustach
+make
+```
+
+See http://pkgsrc.se/devel/mustach
+
+## Known projects using Mustach
+
+This [wiki page](https://gitlab.com/jobol/mustach/-/wikis/projects-using-mustach)
+lists the known project that are using mustach and that kindly told it.
+
+Don't hesitate to tell us if you are interested to be listed there.
+
+## Using Mustach from sources
+
+The file **mustach.h** is the main documentation. Look at it.
+
+The current source files are:
+
+- **mustach.c** core implementation of mustache in C
+- **mustach.h** header file for core definitions
+- **mustach-wrap.c** generic wrapper of mustach for easier integration
+- **mustach-wrap.h** header file for using mustach-wrap
+- **mustach-json-c.c** tiny json wrapper of mustach using [json-c](https://github.com/json-c/json-c)
+- **mustach-json-c.h** header file for using the tiny json-c wrapper
+- **mustach-cjson.c** tiny json wrapper of mustach using [cJSON](https://github.com/DaveGamble/cJSON)
+- **mustach-cjson.h** header file for using the tiny cJSON wrapper
+- **mustach-jansson.c** tiny json wrapper of mustach using [jansson](https://www.digip.org/jansson/)
+- **mustach-jansson.h** header file for using the tiny jansson wrapper
+- **mustach-tool.c** simple tool for applying template files to one JSON file
+
+The file **mustach-json-c.c** is the historical example of use of **mustach** and
+**mustach-wrap** core and it is also a practical implementation that can be used.
+It uses the library json-c. (NOTE for Mac OS: available through homebrew).
+
+Since version 1.0, the project also provide integration of other JSON libraries:
+**cJSON** and **jansson**.
+
+*If you integrate a new library with* **mustach**, *your contribution will be
+welcome here*.
+
+The tool **mustach** is build using `make`, its usage is:
+
+ mustach json template [template]...
+
+It then outputs the result of applying the templates files to the JSON file.
+
+### Portability
+
+Some system does not provide *open_memstream*. In that case, tell your
+preferred compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**.
+Example:
+
+ CFLAGS=-DNO_OPEN_MEMSTREAM make
+
+### Integration
+
+The files **mustach.h** and **mustach-wrap.h** are the main documentation. Look at it.
+
+The file **mustach-json-c.c** provides a good example of integration.
+
+If you intend to use basic HTML/XML escaping and standard C FILE, the callbacks
+of the interface **mustach_itf** that you have to implement are:
+`enter`, `next`, `leave`, `get`.
+
+If you intend to use specific escaping and/or specific output, the callbacks
+of the interface **mustach_itf** that you have to implement are:
+`enter`, `next`, `leave`, `get` and `emit`.
+
+### Compilation Using Make
+
+Building and installing can be done using make.
+
+Example:
+
+ $ make tool=cjson libs=none PREFIX=/usr/local DESTDIR=/ install
+ $ make tool=jsonc libs=single PREFIX=/ DESTDIR=$HOME/.local install
+
+The makefile knows following switches (\*: default):
+
+ Switch name | Values | Description
+ --------------+---------+-----------------------------------------------
+ jsonc | (unset) | Auto detection of json-c
+ | no | Don't compile for json-c
+ | yes | Compile for json-c that must exist
+ --------------+---------+-----------------------------------------------
+ cjson | (unset) | Auto detection of cJSON
+ | no | Don't compile for cJSON
+ | yes | Compile for cJSON that must exist
+ --------------+---------+-----------------------------------------------
+ jansson | (unset) | Auto detection of jansson
+ | no | Don't compile for jansson
+ | yes | Compile for jansson that must exist
+ --------------+---------+-----------------------------------------------
+ tool | (unset) | Auto detection
+ | cjson | Use cjson library
+ | jsonc | Use jsonc library
+ | jansson | Use jansson library
+ | none | Don't compile the tool
+ --------------+---------+----------------------------------------------
+ libs | (unset) | Like 'all'
+ | all | Like 'single' AND 'split'
+ | single | Only libmustach.so
+ | split | All the possible libmustach-XXX.so ...
+ | none | No library is produced
+
+The libraries that can be produced are:
+
+ Library name | Content
+ --------------------+--------------------------------------------------------
+ libmustach-core | mustach.c mustach-wrap.c
+ libmustach-cjson | mustach.c mustach-wrap.c mustach-cjson.c
+ libmustach-jsonc | mustach.c mustach-wrap.c mustach-json-c.c
+ libmustach-jansson | mustach.c mustach-wrap.c mustach-jansson.c
+ libmustach | mustach.c mustach-wrap.c mustach-{cjson,json-c,jansson}.c
+
+There is no dependencies of a library to an other. This is intended and doesn't
+hurt today because the code is small.
+
+### 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`.
+
+## Extensions
+
+The current implementation provides extensions to specifications of **mustache**.
+This extensions can be activated or deactivated using flags.
+
+Here is the summary.
+
+ Flag name | Description
+ -------------------------------+------------------------------------------------
+ Mustach_With_Colon | Explicit tag substitution with colon
+ Mustach_With_EmptyTag | Empty Tag Allowed
+ -------------------------------+------------------------------------------------
+ Mustach_With_Equal | Value Testing Equality
+ Mustach_With_Compare | Value Comparing
+ Mustach_With_JsonPointer | Interpret JSON Pointers
+ Mustach_With_ObjectIter | Iteration On Objects
+ Mustach_With_EscFirstCmp | Escape First Compare
+ Mustach_With_ErrorUndefined | Error when a requested tag is undefined
+ -------------------------------+------------------------------------------------
+ Mustach_With_AllExtensions | Activate all known extensions
+ Mustach_With_NoExtensions | Disable any extension
+
+For the details, see below.
+
+### Explicit Tag Substitution With Colon (Mustach_With_Colon)
+
+In somecases the name of the key used for substitution begins with a
+character reserved for mustach: one of `#`, `^`, `/`, `&`, `{`, `>` and `=`.
+
+This extension introduces the special character `:` to explicitly
+tell mustach to just substitute the value. So `:` becomes a new special
+character.
+
+This is a core extension implemented in file **mustach.c**.
+
+### Empty Tag Allowed (Mustach_With_EmptyTag)
+
+When an empty tag is found, instead of automatically raising the error
+MUSTACH\_ERROR\_EMPTY\_TAG pass it.
+
+This is a core extension implemented in file **mustach.c**.
+
+### Value Testing Equality (Mustach_With_Equal)
+
+This extension allows you to test the value of the selected key.
+It allows to write `key=value` (matching test) or `key=!value`
+(not matching test) in any query.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Value Comparing (Mustach_With_Compare)
+
+These extension extends the extension for testing equality to also
+compare values if greater or lesser.
+Its allows to write `key>value` (greater), `key>=value` (greater or equal),
+`key<value` (lesser) and `key<=value` (lesser or equal).
+
+It the comparator sign appears in the first column it is ignored
+as if it was escaped.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Interpret JSON Pointers (Mustach_With_JsonPointer)
+
+This extension allows to use JSON pointers as defined in IETF RFC 6901.
+If active, any key starting with "/" is a JSON pointer.
+This implies to use the colon to introduce JSON keys.
+
+A special escaping is used for `=`, `<`, `>` signs when
+values comparisons are enabled: `~=` gives `=` in the key.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Iteration On Objects (Mustach_With_ObjectIter)
+
+With this extension, using the pattern `{{#X.*}}...{{/X.*}}`
+allows to iterate on fields of `X`.
+
+Example:
+
+- `{{s.*}} {{*}}:{{.}}{{/s.*}}` applied on `{"s":{"a":1,"b":true}}` produces ` a:1 b:true`
+
+Here the single star `{{*}}` is replaced by the iterated key
+and the single dot `{{.}}` is replaced by its value.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Error when a requested tag is undefined (Mustach_With_ErrorUndefined)
+
+Report the error MUSTACH_ERROR_UNDEFINED_TAG when a requested tag
+is not defined.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Access To Current Value
+
+*this was an extension but is now always enforced*
+
+The value of the current field can be accessed using single dot.
+
+Examples:
+
+- `{{#key}}{{.}}{{/key}}` applied to `{"key":3.14}` produces `3.14`
+- `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces ` 1 2`.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Partial Data First
+
+*this was an extension but is now always enforced*
+
+The default resolution for partial pattern like `{{> name}}`
+is to search for `name` in the current json context and
+as a file named `name` or if not found `name.mustache`.
+
+By default, the order of the search is (1) as a file,
+and if not found, (2) in the current json context.
+
+When this option is set, the order is reverted and content
+of partial is search (1) in the current json context,
+and if not found, (2) as a file.
+
+That option is useful to keep the compatibility with
+versions of *mustach* anteriors to 1.2.0.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+### Escape First Compare
+
+This extension automatically escapes comparisons appears as
+first characters.
+
+This is a wrap extension implemented in file **mustach-wrap.c**.
+
+## Difference with version 0.99 and previous
+
+### Extensions
+
+The extensions can no more be removed at compile time, use
+flags to select your required extension on need.
+
+### Name of functions
+
+Names of functions were improved. Old names remain but are obsolete
+and legacy. Their removal in far future versions is possible.
+
+The table below summarize the changes.
+
+ legacy name | name since version 1.0.0
+ ------------------+-----------------------
+ fmustach | mustach_file
+ fdmustach | mustach_fd
+ mustach | mustach_mem
+ fmustach_json_c | mustach_json_c_file
+ fdmustach_json_c | mustach_json_c_fd
+ mustach_json_c | mustach_json_c_mem
+ mustach_json_c | mustach_json_c_write
diff --git a/src/templating/dotest.sh b/src/templating/dotest.sh
new file mode 100755
index 000000000..32f575c2e
--- /dev/null
+++ b/src/templating/dotest.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Exit, with error message (hard failure)
+exit_fail() {
+ echo " FAIL: " "$@" >&2
+ exit 1
+}
+
+mustach="${mustach:-../mustach}"
+echo "starting test"
+if ! valgrind --version 2> /dev/null
+then
+ $mustach "$@" > resu.last || exit_fail "ERROR! mustach command failed ($?)!"
+else
+ valgrind $mustach "$@" > resu.last 2> vg.last || exit_fail "ERROR! valgrind + mustach command failed ($?)!"
+ sed -i 's:^==[0-9]*== ::' vg.last
+ awk '/^ *total heap usage: .* allocs, .* frees,.*/{if($$4-$$6)exit(1)}' vg.last || exit_fail "ERROR! Alloc/Free issue"
+fi
+if diff -w resu.ref resu.last
+then
+ echo "result ok"
+else
+ exit_fail "ERROR! Result differs"
+fi
+echo
+exit 0
diff --git a/src/templating/meson.build b/src/templating/meson.build
new file mode 100644
index 000000000..c7ecc8dfc
--- /dev/null
+++ b/src/templating/meson.build
@@ -0,0 +1,12 @@
+project('mustach', 'c',
+ version: '1.0.0'
+)
+
+mustach_inc = include_directories('.')
+mustach_lib = shared_library('mustach',
+ 'mustach.c',
+ include_directories: mustach_inc
+)
+
+mustach_dep = declare_dependency(link_with: mustach_lib,
+ include_directories: mustach_inc)
diff --git a/src/templating/mustach-cjson.c b/src/templating/mustach-cjson.c
new file mode 100644
index 000000000..ee65c8038
--- /dev/null
+++ b/src/templating/mustach-cjson.c
@@ -0,0 +1,258 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#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"
+
+struct expl {
+ cJSON null;
+ cJSON *root;
+ cJSON *selection;
+ int depth;
+ struct {
+ cJSON *cont;
+ cJSON *obj;
+ cJSON *next;
+ int is_objiter;
+ } stack[MUSTACH_MAX_DEPTH];
+};
+
+static int start(void *closure)
+{
+ struct expl *e = closure;
+ e->depth = 0;
+ memset(&e->null, 0, sizeof e->null);
+ e->null.type = cJSON_NULL;
+ e->selection = &e->null;
+ e->stack[0].cont = NULL;
+ e->stack[0].obj = e->root;
+ return MUSTACH_OK;
+}
+
+static int compare(void *closure, const char *value)
+{
+ struct expl *e = closure;
+ cJSON *o = e->selection;
+ double d;
+
+ if (cJSON_IsNumber(o)) {
+ d = o->valuedouble - atof(value);
+ return d < 0 ? -1 : d > 0 ? 1 : 0;
+ } else if (cJSON_IsString(o)) {
+ return strcmp(o->valuestring, value);
+ } else if (cJSON_IsTrue(o)) {
+ return strcmp("true", value);
+ } else if (cJSON_IsFalse(o)) {
+ return strcmp("false", value);
+ } else if (cJSON_IsNull(o)) {
+ return strcmp("null", value);
+ } else {
+ return 1;
+ }
+}
+
+static int sel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ cJSON *o;
+ int i, r;
+
+ if (name == NULL) {
+ o = e->stack[e->depth].obj;
+ r = 1;
+ } else {
+ i = e->depth;
+ while (i >= 0 && !(o = cJSON_GetObjectItemCaseSensitive(e->stack[i].obj, name)))
+ i--;
+ if (i >= 0)
+ r = 1;
+ else {
+ o = &e->null;
+ r = 0;
+ }
+ }
+ e->selection = o;
+ return r;
+}
+
+static int subsel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ cJSON *o = NULL;
+ int r = 0;
+
+ if (cJSON_IsObject(e->selection)) {
+ o = cJSON_GetObjectItemCaseSensitive(e->selection, name);
+ r = o != NULL;
+ }
+ else if (cJSON_IsArray(e->selection) && *name) {
+ char *end;
+ int idx = (int)strtol(name, &end, 10);
+ if (!*end && idx >= 0 && idx < cJSON_GetArraySize(e->selection)) {
+ o = cJSON_GetArrayItem(e->selection, idx);
+ r = 1;
+ }
+ }
+ if (r)
+ e->selection = o;
+ return r;
+}
+
+static int enter(void *closure, int objiter)
+{
+ struct expl *e = closure;
+ cJSON *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 (! cJSON_IsObject(o))
+ goto not_entering;
+ if (o->child == NULL)
+ goto not_entering;
+ e->stack[e->depth].obj = o->child;
+ e->stack[e->depth].next = o->child->next;
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].is_objiter = 1;
+ } else if (cJSON_IsArray(o)) {
+ if (o->child == NULL)
+ goto not_entering;
+ e->stack[e->depth].obj = o->child;
+ e->stack[e->depth].next = o->child->next;
+ e->stack[e->depth].cont = o;
+ } else if ((cJSON_IsObject(o) && o->child != NULL)
+ || cJSON_IsTrue(o)
+ || (cJSON_IsString(o) && cJSON_GetStringValue(o)[0] != '\0')
+ || (cJSON_IsNumber(o) && cJSON_GetNumberValue(o) != 0)) {
+ e->stack[e->depth].obj = o;
+ e->stack[e->depth].cont = NULL;
+ e->stack[e->depth].next = NULL;
+ } else
+ goto not_entering;
+ return 1;
+
+not_entering:
+ e->depth--;
+ return 0;
+}
+
+static int next(void *closure)
+{
+ struct expl *e = closure;
+ cJSON *o;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ o = e->stack[e->depth].next;
+ if (o == NULL)
+ return 0;
+
+ e->stack[e->depth].obj = o;
+ e->stack[e->depth].next = o->next;
+ return 1;
+}
+
+static int leave(void *closure)
+{
+ 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 = e->stack[d].obj->string;
+ break;
+ }
+ }
+ else if (cJSON_IsString(e->selection))
+ s = e->selection->valuestring;
+ else if (cJSON_IsNull(e->selection))
+ s = "";
+ else {
+ s = cJSON_PrintUnformatted(e->selection);
+ if (s == NULL)
+ return MUSTACH_ERROR_SYSTEM;
+ sbuf->freecb = cJSON_free;
+ }
+ sbuf->value = s;
+ return 1;
+}
+
+const struct mustach_wrap_itf mustach_cJSON_wrap_itf = {
+ .start = start,
+ .stop = NULL,
+ .compare = compare,
+ .sel = sel,
+ .subsel = subsel,
+ .enter = enter,
+ .next = next,
+ .leave = leave,
+ .get = get
+};
+
+int mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_file(template, length, &mustach_cJSON_wrap_itf, &e, flags, file);
+}
+
+int mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_fd(template, length, &mustach_cJSON_wrap_itf, &e, flags, fd);
+}
+
+int mustach_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_mem(template, length, &mustach_cJSON_wrap_itf, &e, flags, result, size);
+}
+
+int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_write(template, length, &mustach_cJSON_wrap_itf, &e, flags, writecb, closure);
+}
+
+int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_emit(template, length, &mustach_cJSON_wrap_itf, &e, flags, emitcb, closure);
+}
+
diff --git a/src/templating/mustach-cjson.h b/src/templating/mustach-cjson.h
new file mode 100644
index 000000000..e049415f8
--- /dev/null
+++ b/src/templating/mustach-cjson.h
@@ -0,0 +1,96 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_cJSON_h_included_
+#define _mustach_cJSON_h_included_
+
+/*
+ * mustach-cjson is intended to make integration of cJSON
+ * library by providing integrated functions.
+ */
+
+#include <cjson/cJSON.h>
+#include "mustach-wrap.h"
+
+/**
+ * Wrap interface used internally by mustach cJSON functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_wrap_itf mustach_cJSON_wrap_itf;
+
+/**
+ * mustach_cJSON_file - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file);
+
+/**
+ * mustach_cJSON_fd - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd);
+
+
+/**
+ * mustach_cJSON_mem - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size);
+
+/**
+ * mustach_cJSON_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure);
+
+/**
+ * mustach_cJSON_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure);
+
+#endif
+
diff --git a/src/templating/mustach-jansson.c b/src/templating/mustach-jansson.c
new file mode 100644
index 000000000..d9b50b57e
--- /dev/null
+++ b/src/templating/mustach-jansson.c
@@ -0,0 +1,271 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#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];
+};
+
+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;
+}
+
+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;
+ }
+}
+
+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;
+}
+
+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;
+}
+
+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;
+ 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;
+}
+
+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;
+}
+
+static int leave(void *closure)
+{
+ 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
+};
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h
new file mode 100644
index 000000000..8def948e0
--- /dev/null
+++ b/src/templating/mustach-jansson.h
@@ -0,0 +1,96 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_jansson_h_included_
+#define _mustach_jansson_h_included_
+
+/*
+ * mustach-jansson is intended to make integration of jansson
+ * library by providing integrated functions.
+ */
+
+#include <jansson.h>
+#include "mustach-wrap.h"
+
+/**
+ * Wrap interface used internally by mustach jansson functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_wrap_itf mustach_jansson_wrap_itf;
+
+/**
+ * mustach_jansson_file - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file);
+
+/**
+ * mustach_jansson_fd - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd);
+
+
+/**
+ * mustach_jansson_mem - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size);
+
+/**
+ * mustach_jansson_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure);
+
+/**
+ * mustach_jansson_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure);
+
+#endif
+
diff --git a/src/templating/mustach-json-c.c b/src/templating/mustach-json-c.c
new file mode 100644
index 000000000..75251c07e
--- /dev/null
+++ b/src/templating/mustach-json-c.c
@@ -0,0 +1,284 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#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"
+
+struct expl {
+ struct json_object *root;
+ struct json_object *selection;
+ int depth;
+ struct {
+ struct json_object *cont;
+ struct json_object *obj;
+ struct json_object_iterator iter;
+ struct json_object_iterator enditer;
+ int is_objiter;
+ int index, count;
+ } stack[MUSTACH_MAX_DEPTH];
+};
+
+static int start(void *closure)
+{
+ struct expl *e = closure;
+ e->depth = 0;
+ e->selection = 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)
+{
+ struct expl *e = closure;
+ struct json_object *o = e->selection;
+ double d;
+ int64_t i;
+
+ switch (json_object_get_type(o)) {
+ case json_type_double:
+ d = json_object_get_double(o) - atof(value);
+ return d < 0 ? -1 : d > 0 ? 1 : 0;
+ case json_type_int:
+ i = json_object_get_int64(o) - (int64_t)atoll(value);
+ return i < 0 ? -1 : i > 0 ? 1 : 0;
+ default:
+ return strcmp(json_object_get_string(o), value);
+ }
+}
+
+static int sel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ struct json_object *o;
+ int i, r;
+
+ if (name == NULL) {
+ o = e->stack[e->depth].obj;
+ r = 1;
+ } else {
+ i = e->depth;
+ while (i >= 0 && !json_object_object_get_ex(e->stack[i].obj, name, &o))
+ i--;
+ if (i >= 0)
+ r = 1;
+ else {
+ o = NULL;
+ r = 0;
+ }
+ }
+ e->selection = o;
+ return r;
+}
+
+static int subsel(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ struct json_object *o = NULL;
+ int r = 0;
+
+ if (json_object_is_type(e->selection, json_type_object))
+ r = json_object_object_get_ex(e->selection, name, &o);
+ else if (json_object_is_type(e->selection, json_type_array)) {
+ char *end;
+ size_t idx = (size_t)strtol(name, &end, 10);
+ if (!*end && idx < json_object_array_length(e->selection)) {
+ o = json_object_array_get_idx(e->selection, idx);
+ r = 1;
+ }
+ }
+ if (r)
+ e->selection = o;
+ return r;
+}
+
+static int enter(void *closure, int objiter)
+{
+ struct expl *e = closure;
+ struct json_object *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_object_is_type(o, json_type_object))
+ goto not_entering;
+
+ e->stack[e->depth].iter = json_object_iter_begin(o);
+ e->stack[e->depth].enditer = json_object_iter_end(o);
+ if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer))
+ goto not_entering;
+ e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].iter);
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].is_objiter = 1;
+ } else if (json_object_is_type(o, json_type_array)) {
+ e->stack[e->depth].count = json_object_array_length(o);
+ if (e->stack[e->depth].count == 0)
+ goto not_entering;
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].obj = json_object_array_get_idx(o, 0);
+ e->stack[e->depth].index = 0;
+ } else if ((json_object_is_type(o, json_type_object) && json_object_object_length(o) > 0)
+ || json_object_get_boolean(o)) {
+ 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;
+}
+
+static int next(void *closure)
+{
+ struct expl *e = closure;
+
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+
+ if (e->stack[e->depth].is_objiter) {
+ json_object_iter_next(&e->stack[e->depth].iter);
+ if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer))
+ return 0;
+ e->stack[e->depth].obj = json_object_iter_peek_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_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index);
+ return 1;
+}
+
+static int leave(void *closure)
+{
+ 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_peek_name(&e->stack[d].iter);
+ break;
+ }
+ }
+ else
+ switch (json_object_get_type(e->selection)) {
+ case json_type_string:
+ s = json_object_get_string(e->selection);
+ break;
+ case json_type_null:
+ s = "";
+ break;
+ default:
+ s = json_object_to_json_string_ext(e->selection, 0);
+ break;
+ }
+ sbuf->value = s;
+ return 1;
+}
+
+const struct mustach_wrap_itf mustach_json_c_wrap_itf = {
+ .start = start,
+ .stop = NULL,
+ .compare = compare,
+ .sel = sel,
+ .subsel = subsel,
+ .enter = enter,
+ .next = next,
+ .leave = leave,
+ .get = get
+};
+
+int mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_file(template, length, &mustach_json_c_wrap_itf, &e, flags, file);
+}
+
+int mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_fd(template, length, &mustach_json_c_wrap_itf, &e, flags, fd);
+}
+
+int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_mem(template, length, &mustach_json_c_wrap_itf, &e, flags, result, size);
+}
+
+int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_write(template, length, &mustach_json_c_wrap_itf, &e, flags, writecb, closure);
+}
+
+int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ return mustach_wrap_emit(template, length, &mustach_json_c_wrap_itf, &e, flags, emitcb, closure);
+}
+
+int fmustach_json_c(const char *template, struct json_object *root, FILE *file)
+{
+ return mustach_json_c_file(template, 0, root, -1, file);
+}
+
+int fdmustach_json_c(const char *template, struct json_object *root, int fd)
+{
+ return mustach_json_c_fd(template, 0, root, -1, fd);
+}
+
+int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size)
+{
+ return mustach_json_c_mem(template, 0, root, -1, result, size);
+}
+
+int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure)
+{
+ return mustach_json_c_write(template, 0, root, -1, writecb, closure);
+}
+
+
diff --git a/src/templating/mustach-json-c.h b/src/templating/mustach-json-c.h
new file mode 100644
index 000000000..50846c6cb
--- /dev/null
+++ b/src/templating/mustach-json-c.h
@@ -0,0 +1,160 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_json_c_h_included_
+#define _mustach_json_c_h_included_
+
+/*
+ * mustach-json-c is intended to make integration of json-c
+ * library by providing integrated functions.
+ */
+
+#include <json-c/json.h>
+#include "mustach-wrap.h"
+
+/**
+ * Wrap interface used internally by mustach json-c functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_wrap_itf mustach_json_c_wrap_itf;
+
+/**
+ * mustach_json_c_file - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file);
+
+/**
+ * mustach_json_c_fd - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd);
+
+/**
+ * mustach_json_c_mem - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size);
+
+/**
+ * mustach_json_c_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure);
+
+/**
+ * mustach_json_c_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @root: the root json object to render
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure);
+
+/***************************************************************************
+* compatibility with version before 1.0
+*/
+
+/**
+ * OBSOLETE use mustach_json_c_file
+ *
+ * fmustach_json_c - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+
+DEPRECATED_MUSTACH(extern int fmustach_json_c(const char *template, struct json_object *root, FILE *file));
+
+/**
+ * OBSOLETE use mustach_json_c_fd
+ *
+ * fdmustach_json_c - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+
+DEPRECATED_MUSTACH(extern int fdmustach_json_c(const char *template, struct json_object *root, int fd));
+
+/**
+ * OBSOLETE use mustach_json_c_mem
+ *
+ * mustach_json_c - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+
+DEPRECATED_MUSTACH(extern int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size));
+
+/**
+ * OBSOLETE use mustach_json_c_write
+ *
+ * umustach_json_c - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @root: the root json object to render
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+typedef mustach_write_cb_t *mustach_json_write_cb;
+DEPRECATED_MUSTACH(extern int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure));
+
+#endif
diff --git a/src/templating/mustach-original-Makefile b/src/templating/mustach-original-Makefile
new file mode 100644
index 000000000..aee827583
--- /dev/null
+++ b/src/templating/mustach-original-Makefile
@@ -0,0 +1,305 @@
+# version
+MAJOR := 1
+MINOR := 2
+REVIS := 7
+
+# installation settings
+DESTDIR ?=
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+LIBDIR ?= $(PREFIX)/lib
+INCLUDEDIR ?= $(PREFIX)/include
+MANDIR ?= $(PREFIX)/share/man
+PKGDIR ?= $(LIBDIR)/pkgconfig
+
+# Tools (sed must be GNU sed)
+SED ?= sed
+INSTALL ?= install
+
+# initial settings
+VERSION := $(MAJOR).$(MINOR).$(REVIS)
+SOVER := .$(MAJOR)
+SOVEREV := .$(MAJOR).$(MINOR)
+
+HEADERS := mustach.h mustach-wrap.h
+SPLITLIB := libmustach-core.so$(SOVEREV)
+SPLITPC := libmustach-core.pc
+COREOBJS := mustach.o mustach-wrap.o
+SINGLEOBJS := $(COREOBJS)
+SINGLEFLAGS :=
+SINGLELIBS :=
+TESTSPECS :=
+ALL := manuals
+
+# availability of CJSON
+ifneq ($(cjson),no)
+ cjson_cflags := $(shell pkg-config --silence-errors --cflags libcjson)
+ cjson_libs := $(shell pkg-config --silence-errors --libs libcjson)
+ ifdef cjson_libs
+ cjson := yes
+ tool ?= cjson
+ HEADERS += mustach-cjson.h
+ SPLITLIB += libmustach-cjson.so$(SOVEREV)
+ SPLITPC += libmustach-cjson.pc
+ SINGLEOBJS += mustach-cjson.o
+ SINGLEFLAGS += ${cjson_cflags}
+ SINGLELIBS += ${cjson_libs}
+ TESTSPECS += test-specs/test-specs-cjson
+ else
+ ifeq ($(cjson),yes)
+ $(error Can't find required library cjson)
+ endif
+ cjson := no
+ endif
+endif
+
+# availability of JSON-C
+ifneq ($(jsonc),no)
+ jsonc_cflags := $(shell pkg-config --silence-errors --cflags json-c)
+ jsonc_libs := $(shell pkg-config --silence-errors --libs json-c)
+ ifdef jsonc_libs
+ jsonc := yes
+ tool ?= jsonc
+ HEADERS += mustach-json-c.h
+ SPLITLIB += libmustach-json-c.so$(SOVEREV)
+ SPLITPC += libmustach-json-c.pc
+ SINGLEOBJS += mustach-json-c.o
+ SINGLEFLAGS += ${jsonc_cflags}
+ SINGLELIBS += ${jsonc_libs}
+ TESTSPECS += test-specs/test-specs-json-c
+ else
+ ifeq ($(jsonc),yes)
+ $(error Can't find required library json-c)
+ endif
+ jsonc := no
+ endif
+endif
+
+# availability of JANSSON
+ifneq ($(jansson),no)
+ jansson_cflags := $(shell pkg-config --silence-errors --cflags jansson)
+ jansson_libs := $(shell pkg-config --silence-errors --libs jansson)
+ ifdef jansson_libs
+ jansson := yes
+ tool ?= jansson
+ HEADERS += mustach-jansson.h
+ SPLITLIB += libmustach-jansson.so$(SOVEREV)
+ SPLITPC += libmustach-jansson.pc
+ SINGLEOBJS += mustach-jansson.o
+ SINGLEFLAGS += ${jansson_cflags}
+ SINGLELIBS += ${jansson_libs}
+ TESTSPECS += test-specs/test-specs-jansson
+ else
+ ifeq ($(jansson),yes)
+ $(error Can't find required library jansson)
+ endif
+ jansson := no
+ endif
+endif
+
+# tool
+TOOLOBJS = mustach-tool.o $(COREOBJS)
+tool ?= none
+ifneq ($(tool),none)
+ ifeq ($(tool),cjson)
+ TOOLOBJS += mustach-cjson.o
+ TOOLFLAGS := ${cjson_cflags} -DTOOL=MUSTACH_TOOL_CJSON
+ TOOLLIBS := ${cjson_libs}
+ TOOLDEP := mustach-cjson.h
+ else ifeq ($(tool),jsonc)
+ TOOLOBJS += mustach-json-c.o
+ TOOLFLAGS := ${jsonc_cflags} -DTOOL=MUSTACH_TOOL_JSON_C
+ TOOLLIBS := ${jsonc_libs}
+ TOOLDEP := mustach-json-c.h
+ else ifeq ($(tool),jansson)
+ TOOLOBJS += mustach-jansson.o
+ TOOLFLAGS := ${jansson_cflags} -DTOOL=MUSTACH_TOOL_JANSSON
+ TOOLLIBS := ${jansson_libs}
+ TOOLDEP := mustach-jansson.h
+ else
+ $(error Unknown library $(tool) for tool)
+ endif
+ ifneq ($($(tool)),yes)
+ $(error No library found for tool $(tool))
+ endif
+ ALL += mustach
+endif
+
+# compute targets
+libs ?= all
+ifeq (${libs},split)
+ ALL += ${SPLITLIB} ${SPLITPC}
+else ifeq (${libs},single)
+ ALL += libmustach.so$(SOVEREV) libmustach.pc
+else ifeq (${libs},all)
+ ALL += libmustach.so$(SOVEREV) libmustach.pc ${SPLITLIB} ${SPLITPC}
+else ifneq (${libs},none)
+ $(error Unknown libs $(libs))
+endif
+
+# display target
+$(info tool = ${tool})
+$(info libs = ${libs})
+$(info jsonc = ${jsonc})
+$(info jansson = ${jansson})
+$(info cjson = ${cjson})
+
+# settings
+
+EFLAGS = -fPIC -Wall -Wextra -DVERSION=${VERSION}
+
+ifeq ($(shell uname),Darwin)
+ LDFLAGS_single += -install_name $(LIBDIR)/libmustach.so$(SOVEREV)
+ LDFLAGS_core += -install_name $(LIBDIR)/libmustach-core.so$(SOVEREV)
+ LDFLAGS_cjson += -install_name $(LIBDIR)/libmustach-cjson.so$(SOVEREV)
+ LDFLAGS_jsonc += -install_name $(LIBDIR)/libmustach-json-c.so$(SOVEREV)
+ LDFLAGS_jansson += -install_name $(LIBDIR)/libmustach-jansson.so$(SOVEREV)
+else
+ LDFLAGS_single += -Wl,-soname,libmustach.so$(SOVER)
+ LDFLAGS_core += -Wl,-soname,libmustach-core.so$(SOVER)
+ LDFLAGS_cjson += -Wl,-soname,libmustach-cjson.so$(SOVER)
+ LDFLAGS_jsonc += -Wl,-soname,libmustach-json-c.so$(SOVER)
+ LDFLAGS_jansson += -Wl,-soname,libmustach-jansson.so$(SOVER)
+endif
+
+# targets
+
+.PHONY: all
+all: ${ALL}
+
+mustach: $(TOOLOBJS)
+ $(CC) $(LDFLAGS) $(TOOLFLAGS) -o mustach $(TOOLOBJS) $(TOOLLIBS)
+
+libmustach.so$(SOVEREV): $(SINGLEOBJS)
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_single) -o $@ $^ $(SINGLELIBS)
+
+libmustach-core.so$(SOVEREV): $(COREOBJS)
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_core) -o $@ $(COREOBJS) $(lib_OBJ)
+
+libmustach-cjson.so$(SOVEREV): $(COREOBJS) mustach-cjson.o
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_cjson) -o $@ $^ $(cjson_libs)
+
+libmustach-json-c.so$(SOVEREV): $(COREOBJS) mustach-json-c.o
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_jsonc) -o $@ $^ $(jsonc_libs)
+
+libmustach-jansson.so$(SOVEREV): $(COREOBJS) mustach-jansson.o
+ $(CC) -shared $(LDFLAGS) $(LDFLAGS_jansson) -o $@ $^ $(jansson_libs)
+
+# pkgconfigs
+
+%.pc: pkgcfgs
+ $(SED) -E '/^==.*==$$/{h;d};x;/==$@==/{x;s/VERSION/$(VERSION)/;p;d};x;d' $< > $@
+
+# objects
+
+mustach.o: mustach.c mustach.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) -o $@ $<
+
+mustach-wrap.o: mustach-wrap.c mustach.h mustach-wrap.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) -o $@ $<
+
+mustach-tool.o: mustach-tool.c mustach.h mustach-json-c.h $(TOOLDEP)
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(TOOLFLAGS) -o $@ $<
+
+mustach-cjson.o: mustach-cjson.c mustach.h mustach-wrap.h mustach-cjson.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(cjson_cflags) -o $@ $<
+
+mustach-json-c.o: mustach-json-c.c mustach.h mustach-wrap.h mustach-json-c.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(jsonc_cflags) -o $@ $<
+
+mustach-jansson.o: mustach-jansson.c mustach.h mustach-wrap.h mustach-jansson.h
+ $(CC) -c $(EFLAGS) $(CFLAGS) $(jansson_cflags) -o $@ $<
+
+# installing
+.PHONY: install
+install: all
+ $(INSTALL) -d $(DESTDIR)$(BINDIR)
+ if test "${tool}" != "none"; then \
+ $(INSTALL) -m0755 mustach $(DESTDIR)$(BINDIR)/; \
+ fi
+ $(INSTALL) -d $(DESTDIR)$(INCLUDEDIR)/mustach
+ $(INSTALL) -m0644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/mustach
+ $(INSTALL) -d $(DESTDIR)$(LIBDIR)
+ for x in libmustach*.so$(SOVEREV); do \
+ $(INSTALL) -m0755 $$x $(DESTDIR)$(LIBDIR)/ ;\
+ ln -sf $$x $(DESTDIR)$(LIBDIR)/$${x%.so.*}.so$(SOVER) ;\
+ ln -sf $$x $(DESTDIR)$(LIBDIR)/$${x%.so.*}.so ;\
+ done
+ $(INSTALL) -d $(DESTDIR)/$(PKGDIR)
+ $(INSTALL) -m0644 libmustach*.pc $(DESTDIR)/$(PKGDIR)
+ $(INSTALL) -d $(DESTDIR)/$(MANDIR)/man1
+ $(INSTALL) -m0644 mustach.1.gz $(DESTDIR)/$(MANDIR)/man1
+
+# deinstalling
+.PHONY: uninstall
+uninstall:
+ rm -f $(DESTDIR)$(BINDIR)/mustach
+ rm -f $(DESTDIR)$(LIBDIR)/libmustach*.so*
+ rm -rf $(DESTDIR)$(INCLUDEDIR)/mustach
+
+.PHONY: test test-basic test-specs
+test: basic-tests spec-tests
+
+basic-tests: mustach
+ @$(MAKE) -C test1 test
+ @$(MAKE) -C test2 test
+ @$(MAKE) -C test3 test
+ @$(MAKE) -C test4 test
+ @$(MAKE) -C test5 test
+ @$(MAKE) -C test6 test
+ @$(MAKE) -C test7 test
+ @$(MAKE) -C test8 test
+
+spec-tests: $(TESTSPECS)
+
+test-specs/test-specs-%: test-specs/%-test-specs test-specs/specs
+ ./$< test-specs/spec/specs/[a-z]*.json > $@.last || true
+ diff $@.ref $@.last
+
+test-specs/cjson-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-cjson.h
+ $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(cjson_cflags) -DTEST=TEST_CJSON -o $@ $<
+
+test-specs/cjson-test-specs: test-specs/cjson-test-specs.o mustach-cjson.o $(COREOBJS)
+ $(CC) $(LDFLAGS) -o $@ $^ $(cjson_libs)
+
+test-specs/json-c-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-json-c.h
+ $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(jsonc_cflags) -DTEST=TEST_JSON_C -o $@ $<
+
+test-specs/json-c-test-specs: test-specs/json-c-test-specs.o mustach-json-c.o $(COREOBJS)
+ $(CC) $(LDFLAGS) -o $@ $^ $(jsonc_libs)
+
+test-specs/jansson-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-jansson.h
+ $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(jansson_cflags) -DTEST=TEST_JANSSON -o $@ $<
+
+test-specs/jansson-test-specs: test-specs/jansson-test-specs.o mustach-jansson.o $(COREOBJS)
+ $(CC) $(LDFLAGS) -o $@ $^ $(jansson_libs)
+
+.PHONY: test-specs/specs
+test-specs/specs:
+ if test -d test-specs/spec; then \
+ git -C test-specs/spec pull; \
+ else \
+ git -C test-specs clone https://github.com/mustache/spec.git; \
+ fi
+
+#cleaning
+.PHONY: clean
+clean:
+ rm -f mustach libmustach*.so* *.o *.pc
+ rm -f test-specs/*-test-specs test-specs/test-specs-*.last
+ rm -rf *.gcno *.gcda coverage.info gcov-latest
+ @$(MAKE) -C test1 clean
+ @$(MAKE) -C test2 clean
+ @$(MAKE) -C test3 clean
+ @$(MAKE) -C test4 clean
+ @$(MAKE) -C test5 clean
+ @$(MAKE) -C test6 clean
+ @$(MAKE) -C test7 clean
+ @$(MAKE) -C test8 clean
+
+# manpage
+.PHONY: manuals
+manuals: mustach.1.gz
+
+mustach.1.gz: mustach.1.scd
+ if which scdoc >/dev/null 2>&1; then scdoc < mustach.1.scd | gzip > mustach.1.gz; fi
diff --git a/src/templating/mustach-tool.c b/src/templating/mustach-tool.c
new file mode 100644
index 000000000..5f28c1f58
--- /dev/null
+++ b/src/templating/mustach-tool.c
@@ -0,0 +1,258 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <libgen.h>
+
+#include "mustach-wrap.h"
+
+static const size_t BLOCKSIZE = 8192;
+
+static const char *errors[] = {
+ "??? unreferenced ???",
+ "system",
+ "unexpected end",
+ "empty tag",
+ "tag too long",
+ "bad separators",
+ "too depth",
+ "closing",
+ "bad unescape tag",
+ "invalid interface",
+ "item not found",
+ "partial not found",
+ "undefined tag",
+ "too much template nesting"
+};
+
+static const char *errmsg = 0;
+static int flags = 0;
+static FILE *output = 0;
+
+static void help(char *prog)
+{
+ char *name = basename(prog);
+#define STR_INDIR(x) #x
+#define STR(x) STR_INDIR(x)
+ printf("%s version %s\n", name, STR(VERSION));
+#undef STR
+#undef STR_INDIR
+ printf(
+ "\n"
+ "USAGE:\n"
+ " %s [FLAGS] <json-file> <mustach-templates...>\n"
+ "\n"
+ "FLAGS:\n"
+ " -h, --help Prints help information\n"
+ " -s, --strict Error when a tag is undefined\n"
+ "\n"
+ "ARGS: (if a file is -, read standard input)\n"
+ " <json-file> JSON file with input data\n"
+ " <mustach-templates...> Template files to instantiate\n",
+ name);
+ exit(0);
+}
+
+static char *readfile(const char *filename, size_t *length)
+{
+ int f;
+ struct stat s;
+ char *result;
+ size_t size, pos;
+ ssize_t rc;
+
+ result = NULL;
+ if (filename[0] == '-' && filename[1] == 0)
+ f = dup(0);
+ else
+ f = open(filename, O_RDONLY);
+ if (f < 0) {
+ fprintf(stderr, "Can't open file: %s\n", filename);
+ exit(1);
+ }
+
+ fstat(f, &s);
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG:
+ size = s.st_size;
+ break;
+ case S_IFSOCK:
+ case S_IFIFO:
+ size = BLOCKSIZE;
+ break;
+ default:
+ fprintf(stderr, "Bad file: %s\n", filename);
+ exit(1);
+ }
+
+ pos = 0;
+ result = malloc(size + 1);
+ do {
+ if (result == NULL) {
+ fprintf(stderr, "Out of memory\n");
+ exit(1);
+ }
+ rc = read(f, &result[pos], (size - pos) + 1);
+ if (rc < 0) {
+ fprintf(stderr, "Error while reading %s\n", filename);
+ exit(1);
+ }
+ if (rc > 0) {
+ pos += (size_t)rc;
+ if (pos > size) {
+ size = pos + BLOCKSIZE;
+ result = realloc(result, size + 1);
+ }
+ }
+ } while(rc > 0);
+
+ close(f);
+ if (length != NULL)
+ *length = pos;
+ result[pos] = 0;
+ return result;
+}
+
+static int load_json(const char *filename);
+static int process(const char *content, size_t length);
+static void close_json();
+
+int main(int ac, char **av)
+{
+ char *t, *f;
+ char *prog = *av;
+ int s;
+ size_t length;
+
+ (void)ac; /* unused */
+ flags = Mustach_With_AllExtensions;
+ output = stdout;
+
+ for( ++av ; av[0] && av[0][0] == '-' && av[0][1] != 0 ; av++) {
+ if (!strcmp(*av, "-h") || !strcmp(*av, "--help"))
+ help(prog);
+ if (!strcmp(*av, "-s") || !strcmp(*av, "--strict"))
+ flags |= Mustach_With_ErrorUndefined;
+ }
+ if (*av) {
+ f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0];
+ s = load_json(f);
+ if (s < 0) {
+ fprintf(stderr, "Can't load json file %s\n", av[0]);
+ if(errmsg)
+ fprintf(stderr, " reason: %s\n", errmsg);
+ exit(1);
+ }
+ while(*++av) {
+ t = readfile(*av, &length);
+ s = process(t, length);
+ free(t);
+ if (s != MUSTACH_OK) {
+ s = -s;
+ if (s < 1 || s >= (int)(sizeof errors / sizeof * errors))
+ s = 0;
+ fprintf(stderr, "Template error %s (file %s)\n", errors[s], *av);
+ }
+ }
+ close_json();
+ }
+ return 0;
+}
+
+#define MUSTACH_TOOL_JSON_C 1
+#define MUSTACH_TOOL_JANSSON 2
+#define MUSTACH_TOOL_CJSON 3
+
+#if TOOL == MUSTACH_TOOL_JSON_C
+
+#include "mustach-json-c.h"
+
+static struct json_object *o;
+static int load_json(const char *filename)
+{
+ o = json_object_from_file(filename);
+#if JSON_C_VERSION_NUM >= 0x000D00
+ errmsg = json_util_get_last_err();
+ if (errmsg != NULL)
+ return -1;
+#endif
+ if (o == NULL) {
+ errmsg = "null json";
+ return -1;
+ }
+ return 0;
+}
+static int process(const char *content, size_t length)
+{
+ return mustach_json_c_file(content, length, o, flags, output);
+}
+static void close_json()
+{
+ json_object_put(o);
+}
+
+#elif TOOL == MUSTACH_TOOL_JANSSON
+
+#include "mustach-jansson.h"
+
+static json_t *o;
+static json_error_t e;
+static int load_json(const char *filename)
+{
+ o = json_load_file(filename, JSON_DECODE_ANY, &e);
+ if (o == NULL) {
+ errmsg = e.text;
+ return -1;
+ }
+ return 0;
+}
+static int process(const char *content, size_t length)
+{
+ return mustach_jansson_file(content, length, o, flags, output);
+}
+static void close_json()
+{
+ json_decref(o);
+}
+
+#elif TOOL == MUSTACH_TOOL_CJSON
+
+#include "mustach-cjson.h"
+
+static cJSON *o;
+static int load_json(const char *filename)
+{
+ char *t;
+ size_t length;
+
+ t = readfile(filename, &length);
+ o = t ? cJSON_ParseWithLength(t, length) : NULL;
+ free(t);
+ return -!o;
+}
+static int process(const char *content, size_t length)
+{
+ return mustach_cJSON_file(content, length, o, flags, output);
+}
+static void close_json()
+{
+ cJSON_Delete(o);
+}
+
+#else
+#error "no defined json library"
+#endif
diff --git a/src/templating/mustach-wrap.c b/src/templating/mustach-wrap.c
new file mode 100644
index 000000000..2cd00db12
--- /dev/null
+++ b/src/templating/mustach-wrap.c
@@ -0,0 +1,482 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef _WIN32
+#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
+* template is open to any value because it could
+* create leaks (example: {{>/etc/passwd}}).
+*/
+#if MUSTACH_SAFE
+# undef MUSTACH_LOAD_TEMPLATE
+#elif !defined(MUSTACH_LOAD_TEMPLATE)
+# define MUSTACH_LOAD_TEMPLATE 1
+#endif
+
+#if !defined(INCLUDE_PARTIAL_EXTENSION)
+# define INCLUDE_PARTIAL_EXTENSION ".mustache"
+#endif
+
+/* global hook for partials */
+int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL;
+
+/* internal structure for wrapping */
+struct wrap {
+ /* original interface */
+ const struct mustach_wrap_itf *itf;
+
+ /* original closure */
+ void *closure;
+
+ /* flags */
+ int flags;
+
+ /* emiter callback */
+ mustach_emit_cb_t *emitcb;
+
+ /* write callback */
+ mustach_write_cb_t *writecb;
+};
+
+/* length given by masking with 3 */
+enum comp {
+ C_no = 0,
+ C_eq = 1,
+ C_lt = 5,
+ C_le = 6,
+ C_gt = 9,
+ C_ge = 10
+};
+
+enum sel {
+ S_none = 0,
+ S_ok = 1,
+ S_objiter = 2,
+ S_ok_or_objiter = S_ok | S_objiter
+};
+
+static enum comp getcomp(char *head, int sflags)
+{
+ return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq
+ : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt)
+ : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt)
+ : C_no;
+}
+
+static char *keyval(char *head, int sflags, enum comp *comp)
+{
+ char *w, car, escaped;
+ enum comp k;
+
+ k = C_no;
+ w = head;
+ car = *head;
+ escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no);
+ while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) {
+ if (escaped)
+ escaped = 0;
+ else
+ escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\')
+ && (getcomp(head + 1, sflags) != C_no);
+ if (!escaped)
+ *w++ = car;
+ head++;
+ car = *head;
+ }
+ *w = 0;
+ *comp = k;
+ return k == C_no ? NULL : &head[k & 3];
+}
+
+static char *getkey(char **head, int sflags)
+{
+ char *result, *iter, *write, car;
+
+ car = *(iter = *head);
+ if (!car)
+ result = NULL;
+ else {
+ result = write = iter;
+ if (sflags & Mustach_With_JsonPointer)
+ {
+ while (car && car != '/') {
+ if (car == '~')
+ switch (iter[1]) {
+ case '1': car = '/'; /*@fallthrough@*/
+ case '0': iter++;
+ }
+ *write++ = car;
+ car = *++iter;
+ }
+ *write = 0;
+ while (car == '/')
+ car = *++iter;
+ }
+ else
+ {
+ while (car && car != '.') {
+ if (car == '\\' && (iter[1] == '.' || iter[1] == '\\'))
+ car = *++iter;
+ *write++ = car;
+ car = *++iter;
+ }
+ *write = 0;
+ while (car == '.')
+ car = *++iter;
+ }
+ *head = iter;
+ }
+ return result;
+}
+
+static enum sel sel(struct wrap *w, const char *name)
+{
+ enum sel result;
+ int i, j, sflags, scmp;
+ char *key, *value;
+ enum comp k;
+
+ /* make a local writeable copy */
+ size_t lenname = 1 + strlen(name);
+ char buffer[lenname];
+ char *copy = buffer;
+ memcpy(copy, name, lenname);
+
+ /* check if matches json pointer selection */
+ sflags = w->flags;
+ if (sflags & Mustach_With_JsonPointer) {
+ if (copy[0] == '/')
+ copy++;
+ else
+ sflags ^= Mustach_With_JsonPointer;
+ }
+
+ /* extract the value, translate the key and get the comparator */
+ if (sflags & (Mustach_With_Equal | Mustach_With_Compare))
+ value = keyval(copy, sflags, &k);
+ else {
+ k = C_no;
+ value = NULL;
+ }
+
+ /* case of . alone if Mustach_With_SingleDot? */
+ if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/)
+ /* yes, select current */
+ result = w->itf->sel(w->closure, NULL) ? S_ok : S_none;
+ else
+ {
+ /* not the single dot, extract the first key */
+ key = getkey(&copy, sflags);
+ if (key == NULL)
+ return 0;
+
+ /* select the root item */
+ if (w->itf->sel(w->closure, key))
+ result = S_ok;
+ else if (key[0] == '*'
+ && !key[1]
+ && !value
+ && !*copy
+ && (w->flags & Mustach_With_ObjectIter)
+ && w->itf->sel(w->closure, NULL))
+ result = S_ok_or_objiter;
+ else
+ result = S_none;
+ if (result == S_ok) {
+ /* iterate the selection of sub items */
+ key = getkey(&copy, sflags);
+ while(result == S_ok && key) {
+ if (w->itf->subsel(w->closure, key))
+ /* nothing */;
+ else if (key[0] == '*'
+ && !key[1]
+ && !value
+ && !*copy
+ && (w->flags & Mustach_With_ObjectIter))
+ result = S_objiter;
+ else
+ result = S_none;
+ key = getkey(&copy, sflags);
+ }
+ }
+ }
+ /* should it be compared? */
+ if (result == S_ok && value) {
+ if (!w->itf->compare)
+ result = S_none;
+ else {
+ i = value[0] == '!';
+ scmp = w->itf->compare(w->closure, &value[i]);
+ switch (k) {
+ case C_eq: j = scmp == 0; break;
+ case C_lt: j = scmp < 0; break;
+ case C_le: j = scmp <= 0; break;
+ case C_gt: j = scmp > 0; break;
+ case C_ge: j = scmp >= 0; break;
+ default: j = i; break;
+ }
+ if (i == j)
+ result = S_none;
+ }
+ }
+ return result;
+}
+
+static int start_callback(void *closure)
+{
+ struct wrap *w = closure;
+ return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK;
+}
+
+static void stop_callback(void *closure, int status)
+{
+ struct wrap *w = closure;
+ if (w->itf->stop)
+ w->itf->stop(w->closure, status);
+}
+
+static int emit(struct wrap *w, const char *buffer, size_t size, FILE *file)
+{
+ int r;
+
+ if (w->writecb)
+ r = w->writecb(file, buffer, size);
+ else
+ r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM;
+ return r;
+}
+
+static int emit_callback(void *closure, const char *buffer, size_t size, int escape, FILE *file)
+{
+ struct wrap *w = closure;
+ int r;
+ size_t s, i;
+ char car;
+
+ if (w->emitcb)
+ r = w->emitcb(file, buffer, size, escape);
+ else if (!escape)
+ r = emit(w, buffer, size, file);
+ else {
+ i = 0;
+ r = MUSTACH_OK;
+ while(i < size && r == MUSTACH_OK) {
+ s = i;
+ while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"')
+ i++;
+ if (i != s)
+ r = emit(w, &buffer[s], i - s, file);
+ if (i < size && r == MUSTACH_OK) {
+ switch(car) {
+ case '<': r = emit(w, "&lt;", 4, file); break;
+ case '>': r = emit(w, "&gt;", 4, file); break;
+ case '&': r = emit(w, "&amp;", 5, file); break;
+ case '"': r = emit(w, "&quot;", 6, file); break;
+ }
+ i++;
+ }
+ }
+ }
+ return r;
+}
+
+static int enter_callback(void *closure, const char *name)
+{
+ struct wrap *w = closure;
+ enum sel s = sel(w, name);
+ return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter);
+}
+
+static int next_callback(void *closure)
+{
+ struct wrap *w = closure;
+ return w->itf->next(w->closure);
+}
+
+static int leave_callback(void *closure)
+{
+ struct wrap *w = closure;
+ return w->itf->leave(w->closure);
+}
+
+static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf)
+{
+ enum sel s = sel(w, name);
+ if (!(s & S_ok))
+ return 0;
+ return w->itf->get(w->closure, sbuf, s & S_objiter);
+}
+
+static int get_callback(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct wrap *w = closure;
+ if (getoptional(w, name, sbuf) <= 0) {
+ if (w->flags & Mustach_With_ErrorUndefined)
+ return MUSTACH_ERROR_UNDEFINED_TAG;
+ sbuf->value = "";
+ }
+ return MUSTACH_OK;
+}
+
+#if MUSTACH_LOAD_TEMPLATE
+static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf)
+{
+ static char extension[] = INCLUDE_PARTIAL_EXTENSION;
+ size_t s;
+ long pos;
+ FILE *file;
+ char *path, *buffer;
+
+ /* allocate path */
+ s = strlen(name);
+ path = malloc(s + sizeof extension);
+ if (path == NULL)
+ return MUSTACH_ERROR_SYSTEM;
+
+ /* try without extension first */
+ memcpy(path, name, s + 1);
+ file = fopen(path, "r");
+ if (file == NULL) {
+ memcpy(&path[s], extension, sizeof extension);
+ file = fopen(path, "r");
+ }
+ free(path);
+
+ /* if file opened */
+ if (file == NULL)
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+
+ /* compute file size */
+ if (fseek(file, 0, SEEK_END) >= 0
+ && (pos = ftell(file)) >= 0
+ && fseek(file, 0, SEEK_SET) >= 0) {
+ /* allocate value */
+ s = (size_t)pos;
+ buffer = malloc(s + 1);
+ if (buffer != NULL) {
+ /* read value */
+ if (1 == fread(buffer, s, 1, file)) {
+ /* force zero at end */
+ sbuf->value = buffer;
+ buffer[s] = 0;
+ sbuf->freecb = free;
+ fclose(file);
+ return MUSTACH_OK;
+ }
+ free(buffer);
+ }
+ }
+ fclose(file);
+ return MUSTACH_ERROR_SYSTEM;
+}
+#endif
+
+static int partial_callback(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct wrap *w = closure;
+ int rc;
+ if (mustach_wrap_get_partial != NULL) {
+ rc = mustach_wrap_get_partial(name, sbuf);
+ if (rc != MUSTACH_ERROR_PARTIAL_NOT_FOUND) {
+ if (rc != MUSTACH_OK)
+ sbuf->value = "";
+ return rc;
+ }
+ }
+#if MUSTACH_LOAD_TEMPLATE
+ if (w->flags & Mustach_With_PartialDataFirst) {
+ if (getoptional(w, name, sbuf) > 0)
+ rc = MUSTACH_OK;
+ else
+ rc = get_partial_from_file(name, sbuf);
+ }
+ else {
+ rc = get_partial_from_file(name, sbuf);
+ if (rc != MUSTACH_OK && getoptional(w, name, sbuf) > 0)
+ rc = MUSTACH_OK;
+ }
+#else
+ rc = getoptional(w, name, sbuf) > 0 ? MUSTACH_OK : MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+#endif
+ if (rc != MUSTACH_OK)
+ sbuf->value = "";
+ return MUSTACH_OK;
+}
+
+const struct mustach_itf mustach_wrap_itf = {
+ .start = start_callback,
+ .put = NULL,
+ .enter = enter_callback,
+ .next = next_callback,
+ .leave = leave_callback,
+ .partial = partial_callback,
+ .get = get_callback,
+ .emit = emit_callback,
+ .stop = stop_callback
+};
+
+static void wrap_init(struct wrap *wrap, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, mustach_write_cb_t *writecb)
+{
+ if (flags & Mustach_With_Compare)
+ flags |= Mustach_With_Equal;
+ wrap->closure = closure;
+ wrap->itf = itf;
+ wrap->flags = flags;
+ wrap->emitcb = emitcb;
+ wrap->writecb = writecb;
+}
+
+int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, NULL);
+ return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file);
+}
+
+int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, NULL);
+ return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd);
+}
+
+int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, NULL);
+ return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size);
+}
+
+int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, NULL, writecb);
+ return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure);
+}
+
+int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure)
+{
+ struct wrap w;
+ wrap_init(&w, itf, closure, flags, emitcb, NULL);
+ return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure);
+}
+
diff --git a/src/templating/mustach-wrap.h b/src/templating/mustach-wrap.h
new file mode 100644
index 000000000..fedcb9191
--- /dev/null
+++ b/src/templating/mustach-wrap.h
@@ -0,0 +1,235 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_wrap_h_included_
+#define _mustach_wrap_h_included_
+
+/*
+ * mustach-wrap is intended to make integration of JSON
+ * libraries easier by wrapping mustach extensions in a
+ * single place.
+ *
+ * As before, using mustach and only mustach is possible
+ * (by using only mustach.h) but does not implement high
+ * level features coming with extensions implemented by
+ * this high level wrapper.
+ */
+#include "mustach.h"
+/*
+ * Definition of the writing callbacks for mustach functions
+ * producing output to callbacks.
+ *
+ * Two callback types are defined:
+ *
+ * @mustach_write_cb_t:
+ *
+ * callback receiving the escaped data to be written as 3 parameters:
+ *
+ * 1. the 'closure', the same given to the wmustach_... function
+ * 2. a pointer to a 'buffer' containing the characters to be written
+ * 3. the size in bytes of the data pointed by 'buffer'
+ *
+ * @mustach_emit_cb_t:
+ *
+ * callback receiving the data to be written and a flag indicating
+ * if escaping should be done or not as 4 parameters:
+ *
+ * 1. the 'closure', the same given to the emustach_... function
+ * 2. a pointer to a 'buffer' containing the characters to be written
+ * 3. the size in bytes of the data pointed by 'buffer'
+ * 4. a boolean indicating if 'escape' should be done
+ */
+#ifndef _mustach_output_callbacks_defined_
+#define _mustach_output_callbacks_defined_
+typedef int mustach_write_cb_t(void *closure, const char *buffer, size_t size);
+typedef int mustach_emit_cb_t(void *closure, const char *buffer, size_t size, int escape);
+#endif
+
+/**
+ * Flags specific to mustach wrap
+ */
+#define Mustach_With_SingleDot 4 /* obsolete, always set */
+#define Mustach_With_Equal 8
+#define Mustach_With_Compare 16
+#define Mustach_With_JsonPointer 32
+#define Mustach_With_ObjectIter 64
+#define Mustach_With_IncPartial 128 /* obsolete, always set */
+#define Mustach_With_EscFirstCmp 256
+#define Mustach_With_PartialDataFirst 512
+#define Mustach_With_ErrorUndefined 1024
+
+#undef Mustach_With_AllExtensions
+#define Mustach_With_AllExtensions 1023 /* don't include ErrorUndefined */
+
+/**
+ * mustach_wrap_itf - high level wrap of mustach - interface for callbacks
+ *
+ * The functions sel, subsel, enter and next should return 0 or 1.
+ *
+ * All other functions should normally return MUSTACH_OK (zero).
+ *
+ * If any function returns a negative value, it means an error that
+ * stop the processing and that is reported to the caller. Mustach
+ * also has its own error codes. Using the macros MUSTACH_ERROR_USER
+ * and MUSTACH_IS_ERROR_USER could help to avoid clashes.
+ *
+ * @start: If defined (can be NULL), starts the mustach processing
+ * of the closure, called at the very beginning before any
+ * mustach processing occurs.
+ *
+ * @stop: If defined (can be NULL), stops the mustach processing
+ * of the closure, called at the very end after all mustach
+ * processing occurered. The status returned by the processing
+ * is passed to the stop.
+ *
+ * @compare: If defined (can be NULL), compares the value of the
+ * currently selected item with the given value and returns
+ * a negative value if current value is lesser, a positive
+ * value if the current value is greater or zero when
+ * values are equals.
+ * If 'compare' is NULL, any comparison in mustach
+ * is going to fails.
+ *
+ * @sel: Selects the item of the given 'name'. If 'name' is NULL
+ * Selects the current item. Returns 1 if the selection is
+ * effective or else 0 if the selection failed.
+ *
+ * @subsel: Selects from the currently selected object the value of
+ * the field of given name. Returns 1 if the selection is
+ * effective or else 0 if the selection failed.
+ *
+ * @enter: Enters the section of 'name' if possible.
+ * Musts return 1 if entered or 0 if not entered.
+ * When 1 is returned, the function 'leave' will always be called.
+ * Conversely 'leave' is never called when enter returns 0 or
+ * a negative value.
+ * When 1 is returned, the function must activate the first
+ * item of the section.
+ *
+ * @next: Activates the next item of the section if it exists.
+ * Musts return 1 when the next item is activated.
+ * Musts return 0 when there is no item to activate.
+ *
+ * @leave: Leaves the last entered section
+ *
+ * @get: Returns in 'sbuf' the value of the current selection if 'key'
+ * is zero. Otherwise, when 'key' is not zero, return in 'sbuf'
+ * the name of key of the current selection, or if no such key
+ * exists, the empty string. Must return 1 if possible or
+ * 0 when not possible or an error code.
+ */
+struct mustach_wrap_itf {
+ int (*start)(void *closure);
+ void (*stop)(void *closure, int status);
+ int (*compare)(void *closure, const char *value);
+ int (*sel)(void *closure, const char *name);
+ int (*subsel)(void *closure, const char *name);
+ int (*enter)(void *closure, int objiter);
+ int (*next)(void *closure);
+ int (*leave)(void *closure);
+ int (*get)(void *closure, struct mustach_sbuf *sbuf, int key);
+};
+
+/**
+ * Mustach interface used internally by mustach wrapper functions.
+ * Can be used for overriding behaviour.
+ */
+extern const struct mustach_itf mustach_wrap_itf;
+
+/**
+ * Global hook for providing partials. When set to a not NULL value, the pointed
+ * function replaces the default behaviour and is called to provide the partial
+ * of the given 'name' in 'sbuf'.
+ * The function must return MUSTACH_OK when it filled 'sbuf' with value of partial
+ * or must return an error code if it failed. But if MUSTACH_ERROR_PARTIAL_NOT_FOUND
+ * is returned, the default behavior is evaluated.
+ */
+extern int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf);
+
+/**
+ * mustach_wrap_file - Renders the mustache 'template' in 'file' for an abstract
+ * wrapper of interface 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file);
+
+/**
+ * mustach_wrap_fd - Renders the mustache 'template' in 'fd' for an abstract
+ * wrapper of interface 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd);
+
+/**
+ * mustach_wrap_mem - Renders the mustache 'template' in 'result' for an abstract
+ * wrapper of interface 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size);
+
+/**
+ * mustach_wrap_write - Renders the mustache 'template' for an abstract
+ * wrapper of interface 'itf' and 'closure' to custom writer
+ * 'writecb' with 'writeclosure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @writecb: the function that write values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure);
+
+/**
+ * mustach_wrap_emit - Renders the mustache 'template' for an abstract
+ * wrapper of interface 'itf' and 'closure' to custom emiter 'emitcb'
+ * with 'emitclosure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface of the abstract wrapper
+ * @closure: the closure of the abstract wrapper
+ * @emitcb: the function that emit values
+ * @closure: the closure for the write function
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure);
+
+#endif
+
diff --git a/src/templating/mustach.1.gz b/src/templating/mustach.1.gz
new file mode 100644
index 000000000..15b8a9052
--- /dev/null
+++ b/src/templating/mustach.1.gz
Binary files differ
diff --git a/src/templating/mustach.1.scd b/src/templating/mustach.1.scd
new file mode 100644
index 000000000..af4f08ef2
--- /dev/null
+++ b/src/templating/mustach.1.scd
@@ -0,0 +1,60 @@
+mustach(1)
+
+# NAME
+
+mustach - Mustache templating command line engine
+
+# SYNOPSIS
+
+*mustach* [-s|--strict] JSON TEMPLATE...
+
+# DESCRIPTION
+
+Instanciate the TEMPLATE files accordingly to the JSON file.
+
+If one of the given files is *-*, the standard input is used.
+
+Option *--strict* make mustach fail if a tag is not found.
+
+# EXAMPLE
+
+A typical Mustache template file: *temp.must*
+
+```
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+```
+
+Given a JSON file: *inst.json*
+
+```
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true
+}
+```
+
+Calling the command *mustach inst.json temp.must*
+will produce the following output:
+
+```
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000.0 dollars, after taxes.
+```
+
+# LINK
+
+Site of *mustach*, the *C* implementation: https://gitlab.com/jobol/mustach
+
+*Mustache format*: http://mustache.github.io/mustache.5.html
+
+Main site for *Mustache*: http://mustache.github.io/
+
+JSON: https://www.json.org/
+
diff --git a/src/templating/mustach.c b/src/templating/mustach.c
new file mode 100644
index 000000000..9f5af131c
--- /dev/null
+++ b/src/templating/mustach.c
@@ -0,0 +1,561 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef _WIN32
+#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 */
+ 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 */
+ FILE *file;
+ int flags;
+ int nesting;
+};
+
+struct prefix {
+ size_t len;
+ const char *start;
+ struct prefix *prefix;
+};
+
+#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;
+ sbuf->length = 0;
+}
+
+static inline void sbuf_release(struct mustach_sbuf *sbuf)
+{
+ if (sbuf->releasecb)
+ sbuf->releasecb(sbuf->value, sbuf->closure);
+}
+
+static inline size_t sbuf_length(struct mustach_sbuf *sbuf)
+{
+ size_t length = sbuf->length;
+ if (length == 0 && sbuf->value != NULL)
+ length = strlen(sbuf->value);
+ return length;
+}
+
+static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file)
+{
+ size_t i, j, r;
+
+ (void)closure; /* unused */
+
+ if (!escape)
+ return fwrite(buffer, 1, size, file) != size ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK;
+
+ r = i = 0;
+ while (i < size) {
+ j = i;
+ while (j < size && buffer[j] != '<' && 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 '<':
+ r = fwrite("&lt;", 4, 1, file);
+ break;
+ case '>':
+ r = fwrite("&gt;", 4, 1, file);
+ break;
+ case '&':
+ r = fwrite("&amp;", 5, 1, file);
+ break;
+ case '"':
+ r = fwrite("&quot;", 6, 1, file);
+ break;
+ }
+ if (r != 1)
+ return MUSTACH_ERROR_SYSTEM;
+ }
+ 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 = sbuf_length(&sbuf);
+ 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;
+ sbuf->length = size;
+ }
+ }
+ }
+ return rc;
+}
+
+static int emitprefix(struct iwrap *iwrap, struct prefix *prefix)
+{
+ if (prefix->prefix) {
+ int rc = emitprefix(iwrap, prefix->prefix);
+ if (rc < 0)
+ return rc;
+ }
+ return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, iwrap->file) : 0;
+}
+
+static int process(const char *template, size_t length, struct iwrap *iwrap, struct prefix *prefix)
+{
+ struct mustach_sbuf sbuf;
+ char opstr[MUSTACH_MAX_DELIM_LENGTH], clstr[MUSTACH_MAX_DELIM_LENGTH];
+ char name[MUSTACH_MAX_LENGTH + 1], c;
+ const char *beg, *term, *end;
+ struct { const char *name, *again; size_t length; unsigned enabled: 1, entered: 1; } stack[MUSTACH_MAX_DEPTH];
+ size_t oplen, cllen, len, l;
+ int depth, rc, enabled, stdalone;
+ struct prefix pref;
+
+ pref.prefix = prefix;
+ end = template + (length ? length : strlen(template));
+ opstr[0] = opstr[1] = '{';
+ clstr[0] = clstr[1] = '}';
+ oplen = cllen = 2;
+ stdalone = enabled = 1;
+ depth = pref.len = 0;
+ for (;;) {
+ /* search next openning delimiter */
+ for (beg = template ; ; beg++) {
+ c = beg == end ? '\n' : *beg;
+ if (c == '\n') {
+ l = (beg != end) + (size_t)(beg - template);
+ if (stdalone != 2 && enabled) {
+ if (beg != template /* don't prefix empty lines */) {
+ rc = emitprefix(iwrap, &pref);
+ if (rc < 0)
+ return rc;
+ }
+ rc = iwrap->emit(iwrap->closure, template, l, 0, iwrap->file);
+ if (rc < 0)
+ return rc;
+ }
+ if (beg == end) /* no more mustach */
+ return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK;
+ template += l;
+ stdalone = 1;
+ pref.len = 0;
+ pref.prefix = prefix;
+ }
+ else if (!isspace(c)) {
+ if (stdalone == 2 && enabled) {
+ rc = emitprefix(iwrap, &pref);
+ if (rc < 0)
+ return rc;
+ pref.len = 0;
+ stdalone = 0;
+ pref.prefix = NULL;
+ }
+ if (c == *opstr && end - beg >= (ssize_t)oplen) {
+ for (l = 1 ; l < oplen && beg[l] == opstr[l] ; l++);
+ if (l == oplen)
+ break;
+ }
+ stdalone = 0;
+ }
+ }
+
+ pref.start = template;
+ pref.len = enabled ? (size_t)(beg - template) : 0;
+ beg += oplen;
+
+ /* search next closing delimiter */
+ for (term = beg ; ; term++) {
+ if (term == end)
+ return MUSTACH_ERROR_UNEXPECTED_END;
+ if (*term == *clstr && end - term >= (ssize_t)cllen) {
+ for (l = 1 ; l < cllen && term[l] == clstr[l] ; l++);
+ if (l == cllen)
+ break;
+ }
+ }
+ template = term + cllen;
+ len = (size_t)(term - beg);
+ c = *beg;
+ switch(c) {
+ case ':':
+ stdalone = 0;
+ if (iwrap->flags & Mustach_With_Colon)
+ goto exclude_first;
+ goto get_name;
+ case '!':
+ case '=':
+ break;
+ case '{':
+ for (l = 0 ; l < cllen && clstr[l] == '}' ; l++);
+ if (l < cllen) {
+ 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 '&':
+ stdalone = 0;
+ /*@fallthrough@*/
+ case '^':
+ case '#':
+ case '/':
+ case '>':
+exclude_first:
+ beg++;
+ len--;
+ goto get_name;
+ default:
+ stdalone = 0;
+get_name:
+ while (len && isspace(beg[0])) { beg++; len--; }
+ while (len && isspace(beg[len-1])) len--;
+ if (len == 0 && !(iwrap->flags & Mustach_With_EmptyTag))
+ return MUSTACH_ERROR_EMPTY_TAG;
+ if (len > MUSTACH_MAX_LENGTH)
+ return MUSTACH_ERROR_TAG_TOO_LONG;
+ memcpy(name, beg, len);
+ name[len] = 0;
+ break;
+ }
+ if (stdalone)
+ stdalone = 2;
+ else if (enabled) {
+ rc = emitprefix(iwrap, &pref);
+ if (rc < 0)
+ return rc;
+ pref.len = 0;
+ pref.prefix = NULL;
+ }
+ switch(c) {
+ case '!':
+ /* comment */
+ /* nothing to do */
+ break;
+ case '=':
+ /* defines delimiters */
+ if (len < 5 || beg[len - 1] != '=')
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ beg++;
+ len -= 2;
+ while (len && isspace(*beg))
+ beg++, len--;
+ while (len && isspace(beg[len - 1]))
+ len--;
+ for (l = 0; l < len && !isspace(beg[l]) ; l++);
+ if (l == len || l > MUSTACH_MAX_DELIM_LENGTH)
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ oplen = l;
+ memcpy(opstr, beg, l);
+ while (l < len && isspace(beg[l])) l++;
+ if (l == len || len - l > MUSTACH_MAX_DELIM_LENGTH)
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ cllen = len - l;
+ memcpy(clstr, beg + l, cllen);
+ 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 != 0;
+ stack[depth].entered = rc != 0;
+ 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) {
+ if (iwrap->nesting >= MUSTACH_MAX_NESTING)
+ rc = MUSTACH_ERROR_TOO_MUCH_NESTING;
+ else {
+ sbuf_reset(&sbuf);
+ rc = iwrap->partial(iwrap->closure_partial, name, &sbuf);
+ if (rc >= 0) {
+ iwrap->nesting++;
+ rc = process(sbuf.value, sbuf_length(&sbuf), iwrap, &pref);
+ sbuf_release(&sbuf);
+ iwrap->nesting--;
+ }
+ }
+ if (rc < 0)
+ return rc;
+ }
+ break;
+ default:
+ /* replacement */
+ if (enabled) {
+ rc = iwrap->put(iwrap->closure_put, name, c != '&', iwrap->file);
+ if (rc < 0)
+ return rc;
+ }
+ break;
+ }
+ }
+}
+
+int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, 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;
+ iwrap.file = file;
+ iwrap.flags = flags;
+ iwrap.nesting = 0;
+
+ /* process */
+ rc = itf->start ? itf->start(closure) : 0;
+ if (rc == 0)
+ rc = process(template, length, &iwrap, NULL);
+ if (itf->stop)
+ itf->stop(closure, rc);
+ return rc;
+}
+
+int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd)
+{
+ int rc;
+ FILE *file;
+
+ file = fdopen(fd, "w");
+ if (file == NULL) {
+ rc = MUSTACH_ERROR_SYSTEM;
+ errno = ENOMEM;
+ } else {
+ rc = mustach_file(template, length, itf, closure, flags, file);
+ fclose(file);
+ }
+ return rc;
+}
+
+int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, 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 = mustach_file(template, length, itf, closure, flags, file);
+ if (rc < 0)
+ memfile_abort(file, result, size);
+ else
+ rc = memfile_close(file, result, size);
+ }
+ return rc;
+}
+
+int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file)
+{
+ return mustach_file(template, 0, itf, closure, Mustach_With_AllExtensions, file);
+}
+
+int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd)
+{
+ return mustach_fd(template, 0, itf, closure, Mustach_With_AllExtensions, fd);
+}
+
+int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size)
+{
+ return mustach_mem(template, 0, itf, closure, Mustach_With_AllExtensions, result, size);
+}
+
diff --git a/src/templating/mustach.h b/src/templating/mustach.h
new file mode 100644
index 000000000..1b44582d5
--- /dev/null
+++ b/src/templating/mustach.h
@@ -0,0 +1,319 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _mustach_h_included_
+#define _mustach_h_included_
+
+struct mustach_sbuf; /* see below */
+
+/**
+ * Current version of mustach and its derivates
+ */
+#define MUSTACH_VERSION 102
+#define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100)
+#define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100)
+
+/**
+ * Maximum nested section supported
+ */
+#define MUSTACH_MAX_DEPTH 256
+
+/**
+ * Maximum nested template supported
+ */
+#define MUSTACH_MAX_NESTING 64
+
+/**
+ * Maximum length of tags in mustaches {{...}}
+ */
+#define MUSTACH_MAX_LENGTH 4096
+
+/**
+ * Maximum length of delimitors (2 normally but extended here)
+ */
+#define MUSTACH_MAX_DELIM_LENGTH 8
+
+/**
+ * Flags specific to mustach core
+ */
+#define Mustach_With_NoExtensions 0
+#define Mustach_With_Colon 1
+#define Mustach_With_EmptyTag 2
+#define Mustach_With_AllExtensions 3
+
+/*
+ * Definition of error codes returned by mustach
+ */
+#define MUSTACH_OK 0
+#define MUSTACH_ERROR_SYSTEM -1
+#define MUSTACH_ERROR_UNEXPECTED_END -2
+#define MUSTACH_ERROR_EMPTY_TAG -3
+#define MUSTACH_ERROR_TAG_TOO_LONG -4
+#define MUSTACH_ERROR_BAD_SEPARATORS -5
+#define MUSTACH_ERROR_TOO_DEEP -6
+#define MUSTACH_ERROR_CLOSING -7
+#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8
+#define MUSTACH_ERROR_INVALID_ITF -9
+#define MUSTACH_ERROR_ITEM_NOT_FOUND -10
+#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11
+#define MUSTACH_ERROR_UNDEFINED_TAG -12
+#define MUSTACH_ERROR_TOO_MUCH_NESTING -13
+
+/*
+ * You can use definition below for user specific error
+ *
+ * The macro MUSTACH_ERROR_USER is involutive so for any value
+ * value = MUSTACH_ERROR_USER(MUSTACH_ERROR_USER(value))
+ */
+#define MUSTACH_ERROR_USER_BASE -100
+#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x))
+#define MUSTACH_IS_ERROR_USER(x) (MUSTACH_ERROR_USER(x) >= 0)
+
+/**
+ * mustach_itf - pure abstract mustach - interface for callbacks
+ *
+ * The functions enter and next should return 0 or 1.
+ *
+ * All other functions should normally return MUSTACH_OK (zero).
+ *
+ * If any function returns a negative value, it means an error that
+ * stop the processing and that is reported to the caller. Mustach
+ * also has its own error codes. Using the macros MUSTACH_ERROR_USER
+ * and MUSTACH_IS_ERROR_USER could help to avoid clashes.
+ *
+ * @start: If defined (can be NULL), starts the mustach processing
+ * of the closure, called at the very beginning before any
+ * mustach processing occurs.
+ *
+ * @put: If defined (can be NULL), writes the value of 'name'
+ * to 'file' with 'escape' or not.
+ * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be
+ * the empty string. In that later case an implementation can
+ * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names.
+ * If NULL and 'get' NULL the error MUSTACH_ERROR_INVALID_ITF
+ * is returned.
+ *
+ * @enter: Enters the section of 'name' if possible.
+ * Musts return 1 if entered or 0 if not entered.
+ * When 1 is returned, the function 'leave' will always be called.
+ * Conversely 'leave' is never called when enter returns 0 or
+ * a negative value.
+ * When 1 is returned, the function must activate the first
+ * item of the section.
+ *
+ * @next: Activates the next item of the section if it exists.
+ * Musts return 1 when the next item is activated.
+ * Musts return 0 when there is no item to activate.
+ *
+ * @leave: Leaves the last entered section
+ *
+ * @partial: If defined (can be NULL), returns in 'sbuf' the content of the
+ * partial of 'name'. @see mustach_sbuf
+ * If NULL but 'get' not NULL, 'get' is used instead of partial.
+ * If NULL and 'get' NULL and 'put' not NULL, 'put' is called with
+ * a true FILE.
+ *
+ * @emit: If defined (can be NULL), writes the 'buffer' of 'size' with 'escape'.
+ * If NULL the standard function 'fwrite' is used with a true FILE.
+ * If not NULL that function is called instead of 'fwrite' to output
+ * text.
+ * It implies that if you define either 'partial' or 'get' callback,
+ * the meaning of 'FILE *file' is abstract for mustach's process and
+ * then you can use 'FILE*file' pass any kind of pointer (including NULL)
+ * to the function 'fmustach'. An example of a such behaviour is given by
+ * the implementation of 'mustach_json_c_write'.
+ *
+ * @get: If defined (can be NULL), returns in 'sbuf' the value of 'name'.
+ * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be
+ * the empty string. In that later case an implementation can
+ * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names.
+ * If 'get' is NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF
+ * is returned.
+ *
+ * @stop: If defined (can be NULL), stops the mustach processing
+ * of the closure, called at the very end after all mustach
+ * processing occurered. The status returned by the processing
+ * is passed to the stop.
+ *
+ * The array below summarize status of callbacks:
+ *
+ * FULLY OPTIONAL: start partial
+ * MANDATORY: enter next leave
+ * COMBINATORIAL: put emit get
+ *
+ * Not definig a MANDATORY callback returns error MUSTACH_ERROR_INVALID_ITF.
+ *
+ * For COMBINATORIAL callbacks the array below summarize possible combinations:
+ *
+ * combination : put : emit : get : abstract FILE
+ * -------------+---------+---------+---------+-----------------------
+ * HISTORIC : defined : NULL : NULL : NO: standard FILE
+ * MINIMAL : NULL : NULL : defined : NO: standard FILE
+ * CUSTOM : NULL : defined : defined : YES: abstract FILE
+ * DUCK : defined : NULL : defined : NO: standard FILE
+ * DANGEROUS : defined : defined : any : YES or NO, depends on 'partial'
+ * INVALID : NULL : any : NULL : -
+ *
+ * The DUCK case runs on one leg. 'get' is not used if 'partial' is defined
+ * but is used for 'partial' if 'partial' is NULL. Thus for clarity, do not use
+ * it that way but define 'partial' and let 'get' be NULL.
+ *
+ * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined
+ * but forbids abstract FILE when 'partial' is NULL.
+ *
+ * The INVALID case returns error MUSTACH_ERROR_INVALID_ITF.
+ */
+struct mustach_itf {
+ int (*start)(void *closure);
+ int (*put)(void *closure, const char *name, int escape, FILE *file);
+ int (*enter)(void *closure, const char *name);
+ int (*next)(void *closure);
+ int (*leave)(void *closure);
+ int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file);
+ int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ void (*stop)(void *closure, int status);
+};
+
+/**
+ * mustach_sbuf - Interface for handling zero terminated strings
+ *
+ * That structure is used for returning zero terminated strings -in 'value'-
+ * to mustach. The callee can provide a function for releasing the returned
+ * 'value'. Three methods for releasing the string are possible.
+ *
+ * 1. no release: set either 'freecb' or 'releasecb' with NULL (done by default)
+ * 2. release without closure: set 'freecb' to its expected value
+ * 3. release with closure: set 'releasecb' and 'closure' to their expected values
+ *
+ * @value: The value of the string. That value is not changed by mustach -const-.
+ *
+ * @freecb: The function to call for freeing the value without closure.
+ * For convenience, signature of that callback is compatible with 'free'.
+ * Can be NULL.
+ *
+ * @releasecb: The function to release with closure.
+ * Can be NULL.
+ *
+ * @closure: The closure to use for 'releasecb'.
+ *
+ * @length: Length of the value or zero if unknown and value null terminated.
+ * To return the empty string, let it to zero and let value to NULL.
+ */
+struct mustach_sbuf {
+ const char *value;
+ union {
+ void (*freecb)(void*);
+ void (*releasecb)(const char *value, void *closure);
+ };
+ void *closure;
+ size_t length;
+};
+
+/**
+ * mustach_file - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file);
+
+/**
+ * mustach_fd - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd);
+
+/**
+ * mustach_mem - Renders the mustache 'template' in 'result' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate
+ * @length: length of the template or zero if unknown and template null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+extern int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size);
+
+/***************************************************************************
+* compatibility with version before 1.0
+*/
+#ifdef __GNUC__
+#define DEPRECATED_MUSTACH(func) func __attribute__ ((deprecated))
+#elif defined(_MSC_VER)
+#define DEPRECATED_MUSTACH(func) __declspec(deprecated) func
+#elif !defined(DEPRECATED_MUSTACH)
+#pragma message("WARNING: You need to implement DEPRECATED_MUSTACH for this compiler")
+#define DEPRECATED_MUSTACH(func) func
+#endif
+/**
+ * OBSOLETE use mustach_file
+ *
+ * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate, null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @file: the file where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+DEPRECATED_MUSTACH(extern int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file));
+
+/**
+ * OBSOLETE use mustach_fd
+ *
+ * fdmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate, null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @fd: the file descriptor number where to write the result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+DEPRECATED_MUSTACH(extern int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd));
+
+/**
+ * OBSOLETE use mustach_mem
+ *
+ * mustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instantiate, null terminated
+ * @itf: the interface to the functions that mustach calls
+ * @closure: the closure to pass to functions called
+ * @result: the pointer receiving the result when 0 is returned
+ * @size: the size of the returned result
+ *
+ * Returns 0 in case of success, -1 with errno set in case of system error
+ * a other negative value in case of error.
+ */
+DEPRECATED_MUSTACH(extern int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size));
+
+#endif
+
diff --git a/src/templating/pkgcfgs b/src/templating/pkgcfgs
new file mode 100644
index 000000000..c3e84dc0e
--- /dev/null
+++ b/src/templating/pkgcfgs
@@ -0,0 +1,35 @@
+==libmustach.pc==
+Name: libmustach
+Version: VERSION
+Description: C Mustach single library
+Cflags: -Imustach
+Libs: -lmustach
+
+==libmustach-core.pc==
+Name: libmustach-core
+Version: VERSION
+Description: C Mustach core library
+Cflags: -Imustach
+Libs: -lmustach-core
+
+==libmustach-cjson.pc==
+Name: libmustach-cjson
+Version: VERSION
+Description: C Mustach library for cJSON
+Cflags: -Imustach
+Libs: -lmustach-cjson
+
+==libmustach-json-c.pc==
+Name: libmustach-json-c
+Version: VERSION
+Description: C Mustach library for json-c
+Cflags: -Imustach
+Libs: -lmustach-json-c
+
+==libmustach-jansson.pc==
+Name: libmustach-jansson
+Version: VERSION
+Description: C Mustach library for jansson
+Cflags: -Imustach
+Libs: -lmustach-jansson
+
diff --git a/src/templating/run-original-tests.sh b/src/templating/run-original-tests.sh
new file mode 100755
index 000000000..21481a286
--- /dev/null
+++ b/src/templating/run-original-tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+# This file is in the public domain.
+set -eux
+
+export CFLAGS="-g"
+
+echo "Ensuring clean state on entry to upstream tests ..."
+make clean
+
+# The build fails if libjson-c-dev is not installed.
+# That's OK, we don't otherwise need it and don't
+# even bother testing for it in configure.ac.
+# However, in that case, skip the test suite.
+make -f mustach-original-Makefile mustach mustach-json-c.o || exit 77
+make -f mustach-original-Makefile clean || true
+make -f mustach-original-Makefile basic-tests
+make -f mustach-original-Makefile clean || true
+
+exit 0
diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c
new file mode 100644
index 000000000..88a17c682
--- /dev/null
+++ b/src/templating/templating_api.c
@@ -0,0 +1,524 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2022 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 templating_api.c
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+#include "taler_templating_lib.h"
+#include "mustach.h"
+#include "mustach-jansson.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Entry in a key-value array we use to cache templates.
+ */
+struct TVE
+{
+ /**
+ * A name, used as the key. NULL for the last entry.
+ */
+ char *name;
+
+ /**
+ * Language the template is in.
+ */
+ char *lang;
+
+ /**
+ * 0-terminated (!) file data to return for @e name and @e lang.
+ */
+ char *value;
+
+};
+
+
+/**
+ * Array of templates loaded into RAM.
+ */
+static struct TVE *loaded;
+
+/**
+ * Length of the #loaded array.
+ */
+static unsigned int loaded_length;
+
+
+/**
+ * Load Mustach template into memory. Note that we intentionally cache
+ * failures, that is if we ever failed to load a template, we will never try
+ * again.
+ *
+ * @param connection the connection we act upon
+ * @param name name of the template file to load
+ * (MUST be a 'static' string in memory!)
+ * @return NULL on error, otherwise the template
+ */
+static const char *
+lookup_template (struct MHD_Connection *connection,
+ const char *name)
+{
+ struct TVE *best = NULL;
+ const char *lang;
+
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ /* find best match by language */
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ if (0 != strcmp (loaded[i].name,
+ name))
+ continue; /* does not match by name */
+ if ( (NULL == best) ||
+ (TALER_language_matches (lang,
+ loaded[i].lang) >
+ TALER_language_matches (lang,
+ best->lang) ) )
+ best = &loaded[i];
+ }
+ if (NULL == best)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No templates found for `%s'\n",
+ name);
+ return NULL;
+ }
+ return best->value;
+}
+
+
+/**
+ * Get the base URL for static resources.
+ *
+ * @param con the MHD connection
+ * @param instance_id the instance ID
+ * @returns the static files base URL, guaranteed
+ * to have a trailing slash.
+ */
+static char *
+make_static_url (struct MHD_Connection *con,
+ const char *instance_id)
+{
+ const char *host;
+ const char *forwarded_host;
+ const char *uri_path;
+ struct GNUNET_Buffer buf = { 0 };
+
+ host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "Host");
+ forwarded_host = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Host");
+
+ uri_path = MHD_lookup_connection_value (con,
+ MHD_HEADER_KIND,
+ "X-Forwarded-Prefix");
+ if (NULL != forwarded_host)
+ host = forwarded_host;
+
+ if (NULL == host)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ GNUNET_assert (NULL != instance_id);
+
+ if (GNUNET_NO == TALER_mhd_is_https (con))
+ GNUNET_buffer_write_str (&buf,
+ "http://");
+ else
+ GNUNET_buffer_write_str (&buf,
+ "https://");
+ GNUNET_buffer_write_str (&buf,
+ host);
+ if (NULL != uri_path)
+ GNUNET_buffer_write_path (&buf,
+ uri_path);
+ if (0 != strcmp ("default",
+ instance_id))
+ {
+ GNUNET_buffer_write_path (&buf,
+ "instances");
+ GNUNET_buffer_write_path (&buf,
+ instance_id);
+ }
+ GNUNET_buffer_write_path (&buf,
+ "static/");
+ return GNUNET_buffer_reap_str (&buf);
+}
+
+
+int
+TALER_TEMPLATING_fill (const char *tmpl,
+ const json_t *root,
+ void **result,
+ size_t *result_size)
+{
+ int eno;
+
+ if (0 !=
+ (eno = mustach_jansson_mem (tmpl,
+ 0, /* length of tmpl */
+ (json_t *) root,
+ Mustach_With_AllExtensions,
+ (char **) result,
+ result_size)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mustach failed on template with error %d\n",
+ eno);
+ *result = NULL;
+ *result_size = 0;
+ return eno;
+ }
+ return eno;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_build (struct MHD_Connection *connection,
+ unsigned int *http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root,
+ struct MHD_Response **reply)
+{
+ char *body;
+ size_t body_size;
+
+ {
+ const char *tmpl;
+ int eno;
+
+ tmpl = lookup_template (connection,
+ template);
+ if (NULL == tmpl)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load template `%s'\n",
+ template);
+ *http_status = MHD_HTTP_NOT_ACCEPTABLE;
+ *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
+ template);
+ return GNUNET_NO;
+ }
+ /* Add default values to the context */
+ if (NULL != instance_id)
+ {
+ char *static_url = make_static_url (connection,
+ instance_id);
+
+ GNUNET_break (0 ==
+ json_object_set_new ((json_t *) root,
+ "static_url",
+ json_string (static_url)));
+ GNUNET_free (static_url);
+ }
+ if (0 !=
+ (eno = mustach_jansson_mem (tmpl,
+ 0,
+ (json_t *) root,
+ Mustach_With_NoExtensions,
+ &body,
+ &body_size)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mustach failed on template `%s' with error %d\n",
+ template,
+ eno);
+ *http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ *reply = TALER_MHD_make_error (TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
+ template);
+ return GNUNET_NO;
+ }
+ }
+
+ /* try to compress reply if client allows it */
+ {
+ bool compressed = false;
+
+ if (MHD_YES ==
+ TALER_MHD_can_compress (connection))
+ {
+ compressed = TALER_MHD_body_compress ((void **) &body,
+ &body_size);
+ }
+ *reply = MHD_create_response_from_buffer (body_size,
+ body,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL == *reply)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (compressed)
+ {
+ if (MHD_NO ==
+ MHD_add_response_header (*reply,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate"))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (*reply);
+ *reply = NULL;
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ /* Add standard headers */
+ if (NULL != taler_uri)
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (*reply,
+ "Taler",
+ taler_uri));
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (*reply,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/html"));
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_reply (struct MHD_Connection *connection,
+ unsigned int http_status,
+ const char *template,
+ const char *instance_id,
+ const char *taler_uri,
+ const json_t *root)
+{
+ enum GNUNET_GenericReturnValue res;
+ struct MHD_Response *reply;
+ MHD_RESULT ret;
+
+ res = TALER_TEMPLATING_build (connection,
+ &http_status,
+ template,
+ instance_id,
+ taler_uri,
+ root,
+ &reply);
+ if (GNUNET_SYSERR == res)
+ return res;
+ ret = MHD_queue_response (connection,
+ http_status,
+ reply);
+ MHD_destroy_response (reply);
+ if (MHD_NO == ret)
+ return GNUNET_SYSERR;
+ return (res == GNUNET_OK)
+ ? GNUNET_OK
+ : GNUNET_NO;
+}
+
+
+/**
+ * Function called with a template's filename.
+ *
+ * @param cls closure, NULL
+ * @param filename complete filename (absolute path)
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+static enum GNUNET_GenericReturnValue
+load_template (void *cls,
+ const char *filename)
+{
+ char *lang;
+ char *end;
+ int fd;
+ struct stat sb;
+ char *map;
+ const char *name;
+
+ (void) cls;
+ if ('.' == filename[0])
+ return GNUNET_OK;
+ name = strrchr (filename,
+ '/');
+ if (NULL == name)
+ name = filename;
+ else
+ name++;
+ lang = strchr (name,
+ '.');
+ if (NULL == lang)
+ return GNUNET_OK; /* name must include .$LANG */
+ lang++;
+ end = strchr (lang,
+ '.');
+ if ( (NULL == end) ||
+ (0 != strcmp (end,
+ ".must")) )
+ return GNUNET_OK; /* name must end with '.must' */
+
+ /* finally open template */
+ fd = open (filename,
+ O_RDONLY);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ filename);
+
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ fstat (fd,
+ &sb))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "fstat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ map = GNUNET_malloc_large (sb.st_size + 1);
+ if (NULL == map)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ GNUNET_break (0 == close (fd));
+ return GNUNET_SYSERR;
+ }
+ if (sb.st_size !=
+ read (fd,
+ map,
+ sb.st_size))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "read",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ GNUNET_break (0 == close (fd));
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ loaded_length + 1);
+ loaded[loaded_length - 1].name = GNUNET_strndup (name,
+ (lang - 1) - name);
+ loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
+ end - lang);
+ loaded[loaded_length - 1].value = map;
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TALER_TEMPLATING_reply_error (
+ struct MHD_Connection *connection,
+ const char *template_basename,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ json_t *data;
+ enum GNUNET_GenericReturnValue ret;
+
+ data = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("ec",
+ ec),
+ GNUNET_JSON_pack_string ("hint",
+ TALER_ErrorCode_get_hint (ec)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("detail",
+ detail))
+ );
+ ret = TALER_TEMPLATING_reply (connection,
+ http_status,
+ template_basename,
+ NULL,
+ NULL,
+ data);
+ json_decref (data);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ return MHD_YES;
+ case GNUNET_NO:
+ return MHD_YES;
+ case GNUNET_SYSERR:
+ return MHD_NO;
+ }
+ GNUNET_assert (0);
+ return MHD_NO;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_init (const char *subsystem)
+{
+ char *dn;
+ int ret;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/%s/templates/",
+ path,
+ subsystem);
+ GNUNET_free (path);
+ }
+ ret = GNUNET_DISK_directory_scan (dn,
+ &load_template,
+ NULL);
+ GNUNET_free (dn);
+ if (-1 == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+void
+TALER_TEMPLATING_done (void)
+{
+ for (unsigned int i = 0; i<loaded_length; i++)
+ {
+ GNUNET_free (loaded[i].name);
+ GNUNET_free (loaded[i].lang);
+ GNUNET_free (loaded[i].value);
+ }
+ GNUNET_array_grow (loaded,
+ loaded_length,
+ 0);
+}
+
+
+/* end of templating_api.c */
diff --git a/src/templating/test-specs/test-specs-cjson.ref b/src/templating/test-specs/test-specs-cjson.ref
new file mode 100644
index 000000000..8897c66cc
--- /dev/null
+++ b/src/templating/test-specs/test-specs-cjson.ref
@@ -0,0 +1,425 @@
+
+loading test-specs/spec/specs/comments.json
+processing file test-specs/spec/specs/comments.json
+[0] Inline
+ Comment blocks should be removed from the template.
+ => SUCCESS
+[1] Multiline
+ Multiline comments should be permitted.
+ => SUCCESS
+[2] Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[3] Indented Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[4] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[5] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[6] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[7] Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[8] Indented Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[9] Indented Inline
+ Inline comments should not strip whitespace
+ => SUCCESS
+[10] Surrounding Whitespace
+ Comment removal should preserve surrounding whitespace.
+ => SUCCESS
+[11] Variable Name Collision
+ Comments must never render, even if variable with same name exists.
+ => SUCCESS
+
+loading test-specs/spec/specs/delimiters.json
+processing file test-specs/spec/specs/delimiters.json
+[0] Pair Behavior
+ The equals sign (used on both sides) should permit delimiter changes.
+ => SUCCESS
+[1] Special Characters
+ Characters with special meaning regexen should be valid delimiters.
+ => SUCCESS
+[2] Sections
+ Delimiters set outside sections should persist.
+ => SUCCESS
+[3] Inverted Sections
+ Delimiters set outside inverted sections should persist.
+ => SUCCESS
+[4] Partial Inheritence
+ Delimiters set in a parent template should not affect a partial.
+ => SUCCESS
+[5] Post-Partial Behavior
+ Delimiters set in a partial should not affect the parent template.
+ => SUCCESS
+[6] Surrounding Whitespace
+ Surrounding whitespace should be left untouched.
+ => SUCCESS
+[7] Outlying Whitespace (Inline)
+ Whitespace should be left untouched.
+ => SUCCESS
+[8] Standalone Tag
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[9] Indented Standalone Tag
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[10] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[11] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[12] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[13] Pair with Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/interpolation.json
+processing file test-specs/spec/specs/interpolation.json
+[0] No Interpolation
+ Mustache-free templates should render as-is.
+ => SUCCESS
+[1] Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[2] HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[3] Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[4] Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[5] Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[6] Triple Mustache Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[7] Ampersand Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[8] Basic Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[9] Triple Mustache Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[10] Ampersand Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[11] Basic Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[12] Triple Mustache Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[13] Ampersand Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[14] Basic Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[15] Triple Mustache Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[16] Ampersand Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[17] Dotted Names - Basic Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[18] Dotted Names - Triple Mustache Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[19] Dotted Names - Ampersand Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[20] Dotted Names - Arbitrary Depth
+ Dotted names should be functional to any level of nesting.
+ => SUCCESS
+[21] Dotted Names - Broken Chains
+ Any falsey value prior to the last part of the name should yield ''.
+ => SUCCESS
+[22] Dotted Names - Broken Chain Resolution
+ Each part of a dotted name should resolve only against its parent.
+ => SUCCESS
+[23] Dotted Names - Initial Resolution
+ The first part of a dotted name should resolve as any other name.
+ => SUCCESS
+[24] Dotted Names - Context Precedence
+ Dotted names should be resolved against former resolutions.
+ => SUCCESS
+[25] Implicit Iterators - Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[26] Implicit Iterators - HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[27] Implicit Iterators - Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[28] Implicit Iterators - Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[29] Implicit Iterators - Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[30] Interpolation - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[31] Triple Mustache - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[32] Ampersand - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[33] Interpolation - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[34] Triple Mustache - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[35] Ampersand - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[36] Interpolation With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[37] Triple Mustache With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[38] Ampersand With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/inverted.json
+processing file test-specs/spec/specs/inverted.json
+[0] Falsey
+ Falsey sections should have their contents rendered.
+ => SUCCESS
+[1] Truthy
+ Truthy sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should behave like truthy values.
+ => SUCCESS
+[4] List
+ Lists should behave like truthy values.
+ => SUCCESS
+[5] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[6] Doubled
+ Multiple inverted sections per template should be permitted.
+ => SUCCESS
+[7] Nested (Falsey)
+ Nested falsey sections should have their contents rendered.
+ => SUCCESS
+[8] Nested (Truthy)
+ Nested truthy sections should be omitted.
+ => SUCCESS
+[9] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[10] Dotted Names - Truthy
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[11] Dotted Names - Falsey
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[12] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[13] Surrounding Whitespace
+ Inverted sections should not alter surrounding whitespace.
+ => SUCCESS
+[14] Internal Whitespace
+ Inverted should not alter internal whitespace.
+ => SUCCESS
+[15] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[16] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[17] Standalone Indented Lines
+ Standalone indented lines should be removed from the template.
+ => SUCCESS
+[18] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[19] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[20] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[21] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/partials.json
+processing file test-specs/spec/specs/partials.json
+[0] Basic Behavior
+ The greater-than operator should expand to the named partial.
+ => SUCCESS
+[1] Failed Lookup
+ The empty string should be used when the named partial is not found.
+ => SUCCESS
+[2] Context
+ The greater-than operator should operate within the current context.
+ => SUCCESS
+[3] Recursion
+ The greater-than operator should properly recurse.
+ => SUCCESS
+[4] Nested
+ The greater-than operator should work from within partials.
+ => SUCCESS
+[5] Surrounding Whitespace
+ The greater-than operator should not alter surrounding whitespace.
+ => SUCCESS
+[6] Inline Indentation
+ Whitespace should be left untouched.
+ => SUCCESS
+[7] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[8] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[9] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[10] Standalone Indentation
+ Each line of the partial should be indented before rendering.
+ => SUCCESS
+[11] Padding Whitespace
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/sections.json
+processing file test-specs/spec/specs/sections.json
+[0] Truthy
+ Truthy sections should have their contents rendered.
+ => SUCCESS
+[1] Falsey
+ Falsey sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should be pushed onto the context stack.
+ => SUCCESS
+[4] Parent contexts
+ Names missing in the current context are looked up in the stack.
+ => SUCCESS
+[5] Variable test
+ Non-false sections have their value at the top of context,
+accessible as {{.}} or through the parent context. This gives
+a simple way to display content conditionally if a variable exists.
+
+ => SUCCESS
+[6] List Contexts
+ All elements on the context stack should be accessible within lists.
+ => SUCCESS
+[7] Deeply Nested Contexts
+ All elements on the context stack should be accessible.
+ => SUCCESS
+[8] List
+ Lists should be iterated; list items should visit the context stack.
+ => SUCCESS
+[9] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[10] Doubled
+ Multiple sections per template should be permitted.
+ => SUCCESS
+[11] Nested (Truthy)
+ Nested truthy sections should have their contents rendered.
+ => SUCCESS
+[12] Nested (Falsey)
+ Nested falsey sections should be omitted.
+ => SUCCESS
+[13] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[14] Implicit Iterator - String
+ Implicit iterators should directly interpolate strings.
+ => SUCCESS
+[15] Implicit Iterator - Integer
+ Implicit iterators should cast integers to strings and interpolate.
+ => SUCCESS
+[16] Implicit Iterator - Decimal
+ Implicit iterators should cast decimals to strings and interpolate.
+ => SUCCESS
+[17] Implicit Iterator - Array
+ Implicit iterators should allow iterating over nested arrays.
+ => SUCCESS
+[18] Implicit Iterator - HTML Escaping
+ Implicit iterators with basic interpolation should be HTML escaped.
+ => SUCCESS
+[19] Implicit Iterator - Triple mustache
+ Implicit iterators in triple mustache should interpolate without HTML escaping.
+ => SUCCESS
+[20] Implicit Iterator - Ampersand
+ Implicit iterators in an Ampersand tag should interpolate without HTML escaping.
+ => SUCCESS
+[21] Implicit Iterator - Root-level
+ Implicit iterators should work on root-level lists.
+ => SUCCESS
+[22] Dotted Names - Truthy
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[23] Dotted Names - Falsey
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[24] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[25] Surrounding Whitespace
+ Sections should not alter surrounding whitespace.
+ => SUCCESS
+[26] Internal Whitespace
+ Sections should not alter internal whitespace.
+ => SUCCESS
+[27] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[28] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[29] Indented Standalone Lines
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[30] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[31] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[32] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[33] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+summary:
+ error 0
+ differ 0
+ success 133
diff --git a/src/templating/test-specs/test-specs-jansson.ref b/src/templating/test-specs/test-specs-jansson.ref
new file mode 100644
index 000000000..a1cef19c1
--- /dev/null
+++ b/src/templating/test-specs/test-specs-jansson.ref
@@ -0,0 +1,429 @@
+
+loading test-specs/spec/specs/comments.json
+processing file test-specs/spec/specs/comments.json
+[0] Inline
+ Comment blocks should be removed from the template.
+ => SUCCESS
+[1] Multiline
+ Multiline comments should be permitted.
+ => SUCCESS
+[2] Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[3] Indented Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[4] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[5] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[6] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[7] Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[8] Indented Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[9] Indented Inline
+ Inline comments should not strip whitespace
+ => SUCCESS
+[10] Surrounding Whitespace
+ Comment removal should preserve surrounding whitespace.
+ => SUCCESS
+[11] Variable Name Collision
+ Comments must never render, even if variable with same name exists.
+ => SUCCESS
+
+loading test-specs/spec/specs/delimiters.json
+processing file test-specs/spec/specs/delimiters.json
+[0] Pair Behavior
+ The equals sign (used on both sides) should permit delimiter changes.
+ => SUCCESS
+[1] Special Characters
+ Characters with special meaning regexen should be valid delimiters.
+ => SUCCESS
+[2] Sections
+ Delimiters set outside sections should persist.
+ => SUCCESS
+[3] Inverted Sections
+ Delimiters set outside inverted sections should persist.
+ => SUCCESS
+[4] Partial Inheritence
+ Delimiters set in a parent template should not affect a partial.
+ => SUCCESS
+[5] Post-Partial Behavior
+ Delimiters set in a partial should not affect the parent template.
+ => SUCCESS
+[6] Surrounding Whitespace
+ Surrounding whitespace should be left untouched.
+ => SUCCESS
+[7] Outlying Whitespace (Inline)
+ Whitespace should be left untouched.
+ => SUCCESS
+[8] Standalone Tag
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[9] Indented Standalone Tag
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[10] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[11] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[12] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[13] Pair with Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/interpolation.json
+processing file test-specs/spec/specs/interpolation.json
+[0] No Interpolation
+ Mustache-free templates should render as-is.
+ => SUCCESS
+[1] Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[2] HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[3] Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[4] Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[5] Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[6] Triple Mustache Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[7] Ampersand Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[8] Basic Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[9] Triple Mustache Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[10] Ampersand Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[11] Basic Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[12] Triple Mustache Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[13] Ampersand Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[14] Basic Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[15] Triple Mustache Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[16] Ampersand Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[17] Dotted Names - Basic Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[18] Dotted Names - Triple Mustache Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[19] Dotted Names - Ampersand Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[20] Dotted Names - Arbitrary Depth
+ Dotted names should be functional to any level of nesting.
+ => SUCCESS
+[21] Dotted Names - Broken Chains
+ Any falsey value prior to the last part of the name should yield ''.
+ => SUCCESS
+[22] Dotted Names - Broken Chain Resolution
+ Each part of a dotted name should resolve only against its parent.
+ => SUCCESS
+[23] Dotted Names - Initial Resolution
+ The first part of a dotted name should resolve as any other name.
+ => SUCCESS
+[24] Dotted Names - Context Precedence
+ Dotted names should be resolved against former resolutions.
+ => SUCCESS
+[25] Implicit Iterators - Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[26] Implicit Iterators - HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[27] Implicit Iterators - Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[28] Implicit Iterators - Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[29] Implicit Iterators - Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[30] Interpolation - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[31] Triple Mustache - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[32] Ampersand - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[33] Interpolation - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[34] Triple Mustache - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[35] Ampersand - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[36] Interpolation With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[37] Triple Mustache With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[38] Ampersand With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/inverted.json
+processing file test-specs/spec/specs/inverted.json
+[0] Falsey
+ Falsey sections should have their contents rendered.
+ => SUCCESS
+[1] Truthy
+ Truthy sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should behave like truthy values.
+ => SUCCESS
+[4] List
+ Lists should behave like truthy values.
+ => SUCCESS
+[5] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[6] Doubled
+ Multiple inverted sections per template should be permitted.
+ => SUCCESS
+[7] Nested (Falsey)
+ Nested falsey sections should have their contents rendered.
+ => SUCCESS
+[8] Nested (Truthy)
+ Nested truthy sections should be omitted.
+ => SUCCESS
+[9] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[10] Dotted Names - Truthy
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[11] Dotted Names - Falsey
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[12] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[13] Surrounding Whitespace
+ Inverted sections should not alter surrounding whitespace.
+ => SUCCESS
+[14] Internal Whitespace
+ Inverted should not alter internal whitespace.
+ => SUCCESS
+[15] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[16] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[17] Standalone Indented Lines
+ Standalone indented lines should be removed from the template.
+ => SUCCESS
+[18] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[19] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[20] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[21] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/partials.json
+processing file test-specs/spec/specs/partials.json
+[0] Basic Behavior
+ The greater-than operator should expand to the named partial.
+ => SUCCESS
+[1] Failed Lookup
+ The empty string should be used when the named partial is not found.
+ => SUCCESS
+[2] Context
+ The greater-than operator should operate within the current context.
+ => SUCCESS
+[3] Recursion
+ The greater-than operator should properly recurse.
+ => SUCCESS
+[4] Nested
+ The greater-than operator should work from within partials.
+ => SUCCESS
+[5] Surrounding Whitespace
+ The greater-than operator should not alter surrounding whitespace.
+ => SUCCESS
+[6] Inline Indentation
+ Whitespace should be left untouched.
+ => SUCCESS
+[7] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[8] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[9] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[10] Standalone Indentation
+ Each line of the partial should be indented before rendering.
+ => SUCCESS
+[11] Padding Whitespace
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/sections.json
+processing file test-specs/spec/specs/sections.json
+[0] Truthy
+ Truthy sections should have their contents rendered.
+ => SUCCESS
+[1] Falsey
+ Falsey sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should be pushed onto the context stack.
+ => SUCCESS
+[4] Parent contexts
+ Names missing in the current context are looked up in the stack.
+ => SUCCESS
+[5] Variable test
+ Non-false sections have their value at the top of context,
+accessible as {{.}} or through the parent context. This gives
+a simple way to display content conditionally if a variable exists.
+
+ => SUCCESS
+[6] List Contexts
+ All elements on the context stack should be accessible within lists.
+ => SUCCESS
+[7] Deeply Nested Contexts
+ All elements on the context stack should be accessible.
+ => SUCCESS
+[8] List
+ Lists should be iterated; list items should visit the context stack.
+ => SUCCESS
+[9] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[10] Doubled
+ Multiple sections per template should be permitted.
+ => SUCCESS
+[11] Nested (Truthy)
+ Nested truthy sections should have their contents rendered.
+ => SUCCESS
+[12] Nested (Falsey)
+ Nested falsey sections should be omitted.
+ => SUCCESS
+[13] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[14] Implicit Iterator - String
+ Implicit iterators should directly interpolate strings.
+ => SUCCESS
+[15] Implicit Iterator - Integer
+ Implicit iterators should cast integers to strings and interpolate.
+ => SUCCESS
+[16] Implicit Iterator - Decimal
+ Implicit iterators should cast decimals to strings and interpolate.
+ => DIFFERS
+ .. DATA[{"list":[1.1000000000000001,2.2000000000000002,3.2999999999999998,4.4000000000000004,5.5]}]
+ .. TEMPLATE["{{#list}}({{.}}){{/list}}"]
+ .. EXPECTED["(1.1)(2.2)(3.3)(4.4)(5.5)"]
+ .. GOT["(1.1000000000000001)(2.2000000000000002)(3.2999999999999998)(4.4000000000000004)(5.5)"]
+[17] Implicit Iterator - Array
+ Implicit iterators should allow iterating over nested arrays.
+ => SUCCESS
+[18] Implicit Iterator - HTML Escaping
+ Implicit iterators with basic interpolation should be HTML escaped.
+ => SUCCESS
+[19] Implicit Iterator - Triple mustache
+ Implicit iterators in triple mustache should interpolate without HTML escaping.
+ => SUCCESS
+[20] Implicit Iterator - Ampersand
+ Implicit iterators in an Ampersand tag should interpolate without HTML escaping.
+ => SUCCESS
+[21] Implicit Iterator - Root-level
+ Implicit iterators should work on root-level lists.
+ => SUCCESS
+[22] Dotted Names - Truthy
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[23] Dotted Names - Falsey
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[24] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[25] Surrounding Whitespace
+ Sections should not alter surrounding whitespace.
+ => SUCCESS
+[26] Internal Whitespace
+ Sections should not alter internal whitespace.
+ => SUCCESS
+[27] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[28] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[29] Indented Standalone Lines
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[30] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[31] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[32] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[33] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+summary:
+ error 0
+ differ 1
+ success 132
diff --git a/src/templating/test-specs/test-specs-json-c.ref b/src/templating/test-specs/test-specs-json-c.ref
new file mode 100644
index 000000000..8897c66cc
--- /dev/null
+++ b/src/templating/test-specs/test-specs-json-c.ref
@@ -0,0 +1,425 @@
+
+loading test-specs/spec/specs/comments.json
+processing file test-specs/spec/specs/comments.json
+[0] Inline
+ Comment blocks should be removed from the template.
+ => SUCCESS
+[1] Multiline
+ Multiline comments should be permitted.
+ => SUCCESS
+[2] Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[3] Indented Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[4] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[5] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[6] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[7] Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[8] Indented Multiline Standalone
+ All standalone comment lines should be removed.
+ => SUCCESS
+[9] Indented Inline
+ Inline comments should not strip whitespace
+ => SUCCESS
+[10] Surrounding Whitespace
+ Comment removal should preserve surrounding whitespace.
+ => SUCCESS
+[11] Variable Name Collision
+ Comments must never render, even if variable with same name exists.
+ => SUCCESS
+
+loading test-specs/spec/specs/delimiters.json
+processing file test-specs/spec/specs/delimiters.json
+[0] Pair Behavior
+ The equals sign (used on both sides) should permit delimiter changes.
+ => SUCCESS
+[1] Special Characters
+ Characters with special meaning regexen should be valid delimiters.
+ => SUCCESS
+[2] Sections
+ Delimiters set outside sections should persist.
+ => SUCCESS
+[3] Inverted Sections
+ Delimiters set outside inverted sections should persist.
+ => SUCCESS
+[4] Partial Inheritence
+ Delimiters set in a parent template should not affect a partial.
+ => SUCCESS
+[5] Post-Partial Behavior
+ Delimiters set in a partial should not affect the parent template.
+ => SUCCESS
+[6] Surrounding Whitespace
+ Surrounding whitespace should be left untouched.
+ => SUCCESS
+[7] Outlying Whitespace (Inline)
+ Whitespace should be left untouched.
+ => SUCCESS
+[8] Standalone Tag
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[9] Indented Standalone Tag
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[10] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[11] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[12] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[13] Pair with Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/interpolation.json
+processing file test-specs/spec/specs/interpolation.json
+[0] No Interpolation
+ Mustache-free templates should render as-is.
+ => SUCCESS
+[1] Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[2] HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[3] Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[4] Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[5] Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[6] Triple Mustache Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[7] Ampersand Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[8] Basic Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[9] Triple Mustache Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[10] Ampersand Decimal Interpolation
+ Decimals should interpolate seamlessly with proper significance.
+ => SUCCESS
+[11] Basic Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[12] Triple Mustache Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[13] Ampersand Null Interpolation
+ Nulls should interpolate as the empty string.
+ => SUCCESS
+[14] Basic Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[15] Triple Mustache Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[16] Ampersand Context Miss Interpolation
+ Failed context lookups should default to empty strings.
+ => SUCCESS
+[17] Dotted Names - Basic Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[18] Dotted Names - Triple Mustache Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[19] Dotted Names - Ampersand Interpolation
+ Dotted names should be considered a form of shorthand for sections.
+ => SUCCESS
+[20] Dotted Names - Arbitrary Depth
+ Dotted names should be functional to any level of nesting.
+ => SUCCESS
+[21] Dotted Names - Broken Chains
+ Any falsey value prior to the last part of the name should yield ''.
+ => SUCCESS
+[22] Dotted Names - Broken Chain Resolution
+ Each part of a dotted name should resolve only against its parent.
+ => SUCCESS
+[23] Dotted Names - Initial Resolution
+ The first part of a dotted name should resolve as any other name.
+ => SUCCESS
+[24] Dotted Names - Context Precedence
+ Dotted names should be resolved against former resolutions.
+ => SUCCESS
+[25] Implicit Iterators - Basic Interpolation
+ Unadorned tags should interpolate content into the template.
+ => SUCCESS
+[26] Implicit Iterators - HTML Escaping
+ Basic interpolation should be HTML escaped.
+ => SUCCESS
+[27] Implicit Iterators - Triple Mustache
+ Triple mustaches should interpolate without HTML escaping.
+ => SUCCESS
+[28] Implicit Iterators - Ampersand
+ Ampersand should interpolate without HTML escaping.
+ => SUCCESS
+[29] Implicit Iterators - Basic Integer Interpolation
+ Integers should interpolate seamlessly.
+ => SUCCESS
+[30] Interpolation - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[31] Triple Mustache - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[32] Ampersand - Surrounding Whitespace
+ Interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[33] Interpolation - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[34] Triple Mustache - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[35] Ampersand - Standalone
+ Standalone interpolation should not alter surrounding whitespace.
+ => SUCCESS
+[36] Interpolation With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[37] Triple Mustache With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+[38] Ampersand With Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/inverted.json
+processing file test-specs/spec/specs/inverted.json
+[0] Falsey
+ Falsey sections should have their contents rendered.
+ => SUCCESS
+[1] Truthy
+ Truthy sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should behave like truthy values.
+ => SUCCESS
+[4] List
+ Lists should behave like truthy values.
+ => SUCCESS
+[5] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[6] Doubled
+ Multiple inverted sections per template should be permitted.
+ => SUCCESS
+[7] Nested (Falsey)
+ Nested falsey sections should have their contents rendered.
+ => SUCCESS
+[8] Nested (Truthy)
+ Nested truthy sections should be omitted.
+ => SUCCESS
+[9] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[10] Dotted Names - Truthy
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[11] Dotted Names - Falsey
+ Dotted names should be valid for Inverted Section tags.
+ => SUCCESS
+[12] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[13] Surrounding Whitespace
+ Inverted sections should not alter surrounding whitespace.
+ => SUCCESS
+[14] Internal Whitespace
+ Inverted should not alter internal whitespace.
+ => SUCCESS
+[15] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[16] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[17] Standalone Indented Lines
+ Standalone indented lines should be removed from the template.
+ => SUCCESS
+[18] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[19] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[20] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[21] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/partials.json
+processing file test-specs/spec/specs/partials.json
+[0] Basic Behavior
+ The greater-than operator should expand to the named partial.
+ => SUCCESS
+[1] Failed Lookup
+ The empty string should be used when the named partial is not found.
+ => SUCCESS
+[2] Context
+ The greater-than operator should operate within the current context.
+ => SUCCESS
+[3] Recursion
+ The greater-than operator should properly recurse.
+ => SUCCESS
+[4] Nested
+ The greater-than operator should work from within partials.
+ => SUCCESS
+[5] Surrounding Whitespace
+ The greater-than operator should not alter surrounding whitespace.
+ => SUCCESS
+[6] Inline Indentation
+ Whitespace should be left untouched.
+ => SUCCESS
+[7] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[8] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[9] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[10] Standalone Indentation
+ Each line of the partial should be indented before rendering.
+ => SUCCESS
+[11] Padding Whitespace
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+loading test-specs/spec/specs/sections.json
+processing file test-specs/spec/specs/sections.json
+[0] Truthy
+ Truthy sections should have their contents rendered.
+ => SUCCESS
+[1] Falsey
+ Falsey sections should have their contents omitted.
+ => SUCCESS
+[2] Null is falsey
+ Null is falsey.
+ => SUCCESS
+[3] Context
+ Objects and hashes should be pushed onto the context stack.
+ => SUCCESS
+[4] Parent contexts
+ Names missing in the current context are looked up in the stack.
+ => SUCCESS
+[5] Variable test
+ Non-false sections have their value at the top of context,
+accessible as {{.}} or through the parent context. This gives
+a simple way to display content conditionally if a variable exists.
+
+ => SUCCESS
+[6] List Contexts
+ All elements on the context stack should be accessible within lists.
+ => SUCCESS
+[7] Deeply Nested Contexts
+ All elements on the context stack should be accessible.
+ => SUCCESS
+[8] List
+ Lists should be iterated; list items should visit the context stack.
+ => SUCCESS
+[9] Empty List
+ Empty lists should behave like falsey values.
+ => SUCCESS
+[10] Doubled
+ Multiple sections per template should be permitted.
+ => SUCCESS
+[11] Nested (Truthy)
+ Nested truthy sections should have their contents rendered.
+ => SUCCESS
+[12] Nested (Falsey)
+ Nested falsey sections should be omitted.
+ => SUCCESS
+[13] Context Misses
+ Failed context lookups should be considered falsey.
+ => SUCCESS
+[14] Implicit Iterator - String
+ Implicit iterators should directly interpolate strings.
+ => SUCCESS
+[15] Implicit Iterator - Integer
+ Implicit iterators should cast integers to strings and interpolate.
+ => SUCCESS
+[16] Implicit Iterator - Decimal
+ Implicit iterators should cast decimals to strings and interpolate.
+ => SUCCESS
+[17] Implicit Iterator - Array
+ Implicit iterators should allow iterating over nested arrays.
+ => SUCCESS
+[18] Implicit Iterator - HTML Escaping
+ Implicit iterators with basic interpolation should be HTML escaped.
+ => SUCCESS
+[19] Implicit Iterator - Triple mustache
+ Implicit iterators in triple mustache should interpolate without HTML escaping.
+ => SUCCESS
+[20] Implicit Iterator - Ampersand
+ Implicit iterators in an Ampersand tag should interpolate without HTML escaping.
+ => SUCCESS
+[21] Implicit Iterator - Root-level
+ Implicit iterators should work on root-level lists.
+ => SUCCESS
+[22] Dotted Names - Truthy
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[23] Dotted Names - Falsey
+ Dotted names should be valid for Section tags.
+ => SUCCESS
+[24] Dotted Names - Broken Chains
+ Dotted names that cannot be resolved should be considered falsey.
+ => SUCCESS
+[25] Surrounding Whitespace
+ Sections should not alter surrounding whitespace.
+ => SUCCESS
+[26] Internal Whitespace
+ Sections should not alter internal whitespace.
+ => SUCCESS
+[27] Indented Inline Sections
+ Single-line sections should not alter surrounding whitespace.
+ => SUCCESS
+[28] Standalone Lines
+ Standalone lines should be removed from the template.
+ => SUCCESS
+[29] Indented Standalone Lines
+ Indented standalone lines should be removed from the template.
+ => SUCCESS
+[30] Standalone Line Endings
+ "\r\n" should be considered a newline for standalone tags.
+ => SUCCESS
+[31] Standalone Without Previous Line
+ Standalone tags should not require a newline to precede them.
+ => SUCCESS
+[32] Standalone Without Newline
+ Standalone tags should not require a newline to follow them.
+ => SUCCESS
+[33] Padding
+ Superfluous in-tag whitespace should be ignored.
+ => SUCCESS
+
+summary:
+ error 0
+ differ 0
+ success 133
diff --git a/src/templating/test-specs/test-specs.c b/src/templating/test-specs/test-specs.c
new file mode 100644
index 000000000..15c94a80e
--- /dev/null
+++ b/src/templating/test-specs/test-specs.c
@@ -0,0 +1,520 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ https://gitlab.com/jobol/mustach
+
+ SPDX-License-Identifier: ISC
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+
+#include "mustach-wrap.h"
+
+#define TEST_JSON_C 1
+#define TEST_JANSSON 2
+#define TEST_CJSON 3
+
+static const char *errors[] = {
+ "??? unreferenced ???",
+ "system",
+ "unexpected end",
+ "empty tag",
+ "tag too long",
+ "bad separators",
+ "too depth",
+ "closing",
+ "bad unescape tag",
+ "invalid interface",
+ "item not found",
+ "partial not found"
+};
+
+const char *mustach_error_string(int status)
+{
+ return status >= 0 ? "no error"
+ : errors[status <= -(int)(sizeof errors / sizeof * errors) ? 0 : -status];
+}
+
+static const char *errmsg = 0;
+static int flags = 0;
+static FILE *output = 0;
+
+static void help(char *prog)
+{
+ char *name = basename(prog);
+#define STR(x) #x
+ printf("%s version %s\n", name, STR(VERSION));
+#undef STR
+ printf("usage: %s test-files...\n", name);
+ exit(0);
+}
+
+#if TEST == TEST_CJSON
+
+static const size_t BLOCKSIZE = 8192;
+
+static char *readfile(const char *filename, size_t *length)
+{
+ int f;
+ struct stat s;
+ char *result;
+ size_t size, pos;
+ ssize_t rc;
+
+ result = NULL;
+ if (filename[0] == '-' && filename[1] == 0)
+ f = dup(0);
+ else
+ f = open(filename, O_RDONLY);
+ if (f < 0) {
+ fprintf(stderr, "Can't open file: %s\n", filename);
+ exit(1);
+ }
+
+ fstat(f, &s);
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG:
+ size = s.st_size;
+ break;
+ case S_IFSOCK:
+ case S_IFIFO:
+ size = BLOCKSIZE;
+ break;
+ default:
+ fprintf(stderr, "Bad file: %s\n", filename);
+ exit(1);
+ }
+
+ pos = 0;
+ result = malloc(size + 1);
+ do {
+ if (result == NULL) {
+ fprintf(stderr, "Out of memory\n");
+ exit(1);
+ }
+ rc = read(f, &result[pos], (size - pos) + 1);
+ if (rc < 0) {
+ fprintf(stderr, "Error while reading %s\n", filename);
+ exit(1);
+ }
+ if (rc > 0) {
+ pos += (size_t)rc;
+ if (pos > size) {
+ size = pos + BLOCKSIZE;
+ result = realloc(result, size + 1);
+ }
+ }
+ } while(rc > 0);
+
+ close(f);
+ if (length != NULL)
+ *length = pos;
+ result[pos] = 0;
+ return result;
+}
+#endif
+
+typedef struct {
+ unsigned nerror;
+ unsigned ndiffers;
+ unsigned nsuccess;
+ unsigned ninvalid;
+} counters;
+
+static int load_json(const char *filename);
+static int process(counters *c);
+static void close_json();
+static int get_partial(const char *name, struct mustach_sbuf *sbuf);
+
+int main(int ac, char **av)
+{
+ char *f;
+ char *prog = *av;
+ int s;
+ counters c;
+
+ (void)ac; /* unused */
+ flags = Mustach_With_SingleDot | Mustach_With_IncPartial;
+ output = stdout;
+ mustach_wrap_get_partial = get_partial;
+
+ memset(&c, 0, sizeof c);
+ while (*++av) {
+ if (!strcmp(*av, "-h") || !strcmp(*av, "--help"))
+ help(prog);
+ f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0];
+ fprintf(output, "\nloading %s\n", f);
+ s = load_json(f);
+ if (s < 0) {
+ fprintf(stderr, "error when loading %s!\n", f);
+ if(errmsg)
+ fprintf(stderr, " reason: %s\n", errmsg);
+ exit(1);
+ }
+ fprintf(output, "processing file %s\n", f);
+ s = process(&c);
+ if (s < 0) {
+ fprintf(stderr, "error bad test file %s!\n", f);
+ exit(1);
+ }
+ close_json();
+ }
+ fprintf(output, "\nsummary:\n");
+ if (c.ninvalid)
+ fprintf(output, " invalid %u\n", c.ninvalid);
+ fprintf(output, " error %u\n", c.nerror);
+ fprintf(output, " differ %u\n", c.ndiffers);
+ fprintf(output, " success %u\n", c.nsuccess);
+ if (c.nerror)
+ return 2;
+ if (c.ndiffers)
+ return 1;
+ return 0;
+}
+
+void emit(FILE *f, const char *s)
+{
+ for(;;s++) {
+ switch(*s) {
+ case 0: return;
+ case '\\': fprintf(f, "\\\\"); break;
+ case '\t': fprintf(f, "\\t"); break;
+ case '\n': fprintf(f, "\\n"); break;
+ case '\r': fprintf(f, "\\r"); break;
+ default: fprintf(f, "%c", *s); break;
+ }
+ }
+}
+
+#if TEST == TEST_JSON_C
+
+#include "mustach-json-c.h"
+
+static struct json_object *o;
+
+static struct json_object *partials;
+static int get_partial(const char *name, struct mustach_sbuf *sbuf)
+{
+ struct json_object *x;
+ if (partials == NULL || !json_object_object_get_ex(partials, name, &x))
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+ sbuf->value = json_object_get_string(x);
+ return MUSTACH_OK;
+}
+
+static int load_json(const char *filename)
+{
+ o = json_object_from_file(filename);
+#if JSON_C_VERSION_NUM >= 0x000D00
+ errmsg = json_util_get_last_err();
+ if (errmsg != NULL)
+ return -1;
+#endif
+ if (o == NULL) {
+ errmsg = "null json";
+ return -1;
+ }
+ return 0;
+}
+static int process(counters *c)
+{
+ const char *t, *e;
+ char *got;
+ unsigned i, n;
+ size_t length;
+ int s;
+ json_object *tests, *unit, *name, *desc, *data, *template, *expected;
+
+ if (!json_object_object_get_ex(o, "tests", &tests) || json_object_get_type(tests) != json_type_array)
+ return -1;
+
+ i = 0;
+ n = (unsigned)json_object_array_length(tests);
+ while (i < n) {
+ unit = json_object_array_get_idx(tests, i);
+ if (json_object_get_type(unit) != json_type_object
+ || !json_object_object_get_ex(unit, "name", &name)
+ || !json_object_object_get_ex(unit, "desc", &desc)
+ || !json_object_object_get_ex(unit, "data", &data)
+ || !json_object_object_get_ex(unit, "template", &template)
+ || !json_object_object_get_ex(unit, "expected", &expected)
+ || json_object_get_type(name) != json_type_string
+ || json_object_get_type(desc) != json_type_string
+ || json_object_get_type(template) != json_type_string
+ || json_object_get_type(expected) != json_type_string) {
+ fprintf(stderr, "invalid test %u\n", i);
+ c->ninvalid++;
+ }
+ else {
+ fprintf(output, "[%u] %s\n", i, json_object_get_string(name));
+ fprintf(output, "\t%s\n", json_object_get_string(desc));
+ if (!json_object_object_get_ex(unit, "partials", &partials))
+ partials = NULL;
+ t = json_object_get_string(template);
+ e = json_object_get_string(expected);
+ s = mustach_json_c_mem(t, 0, data, flags, &got, &length);
+ if (s == 0 && strcmp(got, e) == 0) {
+ fprintf(output, "\t=> SUCCESS\n");
+ c->nsuccess++;
+ }
+ else {
+ if (s < 0) {
+ fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s));
+ c->nerror++;
+ }
+ else {
+ fprintf(output, "\t=> DIFFERS\n");
+ c->ndiffers++;
+ }
+ if (partials)
+ fprintf(output, "\t.. PARTIALS[%s]\n", json_object_to_json_string_ext(partials, 0));
+ fprintf(output, "\t.. DATA[%s]\n", json_object_to_json_string_ext(data, 0));
+ fprintf(output, "\t.. TEMPLATE[");
+ emit(output, t);
+ fprintf(output, "]\n");
+ fprintf(output, "\t.. EXPECTED[");
+ emit(output, e);
+ fprintf(output, "]\n");
+ if (s == 0) {
+ fprintf(output, "\t.. GOT[");
+ emit(output, got);
+ fprintf(output, "]\n");
+ }
+ }
+ free(got);
+ }
+ i++;
+ }
+ return 0;
+}
+static void close_json()
+{
+ json_object_put(o);
+}
+
+#elif TEST == TEST_JANSSON
+
+#include "mustach-jansson.h"
+
+static json_t *o;
+static json_error_t e;
+
+static json_t *partials;
+static int get_partial(const char *name, struct mustach_sbuf *sbuf)
+{
+ json_t *x;
+ if (partials == NULL || !(x = json_object_get(partials, name)))
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+ sbuf->value = json_string_value(x);
+ return MUSTACH_OK;
+}
+
+static int load_json(const char *filename)
+{
+ o = json_load_file(filename, JSON_DECODE_ANY, &e);
+ if (o == NULL) {
+ errmsg = e.text;
+ return -1;
+ }
+ return 0;
+}
+static int process(counters *c)
+{
+ const char *t, *e;
+ char *got, *tmp;
+ int i, n;
+ size_t length;
+ int s;
+ json_t *tests, *unit, *name, *desc, *data, *template, *expected;
+
+ tests = json_object_get(o, "tests");
+ if (!tests || json_typeof(tests) != JSON_ARRAY)
+ return -1;
+
+ i = 0;
+ n = json_array_size(tests);
+ while (i < n) {
+ unit = json_array_get(tests, i);
+ if (!unit || json_typeof(unit) != JSON_OBJECT
+ || !(name = json_object_get(unit, "name"))
+ || !(desc = json_object_get(unit, "desc"))
+ || !(data = json_object_get(unit, "data"))
+ || !(template = json_object_get(unit, "template"))
+ || !(expected = json_object_get(unit, "expected"))
+ || json_typeof(name) != JSON_STRING
+ || json_typeof(desc) != JSON_STRING
+ || json_typeof(template) != JSON_STRING
+ || json_typeof(expected) != JSON_STRING) {
+ fprintf(stderr, "invalid test %u\n", i);
+ c->ninvalid++;
+ }
+ else {
+ fprintf(output, "[%u] %s\n", i, json_string_value(name));
+ fprintf(output, "\t%s\n", json_string_value(desc));
+ partials = json_object_get(unit, "partials");
+ t = json_string_value(template);
+ e = json_string_value(expected);
+ s = mustach_jansson_mem(t, 0, data, flags, &got, &length);
+ if (s == 0 && strcmp(got, e) == 0) {
+ fprintf(output, "\t=> SUCCESS\n");
+ c->nsuccess++;
+ }
+ else {
+ if (s < 0) {
+ fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s));
+ c->nerror++;
+ }
+ else {
+ fprintf(output, "\t=> DIFFERS\n");
+ c->ndiffers++;
+ }
+ if (partials) {
+ tmp = json_dumps(partials, JSON_ENCODE_ANY | JSON_COMPACT);
+ fprintf(output, "\t.. PARTIALS[%s]\n", tmp);
+ free(tmp);
+ }
+ tmp = json_dumps(data, JSON_ENCODE_ANY | JSON_COMPACT);
+ fprintf(output, "\t.. DATA[%s]\n", tmp);
+ free(tmp);
+ fprintf(output, "\t.. TEMPLATE[");
+ emit(output, t);
+ fprintf(output, "]\n");
+ fprintf(output, "\t.. EXPECTED[");
+ emit(output, e);
+ fprintf(output, "]\n");
+ if (s == 0) {
+ fprintf(output, "\t.. GOT[");
+ emit(output, got);
+ fprintf(output, "]\n");
+ }
+ }
+ free(got);
+ }
+ i++;
+ }
+ return 0;
+}
+static void close_json()
+{
+ json_decref(o);
+}
+
+#elif TEST == TEST_CJSON
+
+#include "mustach-cjson.h"
+
+static cJSON *o;
+static cJSON *partials;
+static int get_partial(const char *name, struct mustach_sbuf *sbuf)
+{
+ cJSON *x;
+ if (partials == NULL || !(x = cJSON_GetObjectItemCaseSensitive(partials, name)))
+ return MUSTACH_ERROR_PARTIAL_NOT_FOUND;
+ sbuf->value = x->valuestring;
+ return MUSTACH_OK;
+}
+
+static int load_json(const char *filename)
+{
+ char *t;
+ size_t length;
+
+ t = readfile(filename, &length);
+ o = t ? cJSON_ParseWithLength(t, length) : NULL;
+ free(t);
+ return -!o;
+}
+static int process(counters *c)
+{
+ const char *t, *e;
+ char *got, *tmp;
+ int i, n;
+ size_t length;
+ int s;
+ cJSON *tests, *unit, *name, *desc, *data, *template, *expected;
+
+ tests = cJSON_GetObjectItemCaseSensitive(o, "tests");
+ if (!tests || tests->type != cJSON_Array)
+ return -1;
+
+ i = 0;
+ n = cJSON_GetArraySize(tests);
+ while (i < n) {
+ unit = cJSON_GetArrayItem(tests, i);
+ if (!unit || unit->type != cJSON_Object
+ || !(name = cJSON_GetObjectItemCaseSensitive(unit, "name"))
+ || !(desc = cJSON_GetObjectItemCaseSensitive(unit, "desc"))
+ || !(data = cJSON_GetObjectItemCaseSensitive(unit, "data"))
+ || !(template = cJSON_GetObjectItemCaseSensitive(unit, "template"))
+ || !(expected = cJSON_GetObjectItemCaseSensitive(unit, "expected"))
+ || name->type != cJSON_String
+ || desc->type != cJSON_String
+ || template->type != cJSON_String
+ || expected->type != cJSON_String) {
+ fprintf(stderr, "invalid test %u\n", i);
+ c->ninvalid++;
+ }
+ else {
+ fprintf(output, "[%u] %s\n", i, name->valuestring);
+ fprintf(output, "\t%s\n", desc->valuestring);
+ partials = cJSON_GetObjectItemCaseSensitive(unit, "partials");
+ t = template->valuestring;
+ e = expected->valuestring;
+ s = mustach_cJSON_mem(t, 0, data, flags, &got, &length);
+ if (s == 0 && strcmp(got, e) == 0) {
+ fprintf(output, "\t=> SUCCESS\n");
+ c->nsuccess++;
+ }
+ else {
+ if (s < 0) {
+ fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s));
+ c->nerror++;
+ }
+ else {
+ fprintf(output, "\t=> DIFFERS\n");
+ c->ndiffers++;
+ }
+ if (partials) {
+ tmp = cJSON_PrintUnformatted(partials);
+ fprintf(output, "\t.. PARTIALS[%s]\n", tmp);
+ free(tmp);
+ }
+ tmp = cJSON_PrintUnformatted(data);
+ fprintf(output, "\t.. DATA[%s]\n", tmp);
+ free(tmp);
+ fprintf(output, "\t.. TEMPLATE[");
+ emit(output, t);
+ fprintf(output, "]\n");
+ fprintf(output, "\t.. EXPECTED[");
+ emit(output, e);
+ fprintf(output, "]\n");
+ if (s == 0) {
+ fprintf(output, "\t.. GOT[");
+ emit(output, got);
+ fprintf(output, "]\n");
+ }
+ }
+ free(got);
+ }
+ i++;
+ }
+ return 0;
+}
+static void close_json()
+{
+ cJSON_Delete(o);
+}
+
+#else
+#error "no defined json library"
+#endif
diff --git a/src/templating/test1/.gitignore b/src/templating/test1/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test1/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test1/Makefile b/src/templating/test1/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test1/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test1/json b/src/templating/test1/json
new file mode 100644
index 000000000..6562fb064
--- /dev/null
+++ b/src/templating/test1/json
@@ -0,0 +1,23 @@
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true,
+ "person": false,
+ "repo": [
+ { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
+ { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
+ { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
+ ],
+ "person?": { "name": "Jon" },
+ "special": "----{{extra}}----\n",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/templating/test1/must b/src/templating/test1/must
new file mode 100644
index 000000000..92d30b0b2
--- /dev/null
+++ b/src/templating/test1/must
@@ -0,0 +1,49 @@
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+Shown.
+{{#person}}
+ Never shown!
+{{/person}}
+{{^person}}
+ No person
+{{/person}}
+
+{{#repo}}
+ <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! gros commentaire %)%
+%(%#repo%)%
+ <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
+%(%/repo%)%
+=====================================
+%(%={{ }}=%)%
+ggggggggg
+{{> special}}
+jjjjjjjjj
+end
+
+{{:#sharp}}
+{{:!bang}}
+{{:~tilde}}
+{{:/~0tilde}}
+{{:/~1slash}} see json pointers IETF RFC 6901
+{{:^circ}}
+{{:\=equal}}
+{{::colon}}
+{{:>greater}}
+
+{{#repo}}
+who 0 {{who.0}}
+who 1 {{who.1}}
+who 2 {{who.2}}
+{{/repo}}
diff --git a/src/templating/test1/resu.ref b/src/templating/test1/resu.ref
new file mode 100644
index 000000000..6cd11bb27
--- /dev/null
+++ b/src/templating/test1/resu.ref
@@ -0,0 +1,41 @@
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000 dollars, after taxes.
+Shown.
+ No person
+
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ Hi Jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+ggggggggg
+----3.14159----
+jjjjjjjjj
+end
+
+#
+!
+~
+~
+/ see json pointers IETF RFC 6901
+^
+=
+:
+&gt;
+
+who 0 {&quot;commiter&quot;:&quot;joe&quot;}
+who 1 {&quot;reviewer&quot;:&quot;avrel&quot;}
+who 2 {&quot;commiter&quot;:&quot;william&quot;}
+who 0 {&quot;commiter&quot;:&quot;jack&quot;}
+who 1 {&quot;reviewer&quot;:&quot;avrel&quot;}
+who 2 {&quot;commiter&quot;:&quot;greg&quot;}
+who 0 {&quot;reviewer&quot;:&quot;joe&quot;}
+who 1 {&quot;reviewer&quot;:&quot;jack&quot;}
+who 2 {&quot;commiter&quot;:&quot;greg&quot;}
diff --git a/src/templating/test1/vg.ref b/src/templating/test1/vg.ref
new file mode 100644
index 000000000..d086e59c5
--- /dev/null
+++ b/src/templating/test1/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 111 allocs, 111 frees, 9,702 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test2/.gitignore b/src/templating/test2/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test2/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test2/Makefile b/src/templating/test2/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test2/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test2/json b/src/templating/test2/json
new file mode 100644
index 000000000..8c668b3b1
--- /dev/null
+++ b/src/templating/test2/json
@@ -0,0 +1,9 @@
+{
+ "header": "Colors",
+ "items": [
+ {"name": "red", "first": true, "url": "#Red"},
+ {"name": "green", "link": true, "url": "#Green"},
+ {"name": "blue", "link": true, "url": "#Blue"}
+ ],
+ "empty": false
+}
diff --git a/src/templating/test2/must b/src/templating/test2/must
new file mode 100644
index 000000000..aa6da7077
--- /dev/null
+++ b/src/templating/test2/must
@@ -0,0 +1,17 @@
+<h1>{{header}}</h1>
+{{#bug}}
+{{/bug}}
+
+{{#items}}
+ {{#first}}
+ <li><strong>{{name}}</strong></li>
+ {{/first}}
+ {{#link}}
+ <li><a href="{{url}}">{{name}}</a></li>
+ {{/link}}
+{{/items}}
+
+{{#empty}}
+ <p>The list is empty.</p>
+{{/empty}}
+
diff --git a/src/templating/test2/resu.ref b/src/templating/test2/resu.ref
new file mode 100644
index 000000000..5a200a9bf
--- /dev/null
+++ b/src/templating/test2/resu.ref
@@ -0,0 +1,7 @@
+<h1>Colors</h1>
+
+ <li><strong>red</strong></li>
+ <li><a href="#Green">green</a></li>
+ <li><a href="#Blue">blue</a></li>
+
+
diff --git a/src/templating/test2/vg.ref b/src/templating/test2/vg.ref
new file mode 100644
index 000000000..e4b4f6d37
--- /dev/null
+++ b/src/templating/test2/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 38 allocs, 38 frees, 5,712 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test3/.gitignore b/src/templating/test3/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test3/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test3/Makefile b/src/templating/test3/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test3/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test3/json b/src/templating/test3/json
new file mode 100644
index 000000000..792788171
--- /dev/null
+++ b/src/templating/test3/json
@@ -0,0 +1,7 @@
+{
+ "name": "Chris",
+ "company": "<b>GitHub & Co</b>",
+ "names": ["Chris", "Kross"],
+ "skills": ["JavaScript", "PHP", "Java"],
+ "age": 18
+}
diff --git a/src/templating/test3/must b/src/templating/test3/must
new file mode 100644
index 000000000..5c490469b
--- /dev/null
+++ b/src/templating/test3/must
@@ -0,0 +1,15 @@
+* {{name}}
+* {{age}}
+* {{company}}
+* {{&company}}
+* {{{company}}}
+{{=<% %>=}}
+* <%company%>
+* <%&company%>
+* <%{company}%>
+
+<%={{ }}=%>
+* <ul>{{#names}}<li>{{.}}</li>{{/names}}</ul>
+* skills: <ul>{{#skills}}<li>{{.}}</li>{{/skills}}</ul>
+{{#age}}* age: {{.}}{{/age}}
+
diff --git a/src/templating/test3/resu.ref b/src/templating/test3/resu.ref
new file mode 100644
index 000000000..ee6dad3fb
--- /dev/null
+++ b/src/templating/test3/resu.ref
@@ -0,0 +1,13 @@
+* Chris
+* 18
+* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
+* <b>GitHub & Co</b>
+* <b>GitHub & Co</b>
+* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
+* <b>GitHub & Co</b>
+* <b>GitHub & Co</b>
+
+* <ul><li>Chris</li><li>Kross</li></ul>
+* skills: <ul><li>JavaScript</li><li>PHP</li><li>Java</li></ul>
+* age: 18
+
diff --git a/src/templating/test3/vg.ref b/src/templating/test3/vg.ref
new file mode 100644
index 000000000..21f7931eb
--- /dev/null
+++ b/src/templating/test3/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 30 allocs, 30 frees, 5,831 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test4/.gitignore b/src/templating/test4/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test4/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test4/Makefile b/src/templating/test4/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test4/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test4/json b/src/templating/test4/json
new file mode 100644
index 000000000..a10836072
--- /dev/null
+++ b/src/templating/test4/json
@@ -0,0 +1,13 @@
+{
+ "person": { "name": "Jon", "age": 25 },
+ "person.name": "Fred",
+ "person.name=Fred": "The other Fred.",
+ "persons": [
+ { "name": "Jon", "age": 25, "lang": "en" },
+ { "name": "Henry", "age": 27, "lang": "en" },
+ { "name": "Amed", "age": 24, "lang": "fr" } ],
+ "fellows": {
+ "Jon": { "age": 25, "lang": "en" },
+ "Henry": { "age": 27, "lang": "en" },
+ "Amed": { "age": 24, "lang": "fr" } }
+}
diff --git a/src/templating/test4/must b/src/templating/test4/must
new file mode 100644
index 000000000..003b93666
--- /dev/null
+++ b/src/templating/test4/must
@@ -0,0 +1,58 @@
+This are extensions!!
+
+{{person.name}}
+{{person.age}}
+
+{{person\.name}}
+{{person\.name\=Fred}}
+
+{{#person.name=Jon}}
+Hello Jon
+{{/person.name=Jon}}
+
+{{^person.name=Jon}}
+No Jon? Hey Jon...
+{{/person.name=Jon}}
+
+{{^person.name=Harry}}
+No Harry? Hey Calahan...
+{{/person.name=Harry}}
+
+{{#person\.name=Fred}}
+Hello Fred
+{{/person\.name=Fred}}
+
+{{^person\.name=Fred}}
+No Fred? Hey Fred...
+{{/person\.name=Fred}}
+
+{{#person\.name\=Fred=The other Fred.}}
+Hello Fred#2
+{{/person\.name\=Fred=The other Fred.}}
+
+{{^person\.name\=Fred=The other Fred.}}
+No Fred#2? Hey Fred#2...
+{{/person\.name\=Fred=The other Fred.}}
+
+{{#persons}}
+{{#lang=!fr}}Hello {{name}}, {{age}} years{{/lang=!fr}}
+{{#lang=fr}}Salut {{name}}, {{age}} ans{{/lang=fr}}
+{{/persons}}
+
+{{#persons}}
+{{name}}: {{age=24}}/{{age}}/{{age=!27}}
+{{/persons}}
+
+{{#fellows.*}}
+{{*}}: {{age=24}}/{{age}}/{{age=!27}}
+{{/fellows.*}}
+
+{{#*}}
+ (1) {{*}}: {{.}}
+ {{#*}}
+ (2) {{*}}: {{.}}
+ {{#*}}
+ (3) {{*}}: {{.}}
+ {{/*}}
+ {{/*}}
+{{/*}}
diff --git a/src/templating/test4/resu.ref b/src/templating/test4/resu.ref
new file mode 100644
index 000000000..8a71c4e82
--- /dev/null
+++ b/src/templating/test4/resu.ref
@@ -0,0 +1,50 @@
+This are extensions!!
+
+Jon
+25
+
+Fred
+The other Fred.
+
+Hello Jon
+
+
+No Harry? Hey Calahan...
+
+Hello Fred
+
+
+Hello Fred#2
+
+
+Hello Jon, 25 years
+
+Hello Henry, 27 years
+
+
+Salut Amed, 24 ans
+
+Jon: /25/25
+Henry: /27/
+Amed: 24/24/24
+
+Jon: /25/25
+Henry: /27/
+Amed: 24/24/24
+
+ (1) person: {&quot;name&quot;:&quot;Jon&quot;,&quot;age&quot;:25}
+ (2) name: Jon
+ (2) age: 25
+ (1) person.name: Fred
+ (1) person.name=Fred: The other Fred.
+ (1) persons: [{&quot;name&quot;:&quot;Jon&quot;,&quot;age&quot;:25,&quot;lang&quot;:&quot;en&quot;},{&quot;name&quot;:&quot;Henry&quot;,&quot;age&quot;:27,&quot;lang&quot;:&quot;en&quot;},{&quot;name&quot;:&quot;Amed&quot;,&quot;age&quot;:24,&quot;lang&quot;:&quot;fr&quot;}]
+ (1) fellows: {&quot;Jon&quot;:{&quot;age&quot;:25,&quot;lang&quot;:&quot;en&quot;},&quot;Henry&quot;:{&quot;age&quot;:27,&quot;lang&quot;:&quot;en&quot;},&quot;Amed&quot;:{&quot;age&quot;:24,&quot;lang&quot;:&quot;fr&quot;}}
+ (2) Jon: {&quot;age&quot;:25,&quot;lang&quot;:&quot;en&quot;}
+ (3) age: 25
+ (3) lang: en
+ (2) Henry: {&quot;age&quot;:27,&quot;lang&quot;:&quot;en&quot;}
+ (3) age: 27
+ (3) lang: en
+ (2) Amed: {&quot;age&quot;:24,&quot;lang&quot;:&quot;fr&quot;}
+ (3) age: 24
+ (3) lang: fr
diff --git a/src/templating/test4/vg.ref b/src/templating/test4/vg.ref
new file mode 100644
index 000000000..922b0676d
--- /dev/null
+++ b/src/templating/test4/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 121 allocs, 121 frees, 14,608 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test5/.gitignore b/src/templating/test5/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test5/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test5/Makefile b/src/templating/test5/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test5/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test5/json b/src/templating/test5/json
new file mode 100644
index 000000000..6562fb064
--- /dev/null
+++ b/src/templating/test5/json
@@ -0,0 +1,23 @@
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true,
+ "person": false,
+ "repo": [
+ { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
+ { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
+ { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
+ ],
+ "person?": { "name": "Jon" },
+ "special": "----{{extra}}----\n",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/templating/test5/must b/src/templating/test5/must
new file mode 100644
index 000000000..44305df24
--- /dev/null
+++ b/src/templating/test5/must
@@ -0,0 +1,23 @@
+=====================================
+from json
+{{> special}}
+=====================================
+not found
+{{> notfound}}
+=====================================
+without extension first
+{{> must2 }}
+=====================================
+last with extension
+{{> must3 }}
+=====================================
+Ensure must3 didn't change specials
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+%(%#person?%)%
+ Hi %(%name%)%!
+%(%/person?%)%
+
diff --git a/src/templating/test5/must2 b/src/templating/test5/must2
new file mode 100644
index 000000000..d4a1d3783
--- /dev/null
+++ b/src/templating/test5/must2
@@ -0,0 +1,14 @@
+must2 == BEGIN
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+Shown.
+{{#person}}
+ Never shown!
+{{/person}}
+{{^person}}
+ No person
+{{/person}}
+must2 == END
diff --git a/src/templating/test5/must2.mustache b/src/templating/test5/must2.mustache
new file mode 100644
index 000000000..33f1ead38
--- /dev/null
+++ b/src/templating/test5/must2.mustache
@@ -0,0 +1 @@
+must2.mustache ==SHOULD NOT BE SEEN==
diff --git a/src/templating/test5/must3.mustache b/src/templating/test5/must3.mustache
new file mode 100644
index 000000000..67eddb1ef
--- /dev/null
+++ b/src/templating/test5/must3.mustache
@@ -0,0 +1,17 @@
+must3.mustache == BEGIN
+{{#repo}}
+ <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! big comment %)%
+%(%#repo%)%
+ <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
+%(%/repo%)%
+=====================================
+must3.mustache == END
diff --git a/src/templating/test5/resu.ref b/src/templating/test5/resu.ref
new file mode 100644
index 000000000..f2a608568
--- /dev/null
+++ b/src/templating/test5/resu.ref
@@ -0,0 +1,38 @@
+=====================================
+from json
+----3.14159----
+=====================================
+not found
+=====================================
+without extension first
+must2 == BEGIN
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000 dollars, after taxes.
+Shown.
+ No person
+must2 == END
+=====================================
+last with extension
+must3.mustache == BEGIN
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ Hi Jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+must3.mustache == END
+=====================================
+Ensure must3 didn't change specials
+
+ Hi Jon!
+
+%(%#person?%)%
+ Hi %(%name%)%!
+%(%/person?%)%
+
diff --git a/src/templating/test5/vg.ref b/src/templating/test5/vg.ref
new file mode 100644
index 000000000..89dc21bcb
--- /dev/null
+++ b/src/templating/test5/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 123 allocs, 123 frees, 20,610 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test6/.gitignore b/src/templating/test6/.gitignore
new file mode 100644
index 000000000..15e6dd5a5
--- /dev/null
+++ b/src/templating/test6/.gitignore
@@ -0,0 +1,3 @@
+resu.last
+vg.last
+test-custom-write
diff --git a/src/templating/test6/Makefile b/src/templating/test6/Makefile
new file mode 100644
index 000000000..ea4f86e79
--- /dev/null
+++ b/src/templating/test6/Makefile
@@ -0,0 +1,12 @@
+.PHONY: test clean
+
+test-custom-write: test-custom-write.c ../mustach-json-c.h ../mustach-json-c.c ../mustach-wrap.c ../mustach.h ../mustach.c
+ @echo building test-custom-write
+ $(CC) $(CFLAGS) $(LDFLAGS) -g -o test-custom-write test-custom-write.c ../mustach.c ../mustach-json-c.c ../mustach-wrap.c -ljson-c
+
+test: test-custom-write
+ @mustach=./test-custom-write ../dotest.sh json -U must -l must -x must
+
+clean:
+ rm -f resu.last vg.last test-custom-write
+
diff --git a/src/templating/test6/json b/src/templating/test6/json
new file mode 100644
index 000000000..6562fb064
--- /dev/null
+++ b/src/templating/test6/json
@@ -0,0 +1,23 @@
+{
+ "name": "Chris",
+ "value": 10000,
+ "taxed_value": 6000,
+ "in_ca": true,
+ "person": false,
+ "repo": [
+ { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] },
+ { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] },
+ { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] }
+ ],
+ "person?": { "name": "Jon" },
+ "special": "----{{extra}}----\n",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/templating/test6/must b/src/templating/test6/must
new file mode 100644
index 000000000..6df523669
--- /dev/null
+++ b/src/templating/test6/must
@@ -0,0 +1,43 @@
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+Shown.
+{{#person}}
+ Never shown!
+{{/person}}
+{{^person}}
+ No person
+{{/person}}
+
+{{#repo}}
+ <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! gros commentaire %)%
+%(%#repo%)%
+ <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)%
+%(%/repo%)%
+=====================================
+%(%={{ }}=%)%
+ggggggggg
+{{> special}}
+jjjjjjjjj
+end
+
+{{:#sharp}}
+{{:!bang}}
+{{:~tilde}}
+{{:/~0tilde}}
+{{:/~1slash}} see json pointers IETF RFC 6901
+{{:^circ}}
+{{:\=equal}}
+{{::colon}}
+{{:>greater}}
diff --git a/src/templating/test6/resu.ref b/src/templating/test6/resu.ref
new file mode 100644
index 000000000..377eb11af
--- /dev/null
+++ b/src/templating/test6/resu.ref
@@ -0,0 +1,93 @@
+HELLO CHRIS
+YOU HAVE JUST WON 10000 DOLLARS!
+WELL, 6000 DOLLARS, AFTER TAXES.
+SHOWN.
+ NO PERSON
+
+ <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM
+ <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG
+ <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG
+
+ HI JON!
+
+=====================================
+ <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM
+ <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG
+ <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG
+=====================================
+GGGGGGGGG
+----3.14159----
+JJJJJJJJJ
+END
+
+#
+!
+~
+~
+/ SEE JSON POINTERS IETF RFC 6901
+^
+=
+:
+&GT;
+hello chris
+you have just won 10000 dollars!
+well, 6000 dollars, after taxes.
+shown.
+ no person
+
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ hi jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+ggggggggg
+----3.14159----
+jjjjjjjjj
+end
+
+#
+!
+~
+~
+/ see json pointers ietf rfc 6901
+^
+=
+:
+&gt;
+Hello Chris
+You have just won 10000 dollars!
+Well, 6000 dollars, after taxes.
+Shown.
+ No person
+
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+
+ Hi Jon!
+
+=====================================
+ <b>resque</b> reviewers: avrel commiters: joe william
+ <b>hub</b> reviewers: avrel commiters: jack greg
+ <b>rip</b> reviewers: joe jack commiters: greg
+=====================================
+ggggggggg
+----3.14159----
+jjjjjjjjj
+end
+
+#
+!
+~
+~
+/ see json pointers IETF RFC 6901
+^
+=
+:
+&gt;
diff --git a/src/templating/test6/test-custom-write.c b/src/templating/test6/test-custom-write.c
new file mode 100644
index 000000000..20042c1ed
--- /dev/null
+++ b/src/templating/test6/test-custom-write.c
@@ -0,0 +1,149 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+
+ 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.
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <libgen.h>
+
+#include "../mustach-json-c.h"
+
+static const size_t BLOCKSIZE = 8192;
+
+static char *readfile(const char *filename)
+{
+ int f;
+ struct stat s;
+ char *result, *ptr;
+ size_t size, pos;
+ ssize_t rc;
+
+ result = NULL;
+ if (filename[0] == '-' && filename[1] == 0)
+ f = dup(0);
+ else
+ f = open(filename, O_RDONLY);
+ if (f < 0) {
+ fprintf(stderr, "Can't open file: %s\n", filename);
+ exit(1);
+ }
+
+ fstat(f, &s);
+ switch (s.st_mode & S_IFMT) {
+ case S_IFREG:
+ size = s.st_size;
+ break;
+ case S_IFSOCK:
+ case S_IFIFO:
+ size = BLOCKSIZE;
+ break;
+ default:
+ fprintf(stderr, "Bad file: %s\n", filename);
+ exit(1);
+ }
+
+ pos = 0;
+ result = malloc(size + 1);
+ do {
+ if (result == NULL) {
+ fprintf(stderr, "Out of memory\n");
+ exit(1);
+ }
+ rc = read(f, &result[pos], (size - pos) + 1);
+ if (rc < 0) {
+ fprintf(stderr, "Error while reading %s\n", filename);
+ exit(1);
+ }
+ if (rc > 0) {
+ pos += (size_t)rc;
+ if (pos > size) {
+ size = pos + BLOCKSIZE;
+ ptr = realloc(result, size + 1);
+ if (!ptr)
+ free(result);
+ result = ptr;
+ }
+ }
+ } while(rc > 0);
+
+ close(f);
+ result[pos] = 0;
+ return result;
+}
+
+enum { None, Upper, Lower } mode = None;
+
+int uwrite(void *closure, const char *buffer, size_t size)
+{
+ switch(mode) {
+ case None:
+ fwrite(buffer, size, 1, stdout);
+ break;
+ case Upper:
+ while(size--)
+ fputc(toupper(*buffer++), stdout);
+ break;
+ case Lower:
+ while(size--)
+ fputc(tolower(*buffer++), stdout);
+ break;
+ }
+ return 0;
+}
+
+int main(int ac, char **av)
+{
+ struct json_object *o;
+ char *t;
+ char *prog = *av;
+ int s;
+
+ if (*++av) {
+ o = json_object_from_file(av[0]);
+ if (o == NULL) {
+ fprintf(stderr, "Aborted: null json (file %s)\n", av[0]);
+ exit(1);
+ }
+ while(*++av) {
+ if (!strcmp(*av, "-U"))
+ mode = Upper;
+ else if (!strcmp(*av, "-l"))
+ mode = Lower;
+ else if (!strcmp(*av, "-x"))
+ mode = None;
+ else {
+ t = readfile(*av);
+ s = mustach_json_c_write(t, 0, o, Mustach_With_AllExtensions, uwrite, NULL);
+ if (s != 0)
+ fprintf(stderr, "Template error %d\n", s);
+ free(t);
+ }
+ }
+ json_object_put(o);
+ }
+ return 0;
+}
+
diff --git a/src/templating/test6/vg.ref b/src/templating/test6/vg.ref
new file mode 100644
index 000000000..fb0e31bd8
--- /dev/null
+++ b/src/templating/test6/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ./test-custom-write json -U must -l must -x must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 174 allocs, 174 frees, 24,250 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test7/Makefile b/src/templating/test7/Makefile
new file mode 100644
index 000000000..8e3a3b990
--- /dev/null
+++ b/src/templating/test7/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json base.mustache
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test7/base.mustache b/src/templating/test7/base.mustache
new file mode 100644
index 000000000..f701e0c65
--- /dev/null
+++ b/src/templating/test7/base.mustache
@@ -0,0 +1,2 @@
+family:
+{{> node}}
diff --git a/src/templating/test7/json b/src/templating/test7/json
new file mode 100644
index 000000000..c9ee47150
--- /dev/null
+++ b/src/templating/test7/json
@@ -0,0 +1,8 @@
+{ "data": "grandparent", "children": [
+ { "data": "parent", "children": [
+ { "data": "child", "children": [] }
+ ]},
+ { "data": "parent2", "children": [
+ { "data": "child2", "children": [ { "data": "pet", "children": false } ]}
+ ]}
+]}
diff --git a/src/templating/test7/node.mustache b/src/templating/test7/node.mustache
new file mode 100644
index 000000000..4154b12ba
--- /dev/null
+++ b/src/templating/test7/node.mustache
@@ -0,0 +1,4 @@
+<{{data}}>
+{{#children}}
+ {{> node}}
+{{/children}}
diff --git a/src/templating/test7/resu.ref b/src/templating/test7/resu.ref
new file mode 100644
index 000000000..d02b24e11
--- /dev/null
+++ b/src/templating/test7/resu.ref
@@ -0,0 +1,7 @@
+family:
+<grandparent>
+ <parent>
+ <child>
+ <parent2>
+ <child2>
+ <pet>
diff --git a/src/templating/test7/vg.ref b/src/templating/test7/vg.ref
new file mode 100644
index 000000000..032e6c447
--- /dev/null
+++ b/src/templating/test7/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json base.mustache
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 69 allocs, 69 frees, 36,313 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test8/.gitignore b/src/templating/test8/.gitignore
new file mode 100644
index 000000000..4d897daa0
--- /dev/null
+++ b/src/templating/test8/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test8/Makefile b/src/templating/test8/Makefile
new file mode 100644
index 000000000..1a3e57914
--- /dev/null
+++ b/src/templating/test8/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test clean
+
+test:
+ @../dotest.sh json must
+
+clean:
+ rm -f resu.last vg.last
+
diff --git a/src/templating/test8/json b/src/templating/test8/json
new file mode 100644
index 000000000..04a1e4aaa
--- /dev/null
+++ b/src/templating/test8/json
@@ -0,0 +1,8 @@
+{
+"val1": "",
+"val2": 0,
+"val3": false,
+"val4": null,
+"val5": [],
+"val6": 0.0
+}
diff --git a/src/templating/test8/must b/src/templating/test8/must
new file mode 100644
index 000000000..a22374438
--- /dev/null
+++ b/src/templating/test8/must
@@ -0,0 +1,6 @@
+x{{#val1}} {{.}} {{/val1}}x
+x{{#val2}} {{.}} {{/val2}}x
+x{{#val3}} {{.}} {{/val3}}x
+x{{#val4}} {{.}} {{/val4}}x
+x{{#val5}} {{.}} {{/val5}}x
+x{{#val6}} {{.}} {{/val6}}x
diff --git a/src/templating/test8/resu.ref b/src/templating/test8/resu.ref
new file mode 100644
index 000000000..e0c16e49a
--- /dev/null
+++ b/src/templating/test8/resu.ref
@@ -0,0 +1,6 @@
+xx
+xx
+xx
+xx
+xx
+xx
diff --git a/src/templating/test8/vg.ref b/src/templating/test8/vg.ref
new file mode 100644
index 000000000..b5b0235f9
--- /dev/null
+++ b/src/templating/test8/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
+Command: ../mustach json must
+
+
+HEAP SUMMARY:
+ in use at exit: 0 bytes in 0 blocks
+ total heap usage: 17 allocs, 17 frees, 4,832 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For lists of detected and suppressed errors, rerun with: -s
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/templating/test_mustach_jansson.c b/src/templating/test_mustach_jansson.c
new file mode 100644
index 000000000..beb155f6d
--- /dev/null
+++ b/src/templating/test_mustach_jansson.c
@@ -0,0 +1,125 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 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_mustach_jansson.c
+ * @brief testcase to test the mustach/jansson integration
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include "mustach-jansson.h"
+#include <gnunet/gnunet_util_lib.h>
+
+static void
+assert_template (const char *template,
+ json_t *root,
+ const char *expected)
+{
+ char *r;
+ size_t sz;
+
+ GNUNET_assert (0 == mustach_jansson_mem (template,
+ 0,
+ root,
+ Mustach_With_AllExtensions,
+ &r,
+ &sz));
+ GNUNET_assert (0 == strcmp (r,
+ expected));
+ GNUNET_free (r);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ json_t *root = json_object ();
+ json_t *arr = json_array ();
+ json_t *obj = json_object ();
+ /* test 1 */
+ const char *t1 = "hello world";
+ const char *x1 = "hello world";
+ /* test 2 */
+ const char *t2 = "hello {{ v1 }}";
+ const char *x2 = "hello world";
+ /* test 3 */
+ const char *t3 = "hello {{ v3.x }}";
+ const char *x3 = "hello baz";
+ /* test 4 */
+ const char *t4 = "hello {{# v2 }}{{ . }}{{/ v2 }}";
+ const char *x4 = "hello foobar";
+ /* test 5 */
+ const char *t5 = "hello {{# v3 }}{{ y }}/{{ x }}{{ z }}{{/ v3 }}";
+ const char *x5 = "hello quux/baz";
+ /* test 8 */
+ const char *t8 = "{{^ v4 }}fallback{{/ v4 }}";
+ const char *x8 = "fallback";
+
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-mustach-jansson",
+ "INFO",
+ NULL);
+ GNUNET_assert (NULL != root);
+ GNUNET_assert (NULL != arr);
+ GNUNET_assert (NULL != obj);
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v1",
+ json_string ("world")));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v4",
+ json_array ()));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string ("foo")));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string ("bar")));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v2",
+ arr));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "v3",
+ obj));
+ GNUNET_assert (0 ==
+ json_object_set_new (root,
+ "amt",
+ json_string ("EUR:123.00")));
+ GNUNET_assert (0 ==
+ json_object_set_new (obj,
+ "x",
+ json_string ("baz")));
+ GNUNET_assert (0 ==
+ json_object_set_new (obj,
+ "y",
+ json_string ("quux")));
+ assert_template (t1, root, x1);
+ assert_template (t2, root, x2);
+ assert_template (t3, root, x3);
+ assert_template (t4, root, x4);
+ assert_template (t5, root, x5);
+ assert_template (t8, root, x8);
+ json_decref (root);
+ return 0;
+}