-move templating library into exchange.git

This commit is contained in:
Christian Grothoff 2022-08-16 13:56:04 +02:00
parent 1e2fdea5a9
commit d6f12190c0
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
53 changed files with 3459 additions and 3 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -22,6 +22,7 @@ SUBDIRS = \
curl \ curl \
$(PQ_DIR) \ $(PQ_DIR) \
$(SQ_DIR) \ $(SQ_DIR) \
templating \
mhd \ mhd \
bank-lib \ bank-lib \
exchangedb \ exchangedb \

View File

@ -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 = \

View 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

View File

@ -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
View File

@ -0,0 +1 @@
test_mustach_jansson

23
src/templating/AUTHORS Normal file
View 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

View 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.

View 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
View 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
View 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.

View 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)

View 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, "&lt;", 4);
break;
case '>':
e->writecb (file, "&gt;", 4);
break;
case '&':
e->writecb (file, "&amp;", 5);
break;
default:
e->writecb (file, buffer, 1);
break;
}
buffer++;
}
while(--size);
return MUSTACH_OK;
}
static int
enter(void *closure, const char *name)
{
struct JanssonClosure *e = closure;
json_t *o = find(e, name);
if (++e->depth >= MUSTACH_MAX_DEPTH)
return MUSTACH_ERROR_TOO_DEEP;
if (json_is_object (o))
{
if (e->found_iter)
{
void *iter = json_object_iter (o);
if (NULL == iter)
{
e->depth--;
return 0;
}
e->stack[e->depth].is_objiter = 1;
e->stack[e->depth].iter = iter;
e->stack[e->depth].obj = json_object_iter_value (iter);
e->stack[e->depth].cont = o;
}
else
{
e->stack[e->depth].is_objiter = 0;
e->stack[e->depth].obj = o;
e->stack[e->depth].cont = o;
}
return 1;
}
if (json_is_array (o))
{
unsigned int size = json_array_size (o);
if (size == 0)
{
e->depth--;
return 0;
}
e->stack[e->depth].count = size;
e->stack[e->depth].cont = o;
e->stack[e->depth].obj = json_array_get (o, 0);
e->stack[e->depth].index = 0;
e->stack[e->depth].is_objiter = 0;
return 1;
}
e->depth--;
return 0;
}
static int
next (void *closure)
{
struct JanssonClosure *e = closure;
struct Context *ctx;
if (e->depth <= 0)
return MUSTACH_ERROR_CLOSING;
ctx = &e->stack[e->depth];
if (ctx->is_objiter)
{
ctx->iter = json_object_iter_next (ctx->obj, ctx->iter);
if (NULL == ctx->iter)
return 0;
ctx->obj = json_object_iter_value (ctx->iter);
return 1;
}
ctx->index++;
if (ctx->index >= ctx->count)
return 0;
ctx->obj = json_array_get (ctx->cont, ctx->index);
return 1;
}
static int
leave (void *closure)
{
struct JanssonClosure *e = closure;
if (e->depth <= 0)
return MUSTACH_ERROR_CLOSING;
e->depth--;
return 0;
}
static void
freecb (void *v)
{
free (v);
}
static int
get (void *closure, const char *name, struct mustach_sbuf *sbuf)
{
struct JanssonClosure *e = closure;
json_t *obj;
if ( (0 == strcmp (name, "*") ) &&
(e->stack[e->depth].is_objiter ) )
{
sbuf->value = json_object_iter_key (e->stack[e->depth].iter);
return MUSTACH_OK;
}
obj = find (e, name);
if (NULL != obj)
{
switch (e->found_bang)
{
case BANG_I18N:
case BANG_NONE:
{
const char *s = json_string_value (obj);
if (NULL != s)
{
sbuf->value = s;
return MUSTACH_OK;
}
}
break;
case BANG_STRINGIFY:
sbuf->value = json_dumps (obj, JSON_INDENT (2));
sbuf->freecb = freecb;
return MUSTACH_OK;
case BANG_AMOUNT_DECIMAL:
{
char *s;
char *c;
if (!json_is_string (obj))
break;
s = strdup (json_string_value (obj));
c = strchr (s, ':');
if (NULL != c)
*c = 0;
sbuf->value = s;
sbuf->freecb = freecb;
return MUSTACH_OK;
}
break;
case BANG_AMOUNT_CURRENCY:
{
const char *s;
if (!json_is_string (obj))
break;
s = json_string_value (obj);
s = strchr (s, ':');
if (NULL == s)
break;
sbuf->value = s + 1;
return MUSTACH_OK;
}
break;
default:
break;
}
}
sbuf->value = "";
return MUSTACH_OK;
}
static struct mustach_itf itf = {
.start = start,
.put = NULL,
.enter = enter,
.next = next,
.leave = leave,
.partial =NULL,
.get = get,
.emit = NULL,
.stop = NULL
};
static struct mustach_itf itfuw = {
.start = start,
.put = NULL,
.enter = enter,
.next = next,
.leave = leave,
.partial = NULL,
.get = get,
.emit = emituw,
.stop = NULL
};
int fmustach_jansson (const char *template, json_t *root, FILE *file)
{
struct JanssonClosure e = { 0 };
e.root = root;
return fmustach(template, &itf, &e, file);
}
int fdmustach_jansson (const char *template, json_t *root, int fd)
{
struct JanssonClosure e = { 0 };
e.root = root;
return fdmustach(template, &itf, &e, fd);
}
int mustach_jansson (const char *template, json_t *root, char **result, size_t *size)
{
struct JanssonClosure e = { 0 };
e.root = root;
e.writecb = NULL;
return mustach(template, &itf, &e, result, size);
}
int umustach_jansson (const char *template, json_t *root, mustach_jansson_write_cb writecb, void *closure)
{
struct JanssonClosure e = { 0 };
e.root = root;
e.writecb = writecb;
return fmustach(template, &itfuw, &e, closure);
}

