aboutsummaryrefslogtreecommitdiff
path: root/src/mustach
diff options
context:
space:
mode:
Diffstat (limited to 'src/mustach')
-rw-r--r--src/mustach/AUTHORS23
-rw-r--r--src/mustach/LICENSE-2.0.txt202
-rw-r--r--src/mustach/Makefile.am19
-rw-r--r--src/mustach/Makefile.orig66
-rw-r--r--src/mustach/ORIGIN9
-rw-r--r--src/mustach/README.md214
-rw-r--r--src/mustach/meson.build12
-rw-r--r--src/mustach/mustach-json-c.c526
-rw-r--r--src/mustach/mustach-json-c.h78
-rw-r--r--src/mustach/mustach-tool.c155
-rw-r--r--src/mustach/mustach.c472
-rw-r--r--src/mustach/mustach.h241
-rwxr-xr-xsrc/mustach/run-original-tests.sh10
-rw-r--r--src/mustach/test1/.gitignore2
-rw-r--r--src/mustach/test1/json23
-rw-r--r--src/mustach/test1/must43
-rw-r--r--src/mustach/test1/resu.ref49
-rw-r--r--src/mustach/test1/vg.ref14
-rw-r--r--src/mustach/test2/.gitignore2
-rw-r--r--src/mustach/test2/json9
-rw-r--r--src/mustach/test2/must17
-rw-r--r--src/mustach/test2/resu.ref22
-rw-r--r--src/mustach/test2/vg.ref14
-rw-r--r--src/mustach/test3/.gitignore2
-rw-r--r--src/mustach/test3/json7
-rw-r--r--src/mustach/test3/must15
-rw-r--r--src/mustach/test3/resu.ref15
-rw-r--r--src/mustach/test3/vg.ref14
-rw-r--r--src/mustach/test4/.gitignore2
-rw-r--r--src/mustach/test4/json13
-rw-r--r--src/mustach/test4/must58
-rw-r--r--src/mustach/test4/resu.ref100
-rw-r--r--src/mustach/test4/vg.ref14
-rw-r--r--src/mustach/test5/.gitignore2
-rw-r--r--src/mustach/test5/json23
-rw-r--r--src/mustach/test5/must23
-rw-r--r--src/mustach/test5/must214
-rw-r--r--src/mustach/test5/must2.mustache1
-rw-r--r--src/mustach/test5/must3.mustache17
-rw-r--r--src/mustach/test5/resu.ref60
-rw-r--r--src/mustach/test5/special1
-rw-r--r--src/mustach/test5/special.mustache1
-rw-r--r--src/mustach/test5/vg.ref14
-rw-r--r--src/mustach/test6/.gitignore3
-rw-r--r--src/mustach/test6/json23
-rw-r--r--src/mustach/test6/must43
-rw-r--r--src/mustach/test6/resu.ref147
-rw-r--r--src/mustach/test6/vg.ref14
48 files changed, 2848 insertions, 0 deletions
diff --git a/src/mustach/AUTHORS b/src/mustach/AUTHORS
new file mode 100644
index 00000000..2fcc6043
--- /dev/null
+++ b/src/mustach/AUTHORS
@@ -0,0 +1,23 @@
+Main author:
+ José Bollo <jobol@nonadev.net>
+
+Contributors:
+ Abhishek Mishra
+ Atlas
+ Harold L Marzan
+ Lailton Fernando Mariano
+ Sami Kerola
+ Sijmen J. Mulder
+ Tomasz Sieprawski
+
+Packagers:
+ pkgsrc: Sijmen J. Mulder
+ alpine linux: Lucas Ramage
+
+Thanks to issue submitters:
+ Dante Torres
+ @fabbe
+ Johann Oskarsson
+ Mark Bucciarelli
+ Paul Wisehart
+ Thierry Fournier
diff --git a/src/mustach/LICENSE-2.0.txt b/src/mustach/LICENSE-2.0.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/src/mustach/LICENSE-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/src/mustach/Makefile.am b/src/mustach/Makefile.am
new file mode 100644
index 00000000..a39a28d0
--- /dev/null
+++ b/src/mustach/Makefile.am
@@ -0,0 +1,19 @@
+# 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
+
+
+lib_LIBRARIES = \
+ libmustach.a
+
+libmustach_a_SOURCES = \
+ mustach.c mustach.h
+
+check_SCRIPTS = \
+ run-original-tests.sh
+
+TESTS = $(check_SCRIPTS)
diff --git a/src/mustach/Makefile.orig b/src/mustach/Makefile.orig
new file mode 100644
index 00000000..3e0fd17d
--- /dev/null
+++ b/src/mustach/Makefile.orig
@@ -0,0 +1,66 @@
+DESTDIR ?=
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+LIBDIR ?= $(PREFIX)/lib
+INCLUDEDIR ?= $(PREFIX)/include
+SOVER = .0
+SOVEREV = .0.99
+
+CFLAGS += -fPIC -Wall -Wextra
+LDLIBS += -ljson-c
+
+lib_OBJ = mustach.o mustach-json-c.o
+tool_OBJ = mustach.o mustach-json-c.o mustach-tool.o
+HEADERS = mustach.h mustach-json-c.h
+
+lib_LDFLAGS += -shared
+ifeq ($(shell uname),Darwin)
+ lib_LDFLAGS += -install_name $(LIBDIR)/libmustach.so$(SOVEREV)
+endif
+
+all: mustach libmustach.so$(SOVEREV)
+
+install: all
+ install -d $(DESTDIR)$(BINDIR)
+ install -d $(DESTDIR)$(LIBDIR)
+ install -d $(DESTDIR)$(INCLUDEDIR)/mustach
+ install -m0755 mustach $(DESTDIR)$(BINDIR)/
+ install -m0644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/mustach
+ install -m0755 libmustach.so* $(DESTDIR)$(LIBDIR)/
+ ln -sf libmustach.so$(SOVEREV) $(DESTDIR)$(LIBDIR)/libmustach.so$(SOVER)
+ ln -sf libmustach.so$(SOVEREV) $(DESTDIR)$(LIBDIR)/libmustach.so
+
+
+uninstall:
+ rm -f $(DESTDIR)$(BINDIR)/mustach
+ rm -f $(DESTDIR)$(LIBDIR)/libmustach.so*
+ rm -rf $(DESTDIR)$(INCLUDEDIR)/mustach
+
+mustach: $(tool_OBJ)
+ $(CC) $(LDFLAGS) -o mustach $(tool_OBJ) $(LDLIBS)
+
+libmustach.so$(SOVEREV): $(lib_OBJ)
+ $(CC) $(LDFLAGS) $(lib_LDFLAGS) -o libmustach.so$(SOVEREV) $(lib_OBJ) $(LDLIBS)
+
+mustach.o: mustach.h
+mustach-json.o: mustach.h mustach-json-c.h
+mustach-tool.o: mustach.h mustach-json-c.h
+
+test: 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
+
+clean:
+ rm -f mustach libmustach.so$(SOVEREV) *.o
+ @$(MAKE) -C test1 clean
+ @$(MAKE) -C test2 clean
+ @$(MAKE) -C test3 clean
+ @$(MAKE) -C test4 clean
+ @$(MAKE) -C test5 clean
+ @$(MAKE) -C test6 clean
+
+.PHONY: test clean install uninstall
diff --git a/src/mustach/ORIGIN b/src/mustach/ORIGIN
new file mode 100644
index 00000000..fafb0ae7
--- /dev/null
+++ b/src/mustach/ORIGIN
@@ -0,0 +1,9 @@
+Cloned originally from https://gitlab.com/jobol/mustach/
+
+Changes:
+========
+
+Renamed original Makefile to Makefile.orig and wrote Makefile.am for us.
+
+Added run-original-tests.sh shell script as a wrapper around Makefile.org
+to us the original build process for the test suite.
diff --git a/src/mustach/README.md b/src/mustach/README.md
new file mode 100644
index 00000000..1ad57262
--- /dev/null
+++ b/src/mustach/README.md
@@ -0,0 +1,214 @@
+# Introduction to Mustach 0.99
+
+`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 best way to use mustach is to copy the files **mustach.h** and **mustach.c**
+directly into your project and use it.
+
+Alternatively, make and meson files are provided for building `mustach` and
+`libmustach.so` shared library.
+
+## 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
+
+## 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-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 wrapper
+- **mustach-tool.c** simple tool for applying template files to a JSON file
+
+The file **mustach-json-c.c** is the main example of use of **mustach** 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).
+
+HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...).
+
+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
+prefered compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**.
+Example:
+
+ gcc -DNO_OPEN_MEMSTREAM
+
+### Integration
+
+The file **mustach.h** is 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`.
+
+### Extensions
+
+By default, the current implementation provides the following extensions:
+
+#### Explicit Substitution
+
+This is a core extension implemented in file **mustach.c**.
+
+In somecases the name of the key used for substition 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.
+
+#### Value Testing and Comparing
+
+This are a tool extension implemented in file **mustach-json-c.c**.
+
+These extensions allows you to test the value of the selected key.
+They allow to write `key=value` (matching test) or `key=!value`
+(not matching test) in any query.
+
+The specific comparison extension also allows to compare if greater,
+lesser, etc.. than a value. It allows to write `key>value`.
+
+It the comparator sign appears in the first column it is ignored
+as if it was escaped.
+
+#### Access to current value
+
+The value of the current field can be accessed using single dot like
+in `{{#key}}{{.}}{{/key}}` that applied to `{"key":3.14}` produces `3.14`
+and `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces
+` 1 2`.
+
+#### Iteration on objects
+
+Using the pattern `{{#X.*}}...{{/X.*}}` it is possible 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.
+
+### Removing Extensions
+
+When compiling mustach.c or mustach-json-c.c,
+extensions can be removed by defining macros
+using option -D.
+
+The possible macros are of 3 categories, the global,
+the mustach core specific and the mustach-json-c example
+of implementation specific.
+
+#### Global macros
+
+- `NO_EXTENSION_FOR_MUSTACH`
+
+ This macro disables any current or future
+ extensions for the core or the example.
+
+#### Macros for the core mustach engine (mustach.c)
+
+- `NO_COLON_EXTENSION_FOR_MUSTACH`
+
+ This macro remove the ability to use colon (:)
+ as explicit command for variable substituion.
+ This extension allows to have name starting
+ with one of the mustach character `:#^/&{=>`
+
+- `NO_ALLOW_EMPTY_TAG`
+
+ Generate the error MUSTACH_ERROR_EMPTY_TAG automatically
+ when an empty tag is encountered.
+
+#### Macros for the implementation example (mustach-json-c.c)
+
+- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH`
+
+ This macro allows the program to check whether
+ the actual value is equal to an expected value.
+ This is useful in `{{#key=val}}` or `{{^key=val}}`
+ with the corresponding `{{/key=val}}`.
+ It can also be used in `{{key=val}}` but this
+ doesn't seem to be useful.
+
+- `NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH`
+
+ This macro allows the program to compare the actual
+ value with an expected value. The comparison operators
+ are `=`, `>`, `<`, `>=`, `<=`. The meaning of the
+ comparison numeric or alphabetic depends on the type
+ of the inspected value. Also the result of the comparison
+ can be inverted if the value starts with `!`.
+ Example of use: `{{key>=val}}`, or `{{#key>=val}}` and
+ `{{^key>=val}}` with their matching `{{/key>=val}}`.
+
+- `NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH`
+
+ This macro fordids automatic escaping of coparison
+ sign appearing at first column.
+
+- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`
+
+ This macro removes the possible use of JSON pointers.
+ JSON pointers are defined in IETF RFC 6901.
+ If not set, any key starting with "/" is a JSON pointer.
+ This implies to use the colon to introduce keys.
+ So `NO_COLON_EXTENSION_FOR_MUSTACH` implies
+ `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`.
+ A special escaping is used for `=`, `<`, `>` signs when
+ values comparisons are enabled: `~=` gives `=` in the key.
+
+- `NO_OBJECT_ITERATION_FOR_MUSTACH`
+
+ Disable the object iteration extension. That extension allows
+ to iterate over the keys of an object. The iteration on object
+ is selected by using the selector `{{#key.*}}`. In the context
+ of iterating over object keys, the single key `{{*}}` returns the
+ key and `{{.}}` returns the value.
+
+- `NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH`
+
+ Disable access to current object value using single dot
+ like in `{{.}}`.
+
+- `NO_INCLUDE_PARTIAL_FALLBACK`
+
+ Disable include of file by partial pattern like `{{> name}}`.
+ By default if a such pattern is found, **mustach** search
+ for `name` in the current json context. This what is done
+ historically and when `NO_INCLUDE_PARTIAL_FALLBACK` is defined.
+ When `NO_INCLUDE_PARTIAL_FALLBACK` is defined, if the value is
+ found in the json context, the files `name` and `name.mustache`
+ are searched in that order and the first file found is used
+ as partial content. The macro `INCLUDE_PARTIAL_EXTENSION` can
+ be use for changing the extension added.
diff --git a/src/mustach/meson.build b/src/mustach/meson.build
new file mode 100644
index 00000000..c7ecc8df
--- /dev/null
+++ b/src/mustach/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/mustach/mustach-json-c.c b/src/mustach/mustach-json-c.c
new file mode 100644
index 00000000..df901dcd
--- /dev/null
+++ b/src/mustach/mustach-json-c.c
@@ -0,0 +1,526 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <string.h>
+#ifdef _WIN32
+#include <malloc.h>
+#endif
+#ifdef __sun
+# include <alloca.h>
+#endif
+
+#include "mustach.h"
+#include "mustach-json-c.h"
+
+#if defined(NO_EXTENSION_FOR_MUSTACH)
+# undef NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH
+# define NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH
+# undef NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH
+# define NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH
+# undef NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH
+# define NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH
+# undef NO_JSON_POINTER_EXTENSION_FOR_MUSTACH
+# define NO_JSON_POINTER_EXTENSION_FOR_MUSTACH
+# undef NO_OBJECT_ITERATION_FOR_MUSTACH
+# define NO_OBJECT_ITERATION_FOR_MUSTACH
+# undef NO_INCLUDE_PARTIAL_FALLBACK
+# define NO_INCLUDE_PARTIAL_FALLBACK
+#endif
+
+#if !defined(NO_INCLUDE_PARTIAL_FALLBACK) \
+ && !defined(INCLUDE_PARTIAL_EXTENSION)
+# define INCLUDE_PARTIAL_EXTENSION ".mustache"
+#endif
+
+#if !defined(NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH)
+# undef NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH
+#endif
+
+struct expl {
+ struct json_object *root;
+ mustach_json_c_write_cb writecb;
+ int depth;
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ int found_objiter;
+#endif
+ struct {
+ struct json_object *cont;
+ struct json_object *obj;
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ struct json_object_iterator biter, eiter;
+ int is_objiter;
+#endif
+ int index, count;
+ } stack[MUSTACH_MAX_DEPTH];
+};
+
+enum comp {
+ C_no = 0,
+ C_eq = 1,
+ C_lt = 5,
+ C_le = 6,
+ C_gt = 9,
+ C_ge = 10
+};
+
+#if !defined(NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH)
+static enum comp getcomp(char *head)
+{
+ return head[0] == '=' ? C_eq
+#if !defined(NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH)
+ : head[0] == '<' ? (head[1] == '=' ? C_le : C_lt)
+ : head[0] == '>' ? (head[1] == '=' ? C_ge : C_gt)
+#endif
+ : C_no;
+}
+
+static char *keyval(char *head, int isptr, enum comp *comp)
+{
+ char *w, c, s;
+ enum comp k;
+
+ k = C_no;
+#if !defined(NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH)
+ s = getcomp(head) != C_no;
+#else
+ s = 0;
+#endif
+ c = *(w = head);
+ while (c && (s || (k = getcomp(head)) == C_no)) {
+ if (s)
+ s = 0;
+ else
+ s = (isptr ? c == '~' : c == '\\')
+ && (getcomp(head + 1) != C_no);
+ if (!s)
+ *w++ = c;
+ c = *++head;
+ }
+ *w = 0;
+ *comp = k;
+ return k == C_no ? NULL : &head[k & 3];
+}
+
+static int compare(struct json_object *o, const char *value)
+{
+ switch (json_object_get_type(o)) {
+ case json_type_double:
+ return json_object_get_double(o) - atof(value);
+ case json_type_int:
+ return json_object_get_int64(o) - (int64_t)atoll(value);
+ default:
+ return strcmp(json_object_get_string(o), value);
+ }
+}
+
+static int evalcomp(struct json_object *o, char *value, enum comp k)
+{
+ int r, c;
+
+ c = compare(o, value);
+ switch (k) {
+ case C_eq: r = c == 0; break;
+#if !defined(NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH)
+ case C_lt: r = c < 0; break;
+ case C_le: r = c <= 0; break;
+ case C_gt: r = c > 0; break;
+ case C_ge: r = c >= 0; break;
+#endif
+ default: r = 0; break;
+ }
+ return r;
+}
+#else
+static inline char *keyval(char *head, int isptr, enum comp *comp)
+{
+ *comp = C_no;
+ return NULL;
+}
+static inline int compare(struct json_object *o, char *value, enum comp k)
+{
+ return 0;
+}
+#endif
+
+static char *key(char **head, int isptr)
+{
+ char *r, *i, *w, c;
+
+ c = *(i = *head);
+ if (!c)
+ r = NULL;
+ else {
+ r = w = i;
+#if !defined(NO_JSON_POINTER_EXTENSION_FOR_MUSTACH)
+ if (isptr)
+ while (c && c != '/') {
+ if (c == '~')
+ switch (i[1]) {
+ case '1': c = '/'; /*@fallthrough@*/
+ case '0': i++;
+ }
+ *w++ = c;
+ c = *++i;
+ }
+ else
+#endif
+ while (c && c != '.') {
+ if (c == '\\' && (i[1] == '.' || i[1] == '\\'))
+ c = *++i;
+ *w++ = c;
+ c = *++i;
+ }
+ *w = 0;
+ *head = i + !!c;
+ }
+ return r;
+}
+
+static struct json_object *find(struct expl *e, const char *name)
+{
+ int i, isptr;
+ struct json_object *o, *no;
+ char *n, *c, *v;
+ enum comp k;
+
+ n = alloca(1 + strlen(name));
+ strcpy(n, name);
+ isptr = 0;
+#if !defined(NO_JSON_POINTER_EXTENSION_FOR_MUSTACH)
+ isptr = n[0] == '/';
+ n += isptr;
+#endif
+
+ v = keyval(n, isptr, &k);
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ e->found_objiter = 0;
+#endif
+#if !defined(NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH)
+ if (n[0] == '.' && !n[1]) {
+ /* case of . alone */
+ o = e->stack[e->depth].obj;
+ } else
+#endif
+ {
+ c = key(&n, isptr);
+ if (c == NULL)
+ return NULL;
+ o = NULL;
+ i = e->depth;
+ while (i >= 0 && !json_object_object_get_ex(e->stack[i].obj, c, &o))
+ i--;
+ if (i < 0) {
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ o = e->stack[e->depth].obj;
+ if (c[0] == '*' && !c[1] && !v && !key(&n, isptr) && json_object_is_type(o, json_type_object)) {
+ e->found_objiter = 1;
+ return o;
+ }
+#endif
+ return NULL;
+ }
+ c = key(&n, isptr);
+ while(c) {
+ if (!json_object_object_get_ex(o, c, &no)) {
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ if (c[0] == '*' && !c[1] && !v && !key(&n, isptr) && json_object_is_type(o, json_type_object)) {
+ e->found_objiter = 1;
+ return o;
+ }
+#endif
+ return NULL;
+ }
+ o = no;
+ c = key(&n, isptr);
+ }
+ }
+ if (v) {
+ i = v[0] == '!';
+ if (i == evalcomp(o, &v[i], k))
+ o = NULL;
+ }
+ return o;
+}
+
+static int start(void *closure)
+{
+ struct expl *e = closure;
+ e->depth = 0;
+ 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 write(struct expl *e, const char *buffer, size_t size, FILE *file)
+{
+ return e->writecb(file, buffer, size);
+}
+
+static int emituw(void *closure, const char *buffer, size_t size, int escape, FILE *file)
+{
+ struct expl *e = closure;
+ if (!escape)
+ write(e, buffer, size, file);
+ else
+ do {
+ switch(*buffer) {
+ case '<': write(e, "&lt;", 4, file); break;
+ case '>': write(e, "&gt;", 4, file); break;
+ case '&': write(e, "&amp;", 5, file); break;
+ default: write(e, buffer, 1, file); break;
+ }
+ buffer++;
+ } while(--size);
+ return MUSTACH_OK;
+}
+
+static const char *item(struct expl *e, const char *name)
+{
+ struct json_object *o;
+ const char *s;
+
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ if (name[0] == '*' && !name[1] && e->stack[e->depth].is_objiter)
+ s = json_object_iter_peek_name(&e->stack[e->depth].biter);
+ else
+ s = (o = find(e, name)) && !e->found_objiter ? json_object_get_string(o) : NULL;
+#else
+ s = (o = find(e, name)) ? json_object_get_string(o) : NULL;
+#endif
+ return s;
+}
+
+static int enter(void *closure, const char *name)
+{
+ struct expl *e = closure;
+ struct json_object *o = find(e, name);
+ if (++e->depth >= MUSTACH_MAX_DEPTH)
+ return MUSTACH_ERROR_TOO_DEEP;
+ 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) {
+ e->depth--;
+ return 0;
+ }
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].obj = json_object_array_get_idx(o, 0);
+ e->stack[e->depth].index = 0;
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ e->stack[e->depth].is_objiter = 0;
+ } else if (json_object_is_type(o, json_type_object) && e->found_objiter) {
+ e->stack[e->depth].biter = json_object_iter_begin(o);
+ e->stack[e->depth].eiter = json_object_iter_end(o);
+ if (json_object_iter_equal(&e->stack[e->depth].biter, &e->stack[e->depth].eiter)) {
+ e->depth--;
+ return 0;
+ }
+ e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].biter);
+ e->stack[e->depth].cont = o;
+ e->stack[e->depth].is_objiter = 1;
+#endif
+ } else if (json_object_is_type(o, json_type_object) || 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;
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ e->stack[e->depth].is_objiter = 0;
+#endif
+ } else {
+ e->depth--;
+ return 0;
+ }
+ return 1;
+}
+
+static int next(void *closure)
+{
+ struct expl *e = closure;
+ if (e->depth <= 0)
+ return MUSTACH_ERROR_CLOSING;
+#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH)
+ if (e->stack[e->depth].is_objiter) {
+ json_object_iter_next(&e->stack[e->depth].biter);
+ if (json_object_iter_equal(&e->stack[e->depth].biter, &e->stack[e->depth].eiter))
+ return 0;
+ e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].biter);
+ return 1;
+ }
+#endif
+ 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;
+}
+
+#if !defined(NO_INCLUDE_PARTIAL_FALLBACK)
+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) {
+ /* 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;
+}
+
+static int partial(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct expl *e = closure;
+ const char *s;
+
+ s = item(e, name);
+ if (s)
+ sbuf->value = s;
+ else if (get_partial_from_file(name, sbuf) < 0)
+ sbuf->value = "";
+ return MUSTACH_OK;
+}
+#endif
+
+static int get(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct expl *e = closure;
+ const char *s;
+
+ s = item(e, name);
+ if (s)
+ sbuf->value = s;
+ else
+ sbuf->value = "";
+ return MUSTACH_OK;
+}
+
+static struct mustach_itf itf = {
+ .start = start,
+ .put = NULL,
+ .enter = enter,
+ .next = next,
+ .leave = leave,
+#if !defined(NO_INCLUDE_PARTIAL_FALLBACK)
+ .partial = partial,
+#else
+ .partial =NULL,
+#endif
+ .get = get,
+ .emit = NULL,
+ .stop = NULL
+};
+
+static struct mustach_itf itfuw = {
+ .start = start,
+ .put = NULL,
+ .enter = enter,
+ .next = next,
+ .leave = leave,
+#if !defined(NO_INCLUDE_PARTIAL_FALLBACK)
+ .partial = partial,
+#else
+ .partial =NULL,
+#endif
+ .get = get,
+ .emit = emituw,
+ .stop = NULL
+};
+
+int fmustach_json_c(const char *template, struct json_object *root, FILE *file)
+{
+ struct expl e;
+ e.root = root;
+ return fmustach(template, &itf, &e, file);
+}
+
+int fdmustach_json_c(const char *template, struct json_object *root, int fd)
+{
+ struct expl e;
+ e.root = root;
+ return fdmustach(template, &itf, &e, fd);
+}
+
+int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size)
+{
+ struct expl e;
+ e.root = root;
+ e.writecb = NULL;
+ return mustach(template, &itf, &e, result, size);
+}
+
+int umustach_json_c(const char *template, struct json_object *root, mustach_json_c_write_cb writecb, void *closure)
+{
+ struct expl e;
+ e.root = root;
+ e.writecb = writecb;
+ return fmustach(template, &itfuw, &e, closure);
+}
+
diff --git a/src/mustach/mustach-json-c.h b/src/mustach/mustach-json-c.h
new file mode 100644
index 00000000..3dfa228b
--- /dev/null
+++ b/src/mustach/mustach-json-c.h
@@ -0,0 +1,78 @@
+/*
+ Author: José Bollo <jose.bollo@iot.bzh>
+ 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 _mustach_json_c_h_included_
+#define _mustach_json_c_h_included_
+
+#include <json-c/json.h>
+
+/**
+ * fmustach_json_c - Renders the mustache 'template' in 'file' for 'root'.
+ *
+ * @template: the template string to instanciate
+ * @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 fmustach_json_c(const char *template, struct json_object *root, FILE *file);
+
+/**
+ * fmustach_json_c - Renders the mustache 'template' in 'fd' for 'root'.
+ *
+ * @template: the template string to instanciate
+ * @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 fdmustach_json_c(const char *template, struct json_object *root, int fd);
+
+
+/**
+ * fmustach_json_c - Renders the mustache 'template' in 'result' for 'root'.
+ *
+ * @template: the template string to instanciate
+ * @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(const char *template, struct json_object *root, char **result, size_t *size);
+
+/**
+ * umustach_json_c - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'.
+ *
+ * @template: the template string to instanciate
+ * @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 int (*mustach_json_c_write_cb)(void*closure, const char*buffer, size_t size);
+extern int umustach_json_c(const char *template, struct json_object *root, mustach_json_c_write_cb writecb, void *closure);
+
+#endif
+
diff --git a/src/mustach/mustach-tool.c b/src/mustach/mustach-tool.c
new file mode 100644
index 00000000..364e34a8
--- /dev/null
+++ b/src/mustach/mustach-tool.c
@@ -0,0 +1,155 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <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 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"
+};
+
+static void help(char *prog)
+{
+ printf("usage: %s json-file mustach-templates...\n", basename(prog));
+ exit(0);
+}
+
+static char *readfile(const char *filename)
+{
+ 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);
+ result[pos] = 0;
+ return result;
+}
+
+int main(int ac, char **av)
+{
+ struct json_object *o;
+ char *t;
+ char *prog = *av;
+ int s;
+
+ (void)ac; /* unused */
+
+ if (*++av) {
+ if (!strcmp(*av, "-h") || !strcmp(*av, "--help"))
+ help(prog);
+ if (av[0][0] == '-' && !av[0][1])
+ o = json_object_from_fd(0);
+ else
+ o = json_object_from_file(av[0]);
+#if JSON_C_VERSION_NUM >= 0x000D00
+ if (json_util_get_last_err() != NULL) {
+ fprintf(stderr, "Bad json: %s (file %s)\n", json_util_get_last_err(), av[0]);
+ exit(1);
+ }
+ else
+#endif
+ if (o == NULL) {
+ fprintf(stderr, "Aborted: null json (file %s)\n", av[0]);
+ exit(1);
+ }
+ while(*++av) {
+ t = readfile(*av);
+ s = fmustach_json_c(t, o, stdout);
+ if (s != 0) {
+ s = -s;
+ if (s < 1 || s >= (int)(sizeof errors / sizeof * errors))
+ s = 0;
+ fprintf(stderr, "Template error %s (file %s)\n", errors[s], *av);
+ }
+ free(t);
+ }
+ json_object_put(o);
+ }
+ return 0;
+}
+
diff --git a/src/mustach/mustach.c b/src/mustach/mustach.c
new file mode 100644
index 00000000..caa80dcc
--- /dev/null
+++ b/src/mustach/mustach.c
@@ -0,0 +1,472 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef _WIN32
+#include <malloc.h>
+#endif
+#ifdef __sun
+# include <alloca.h>
+#endif
+
+#include "mustach.h"
+
+#if defined(NO_EXTENSION_FOR_MUSTACH)
+# undef NO_COLON_EXTENSION_FOR_MUSTACH
+# define NO_COLON_EXTENSION_FOR_MUSTACH
+# undef NO_ALLOW_EMPTY_TAG
+# define NO_ALLOW_EMPTY_TAG
+#endif
+
+struct iwrap {
+ int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file);
+ void *closure; /* closure for: enter, next, leave, emit, get */
+ int (*put)(void *closure, const char *name, int escape, FILE *file);
+ void *closure_put; /* closure for put */
+ int (*enter)(void *closure, const char *name);
+ int (*next)(void *closure);
+ int (*leave)(void *closure);
+ int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+ void *closure_partial; /* closure for partial */
+};
+
+#if !defined(NO_OPEN_MEMSTREAM)
+static FILE *memfile_open(char **buffer, size_t *size)
+{
+ return open_memstream(buffer, size);
+}
+static void memfile_abort(FILE *file, char **buffer, size_t *size)
+{
+ fclose(file);
+ free(*buffer);
+ *buffer = NULL;
+ *size = 0;
+}
+static int memfile_close(FILE *file, char **buffer, size_t *size)
+{
+ int rc;
+
+ /* adds terminating null */
+ rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
+ fclose(file);
+ if (rc == 0)
+ /* removes terminating null of the length */
+ (*size)--;
+ else {
+ free(*buffer);
+ *buffer = NULL;
+ *size = 0;
+ }
+ return rc;
+}
+#else
+static FILE *memfile_open(char **buffer, size_t *size)
+{
+ /*
+ * We can't provide *buffer and *size as open_memstream does but
+ * at least clear them so the caller won't get bad data.
+ */
+ *buffer = NULL;
+ *size = 0;
+
+ return tmpfile();
+}
+static void memfile_abort(FILE *file, char **buffer, size_t *size)
+{
+ fclose(file);
+ *buffer = NULL;
+ *size = 0;
+}
+static int memfile_close(FILE *file, char **buffer, size_t *size)
+{
+ int rc;
+ size_t s;
+ char *b;
+
+ s = (size_t)ftell(file);
+ b = malloc(s + 1);
+ if (b == NULL) {
+ rc = MUSTACH_ERROR_SYSTEM;
+ errno = ENOMEM;
+ s = 0;
+ } else {
+ rewind(file);
+ if (1 == fread(b, s, 1, file)) {
+ rc = 0;
+ b[s] = 0;
+ } else {
+ rc = MUSTACH_ERROR_SYSTEM;
+ free(b);
+ b = NULL;
+ s = 0;
+ }
+ }
+ *buffer = b;
+ *size = s;
+ return rc;
+}
+#endif
+
+static inline void sbuf_reset(struct mustach_sbuf *sbuf)
+{
+ sbuf->value = NULL;
+ sbuf->freecb = NULL;
+ sbuf->closure = NULL;
+}
+
+static inline void sbuf_release(struct mustach_sbuf *sbuf)
+{
+ if (sbuf->releasecb)
+ sbuf->releasecb(sbuf->value, sbuf->closure);
+}
+
+static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file)
+{
+ size_t i, j;
+
+ (void)closure; /* unused */
+
+ if (!escape)
+ return fwrite(buffer, size, 1, file) != 1 ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK;
+
+ i = 0;
+ while (i < size) {
+ j = i;
+ while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&')
+ j++;
+ if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1)
+ return MUSTACH_ERROR_SYSTEM;
+ if (j < size) {
+ switch(buffer[j++]) {
+ case '<':
+ if (fwrite("&lt;", 4, 1, file) != 1)
+ return MUSTACH_ERROR_SYSTEM;
+ break;
+ case '>':
+ if (fwrite("&gt;", 4, 1, file) != 1)
+ return MUSTACH_ERROR_SYSTEM;
+ break;
+ case '&':
+ if (fwrite("&amp;", 5, 1, file) != 1)
+ return MUSTACH_ERROR_SYSTEM;
+ break;
+ default: break;
+ }
+ }
+ i = j;
+ }
+ return MUSTACH_OK;
+}
+
+static int iwrap_put(void *closure, const char *name, int escape, FILE *file)
+{
+ struct iwrap *iwrap = closure;
+ int rc;
+ struct mustach_sbuf sbuf;
+ size_t length;
+
+ sbuf_reset(&sbuf);
+ rc = iwrap->get(iwrap->closure, name, &sbuf);
+ if (rc >= 0) {
+ length = strlen(sbuf.value);
+ if (length)
+ rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file);
+ sbuf_release(&sbuf);
+ }
+ return rc;
+}
+
+static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+ struct iwrap *iwrap = closure;
+ int rc;
+ FILE *file;
+ size_t size;
+ char *result;
+
+ result = NULL;
+ file = memfile_open(&result, &size);
+ if (file == NULL)
+ rc = MUSTACH_ERROR_SYSTEM;
+ else {
+ rc = iwrap->put(iwrap->closure_put, name, 0, file);
+ if (rc < 0)
+ memfile_abort(file, &result, &size);
+ else {
+ rc = memfile_close(file, &result, &size);
+ if (rc == 0) {
+ sbuf->value = result;
+ sbuf->freecb = free;
+ }
+ }
+ }
+ return rc;
+}
+
+static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr)
+{
+ struct mustach_sbuf sbuf;
+ char name[MUSTACH_MAX_LENGTH + 1], c, *tmp;
+ const char *beg, *term;
+ struct { const char *name, *again; size_t length; int enabled, entered; } stack[MUSTACH_MAX_DEPTH];
+ size_t oplen, cllen, len, l;
+ int depth, rc, enabled;
+
+ enabled = 1;
+ oplen = strlen(opstr);
+ cllen = strlen(clstr);
+ depth = 0;
+ for(;;) {
+ beg = strstr(template, opstr);
+ if (beg == NULL) {
+ /* no more mustach */
+ if (enabled && template[0]) {
+ rc = iwrap->emit(iwrap->closure, template, strlen(template), 0, file);
+ if (rc < 0)
+ return rc;
+ }
+ return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK;
+ }
+ if (enabled && beg != template) {
+ rc = iwrap->emit(iwrap->closure, template, (size_t)(beg - template), 0, file);
+ if (rc < 0)
+ return rc;
+ }
+ beg += oplen;
+ term = strstr(beg, clstr);
+ if (term == NULL)
+ return MUSTACH_ERROR_UNEXPECTED_END;
+ template = term + cllen;
+ len = (size_t)(term - beg);
+ c = *beg;
+ switch(c) {
+ case '!':
+ case '=':
+ break;
+ case '{':
+ for (l = 0 ; clstr[l] == '}' ; l++);
+ if (clstr[l]) {
+ if (!len || beg[len-1] != '}')
+ return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
+ len--;
+ } else {
+ if (term[l] != '}')
+ return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
+ template++;
+ }
+ c = '&';
+ /*@fallthrough@*/
+ case '^':
+ case '#':
+ case '/':
+ case '&':
+ case '>':
+#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
+ case ':':
+#endif
+ beg++; len--;
+ default:
+ while (len && isspace(beg[0])) { beg++; len--; }
+ while (len && isspace(beg[len-1])) len--;
+#if !defined(NO_ALLOW_EMPTY_TAG)
+ if (len == 0)
+ return MUSTACH_ERROR_EMPTY_TAG;
+#endif
+ if (len > MUSTACH_MAX_LENGTH)
+ return MUSTACH_ERROR_TAG_TOO_LONG;
+ memcpy(name, beg, len);
+ name[len] = 0;
+ break;
+ }
+ switch(c) {
+ case '!':
+ /* comment */
+ /* nothing to do */
+ break;
+ case '=':
+ /* defines separators */
+ if (len < 5 || beg[len - 1] != '=')
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ beg++;
+ len -= 2;
+ for (l = 0; l < len && !isspace(beg[l]) ; l++);
+ if (l == len)
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ oplen = l;
+ tmp = alloca(oplen + 1);
+ memcpy(tmp, beg, oplen);
+ tmp[oplen] = 0;
+ opstr = tmp;
+ while (l < len && isspace(beg[l])) l++;
+ if (l == len)
+ return MUSTACH_ERROR_BAD_SEPARATORS;
+ cllen = len - l;
+ tmp = alloca(cllen + 1);
+ memcpy(tmp, beg + l, cllen);
+ tmp[cllen] = 0;
+ clstr = tmp;
+ break;
+ case '^':
+ case '#':
+ /* begin section */
+ if (depth == MUSTACH_MAX_DEPTH)
+ return MUSTACH_ERROR_TOO_DEEP;
+ rc = enabled;
+ if (rc) {
+ rc = iwrap->enter(iwrap->closure, name);
+ if (rc < 0)
+ return rc;
+ }
+ stack[depth].name = beg;
+ stack[depth].again = template;
+ stack[depth].length = len;
+ stack[depth].enabled = enabled;
+ stack[depth].entered = rc;
+ if ((c == '#') == (rc == 0))
+ enabled = 0;
+ depth++;
+ break;
+ case '/':
+ /* end section */
+ if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
+ return MUSTACH_ERROR_CLOSING;
+ rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0;
+ if (rc < 0)
+ return rc;
+ if (rc) {
+ template = stack[depth++].again;
+ } else {
+ enabled = stack[depth].enabled;
+ if (enabled && stack[depth].entered)
+ iwrap->leave(iwrap->closure);
+ }
+ break;
+ case '>':
+ /* partials */
+ if (enabled) {
+ sbuf_reset(&sbuf);
+ rc = iwrap->partial(iwrap->closure_partial, name, &sbuf);
+ if (rc >= 0) {
+ rc = process(sbuf.value, iwrap, file, opstr, clstr);
+ sbuf_release(&sbuf);
+ }
+ if (rc < 0)
+ return rc;
+ }
+ break;
+ default:
+ /* replacement */
+ if (enabled) {
+ rc = iwrap->put(iwrap->closure_put, name, c != '&', file);
+ if (rc < 0)
+ return rc;
+ }
+ break;
+ }
+ }
+}
+
+int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file)
+{
+ int rc;
+ struct iwrap iwrap;
+
+ /* check validity */
+ if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get))
+ return MUSTACH_ERROR_INVALID_ITF;
+
+ /* init wrap structure */
+ iwrap.closure = closure;
+ if (itf->put) {
+ iwrap.put = itf->put;
+ iwrap.closure_put = closure;
+ } else {
+ iwrap.put = iwrap_put;
+ iwrap.closure_put = &iwrap;
+ }
+ if (itf->partial) {
+ iwrap.partial = itf->partial;
+ iwrap.closure_partial = closure;
+ } else if (itf->get) {
+ iwrap.partial = itf->get;
+ iwrap.closure_partial = closure;
+ } else {
+ iwrap.partial = iwrap_partial;
+ iwrap.closure_partial = &iwrap;
+ }
+ iwrap.emit = itf->emit ? itf->emit : iwrap_emit;
+ iwrap.enter = itf->enter;
+ iwrap.next = itf->next;
+ iwrap.leave = itf->leave;
+ iwrap.get = itf->get;
+
+ /* process */
+ rc = itf->start ? itf->start(closure) : 0;
+ if (rc == 0)
+ rc = process(template, &iwrap, file, "{{", "}}");
+ if (itf->stop)
+ itf->stop(closure, rc);
+ return rc;
+}
+
+int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd)
+{
+ int rc;
+ FILE *file;
+
+ file = fdopen(fd, "w");
+ if (file == NULL) {
+ rc = MUSTACH_ERROR_SYSTEM;
+ errno = ENOMEM;
+ } else {
+ rc = fmustach(template, itf, closure, file);
+ fclose(file);
+ }
+ return rc;
+}
+
+int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size)
+{
+ int rc;
+ FILE *file;
+ size_t s;
+
+ *result = NULL;
+ if (size == NULL)
+ size = &s;
+ file = memfile_open(result, size);
+ if (file == NULL)
+ rc = MUSTACH_ERROR_SYSTEM;
+ else {
+ rc = fmustach(template, itf, closure, file);
+ if (rc < 0)
+ memfile_abort(file, result, size);
+ else
+ rc = memfile_close(file, result, size);
+ }
+ return rc;
+}
+
diff --git a/src/mustach/mustach.h b/src/mustach/mustach.h
new file mode 100644
index 00000000..b2e5f8f2
--- /dev/null
+++ b/src/mustach/mustach.h
@@ -0,0 +1,241 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#ifndef _mustach_h_included_
+#define _mustach_h_included_
+
+struct mustach_sbuf; /* see below */
+
+/**
+ * Current version of mustach and its derivates
+ */
+#define MUSTACH_VERSION 99
+#define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100)
+#define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100)
+
+/**
+ * Maximum nested imbrications supported
+ */
+#define MUSTACH_MAX_DEPTH 256
+
+/**
+ * Maximum length of tags in mustaches {{...}}
+ */
+#define MUSTACH_MAX_LENGTH 1024
+
+/**
+ * mustach_itf - interface for callbacks
+ *
+ * All of this function should return a negative value to stop
+ * the mustache processing. The returned negative value will be
+ * then returned to the caller of mustach as is.
+ *
+ * The functions enter and next should return 0 or 1.
+ *
+ * All other functions should normally return MUSTACH_OK (zero).
+ * If it returns a negative value, it means an error that stop
+ * the process and that is reported to the caller.
+ *
+ * @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 'umustach_json_c'.
+ *
+ * @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 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 occurerd. 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' 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'.
+ */
+struct mustach_sbuf {
+ const char *value;
+ union {
+ void (*freecb)(void*);
+ void (*releasecb)(const char *value, void *closure);
+ };
+ void *closure;
+};
+
+/*
+ * 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
+
+/* You can use definition below for user specific error */
+#define MUSTACH_ERROR_USER_BASE -100
+#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x))
+
+/**
+ * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instanciate
+ * @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 fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file);
+
+/**
+ * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instanciate
+ * @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 fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd);
+
+/**
+ * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'.
+ *
+ * @template: the template string to instanciate
+ * @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(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size);
+
+#endif
+
diff --git a/src/mustach/run-original-tests.sh b/src/mustach/run-original-tests.sh
new file mode 100755
index 00000000..9c7d34cd
--- /dev/null
+++ b/src/mustach/run-original-tests.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -eu
+# 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 Makefile.orig mustach || exit 77
+make -f Makefile.orig test
+make -f Makefile.orig clean || true
diff --git a/src/mustach/test1/.gitignore b/src/mustach/test1/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/mustach/test1/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/mustach/test1/json b/src/mustach/test1/json
new file mode 100644
index 00000000..bf8e37a1
--- /dev/null
+++ b/src/mustach/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}}----",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/mustach/test1/must b/src/mustach/test1/must
new file mode 100644
index 00000000..6df52366
--- /dev/null
+++ b/src/mustach/test1/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/mustach/test1/resu.ref b/src/mustach/test1/resu.ref
new file mode 100644
index 00000000..5d4ce862
--- /dev/null
+++ b/src/mustach/test1/resu.ref
@@ -0,0 +1,49 @@
+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/mustach/test1/vg.ref b/src/mustach/test1/vg.ref
new file mode 100644
index 00000000..92e39d4c
--- /dev/null
+++ b/src/mustach/test1/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.14.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: 169 allocs, 169 frees, 24,725 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For counts of detected and suppressed errors, rerun with: -v
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/mustach/test2/.gitignore b/src/mustach/test2/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/mustach/test2/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/mustach/test2/json b/src/mustach/test2/json
new file mode 100644
index 00000000..8c668b3b
--- /dev/null
+++ b/src/mustach/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/mustach/test2/must b/src/mustach/test2/must
new file mode 100644
index 00000000..aa6da707
--- /dev/null
+++ b/src/mustach/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/mustach/test2/resu.ref b/src/mustach/test2/resu.ref
new file mode 100644
index 00000000..67d1f547
--- /dev/null
+++ b/src/mustach/test2/resu.ref
@@ -0,0 +1,22 @@
+<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/mustach/test2/vg.ref b/src/mustach/test2/vg.ref
new file mode 100644
index 00000000..1218d583
--- /dev/null
+++ b/src/mustach/test2/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.14.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: 62 allocs, 62 frees, 11,187 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For counts of detected and suppressed errors, rerun with: -v
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/mustach/test3/.gitignore b/src/mustach/test3/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/mustach/test3/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/mustach/test3/json b/src/mustach/test3/json
new file mode 100644
index 00000000..79278817
--- /dev/null
+++ b/src/mustach/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/mustach/test3/must b/src/mustach/test3/must
new file mode 100644
index 00000000..5c490469
--- /dev/null
+++ b/src/mustach/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/mustach/test3/resu.ref b/src/mustach/test3/resu.ref
new file mode 100644
index 00000000..e89ce902
--- /dev/null
+++ b/src/mustach/test3/resu.ref
@@ -0,0 +1,15 @@
+* 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/mustach/test3/vg.ref b/src/mustach/test3/vg.ref
new file mode 100644
index 00000000..4d0d4f7a
--- /dev/null
+++ b/src/mustach/test3/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.14.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: 41 allocs, 41 frees, 8,834 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For counts of detected and suppressed errors, rerun with: -v
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/mustach/test4/.gitignore b/src/mustach/test4/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/mustach/test4/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/mustach/test4/json b/src/mustach/test4/json
new file mode 100644
index 00000000..a1083607
--- /dev/null
+++ b/src/mustach/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/mustach/test4/must b/src/mustach/test4/must
new file mode 100644
index 00000000..003b9366
--- /dev/null
+++ b/src/mustach/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/mustach/test4/resu.ref b/src/mustach/test4/resu.ref
new file mode 100644
index 00000000..2d48918a
--- /dev/null
+++ b/src/mustach/test4/resu.ref
@@ -0,0 +1,100 @@
+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: { "name": "Jon", "age": 25 }
+
+ (2) name: Jon
+
+
+ (2) age: 25
+
+
+
+ (1) person.name: Fred
+
+
+ (1) person.name=Fred: The other Fred.
+
+
+ (1) persons: [ { "name": "Jon", "age": 25, "lang": "en" }, { "name": "Henry", "age": 27, "lang": "en" }, { "name": "Amed", "age": 24, "lang": "fr" } ]
+
+
+ (1) fellows: { "Jon": { "age": 25, "lang": "en" }, "Henry": { "age": 27, "lang": "en" }, "Amed": { "age": 24, "lang": "fr" } }
+
+ (2) Jon: { "age": 25, "lang": "en" }
+
+ (3) age: 25
+
+ (3) lang: en
+
+
+ (2) Henry: { "age": 27, "lang": "en" }
+
+ (3) age: 27
+
+ (3) lang: en
+
+
+ (2) Amed: { "age": 24, "lang": "fr" }
+
+ (3) age: 24
+
+ (3) lang: fr
+
+
+
diff --git a/src/mustach/test4/vg.ref b/src/mustach/test4/vg.ref
new file mode 100644
index 00000000..0aa92dc7
--- /dev/null
+++ b/src/mustach/test4/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.14.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: 142 allocs, 142 frees, 18,276 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For counts of detected and suppressed errors, rerun with: -v
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/mustach/test5/.gitignore b/src/mustach/test5/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/mustach/test5/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/mustach/test5/json b/src/mustach/test5/json
new file mode 100644
index 00000000..bf8e37a1
--- /dev/null
+++ b/src/mustach/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}}----",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/mustach/test5/must b/src/mustach/test5/must
new file mode 100644
index 00000000..44305df2
--- /dev/null
+++ b/src/mustach/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/mustach/test5/must2 b/src/mustach/test5/must2
new file mode 100644
index 00000000..d4a1d378
--- /dev/null
+++ b/src/mustach/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/mustach/test5/must2.mustache b/src/mustach/test5/must2.mustache
new file mode 100644
index 00000000..33f1ead3
--- /dev/null
+++ b/src/mustach/test5/must2.mustache
@@ -0,0 +1 @@
+must2.mustache ==SHOULD NOT BE SEEN==
diff --git a/src/mustach/test5/must3.mustache b/src/mustach/test5/must3.mustache
new file mode 100644
index 00000000..67eddb1e
--- /dev/null
+++ b/src/mustach/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/mustach/test5/resu.ref b/src/mustach/test5/resu.ref
new file mode 100644
index 00000000..117499fc
--- /dev/null
+++ b/src/mustach/test5/resu.ref
@@ -0,0 +1,60 @@
+=====================================
+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/mustach/test5/special b/src/mustach/test5/special
new file mode 100644
index 00000000..02d9975c
--- /dev/null
+++ b/src/mustach/test5/special
@@ -0,0 +1 @@
+special ==SHOULD NOT BE SEEN==
diff --git a/src/mustach/test5/special.mustache b/src/mustach/test5/special.mustache
new file mode 100644
index 00000000..70a771fd
--- /dev/null
+++ b/src/mustach/test5/special.mustache
@@ -0,0 +1 @@
+special.mustache ==SHOULD NOT BE SEEN==
diff --git a/src/mustach/test5/vg.ref b/src/mustach/test5/vg.ref
new file mode 100644
index 00000000..83f48b62
--- /dev/null
+++ b/src/mustach/test5/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.14.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: 181 allocs, 181 frees, 36,033 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For counts of detected and suppressed errors, rerun with: -v
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
diff --git a/src/mustach/test6/.gitignore b/src/mustach/test6/.gitignore
new file mode 100644
index 00000000..15e6dd5a
--- /dev/null
+++ b/src/mustach/test6/.gitignore
@@ -0,0 +1,3 @@
+resu.last
+vg.last
+test-custom-write
diff --git a/src/mustach/test6/json b/src/mustach/test6/json
new file mode 100644
index 00000000..bf8e37a1
--- /dev/null
+++ b/src/mustach/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}}----",
+ "extra": 3.14159,
+ "#sharp": "#",
+ "!bang": "!",
+ "/slash": "/",
+ "^circ": "^",
+ "=equal": "=",
+ ":colon": ":",
+ ">greater": ">",
+ "~tilde": "~"
+}
diff --git a/src/mustach/test6/must b/src/mustach/test6/must
new file mode 100644
index 00000000..6df52366
--- /dev/null
+++ b/src/mustach/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/mustach/test6/resu.ref b/src/mustach/test6/resu.ref
new file mode 100644
index 00000000..e983fee6
--- /dev/null
+++ b/src/mustach/test6/resu.ref
@@ -0,0 +1,147 @@
+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/mustach/test6/vg.ref b/src/mustach/test6/vg.ref
new file mode 100644
index 00000000..2ad202f8
--- /dev/null
+++ b/src/mustach/test6/vg.ref
@@ -0,0 +1,14 @@
+Memcheck, a memory error detector
+Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
+Using Valgrind-3.14.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: 171 allocs, 171 frees, 26,351 bytes allocated
+
+All heap blocks were freed -- no leaks are possible
+
+For counts of detected and suppressed errors, rerun with: -v
+ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)