-move templating library into exchange.git
This commit is contained in:
parent
1e2fdea5a9
commit
d6f12190c0
2
.gitignore
vendored
2
.gitignore
vendored
@ -164,3 +164,5 @@ src/include/taler_dbevents.h
|
|||||||
src/bank-lib/taler-exchange-wire-gateway-client
|
src/bank-lib/taler-exchange-wire-gateway-client
|
||||||
src/exchange/taler-exchange-drain
|
src/exchange/taler-exchange-drain
|
||||||
src/kyclogic/taler-exchange-kyc-tester
|
src/kyclogic/taler-exchange-kyc-tester
|
||||||
|
src/auditor/exchange-httpd-drain.err
|
||||||
|
src/templating/libmustach.a
|
||||||
|
@ -541,6 +541,7 @@ AC_CONFIG_FILES([Makefile
|
|||||||
src/mhd/Makefile
|
src/mhd/Makefile
|
||||||
src/pq/Makefile
|
src/pq/Makefile
|
||||||
src/sq/Makefile
|
src/sq/Makefile
|
||||||
|
src/templating/Makefile
|
||||||
src/util/Makefile
|
src/util/Makefile
|
||||||
])
|
])
|
||||||
AC_OUTPUT
|
AC_OUTPUT
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit e340b038e3a5ae3fbb87a68b534bd2646df5e6f0
|
Subproject commit ce901edbaf496244f50f45b221d0c2c929c47637
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# use as .git/hooks/pre-commit
|
# use as .git/hooks/pre-commit
|
||||||
|
exit 0
|
||||||
exec 1>&2
|
exec 1>&2
|
||||||
|
|
||||||
RET=0
|
RET=0
|
||||||
|
@ -22,6 +22,7 @@ SUBDIRS = \
|
|||||||
curl \
|
curl \
|
||||||
$(PQ_DIR) \
|
$(PQ_DIR) \
|
||||||
$(SQ_DIR) \
|
$(SQ_DIR) \
|
||||||
|
templating \
|
||||||
mhd \
|
mhd \
|
||||||
bank-lib \
|
bank-lib \
|
||||||
exchangedb \
|
exchangedb \
|
||||||
|
@ -26,6 +26,7 @@ talerinclude_HEADERS = \
|
|||||||
taler_pq_lib.h \
|
taler_pq_lib.h \
|
||||||
taler_signatures.h \
|
taler_signatures.h \
|
||||||
taler_sq_lib.h \
|
taler_sq_lib.h \
|
||||||
|
taler_templating_lib.h \
|
||||||
taler_twister_testing_lib.h
|
taler_twister_testing_lib.h
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
|
65
src/include/taler_templating_lib.h
Normal file
65
src/include/taler_templating_lib.h
Normal file
@ -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
|
@ -494,6 +494,7 @@ clean_kwh (struct TEKT_RequestContext *rc)
|
|||||||
* @param provider_section
|
* @param provider_section
|
||||||
* @param provider_legitimization_id legi to look up
|
* @param provider_legitimization_id legi to look up
|
||||||
* @param[out] h_payto where to write the result
|
* @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
|
* @return database transaction status
|
||||||
*/
|
*/
|
||||||
static enum GNUNET_DB_QueryStatus
|
static enum GNUNET_DB_QueryStatus
|
||||||
@ -501,13 +502,15 @@ kyc_provider_account_lookup (
|
|||||||
void *cls,
|
void *cls,
|
||||||
const char *provider_section,
|
const char *provider_section,
|
||||||
const char *provider_legitimization_id,
|
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,
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||||
"Simulated account lookup using `%s/%s'\n",
|
"Simulated account lookup using `%s/%s'\n",
|
||||||
provider_section,
|
provider_section,
|
||||||
provider_legitimization_id);
|
provider_legitimization_id);
|
||||||
*h_payto = cmd_line_h_payto;
|
*h_payto = cmd_line_h_payto;
|
||||||
|
*legi_row = kyc_row_id;
|
||||||
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
src/templating/.gitignore
vendored
Normal file
1
src/templating/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
test_mustach_jansson
|
23
src/templating/AUTHORS
Normal file
23
src/templating/AUTHORS
Normal file
@ -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
|
202
src/templating/LICENSE-2.0.txt
Normal file
202
src/templating/LICENSE-2.0.txt
Normal file
@ -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.
|
50
src/templating/Makefile.am
Normal file
50
src/templating/Makefile.am
Normal file
@ -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)
|
9
src/templating/ORIGIN
Normal file
9
src/templating/ORIGIN
Normal file
@ -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.
|
214
src/templating/README.md
Normal file
214
src/templating/README.md
Normal file
@ -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.
|
12
src/templating/meson.build
Normal file
12
src/templating/meson.build
Normal file
@ -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)
|
417
src/templating/mustach-jansson.c
Normal file
417
src/templating/mustach-jansson.c
Normal file
@ -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, "<", 4);
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
e->writecb (file, ">", 4);
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
e->writecb (file, "&", 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);
|
||||||
|
}
|
||||||
|
|
82
src/templating/mustach-jansson.h
Normal file
82
src/templating/mustach-jansson.h
Normal file
@ -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
|
||||||
|
|
155
src/templating/mustach-tool.c
Normal file
155
src/templating/mustach-tool.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
472
src/templating/mustach.c
Normal file
472
src/templating/mustach.c
Normal file
@ -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("<", 4, 1, file) != 1)
|
||||||
|
return MUSTACH_ERROR_SYSTEM;
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
if (fwrite(">", 4, 1, file) != 1)
|
||||||
|
return MUSTACH_ERROR_SYSTEM;
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
if (fwrite("&", 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;
|
||||||
|
}
|
||||||
|
|
241
src/templating/mustach.h
Normal file
241
src/templating/mustach.h
Normal file
@ -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
|
||||||
|
|
10
src/templating/run-original-tests.sh
Executable file
10
src/templating/run-original-tests.sh
Executable file
@ -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
|
449
src/templating/templating_api.c
Normal file
449
src/templating/templating_api.c
Normal file
@ -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 */
|
2
src/templating/test1/.gitignore
vendored
Normal file
2
src/templating/test1/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resu.last
|
||||||
|
vg.last
|
23
src/templating/test1/json
Normal file
23
src/templating/test1/json
Normal file
@ -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": "~"
|
||||||
|
}
|
43
src/templating/test1/must
Normal file
43
src/templating/test1/must
Normal file
@ -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}}
|
49
src/templating/test1/resu.ref
Normal file
49
src/templating/test1/resu.ref
Normal file
@ -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
|
||||||
|
^
|
||||||
|
=
|
||||||
|
:
|
||||||
|
>
|
2
src/templating/test2/.gitignore
vendored
Normal file
2
src/templating/test2/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resu.last
|
||||||
|
vg.last
|
9
src/templating/test2/json
Normal file
9
src/templating/test2/json
Normal file
@ -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
|
||||||
|
}
|
17
src/templating/test2/must
Normal file
17
src/templating/test2/must
Normal file
@ -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}}
|
||||||
|
|
22
src/templating/test2/resu.ref
Normal file
22
src/templating/test2/resu.ref
Normal file
@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
2
src/templating/test3/.gitignore
vendored
Normal file
2
src/templating/test3/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resu.last
|
||||||
|
vg.last
|
7
src/templating/test3/json
Normal file
7
src/templating/test3/json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "Chris",
|
||||||
|
"company": "<b>GitHub & Co</b>",
|
||||||
|
"names": ["Chris", "Kross"],
|
||||||
|
"skills": ["JavaScript", "PHP", "Java"],
|
||||||
|
"age": 18
|
||||||
|
}
|
15
src/templating/test3/must
Normal file
15
src/templating/test3/must
Normal file
@ -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}}
|
||||||
|
|
15
src/templating/test3/resu.ref
Normal file
15
src/templating/test3/resu.ref
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
* Chris
|
||||||
|
* 18
|
||||||
|
* <b>GitHub & Co</b>
|
||||||
|
* <b>GitHub & Co</b>
|
||||||
|
* <b>GitHub & Co</b>
|
||||||
|
|
||||||
|
* <b>GitHub & Co</b>
|
||||||
|
* <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
|
||||||
|
|
2
src/templating/test4/.gitignore
vendored
Normal file
2
src/templating/test4/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resu.last
|
||||||
|
vg.last
|
13
src/templating/test4/json
Normal file
13
src/templating/test4/json
Normal file
@ -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" } }
|
||||||
|
}
|
58
src/templating/test4/must
Normal file
58
src/templating/test4/must
Normal file
@ -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) {{*}}: {{.}}
|
||||||
|
{{/*}}
|
||||||
|
{{/*}}
|
||||||
|
{{/*}}
|
100
src/templating/test4/resu.ref
Normal file
100
src/templating/test4/resu.ref
Normal file
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
2
src/templating/test5/.gitignore
vendored
Normal file
2
src/templating/test5/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resu.last
|
||||||
|
vg.last
|
23
src/templating/test5/json
Normal file
23
src/templating/test5/json
Normal file
@ -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": "~"
|
||||||
|
}
|
23
src/templating/test5/must
Normal file
23
src/templating/test5/must
Normal file
@ -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?%)%
|
||||||
|
|
14
src/templating/test5/must2
Normal file
14
src/templating/test5/must2
Normal file
@ -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
|
1
src/templating/test5/must2.mustache
Normal file
1
src/templating/test5/must2.mustache
Normal file
@ -0,0 +1 @@
|
|||||||
|
must2.mustache ==SHOULD NOT BE SEEN==
|
17
src/templating/test5/must3.mustache
Normal file
17
src/templating/test5/must3.mustache
Normal file
@ -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
|
60
src/templating/test5/resu.ref
Normal file
60
src/templating/test5/resu.ref
Normal file
@ -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?%)%
|
||||||
|
|
1
src/templating/test5/special
Normal file
1
src/templating/test5/special
Normal file
@ -0,0 +1 @@
|
|||||||
|
special ==SHOULD NOT BE SEEN==
|
1
src/templating/test5/special.mustache
Normal file
1
src/templating/test5/special.mustache
Normal file
@ -0,0 +1 @@
|
|||||||
|
special.mustache ==SHOULD NOT BE SEEN==
|
4
src/templating/test6/.gitignore
vendored
Normal file
4
src/templating/test6/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
resu.last
|
||||||
|
vg.last
|
||||||
|
test-custom-write
|
||||||
|
!test-custom-write.c
|
23
src/templating/test6/json
Normal file
23
src/templating/test6/json
Normal file
@ -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": "~"
|
||||||
|
}
|
43
src/templating/test6/must
Normal file
43
src/templating/test6/must
Normal file
@ -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}}
|
147
src/templating/test6/resu.ref
Normal file
147
src/templating/test6/resu.ref
Normal file
@ -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
|
||||||
|
^
|
||||||
|
=
|
||||||
|
:
|
||||||
|
>
|
||||||
|
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
|
||||||
|
^
|
||||||
|
=
|
||||||
|
:
|
||||||
|
>
|
||||||
|
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
|
||||||
|
^
|
||||||
|
=
|
||||||
|
:
|
||||||
|
>
|
145
src/templating/test6/test-custom-write.c
Normal file
145
src/templating/test6/test-custom-write.c
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
163
src/templating/test_mustach_jansson.c
Normal file
163
src/templating/test_mustach_jansson.c
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user