View 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

View 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
View 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("&lt;", 4, 1, file) != 1)
return MUSTACH_ERROR_SYSTEM;
break;
case '>':
if (fwrite("&gt;", 4, 1, file) != 1)
return MUSTACH_ERROR_SYSTEM;
break;
case '&':
if (fwrite("&amp;", 5, 1, file) != 1)
return MUSTACH_ERROR_SYSTEM;
break;
default: break;
}
}
i = j;
}
return MUSTACH_OK;
}
static int iwrap_put(void *closure, const char *name, int escape, FILE *file)
{
struct iwrap *iwrap = closure;
int rc;
struct mustach_sbuf sbuf;
size_t length;
sbuf_reset(&sbuf);
rc = iwrap->get(iwrap->closure, name, &sbuf);
if (rc >= 0) {
length = strlen(sbuf.value);
if (length)
rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file);
sbuf_release(&sbuf);
}
return rc;
}
static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf)
{
struct iwrap *iwrap = closure;
int rc;
FILE *file;
size_t size;
char *result;
result = NULL;
file = memfile_open(&result, &size);
if (file == NULL)
rc = MUSTACH_ERROR_SYSTEM;
else {
rc = iwrap->put(iwrap->closure_put, name, 0, file);
if (rc < 0)
memfile_abort(file, &result, &size);
else {
rc = memfile_close(file, &result, &size);
if (rc == 0) {
sbuf->value = result;
sbuf->freecb = free;
}
}
}
return rc;
}
static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr)
{
struct mustach_sbuf sbuf;
char name[MUSTACH_MAX_LENGTH + 1], c, *tmp;
const char *beg, *term;
struct { const char *name, *again; size_t length; int enabled, entered; } stack[MUSTACH_MAX_DEPTH];
size_t oplen, cllen, len, l;
int depth, rc, enabled;
enabled = 1;
oplen = strlen(opstr);
cllen = strlen(clstr);
depth = 0;
for(;;) {
beg = strstr(template, opstr);
if (beg == NULL) {
/* no more mustach */
if (enabled && template[0]) {
rc = iwrap->emit(iwrap->closure, template, strlen(template), 0, file);
if (rc < 0)
return rc;
}
return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK;
}
if (enabled && beg != template) {
rc = iwrap->emit(iwrap->closure, template, (size_t)(beg - template), 0, file);
if (rc < 0)
return rc;
}
beg += oplen;
term = strstr(beg, clstr);
if (term == NULL)
return MUSTACH_ERROR_UNEXPECTED_END;
template = term + cllen;
len = (size_t)(term - beg);
c = *beg;
switch(c) {
case '!':
case '=':
break;
case '{':
for (l = 0 ; clstr[l] == '}' ; l++);
if (clstr[l]) {
if (!len || beg[len-1] != '}')
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
len--;
} else {
if (term[l] != '}')
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
template++;
}
c = '&';
/*@fallthrough@*/
case '^':
case '#':
case '/':
case '&':
case '>':
#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
case ':':
#endif
beg++; len--;
default:
while (len && isspace(beg[0])) { beg++; len--; }
while (len && isspace(beg[len-1])) len--;
#if !defined(NO_ALLOW_EMPTY_TAG)
if (len == 0)
return MUSTACH_ERROR_EMPTY_TAG;
#endif
if (len > MUSTACH_MAX_LENGTH)
return MUSTACH_ERROR_TAG_TOO_LONG;
memcpy(name, beg, len);
name[len] = 0;
break;
}
switch(c) {
case '!':
/* comment */
/* nothing to do */
break;
case '=':
/* defines separators */
if (len < 5 || beg[len - 1] != '=')
return MUSTACH_ERROR_BAD_SEPARATORS;
beg++;
len -= 2;
for (l = 0; l < len && !isspace(beg[l]) ; l++);
if (l == len)
return MUSTACH_ERROR_BAD_SEPARATORS;
oplen = l;
tmp = alloca(oplen + 1);
memcpy(tmp, beg, oplen);
tmp[oplen] = 0;
opstr = tmp;
while (l < len && isspace(beg[l])) l++;
if (l == len)
return MUSTACH_ERROR_BAD_SEPARATORS;
cllen = len - l;
tmp = alloca(cllen + 1);
memcpy(tmp, beg + l, cllen);
tmp[cllen] = 0;
clstr = tmp;
break;
case '^':
case '#':
/* begin section */
if (depth == MUSTACH_MAX_DEPTH)
return MUSTACH_ERROR_TOO_DEEP;
rc = enabled;
if (rc) {
rc = iwrap->enter(iwrap->closure, name);
if (rc < 0)
return rc;
}
stack[depth].name = beg;
stack[depth].again = template;
stack[depth].length = len;
stack[depth].enabled = enabled;
stack[depth].entered = rc;
if ((c == '#') == (rc == 0))
enabled = 0;
depth++;
break;
case '/':
/* end section */
if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
return MUSTACH_ERROR_CLOSING;
rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0;
if (rc < 0)
return rc;
if (rc) {
template = stack[depth++].again;
} else {
enabled = stack[depth].enabled;
if (enabled && stack[depth].entered)
iwrap->leave(iwrap->closure);
}
break;
case '>':
/* partials */
if (enabled) {
sbuf_reset(&sbuf);
rc = iwrap->partial(iwrap->closure_partial, name, &sbuf);
if (rc >= 0) {
rc = process(sbuf.value, iwrap, file, opstr, clstr);
sbuf_release(&sbuf);
}
if (rc < 0)
return rc;
}
break;
default:
/* replacement */
if (enabled) {
rc = iwrap->put(iwrap->closure_put, name, c != '&', file);
if (rc < 0)
return rc;
}
break;
}
}
}
int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file)
{
int rc;
struct iwrap iwrap;
/* check validity */
if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get))
return MUSTACH_ERROR_INVALID_ITF;
/* init wrap structure */
iwrap.closure = closure;
if (itf->put) {
iwrap.put = itf->put;
iwrap.closure_put = closure;
} else {
iwrap.put = iwrap_put;
iwrap.closure_put = &iwrap;
}
if (itf->partial) {
iwrap.partial = itf->partial;
iwrap.closure_partial = closure;
} else if (itf->get) {
iwrap.partial = itf->get;
iwrap.closure_partial = closure;
} else {
iwrap.partial = iwrap_partial;
iwrap.closure_partial = &iwrap;
}
iwrap.emit = itf->emit ? itf->emit : iwrap_emit;
iwrap.enter = itf->enter;
iwrap.next = itf->next;
iwrap.leave = itf->leave;
iwrap.get = itf->get;
/* process */
rc = itf->start ? itf->start(closure) : 0;
if (rc == 0)
rc = process(template, &iwrap, file, "{{", "}}");
if (itf->stop)
itf->stop(closure, rc);
return rc;
}
int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd)
{
int rc;
FILE *file;
file = fdopen(fd, "w");
if (file == NULL) {
rc = MUSTACH_ERROR_SYSTEM;
errno = ENOMEM;
} else {
rc = fmustach(template, itf, closure, file);
fclose(file);
}
return rc;
}
int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size)
{
int rc;
FILE *file;
size_t s;
*result = NULL;
if (size == NULL)
size = &s;
file = memfile_open(result, size);
if (file == NULL)
rc = MUSTACH_ERROR_SYSTEM;
else {
rc = fmustach(template, itf, closure, file);
if (rc < 0)
memfile_abort(file, result, size);
else
rc = memfile_close(file, result, size);
}
return rc;
}

