gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated (1e2fdea5 -> 94fa05ec)


From: gnunet
Subject: [taler-exchange] branch master updated (1e2fdea5 -> 94fa05ec)
Date: Tue, 16 Aug 2022 13:57:29 +0200

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

grothoff pushed a change to branch master
in repository exchange.

    from 1e2fdea5 do not use illegal '+' in payment target type
     new d6f12190 -move templating library into exchange.git
     new 94fa05ec -move templating library into exchange.git

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                               |   2 +
 configure.ac                             |   1 +
 contrib/gana                             |   2 +-
 contrib/uncrustify_precommit             |   1 -
 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, 3458 insertions(+), 3 deletions(-)
 create mode 100644 src/include/taler_templating_lib.h
 create mode 100644 src/templating/.gitignore
 create mode 100644 src/templating/AUTHORS
 create mode 100644 src/templating/LICENSE-2.0.txt
 create mode 100644 src/templating/Makefile.am
 create mode 100644 src/templating/ORIGIN
 create mode 100644 src/templating/README.md
 create mode 100644 src/templating/meson.build
 create mode 100644 src/templating/mustach-jansson.c
 create mode 100644 src/templating/mustach-jansson.h
 create mode 100644 src/templating/mustach-tool.c
 create mode 100644 src/templating/mustach.c
 create mode 100644 src/templating/mustach.h
 create mode 100755 src/templating/run-original-tests.sh
 create mode 100644 src/templating/templating_api.c
 create mode 100644 src/templating/test1/.gitignore
 create mode 100644 src/templating/test1/json
 create mode 100644 src/templating/test1/must
 create mode 100644 src/templating/test1/resu.ref
 create mode 100644 src/templating/test2/.gitignore
 create mode 100644 src/templating/test2/json
 create mode 100644 src/templating/test2/must
 create mode 100644 src/templating/test2/resu.ref
 create mode 100644 src/templating/test3/.gitignore
 create mode 100644 src/templating/test3/json
 create mode 100644 src/templating/test3/must
 create mode 100644 src/templating/test3/resu.ref
 create mode 100644 src/templating/test4/.gitignore
 create mode 100644 src/templating/test4/json
 create mode 100644 src/templating/test4/must
 create mode 100644 src/templating/test4/resu.ref
 create mode 100644 src/templating/test5/.gitignore
 create mode 100644 src/templating/test5/json
 create mode 100644 src/templating/test5/must
 create mode 100644 src/templating/test5/must2
 create mode 100644 src/templating/test5/must2.mustache
 create mode 100644 src/templating/test5/must3.mustache
 create mode 100644 src/templating/test5/resu.ref
 create mode 100644 src/templating/test5/special
 create mode 100644 src/templating/test5/special.mustache
 create mode 100644 src/templating/test6/.gitignore
 create mode 100644 src/templating/test6/json
 create mode 100644 src/templating/test6/must
 create mode 100644 src/templating/test6/resu.ref
 create mode 100644 src/templating/test6/test-custom-write.c
 create mode 100644 src/templating/test_mustach_jansson.c

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..b932d610 100755
--- a/contrib/uncrustify_precommit
+++ b/contrib/uncrustify_precommit
@@ -1,7 +1,6 @@
 #!/bin/sh
 
 # use as .git/hooks/pre-commit
-
 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]