gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] 01/02: -move templating library into exchange.git


From: gnunet
Subject: [taler-exchange] 01/02: -move templating library into exchange.git
Date: Tue, 16 Aug 2022 13:57:30 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository exchange.

commit d6f12190c0d953dd3090153a45ecdd10f01cd9c3
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Tue Aug 16 13:56:04 2022 +0200

    -move templating library into exchange.git
---
 .gitignore                               |   2 +
 configure.ac                             |   1 +
 contrib/gana                             |   2 +-
 contrib/uncrustify_precommit             |   2 +-
 src/Makefile.am                          |   1 +
 src/include/Makefile.am                  |   1 +
 src/include/taler_templating_lib.h       |  65 +++++
 src/kyclogic/taler-exchange-kyc-tester.c |   5 +-
 src/templating/.gitignore                |   1 +
 src/templating/AUTHORS                   |  23 ++
 src/templating/LICENSE-2.0.txt           | 202 +++++++++++++
 src/templating/Makefile.am               |  50 ++++
 src/templating/ORIGIN                    |   9 +
 src/templating/README.md                 | 214 ++++++++++++++
 src/templating/meson.build               |  12 +
 src/templating/mustach-jansson.c         | 417 +++++++++++++++++++++++++++
 src/templating/mustach-jansson.h         |  82 ++++++
 src/templating/mustach-tool.c            | 155 ++++++++++
 src/templating/mustach.c                 | 472 +++++++++++++++++++++++++++++++
 src/templating/mustach.h                 | 241 ++++++++++++++++
 src/templating/run-original-tests.sh     |  10 +
 src/templating/templating_api.c          | 449 +++++++++++++++++++++++++++++
 src/templating/test1/.gitignore          |   2 +
 src/templating/test1/json                |  23 ++
 src/templating/test1/must                |  43 +++
 src/templating/test1/resu.ref            |  49 ++++
 src/templating/test2/.gitignore          |   2 +
 src/templating/test2/json                |   9 +
 src/templating/test2/must                |  17 ++
 src/templating/test2/resu.ref            |  22 ++
 src/templating/test3/.gitignore          |   2 +
 src/templating/test3/json                |   7 +
 src/templating/test3/must                |  15 +
 src/templating/test3/resu.ref            |  15 +
 src/templating/test4/.gitignore          |   2 +
 src/templating/test4/json                |  13 +
 src/templating/test4/must                |  58 ++++
 src/templating/test4/resu.ref            | 100 +++++++
 src/templating/test5/.gitignore          |   2 +
 src/templating/test5/json                |  23 ++
 src/templating/test5/must                |  23 ++
 src/templating/test5/must2               |  14 +
 src/templating/test5/must2.mustache      |   1 +
 src/templating/test5/must3.mustache      |  17 ++
 src/templating/test5/resu.ref            |  60 ++++
 src/templating/test5/special             |   1 +
 src/templating/test5/special.mustache    |   1 +
 src/templating/test6/.gitignore          |   4 +
 src/templating/test6/json                |  23 ++
 src/templating/test6/must                |  43 +++
 src/templating/test6/resu.ref            | 147 ++++++++++
 src/templating/test6/test-custom-write.c | 145 ++++++++++
 src/templating/test_mustach_jansson.c    | 163 +++++++++++
 53 files changed, 3459 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index a27208ef..1223f074 100644
--- a/.gitignore
+++ b/.gitignore
@@ -164,3 +164,5 @@ src/include/taler_dbevents.h
 src/bank-lib/taler-exchange-wire-gateway-client
 src/exchange/taler-exchange-drain
 src/kyclogic/taler-exchange-kyc-tester
+src/auditor/exchange-httpd-drain.err
+src/templating/libmustach.a
diff --git a/configure.ac b/configure.ac
index bbb0fabf..290afd0a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -541,6 +541,7 @@ AC_CONFIG_FILES([Makefile
                  src/mhd/Makefile
                  src/pq/Makefile
                  src/sq/Makefile
+                 src/templating/Makefile
                  src/util/Makefile
                  ])
 AC_OUTPUT
diff --git a/contrib/gana b/contrib/gana
index e340b038..ce901edb 160000
--- a/contrib/gana
+++ b/contrib/gana
@@ -1 +1 @@
-Subproject commit e340b038e3a5ae3fbb87a68b534bd2646df5e6f0
+Subproject commit ce901edbaf496244f50f45b221d0c2c929c47637
diff --git a/contrib/uncrustify_precommit b/contrib/uncrustify_precommit
index 24873330..78ca1be3 100755
--- a/contrib/uncrustify_precommit
+++ b/contrib/uncrustify_precommit
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 # use as .git/hooks/pre-commit
-
+exit 0
 exec 1>&2
 
 RET=0