241
src/templating/mustach.h Normal file
View 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

View 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

View 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
View File

@ -0,0 +1,2 @@
resu.last
vg.last

23
src/templating/test1/json Normal file
View 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
View 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}}

View 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
^
=
:
&gt;

2
src/templating/test2/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
resu.last
vg.last

View 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
View 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}}

View 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
View File

@ -0,0 +1,2 @@
resu.last
vg.last

View 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
View 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}}

View File

@ -0,0 +1,15 @@
* Chris
* 18
* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
* <b>GitHub & Co</b>
* <b>GitHub & Co</b>
* &lt;b&gt;GitHub &amp; Co&lt;/b&gt;
* <b>GitHub & Co</b>
* <b>GitHub & Co</b>
* <ul><li>Chris</li><li>Kross</li></ul>
* skills: <ul><li>JavaScript</li><li>PHP</li><li>Java</li></ul>
* age: 18

2
src/templating/test4/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
resu.last
vg.last

13
src/templating/test4/json Normal file
View 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
View 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) {{*}}: {{.}}
{{/*}}
{{/*}}
{{/*}}

View 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
View File

@ -0,0 +1,2 @@
resu.last
vg.last

23
src/templating/test5/json Normal file
View 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
View 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?%)%

View 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

View File

@ -0,0 +1 @@
must2.mustache ==SHOULD NOT BE SEEN==

View 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

View 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?%)%

View File

@ -0,0 +1 @@
special ==SHOULD NOT BE SEEN==

View File

@ -0,0 +1 @@
special.mustache ==SHOULD NOT BE SEEN==

4
src/templating/test6/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
resu.last
vg.last
test-custom-write
!test-custom-write.c

23
src/templating/test6/json Normal file
View 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
View 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}}

View 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
^
=
:
&GT;
hello chris
you have just won 10000 dollars!
well, 6000 dollars, after taxes.
shown.
no person
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
hi jon!
=====================================
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
=====================================
ggggggggg
----3.14159----
jjjjjjjjj
end
#
!
~
~
/ see json pointers ietf rfc 6901
^
=
:
&gt;
Hello Chris
You have just won 10000 dollars!
Well, 6000 dollars, after taxes.
Shown.
No person
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
Hi Jon!
=====================================
<b>resque</b> reviewers: avrel committers: joe william
<b>hub</b> reviewers: avrel committers: jack greg
<b>rip</b> reviewers: joe jack committers: greg
=====================================
ggggggggg
----3.14159----
jjjjjjjjj
end
#
!
~
~
/ see json pointers IETF RFC 6901
^
=
:
&gt;

View 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;
}

View 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;
}