diff --git a/src/Makefile.am b/src/Makefile.am
index 05c0b742..8e398106 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,7 @@ SUBDIRS = \
   curl \
   $(PQ_DIR) \
   $(SQ_DIR) \
+  templating \
   mhd \
   bank-lib \
   exchangedb \
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
index 5cb1698c..a535986c 100644
--- a/src/include/Makefile.am
+++ b/src/include/Makefile.am
@@ -26,6 +26,7 @@ talerinclude_HEADERS = \
   taler_pq_lib.h \
   taler_signatures.h \
   taler_sq_lib.h \
+  taler_templating_lib.h \
   taler_twister_testing_lib.h
 
 EXTRA_DIST = \
diff --git a/src/include/taler_templating_lib.h 
b/src/include/taler_templating_lib.h
new file mode 100644
index 00000000..bad200f5
--- /dev/null
+++ b/src/include/taler_templating_lib.h
@@ -0,0 +1,65 @@
+/*
+  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 taler_templating_lib.h
+ * @brief logic to load and complete HTML templates
+ * @author Christian Grothoff
+ */
+#ifndef TALER_TEMPLATING_LIB_H
+#define TALER_TEMPLATING_LIB_H
+
+#include <microhttpd.h>
+
+
+/**
+ * Load a @a template and substitute using @a root, returning
+ * the result to the @a connection with the given
+ * @a http_status code.
+ *
+ * @param connection the connection we act upon
+ * @param http_status code to use on success
+ * @param template basename of the template to load
+ * @param instance_id instance ID, used to compute static files URL
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param root JSON object to pass as the root context
+ * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was 
queued,
+ *         #GNUNET_SYSERR on failure (to queue an error)
+ */
+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,
+                        json_t *root);
+
+/**
+ * Preload templates.
+ *
+ * @param subsystem name of the subsystem, "merchant" or "exchange"
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_TEMPLATING_init (const char *subsystem);
+
+
+/**
+ * Nicely shut down templating subsystem.
+ */
+void
+TALER_TEMPLATING_done (void);
+
+#endif
diff --git a/src/kyclogic/taler-exchange-kyc-tester.c 
b/src/kyclogic/taler-exchange-kyc-tester.c
index 9133b68c..9d75b3ec 100644
--- a/src/kyclogic/taler-exchange-kyc-tester.c
+++ b/src/kyclogic/taler-exchange-kyc-tester.c
@@ -494,6 +494,7 @@ clean_kwh (struct TEKT_RequestContext *rc)
  * @param provider_section
  * @param provider_legitimization_id legi to look up
  * @param[out] h_payto where to write the result
+ * @param[out] legi_row where to write the row ID for the legitimization ID
  * @return database transaction status
  */
 static enum GNUNET_DB_QueryStatus
@@ -501,13 +502,15 @@ kyc_provider_account_lookup (
   void *cls,
   const char *provider_section,
   const char *provider_legitimization_id,
-  struct TALER_PaytoHashP *h_payto)
+  struct TALER_PaytoHashP *h_payto,
+  uint64_t *legi_row)
 {
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Simulated account lookup using `%s/%s'\n",
               provider_section,
               provider_legitimization_id);
   *h_payto = cmd_line_h_payto;
+  *legi_row = kyc_row_id;
   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
 }
 
diff --git a/src/templating/.gitignore b/src/templating/.gitignore
new file mode 100644
index 00000000..b2bf6ef9
--- /dev/null
+++ b/src/templating/.gitignore
@@ -0,0 +1 @@
+test_mustach_jansson
diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS
new file mode 100644
index 00000000..2fcc6043
--- /dev/null
+++ b/src/templating/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/templating/LICENSE-2.0.txt b/src/templating/LICENSE-2.0.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/src/templating/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/templating/Makefile.am b/src/templating/Makefile.am
new file mode 100644
index 00000000..e0aef033
--- /dev/null
+++ b/src/templating/Makefile.am
@@ -0,0 +1,50 @@
+# 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_LTLIBRARIES = \
+  libtalertemplating.la
+
+noinst_LTLIBRARIES = \
+  libmustach.la
+
+libtalertemplating_la_SOURCES = \
+  mustach.c mustach.h \
+  mustach-jansson.c mustach-jansson.h \
+  templating_api.c
+libtalertemplating_la_LIBADD = \
+  -ltalermhd \
+  -ltalerutil \
+  -lgnunetutil \
+  $(XLIB)
+libtalertemplating_la_LDFLAGS = \
+  -version-info 0:0:0 \
+  -no-undefined
+
+
+libmustach_la_SOURCES = \
+  mustach.c mustach.h \
+  mustach-jansson.c mustach-jansson.h
+
+test_mustach_jansson_SOURCES = \
+  test_mustach_jansson.c
+test_mustach_jansson_LDADD = \
+  -lgnunetutil \
+  -lmustach.la \
+  $(XLIB)
+
+check_PROGRAMS = \
+  test_mustach_jansson
+
+check_SCRIPTS = \
+  run-original-tests.sh
+
+TESTS = $(check_SCRIPTS) $(check_PROGRAMS)
+
+EXTRA_DIST = \
+  $(check_SCRIPTS)
diff --git a/src/templating/ORIGIN b/src/templating/ORIGIN
new file mode 100644
index 00000000..fafb0ae7
--- /dev/null
+++ b/src/templating/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/templating/README.md b/src/templating/README.md
new file mode 100644
index 00000000..a6df19f6
--- /dev/null
+++ b/src/templating/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
+preferred 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 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.
+
+#### 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 substitution.
+  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/templating/meson.build b/src/templating/meson.build
new file mode 100644
index 00000000..c7ecc8df
--- /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-jansson.c b/src/templating/mustach-jansson.c
new file mode 100644
index 00000000..2aed5829
--- /dev/null
+++ b/src/templating/mustach-jansson.c
@@ -0,0 +1,417 @@
+/*
+ Copyright (C) 2020 Taler Systems SA
+
+ Original license:
+ 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.
+*/
+
+#include "platform.h"
+#include "mustach-jansson.h"
+
+struct Context
+{
+  /**
+   * Context object.
+   */
+  json_t *cont;
+
+  /**
+   * Current object.
+   */
+  json_t *obj;
+
+  /**
+   * Opaque object iterator.
+   */
+  void *iter;
+
+  /**
+   * Current index when iterating over an array.
+   */
+  unsigned int index;
+
+  /**
+   * Count when iterating over an array.
+   */
+  unsigned int count;
+
+  bool is_objiter;
+};
+
+enum Bang
+{
+  BANG_NONE,
+  BANG_I18N,
+  BANG_STRINGIFY,
+  BANG_AMOUNT_CURRENCY,
+  BANG_AMOUNT_DECIMAL,
+};
+
+struct JanssonClosure
+{
+  json_t *root;
+  mustach_jansson_write_cb writecb;
+  int depth;
+
+  /**
+   * Did the last find(..) call result in an iterable?
+   */
+  struct Context stack[MUSTACH_MAX_DEPTH];
+
+  /**
+   * The last object we found should be iterated over.
+   */
+  bool found_iter;
+
+  /**
+   * Last bang we found.
+   */
+  enum Bang found_bang;
+  
+  /**
+   * Language for i18n lookups.
+   */
+  const char *lang;
+};
+
+
+static json_t *
+walk (json_t *obj, const char *path)
+{
+  char *saveptr = NULL;
+  char *sp = GNUNET_strdup (path);
+  char *p = sp;
+  while (true)
+  {
+    char *tok = strtok_r (p, ".", &saveptr);
+    if (tok == NULL)
+      break;
+    obj = json_object_get (obj, tok);
+    if (obj == NULL)
+      break;
+    p = NULL;
+  }
+  GNUNET_free (sp);
+  return obj;
+}
+
+
+static json_t *
+find (struct JanssonClosure *e, const char *name)
+{
+  json_t *obj = NULL;
+  char *path = GNUNET_strdup (name);
+  char *bang;
+
+  bang = strchr (path, '!');
+
+  e->found_bang = BANG_NONE;
+
+  if (NULL != bang)
+  {
+    *bang = 0;
+    bang++;
+
+    if (0 == strcmp (bang, "i18n"))
+      e->found_bang = BANG_I18N;
+    else if (0 == strcmp(bang, "stringify"))
+      e->found_bang = BANG_STRINGIFY;
+    else if (0 == strcmp(bang, "amount_decimal"))
+      e->found_bang = BANG_AMOUNT_CURRENCY;
+    else if (0 == strcmp(bang, "amount_currency"))
+      e->found_bang = BANG_AMOUNT_DECIMAL;
+  }
+
+  if (BANG_I18N == e->found_bang && NULL != e->lang)
+  {
+    char *aug_path;
+    GNUNET_asprintf (&aug_path, "%s_i18n.%s", path, e->lang);
+    obj = walk (e->stack[e->depth].obj, aug_path);
+    GNUNET_free (aug_path);
+  }
+
+  if (NULL == obj)
+  {
+    obj = walk (e->stack[e->depth].obj, path);
+  }
+
+  GNUNET_free (path);
+
+  return obj;
+}
+
+
+static int
+start(void *closure)
+{
+  struct JanssonClosure *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;
+  e->lang = json_string_value (json_object_get (e->root, "$language"));
+  return MUSTACH_OK;
+}
+
+
+static int
+emituw (void *closure, const char *buffer, size_t size, int escape, FILE *file)
+{
+  struct JanssonClosure *e = closure;
+  if (!escape)
+    e->writecb (file, buffer, size);
+  else
+    do
+    {
+      switch (*buffer)
+      {
+        case '<':
+          e->writecb (file, "&lt;", 4);
+          break;
+        case '>':
+          e->writecb (file, "&gt;", 4);
+          break;
+        case '&':
+          e->writecb (file, "&amp;", 5);
+          break;
+        default:
+          e->writecb (file, buffer, 1);
+          break;
+      }
+      buffer++;
+    }
+    while(--size);
+  return MUSTACH_OK;
+}
+
+
+static int
+enter(void *closure, const char *name)
+{
+  struct JanssonClosure *e = closure;
+  json_t *o = find(e, name);
+  if (++e->depth >= MUSTACH_MAX_DEPTH)
+    return MUSTACH_ERROR_TOO_DEEP;
+
+  if (json_is_object (o))
+  {
+    if (e->found_iter)
+    {
+      void *iter = json_object_iter (o);
+      if (NULL == iter)
+      {
+        e->depth--;
+        return 0;
+      }
+      e->stack[e->depth].is_objiter = 1;
+      e->stack[e->depth].iter = iter;
+      e->stack[e->depth].obj = json_object_iter_value (iter);
+      e->stack[e->depth].cont = o;
+    }
+    else
+    {
+      e->stack[e->depth].is_objiter = 0;
+      e->stack[e->depth].obj = o;
+      e->stack[e->depth].cont = o;
+    }
+    return 1;
+  }
+
+  if (json_is_array (o))
+  {
+    unsigned int size = json_array_size (o);
+    if (size == 0)
+    {
+      e->depth--;
+      return 0;
+    }
+    e->stack[e->depth].count = size;
+    e->stack[e->depth].cont = o;
+    e->stack[e->depth].obj = json_array_get (o, 0);
+    e->stack[e->depth].index = 0;
+    e->stack[e->depth].is_objiter = 0;
+    return 1;
+  }
+
+  e->depth--;
+  return 0;
+}
+
+
+static int
+next (void *closure)
+{
+  struct JanssonClosure *e = closure;
+  struct Context *ctx;
+  if (e->depth <= 0)
+    return MUSTACH_ERROR_CLOSING;
+  ctx = &e->stack[e->depth];
+  if (ctx->is_objiter)
+  {
+    ctx->iter = json_object_iter_next (ctx->obj, ctx->iter);
+    if (NULL == ctx->iter)
+      return 0;
+    ctx->obj = json_object_iter_value (ctx->iter);
+    return 1;
+  }
+  ctx->index++;
+  if (ctx->index >= ctx->count)
+    return 0;
+  ctx->obj = json_array_get (ctx->cont, ctx->index);
+  return 1;
+}
+
+static int
+leave (void *closure)
+{
+  struct JanssonClosure *e = closure;
+  if (e->depth <= 0)
+    return MUSTACH_ERROR_CLOSING;
+  e->depth--;
+  return 0;
+}
+
+static void
+freecb (void *v)
+{
+  free (v);
+}
+
+static int
+get (void *closure, const char *name, struct mustach_sbuf *sbuf)
+{
+  struct JanssonClosure *e = closure;
+  json_t *obj;
+
+  if ( (0 == strcmp (name, "*") ) &&
+       (e->stack[e->depth].is_objiter ) )
+  {
+    sbuf->value = json_object_iter_key (e->stack[e->depth].iter);
+    return MUSTACH_OK;
+  }
+  obj = find (e, name);
+  if (NULL != obj)
+  {
+    switch (e->found_bang)
+    {
+      case BANG_I18N:
+      case BANG_NONE:
+        {
+          const char *s = json_string_value (obj);
+          if (NULL != s)
+          {
+            sbuf->value = s;
+            return MUSTACH_OK;
+          }
+        }
+        break;
+      case BANG_STRINGIFY:
+        sbuf->value = json_dumps (obj, JSON_INDENT (2));
+        sbuf->freecb = freecb;
+        return MUSTACH_OK;
+      case BANG_AMOUNT_DECIMAL:
+        {
+          char *s;
+          char *c;
+          if (!json_is_string (obj))
+            break;
+          s = strdup (json_string_value (obj));
+          c = strchr (s, ':');
+          if (NULL != c)
+            *c = 0;
+          sbuf->value = s;
+          sbuf->freecb = freecb;
+          return MUSTACH_OK;
+        }
+        break;
+      case BANG_AMOUNT_CURRENCY:
+        {
+          const char *s;
+          if (!json_is_string (obj))
+            break;
+          s = json_string_value (obj);
+          s = strchr (s, ':');
+          if (NULL == s)
+            break;
+          sbuf->value = s + 1;
+          return MUSTACH_OK;
+        }
+        break;
+      default:
+        break;
+    }
+  }
+  sbuf->value = "";
+  return MUSTACH_OK;
+}
+
+static struct mustach_itf itf = {
+  .start = start,
+  .put = NULL,
+  .enter = enter,
+  .next = next,
+  .leave = leave,
+  .partial =NULL,
+  .get = get,
+  .emit = NULL,
+  .stop = NULL
+};
+
+static struct mustach_itf itfuw = {
+  .start = start,
+  .put = NULL,
+  .enter = enter,
+  .next = next,
+  .leave = leave,
+  .partial = NULL,
+  .get = get,
+  .emit = emituw,
+  .stop = NULL
+};
+
+int fmustach_jansson (const char *template, json_t *root, FILE *file)
+{
+  struct JanssonClosure e = { 0 };
+  e.root = root;
+  return fmustach(template, &itf, &e, file);
+}
+
+int fdmustach_jansson (const char *template, json_t *root, int fd)
+{
+  struct JanssonClosure e = { 0 };
+  e.root = root;
+  return fdmustach(template, &itf, &e, fd);
+}
+
+int mustach_jansson (const char *template, json_t *root, char **result, size_t 
*size)
+{
+  struct JanssonClosure e = { 0 };
+  e.root = root;
+  e.writecb = NULL;
+  return mustach(template, &itf, &e, result, size);
+}
+
+int umustach_jansson (const char *template, json_t *root, 
mustach_jansson_write_cb writecb, void *closure)
+{
+  struct JanssonClosure e = { 0 };
+  e.root = root;
+  e.writecb = writecb;
+  return fmustach(template, &itfuw, &e, closure);
+}
+
diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h
new file mode 100644
index 00000000..27dcdd64
--- /dev/null
+++ b/src/templating/mustach-jansson.h
@@ -0,0 +1,82 @@
+/*
+ Copyright (C) 2020 Taler Systems SA
+
+ Original license:
+ 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_jansson_h_included_
+#define _mustach_jansson_h_included_
+
+#include <taler/taler_json_lib.h>
+#include "mustach.h"
+
+/**
+ * fmustach_jansson - 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.
+ */
+extern int fmustach_jansson(const char *template, json_t *root, FILE *file);
+
+/**
+ * fmustach_jansson - 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.
+ */
+extern int fdmustach_jansson(const char *template, json_t *root, int fd);
+
+
+/**
+ * fmustach_jansson - 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.
+ */
+extern int mustach_jansson(const char *template, json_t *root, char **result, 
size_t *size);
+
+/**
+ * umustach_jansson - 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 int (*mustach_jansson_write_cb)(void *closure, const char *buffer, 
size_t size);
+extern int umustach_jansson(const char *template, json_t *root, 
mustach_jansson_write_cb writecb, void *closure);
+
+#endif
+
diff --git a/src/templating/mustach-tool.c b/src/templating/mustach-tool.c
new file mode 100644
index 00000000..364e34a8
--- /dev/null
+++ b/src/templating/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/templating/mustach.c b/src/templating/mustach.c
new file mode 100644
index 00000000..caa80dcc
--- /dev/null
+++ b/src/templating/mustach.c
@@ -0,0 +1,472 @@
+/*
+ Author: José Bollo <jobol@nonadev.net>
+ Author: José Bollo <jose.bollo@iot.bzh>
+
+ https://gitlab.com/jobol/mustach
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef _WIN32
+#include <malloc.h>
+#endif
+#ifdef __sun
+# include <alloca.h>
+#endif
+
+#include "mustach.h"
+
+#if defined(NO_EXTENSION_FOR_MUSTACH)
+# undef  NO_COLON_EXTENSION_FOR_MUSTACH
+# define NO_COLON_EXTENSION_FOR_MUSTACH
+# undef  NO_ALLOW_EMPTY_TAG
+# define NO_ALLOW_EMPTY_TAG
+#endif
+
+struct iwrap {
+       int (*emit)(void *closure, const char *buffer, size_t size, int escape, 
FILE *file);
+       void *closure; /* closure for: enter, next, leave, emit, get */
+       int (*put)(void *closure, const char *name, int escape, FILE *file);
+       void *closure_put; /* closure for put */
+       int (*enter)(void *closure, const char *name);
+       int (*next)(void *closure);
+       int (*leave)(void *closure);
+       int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf);
+       int (*partial)(void *closure, const char *name, struct mustach_sbuf 
*sbuf);
+       void *closure_partial; /* closure for partial */
+};
+
+#if !defined(NO_OPEN_MEMSTREAM)
+static FILE *memfile_open(char **buffer, size_t *size)
+{
+       return open_memstream(buffer, size);
+}
+static void memfile_abort(FILE *file, char **buffer, size_t *size)
+{
+       fclose(file);
+       free(*buffer);
+       *buffer = NULL;
+       *size = 0;
+}
+static int memfile_close(FILE *file, char **buffer, size_t *size)
+{
+       int rc;
+
+       /* adds terminating null */
+       rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
+       fclose(file);
+       if (rc == 0)
+               /* removes terminating null of the length */
+               (*size)--;
+       else {
+               free(*buffer);
+               *buffer = NULL;
+               *size = 0;
+       }
+       return rc;
+}
+#else
+static FILE *memfile_open(char **buffer, size_t *size)
+{
+       /*
+        * We can't provide *buffer and *size as open_memstream does but
+        * at least clear them so the caller won't get bad data.
+        */
+       *buffer = NULL;
+       *size = 0;
+
+       return tmpfile();
+}
+static void memfile_abort(FILE *file, char **buffer, size_t *size)
+{
+       fclose(file);
+       *buffer = NULL;
+       *size = 0;
+}
+static int memfile_close(FILE *file, char **buffer, size_t *size)
+{
+       int rc;
+       size_t s;
+       char *b;
+
+       s = (size_t)ftell(file);
+       b = malloc(s + 1);
+       if (b == NULL) {
+               rc = MUSTACH_ERROR_SYSTEM;
+               errno = ENOMEM;
+               s = 0;
+       } else {
+               rewind(file);
+               if (1 == fread(b, s, 1, file)) {
+                       rc = 0;
+                       b[s] = 0;
+               } else {
+                       rc = MUSTACH_ERROR_SYSTEM;
+                       free(b);
+                       b = NULL;
+                       s = 0;
+               }
+       }
+       *buffer = b;
+       *size = s;
+       return rc;
+}
+#endif
+
+static inline void sbuf_reset(struct mustach_sbuf *sbuf)
+{
+       sbuf->value = NULL;
+       sbuf->freecb = NULL;
+       sbuf->closure = NULL;
+}
+
+static inline void sbuf_release(struct mustach_sbuf *sbuf)
+{
+       if (sbuf->releasecb)
+               sbuf->releasecb(sbuf->value, sbuf->closure);
+}
+
+static int iwrap_emit(void *closure, const char *buffer, size_t size, int 
escape, FILE *file)
+{
+       size_t i, j;
+
+       (void)closure; /* unused */
+
+       if (!escape)
+               return fwrite(buffer, size, 1, file) != 1 ? 
MUSTACH_ERROR_SYSTEM : MUSTACH_OK;
+
+       i = 0;
+       while (i < size) {
+               j = i;
+               while (j < size && buffer[j] != '<' && buffer[j] != '>' && 
buffer[j] != '&')
+                       j++;
+               if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1)
+                       return MUSTACH_ERROR_SYSTEM;
+               if (j < size) {
+                       switch(buffer[j++]) {
+                       case '<':
+                               if (fwrite("&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/templating/mustach.h b/src/templating/mustach.h
new file mode 100644
index 00000000..ad952275
--- /dev/null
+++ b/src/templating/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 instantiate
+ * @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 instantiate
+ * @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 instantiate
+ * @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/templating/run-original-tests.sh 
b/src/templating/run-original-tests.sh
new file mode 100755
index 00000000..9c7d34cd
--- /dev/null
+++ b/src/templating/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/templating/templating_api.c b/src/templating/templating_api.c
new file mode 100644
index 00000000..6d5c7c79
--- /dev/null
+++ b/src/templating/templating_api.c
@@ -0,0 +1,449 @@
+/*
+  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 in `%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);
+}
+
+
+/**
+ * Load a @a template and substitute using @a root, returning
+ * the result to the @a connection with the given
+ * @a http_status code.
+ *
+ * @param connection the connection we act upon
+ * @param http_status code to use on success
+ * @param template basename of the template to load
+ * @param instance_id instance ID, used to compute static files URL
+ * @param taler_uri value for "Taler:" header to set, or NULL
+ * @param root JSON object to pass as the root context
+ * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was 
queued,
+ *         #GNUNET_SYSERR on failure (to queue an error)
+ */
+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,
+                        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);
+      if (MHD_YES !=
+          TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_NOT_ACCEPTABLE,
+                                      TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE,
+                                      template))
+        return GNUNET_SYSERR;
+      return GNUNET_NO;
+    }
+    /* Add default values to the context */
+    {
+      char *static_url = make_static_url (connection,
+                                          instance_id);
+      json_object_set (root,
+                       "static_url",
+                       json_string (static_url));
+      GNUNET_free (static_url);
+    }
+    if (0 !=
+        (eno = mustach_jansson (tmpl,
+                                root,
+                                &body,
+                                &body_size)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "mustach failed on template `%s' with error %d\n",
+                  template,
+                  eno);
+      if (MHD_YES !=
+          TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                      
TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE,
+                                      template))
+        return GNUNET_SYSERR;
+      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);
+        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"));
+
+  /* Actually return reply */
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (connection,
+                              http_status,
+                              reply);
+    MHD_destroy_response (reply);
+    if (MHD_NO == ret)
+      return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with a template's filename.
+ *
+ * @param cls closure
+ * @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;
+
+  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,
+                              "open",
+                              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;
+}
+
+
+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/",
+                     subsystem,
+                     path);
+    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/test1/.gitignore b/src/templating/test1/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/templating/test1/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test1/json b/src/templating/test1/json
new file mode 100644
index 00000000..5b2e3d83
--- /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": [ { "committer": "joe" }, { "reviewer": "avrel" 
}, { "committer": "william" } ] },
+    { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" 
}, { "committer": "greg" } ]  },
+    { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { 
"committer": "greg" } ]  }
+  ],
+  "person?": { "name": "Jon" },
+  "special": "----{{extra}}----",
+  "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 00000000..723f966c
--- /dev/null
+++ b/src/templating/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}} committers:{{#who}} 
{{committer}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+  Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! gros commentaire %)%
+%(%#repo%)%
+  <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% 
committers:%(%#who%)% %(%committer%)%%(%/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/test1/resu.ref b/src/templating/test1/resu.ref
new file mode 100644
index 00000000..545e5857
--- /dev/null
+++ b/src/templating/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  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   greg
+
+
+
+  Hi Jon!
+
+
+
+=====================================
+
+
+  <b>resque</b> reviewers:  avrel  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   greg
+
+=====================================
+
+ggggggggg
+----3.14159----
+jjjjjjjjj
+end
+
+#
+!
+~
+~
+/ see json pointers IETF RFC 6901
+^
+=
+:
+&gt;
diff --git a/src/templating/test2/.gitignore b/src/templating/test2/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/templating/test2/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test2/json b/src/templating/test2/json
new file mode 100644
index 00000000..8c668b3b
--- /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 00000000..aa6da707
--- /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 00000000..67d1f547
--- /dev/null
+++ b/src/templating/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/templating/test3/.gitignore b/src/templating/test3/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/templating/test3/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test3/json b/src/templating/test3/json
new file mode 100644
index 00000000..79278817
--- /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 00000000..5c490469
--- /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 00000000..e89ce902
--- /dev/null
+++ b/src/templating/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/templating/test4/.gitignore b/src/templating/test4/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/templating/test4/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test4/json b/src/templating/test4/json
new file mode 100644
index 00000000..a1083607
--- /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 00000000..003b9366
--- /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 00000000..2d48918a
--- /dev/null
+++ b/src/templating/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/templating/test5/.gitignore b/src/templating/test5/.gitignore
new file mode 100644
index 00000000..4d897daa
--- /dev/null
+++ b/src/templating/test5/.gitignore
@@ -0,0 +1,2 @@
+resu.last
+vg.last
diff --git a/src/templating/test5/json b/src/templating/test5/json
new file mode 100644
index 00000000..5b2e3d83
--- /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": [ { "committer": "joe" }, { "reviewer": "avrel" 
}, { "committer": "william" } ] },
+    { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" 
}, { "committer": "greg" } ]  },
+    { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { 
"committer": "greg" } ]  }
+  ],
+  "person?": { "name": "Jon" },
+  "special": "----{{extra}}----",
+  "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 00000000..44305df2
--- /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 00000000..d4a1d378
--- /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 00000000..33f1ead3
--- /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 00000000..821aaac3
--- /dev/null
+++ b/src/templating/test5/must3.mustache
@@ -0,0 +1,17 @@
+must3.mustache == BEGIN
+{{#repo}}
+  <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} 
{{committer}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+  Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! big comment %)%
+%(%#repo%)%
+  <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% 
committers:%(%#who%)% %(%committer%)%%(%/who%)%
+%(%/repo%)%
+=====================================
+must3.mustache == END
diff --git a/src/templating/test5/resu.ref b/src/templating/test5/resu.ref
new file mode 100644
index 00000000..afc39659
--- /dev/null
+++ b/src/templating/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  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   greg
+
+
+
+  Hi Jon!
+
+
+
+=====================================
+
+
+  <b>resque</b> reviewers:  avrel  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   greg
+
+=====================================
+must3.mustache == END
+
+=====================================
+Ensure must3 didn't change specials
+
+
+  Hi Jon!
+
+
+%(%#person?%)%
+  Hi %(%name%)%!
+%(%/person?%)%
+
diff --git a/src/templating/test5/special b/src/templating/test5/special
new file mode 100644
index 00000000..02d9975c
--- /dev/null
+++ b/src/templating/test5/special
@@ -0,0 +1 @@
+special ==SHOULD NOT BE SEEN==
diff --git a/src/templating/test5/special.mustache 
b/src/templating/test5/special.mustache
new file mode 100644
index 00000000..70a771fd
--- /dev/null
+++ b/src/templating/test5/special.mustache
@@ -0,0 +1 @@
+special.mustache ==SHOULD NOT BE SEEN==
diff --git a/src/templating/test6/.gitignore b/src/templating/test6/.gitignore
new file mode 100644
index 00000000..62f4d919
--- /dev/null
+++ b/src/templating/test6/.gitignore
@@ -0,0 +1,4 @@
+resu.last
+vg.last
+test-custom-write
+!test-custom-write.c
diff --git a/src/templating/test6/json b/src/templating/test6/json
new file mode 100644
index 00000000..5b2e3d83
--- /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": [ { "committer": "joe" }, { "reviewer": "avrel" 
}, { "committer": "william" } ] },
+    { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" 
}, { "committer": "greg" } ]  },
+    { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { 
"committer": "greg" } ]  }
+  ],
+  "person?": { "name": "Jon" },
+  "special": "----{{extra}}----",
+  "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 00000000..723f966c
--- /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}} committers:{{#who}} 
{{committer}}{{/who}}
+{{/repo}}
+
+{{#person?}}
+  Hi {{name}}!
+{{/person?}}
+
+{{=%(% %)%=}}
+=====================================
+%(%! gros commentaire %)%
+%(%#repo%)%
+  <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% 
committers:%(%#who%)% %(%committer%)%%(%/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 00000000..345d3aef
--- /dev/null
+++ b/src/templating/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  COMMITTERS: JOE  WILLIAM
+
+  <B>HUB</B> REVIEWERS:  AVREL  COMMITTERS: JACK  GREG
+
+  <B>RIP</B> REVIEWERS: JOE JACK  COMMITTERS:   GREG
+
+
+
+  HI JON!
+
+
+
+=====================================
+
+
+  <B>RESQUE</B> REVIEWERS:  AVREL  COMMITTERS: JOE  WILLIAM
+
+  <B>HUB</B> REVIEWERS:  AVREL  COMMITTERS: JACK  GREG
+
+  <B>RIP</B> REVIEWERS: JOE JACK  COMMITTERS:   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  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   greg
+
+
+
+  hi jon!
+
+
+
+=====================================
+
+
+  <b>resque</b> reviewers:  avrel  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   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  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   greg
+
+
+
+  Hi Jon!
+
+
+
+=====================================
+
+
+  <b>resque</b> reviewers:  avrel  committers: joe  william
+
+  <b>hub</b> reviewers:  avrel  committers: jack  greg
+
+  <b>rip</b> reviewers: joe jack  committers:   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 00000000..cc50a47c
--- /dev/null
+++ b/src/templating/test6/test-custom-write.c
@@ -0,0 +1,145 @@
+/*
+ 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 <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;
+       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;
+}
+
+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 = umustach_json_c(t, o, 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/test_mustach_jansson.c 
b/src/templating/test_mustach_jansson.c
new file mode 100644
index 00000000..11af86fa
--- /dev/null
+++ b/src/templating/test_mustach_jansson.c
@@ -0,0 +1,163 @@
+/*
+  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"
+
+
+static void
+assert_template (const char *template,
+                 json_t *root,
+                 const char *expected)
+{
+  char *r;
+  size_t sz;
+
+  GNUNET_assert (0 == mustach_jansson (template,
+                                       root,
+                                       &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 ();
+  json_t *contract;
+  /* 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 6 */
+  const char *t6 = "hello {{ v2!stringify }}";
+  const char *x6 = "hello [\n  \"foo\",\n  \"bar\"\n]";
+  /* test 7 */
+  const char *t7 = "amount: {{ amt!amount_decimal }} {{ amt!amount_currency 
}}";
+  const char *x7 = "amount: 123.00 EUR";
+  /* test 8 */
+  const char *t8 = "{{^ v4 }}fallback{{/ v4 }}";
+  const char *x8 = "fallback";
+
+  /* contract test 8 (contract) */
+  const char *tc = "summary: {{ summary!i18n }}";
+  const char *xc_en = "summary: ENGLISH";
+  const char *xc_de = "summary: DEUTSCH";
+  const char *xc_fr = "summary: FRANCAISE";
+
+  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")));
+  contract = json_pack ("{ s:s, s:{s:s, s:s}}",
+                        "summary",
+                        "ENGLISH",
+                        "summary_i18n",
+                        "de",
+                        "DEUTSCH",
+                        "fr",
+                        "FRANCAISE");
+  GNUNET_assert (NULL != contract);
+
+  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 (t6, root, x6);
+  assert_template (t7, root, x7);
+  assert_template (t8, root, x8);
+  assert_template (tc, contract, xc_en);
+
+  GNUNET_assert (0 ==
+                 json_object_set_new (contract,
+                                      "$language",
+                                      json_string ("de")));
+  assert_template (tc, contract, xc_de);
+
+  GNUNET_assert (0 ==
+                 json_object_set_new (contract,
+                                      "$language",
+                                      json_string ("fr")));
+  assert_template (tc, contract, xc_fr);
+
+  GNUNET_assert (0 ==
+                 json_object_set_new (contract,
+                                      "$language",
+                                      json_string ("it")));
+  assert_template (tc, contract, xc_en);
+  json_decref (root);
+  json_decref (contract);
+  return 0;
+}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]