diff options
Diffstat (limited to 'src/templating')
71 files changed, 5343 insertions, 1175 deletions
diff --git a/src/templating/.gitignore b/src/templating/.gitignore index b2bf6ef99..9ed2f3ff9 100644 --- a/src/templating/.gitignore +++ b/src/templating/.gitignore @@ -1 +1,3 @@ test_mustach_jansson +taler-mustach-tool +mustach diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS index 2fcc60437..110b36981 100644 --- a/src/templating/AUTHORS +++ b/src/templating/AUTHORS @@ -4,20 +4,35 @@ Main author: Contributors: Abhishek Mishra Atlas + Ben Beasley + Christian Grothoff + Dominik Kummer + Gabriel Zachmann Harold L Marzan + Kurt Jung Lailton Fernando Mariano + Lucas Ramage + Paramtamtam + RekGRpth + Ryan Fox Sami Kerola Sijmen J. Mulder + Steve-Chavez Tomasz Sieprawski Packagers: pkgsrc: Sijmen J. Mulder alpine linux: Lucas Ramage + RPM & DEB: Marcus Hardt Thanks to issue submitters: Dante Torres @fabbe + Felix von Leitner Johann Oskarsson Mark Bucciarelli + Nigel Hathaway Paul Wisehart + Tarik @tr001 Thierry Fournier + SASU OKFT diff --git a/src/templating/CHANGELOG.md b/src/templating/CHANGELOG.md new file mode 100644 index 000000000..003652ebf --- /dev/null +++ b/src/templating/CHANGELOG.md @@ -0,0 +1,161 @@ +1.2.7 (2024-03-21) +------------------ + +New: + - fallback to default when mustach_wrap_get_partial + returns MUSTACH_ERROR_PARTIAL_NOT_FOUND + - remove at compile time the load of files for templates + if MUSTACH_LOAD_TEMPLATE is defined as 0 + - add compile time flag MUSTACH_SAFE for enforcing + safety behaviours + +Fix: + - selection of subitem by index (#47) + - get latest iterated key when getting key name (#52) + - allow tests without valgrind + - avoid recursive template expansion (#55) + +1.2.6 (2024-01-08) +------------------ + +Fix: + - improve naming (#42) + - magical spaces in recursive partials (#43) + - installation when tool isn't built + - correct detection of falsey values (#45) + +Minor: + - update to newer reference tests + +1.2.5 (2023-02-18) +------------------ + +Fix: + - Don't override CFLAGS in Makefile + - Use of $(INSTALL) in Makefile for setting options + +Minor: + - Orthograf of 'instantiate' + +1.2.4 (2023-01-02) +------------------ + +Fix: + - Latent SIGSEGV using cJSON + +1.2.3 (2022-12-21) +------------------ + +New: + - Flag Mustach_With_ErrorUndefined (and option --strict for the tool) + for returning a requested tag is not defined + - Test of specifications in separate directory + +Fix: + - Version printing is now okay + - Compiling libraries on Darwin (no soname but install_name) + - Compiling test6 with correct flags + - Update test from specifications + - Better use of valgrind reports + +1.2.2 (2021-10-28) +------------------ + +Fix: + - SONAME of libmustach-json-c.so + +1.2.1 (2021-10-19) +------------------ + +New: + - Add SONAME in libraries. + - Flag Mustach_With_PartialDataFirst to switch the + policy of resolving partials. + +Fix: + - Identification of types in cJSON + +1.2.0 (2021-08-24) +------------------ + +New: + - Add hook 'mustach_wrap_get_partial' for handling partials. + - Add test of mustache specifications https://github.com/mustache/spec. + +Changes: + - Mustach_With_SingleDot is always set. + - Mustach_With_IncPartial is always set. + - Mustach_With_AllExtensions is changed to use currently known extensions. + - Output of tests changed. + - Makefile improved. + - Partials are first searched as file then in current selection. + - Improved management of delimiters. + +Fixes: + - Improved output accordingly to https://github.com/mustache/spec: + - escaping of quote " + - interpolating null with empty string + - removal of empty lines with standalone tag + - don't enter section if null + - indentation of partials + - comment improved for get of mustach_wrap_itf. + +1.1.1 (2021-08-19) +------------------ +Fixes: + - Avoid conflicting with getopt. + - Remove unexpected build artifact. + - Handle correctly a size of 0. + +1.1.0 (2021-05-01) +------------------ +New: + - API refactored to take lengths to ease working with partial or + non-NULL-terminated strings. (ABI break) + +Fixes: + - Use correct int type for jansson (json_int_t instead of int64_t). + - JSON output of different backends is now the same. + +1.0 (2021-04-28, retracted) +--------------------------- +Legal: + - License changed to ISC. + +Fixes: + - Possible data leak in memfile_open() by clearing buffers. + - Fix build on Solaris-likes by including alloca.h. + - Fix Windows build by including malloc.h, using size_t instead of + ssize_t, and using the standard ternary operator syntax. + - Fix JSON in test3 by using double quote characters. + - Fix installation in alternative directories such as + /opt/pkg/lib on macOS by setting install_name. + - Normalise return values in compare() implementations. + +New: + - Support for cJSON and jansson libraries. + - Version info now embedded at build time and shown with mustach(1) + usage. + - Versioned so-names (e.g. libxlsx.so.1.0). + - BINDIR, LIBDIR and INCLUDEDIR variables in Makefile. + - New mustach-wrap.{c,h} to ease implementation new libraries, + extracted and refactored from the existing implementations. + - Makefile now supports 3 modes: single libmustach (default), split + libmustache-core etc, and both. + - Any or all backends (json-c, jansson, etc) can be enabled at compile + time. By default, all available libraries are used. + - mustach(1) can use any JSON backend instead of only json-c. + - MUSTACH_COMPATIBLE_0_99 can be defined for backwards source + compatibility. + - 'No extensions' can now be set Mustach_With_NoExtensions instead of + passing 0. + - pkgconfig (.pc) file for library. + - Manual page for mustach(1). + +Changed: + - Many renames. + - Maximum tag length increased from 1024 to 4096. + - Other headers include json-c.h instead of using forward declarations. + - mustach(1) reads from /dev/stdin instead of fd 0. + - Several structures are now taken as const. + - New/changed Makefile targets. diff --git a/src/templating/LICENSE-2.0.txt b/src/templating/LICENSE-2.0.txt deleted file mode 100644 index d64569567..000000000 --- a/src/templating/LICENSE-2.0.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/templating/LICENSE.txt b/src/templating/LICENSE.txt new file mode 100644 index 000000000..495aeefd5 --- /dev/null +++ b/src/templating/LICENSE.txt @@ -0,0 +1,14 @@ + +Copyright (c) 2017-2020 by José Bollo + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, +OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. diff --git a/src/templating/Makefile.am b/src/templating/Makefile.am index 4cd180095..a79b109d1 100644 --- a/src/templating/Makefile.am +++ b/src/templating/Makefile.am @@ -6,6 +6,19 @@ if USE_COVERAGE XLIB = -lgcov endif +noinst_PROGRAMS = \ + taler-mustach-tool + +taler_mustach_tool_SOURCES = \ + mustach-tool.c \ + mustach-jansson.h +taler_mustach_tool_LDADD = \ + libmustach.la \ + -ljansson +taler_mustach_tool_CFLAGS = \ + -DTOOL=MUSTACH_TOOL_JANSSON \ + -DMUSTACH_SAFE=1 \ + -DMUSTACH_LOAD_TEMPLATE=0 lib_LTLIBRARIES = \ libtalertemplating.la @@ -15,21 +28,27 @@ noinst_LTLIBRARIES = \ libtalertemplating_la_SOURCES = \ mustach.c mustach.h \ + mustach-wrap.c mustach-wrap.h \ mustach-jansson.c mustach-jansson.h \ templating_api.c libtalertemplating_la_LIBADD = \ $(top_builddir)/src/mhd/libtalermhd.la \ $(top_builddir)/src/util/libtalerutil.la \ - -ljansson \ + -lmicrohttpd \ + -lgnunetjson \ -lgnunetutil \ + -ljansson \ $(XLIB) libtalertemplating_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined - +libtalertemplating_la_CFLAGS = \ + -DMUSTACH_SAFE=1 \ + -DMUSTACH_LOAD_TEMPLATE=0 libmustach_la_SOURCES = \ mustach.c mustach.h \ + mustach-wrap.c mustach-wrap.h \ mustach-jansson.c mustach-jansson.h test_mustach_jansson_SOURCES = \ @@ -43,10 +62,71 @@ test_mustach_jansson_LDADD = \ check_PROGRAMS = \ test_mustach_jansson -check_SCRIPTS = \ - run-original-tests.sh - -TESTS = $(check_SCRIPTS) $(check_PROGRAMS) +TESTS = $(check_PROGRAMS) EXTRA_DIST = \ - $(check_SCRIPTS) + $(check_SCRIPTS) \ + mustach-original-Makefile \ + mustach.1.gz \ + mustach.1.scd \ + meson.build \ + LICENSE.txt \ + ORIGIN \ + pkgcfgs \ + README.md \ + dotest.sh \ + AUTHORS \ + CHANGELOG.md \ + mustach-json-c.h \ + mustach-json-c.c \ + mustach-cjson.h \ + mustach-cjson.c \ + test1/json \ + test1/Makefile \ + test1/must \ + test1/resu.ref \ + test1/vg.ref \ + test2/json \ + test2/Makefile \ + test2/must \ + test2/resu.ref \ + test2/vg.ref \ + test3/json \ + test3/Makefile \ + test3/must \ + test3/resu.ref \ + test3/vg.ref \ + test4/json \ + test4/Makefile \ + test4/must \ + test4/resu.ref \ + test4/vg.ref \ + test5/json \ + test5/Makefile \ + test5/must \ + test5/must2 \ + test5/must2.mustache \ + test5/must3.mustache \ + test5/resu.ref \ + test5/vg.ref \ + test6/json \ + test6/Makefile \ + test6/must \ + test6/resu.ref \ + test6/test-custom-write.c \ + test6/vg.ref \ + test7/base.mustache \ + test7/json \ + test7/Makefile \ + test7/node.mustache \ + test7/resu.ref \ + test7/vg.ref \ + test8/json \ + test8/Makefile \ + test8/must \ + test8/resu.ref \ + test8/vg.ref \ + test-specs/test-specs.c \ + test-specs/test-specs-cjson.ref \ + test-specs/test-specs-jansson.ref \ + test-specs/test-specs-json-c.ref diff --git a/src/templating/ORIGIN b/src/templating/ORIGIN index fafb0ae79..14902983a 100644 --- a/src/templating/ORIGIN +++ b/src/templating/ORIGIN @@ -3,7 +3,9 @@ Cloned originally from https://gitlab.com/jobol/mustach/ Changes: ======== -Renamed original Makefile to Makefile.orig and wrote Makefile.am for us. +Renamed original Makefile to mustach-original-Makefile +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. +Added run-original-tests.sh shell script as a wrapper around +mustach-original-Makefile to use the original build process for the test +suite. diff --git a/src/templating/README.md b/src/templating/README.md index a6df19f64..6e7a6c956 100644 --- a/src/templating/README.md +++ b/src/templating/README.md @@ -1,16 +1,27 @@ -# Introduction to Mustach 0.99 +# Introduction to Mustach 1.2 `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** +The simplest 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 +If you are using one of the JSON libraries listed below, you can get extended feature +by also including **mustach-wrap.h**, **mustach-wrap.c**, **mustach-XXX.h** and +**mustach-XXX.c** in your project (see below for **XXX**) + +- [json-c](https://github.com/json-c/json-c): use **XXX** = **json-c** +- [jansson](http://www.digip.org/jansson/): use **XXX** = **jansson** +- [cJSON](https://github.com/DaveGamble/cJSON): use **XXX** = **cjson** + +Alternatively, make and meson files are provided for building `mustach` and `libmustach.so` shared library. +Since version 1.0, the makefile allows to compile and install different +flavours. See below for details. + ## Distributions offering mustach package ### Alpine Linux @@ -30,6 +41,13 @@ make See http://pkgsrc.se/devel/mustach +## Known projects using Mustach + +This [wiki page](https://gitlab.com/jobol/mustach/-/wikis/projects-using-mustach) +lists the known project that are using mustach and that kindly told it. + +Don't hesitate to tell us if you are interested to be listed there. + ## Using Mustach from sources The file **mustach.h** is the main documentation. Look at it. @@ -38,15 +56,25 @@ The current source files are: - **mustach.c** core implementation of mustache in C - **mustach.h** header file for core definitions +- **mustach-wrap.c** generic wrapper of mustach for easier integration +- **mustach-wrap.h** header file for using mustach-wrap - **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 +- **mustach-json-c.h** header file for using the tiny json-c wrapper +- **mustach-cjson.c** tiny json wrapper of mustach using [cJSON](https://github.com/DaveGamble/cJSON) +- **mustach-cjson.h** header file for using the tiny cJSON wrapper +- **mustach-jansson.c** tiny json wrapper of mustach using [jansson](https://www.digip.org/jansson/) +- **mustach-jansson.h** header file for using the tiny jansson wrapper +- **mustach-tool.c** simple tool for applying template files to one JSON file + +The file **mustach-json-c.c** is the historical example of use of **mustach** and +**mustach-wrap** 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). -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). +Since version 1.0, the project also provide integration of other JSON libraries: +**cJSON** and **jansson**. -HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...). +*If you integrate a new library with* **mustach**, *your contribution will be +welcome here*. The tool **mustach** is build using `make`, its usage is: @@ -60,11 +88,11 @@ 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 + CFLAGS=-DNO_OPEN_MEMSTREAM make ### Integration -The file **mustach.h** is the main documentation. Look at it. +The files **mustach.h** and **mustach-wrap.h** are the main documentation. Look at it. The file **mustach-json-c.c** provides a good example of integration. @@ -76,139 +104,217 @@ 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 +### Compilation Using Make -By default, the current implementation provides the following extensions: +Building and installing can be done using make. -#### Explicit Substitution +Example: -This is a core extension implemented in file **mustach.c**. + $ make tool=cjson libs=none PREFIX=/usr/local DESTDIR=/ install + $ make tool=jsonc libs=single PREFIX=/ DESTDIR=$HOME/.local install + +The makefile knows following switches (\*: default): + + Switch name | Values | Description + --------------+---------+----------------------------------------------- + jsonc | (unset) | Auto detection of json-c + | no | Don't compile for json-c + | yes | Compile for json-c that must exist + --------------+---------+----------------------------------------------- + cjson | (unset) | Auto detection of cJSON + | no | Don't compile for cJSON + | yes | Compile for cJSON that must exist + --------------+---------+----------------------------------------------- + jansson | (unset) | Auto detection of jansson + | no | Don't compile for jansson + | yes | Compile for jansson that must exist + --------------+---------+----------------------------------------------- + tool | (unset) | Auto detection + | cjson | Use cjson library + | jsonc | Use jsonc library + | jansson | Use jansson library + | none | Don't compile the tool + --------------+---------+---------------------------------------------- + libs | (unset) | Like 'all' + | all | Like 'single' AND 'split' + | single | Only libmustach.so + | split | All the possible libmustach-XXX.so ... + | none | No library is produced + +The libraries that can be produced are: + + Library name | Content + --------------------+-------------------------------------------------------- + libmustach-core | mustach.c mustach-wrap.c + libmustach-cjson | mustach.c mustach-wrap.c mustach-cjson.c + libmustach-jsonc | mustach.c mustach-wrap.c mustach-json-c.c + libmustach-jansson | mustach.c mustach-wrap.c mustach-jansson.c + libmustach | mustach.c mustach-wrap.c mustach-{cjson,json-c,jansson}.c + +There is no dependencies of a library to an other. This is intended and doesn't +hurt today because the code is small. + +### Testing + +The makefile offers the way to execute basic tests. Just type `make test`. + +By default, if valgrind is available, tests are using it. It can be disabled +by typing `make test valgrind=no` or `NOVALGRIND=1 make test`. + +## Extensions + +The current implementation provides extensions to specifications of **mustache**. +This extensions can be activated or deactivated using flags. + +Here is the summary. + + Flag name | Description + -------------------------------+------------------------------------------------ + Mustach_With_Colon | Explicit tag substitution with colon + Mustach_With_EmptyTag | Empty Tag Allowed + -------------------------------+------------------------------------------------ + Mustach_With_Equal | Value Testing Equality + Mustach_With_Compare | Value Comparing + Mustach_With_JsonPointer | Interpret JSON Pointers + Mustach_With_ObjectIter | Iteration On Objects + Mustach_With_EscFirstCmp | Escape First Compare + Mustach_With_ErrorUndefined | Error when a requested tag is undefined + -------------------------------+------------------------------------------------ + Mustach_With_AllExtensions | Activate all known extensions + Mustach_With_NoExtensions | Disable any extension + +For the details, see below. + +### Explicit Tag Substitution With Colon (Mustach_With_Colon) 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 is a core extension implemented in file **mustach.c**. + +### Empty Tag Allowed (Mustach_With_EmptyTag) -This are a tool extension implemented in file **mustach-json-c.c**. +When an empty tag is found, instead of automatically raising the error +MUSTACH\_ERROR\_EMPTY\_TAG pass it. -These extensions allows you to test the value of the selected key. -They allow to write `key=value` (matching test) or `key=!value` +This is a core extension implemented in file **mustach.c**. + +### Value Testing Equality (Mustach_With_Equal) + +This extension allows you to test the value of the selected key. +It allows 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`. +This is a wrap extension implemented in file **mustach-wrap.c**. + +### Value Comparing (Mustach_With_Compare) + +These extension extends the extension for testing equality to also +compare values if greater or lesser. +Its allows to write `key>value` (greater), `key>=value` (greater or equal), +`key<value` (lesser) and `key<=value` (lesser or equal). It the comparator sign appears in the first column it is ignored as if it was escaped. -#### Access to current value +This is a wrap extension implemented in file **mustach-wrap.c**. + +### Interpret JSON Pointers (Mustach_With_JsonPointer) + +This extension allows to use JSON pointers as defined in IETF RFC 6901. +If active, any key starting with "/" is a JSON pointer. +This implies to use the colon to introduce JSON keys. -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`. +A special escaping is used for `=`, `<`, `>` signs when +values comparisons are enabled: `~=` gives `=` in the key. -#### Iteration on objects +This is a wrap extension implemented in file **mustach-wrap.c**. -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. +### Iteration On Objects (Mustach_With_ObjectIter) -### Removing Extensions +With this extension, using the pattern `{{#X.*}}...{{/X.*}}` +allows to iterate on fields of `X`. -When compiling mustach.c or mustach-json-c.c, -extensions can be removed by defining macros -using option -D. +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. + +This is a wrap extension implemented in file **mustach-wrap.c**. -The possible macros are of 3 categories, the global, -the mustach core specific and the mustach-json-c example -of implementation specific. +### Error when a requested tag is undefined (Mustach_With_ErrorUndefined) -#### Global macros +Report the error MUSTACH_ERROR_UNDEFINED_TAG when a requested tag +is not defined. -- `NO_EXTENSION_FOR_MUSTACH` +This is a wrap extension implemented in file **mustach-wrap.c**. - This macro disables any current or future - extensions for the core or the example. +### Access To Current Value -#### Macros for the core mustach engine (mustach.c) +*this was an extension but is now always enforced* -- `NO_COLON_EXTENSION_FOR_MUSTACH` +The value of the current field can be accessed using single dot. - 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 `:#^/&{=>` +Examples: -- `NO_ALLOW_EMPTY_TAG` +- `{{#key}}{{.}}{{/key}}` applied to `{"key":3.14}` produces `3.14` +- `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces ` 1 2`. - Generate the error MUSTACH_ERROR_EMPTY_TAG automatically - when an empty tag is encountered. +This is a wrap extension implemented in file **mustach-wrap.c**. -#### Macros for the implementation example (mustach-json-c.c) +### Partial Data First -- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH` +*this was an extension but is now always enforced* - 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. +The default resolution for partial pattern like `{{> name}}` +is to search for `name` in the current json context and +as a file named `name` or if not found `name.mustache`. -- `NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH` +By default, the order of the search is (1) as a file, +and if not found, (2) in the current json context. - 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}}`. +When this option is set, the order is reverted and content +of partial is search (1) in the current json context, +and if not found, (2) as a file. -- `NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH` +That option is useful to keep the compatibility with +versions of *mustach* anteriors to 1.2.0. - This macro fordids automatic escaping of coparison - sign appearing at first column. +This is a wrap extension implemented in file **mustach-wrap.c**. -- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH` +### Escape First Compare - 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. +This extension automatically escapes comparisons appears as +first characters. -- `NO_OBJECT_ITERATION_FOR_MUSTACH` +This is a wrap extension implemented in file **mustach-wrap.c**. + +## Difference with version 0.99 and previous + +### Extensions - 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. +The extensions can no more be removed at compile time, use +flags to select your required extension on need. -- `NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH` +### Name of functions - Disable access to current object value using single dot - like in `{{.}}`. +Names of functions were improved. Old names remain but are obsolete +and legacy. Their removal in far future versions is possible. -- `NO_INCLUDE_PARTIAL_FALLBACK` +The table below summarize the changes. - 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. + legacy name | name since version 1.0.0 + ------------------+----------------------- + fmustach | mustach_file + fdmustach | mustach_fd + mustach | mustach_mem + fmustach_json_c | mustach_json_c_file + fdmustach_json_c | mustach_json_c_fd + mustach_json_c | mustach_json_c_mem + mustach_json_c | mustach_json_c_write diff --git a/src/templating/dotest.sh b/src/templating/dotest.sh new file mode 100755 index 000000000..32f575c2e --- /dev/null +++ b/src/templating/dotest.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# Exit, with error message (hard failure) +exit_fail() { + echo " FAIL: " "$@" >&2 + exit 1 +} + +mustach="${mustach:-../mustach}" +echo "starting test" +if ! valgrind --version 2> /dev/null +then + $mustach "$@" > resu.last || exit_fail "ERROR! mustach command failed ($?)!" +else + valgrind $mustach "$@" > resu.last 2> vg.last || exit_fail "ERROR! valgrind + mustach command failed ($?)!" + sed -i 's:^==[0-9]*== ::' vg.last + awk '/^ *total heap usage: .* allocs, .* frees,.*/{if($$4-$$6)exit(1)}' vg.last || exit_fail "ERROR! Alloc/Free issue" +fi +if diff -w resu.ref resu.last +then + echo "result ok" +else + exit_fail "ERROR! Result differs" +fi +echo +exit 0 diff --git a/src/templating/mustach-cjson.c b/src/templating/mustach-cjson.c new file mode 100644 index 000000000..ee65c8038 --- /dev/null +++ b/src/templating/mustach-cjson.c @@ -0,0 +1,258 @@ +/* + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "mustach.h" +#include "mustach-wrap.h" +#include "mustach-cjson.h" + +struct expl { + cJSON null; + cJSON *root; + cJSON *selection; + int depth; + struct { + cJSON *cont; + cJSON *obj; + cJSON *next; + int is_objiter; + } stack[MUSTACH_MAX_DEPTH]; +}; + +static int start(void *closure) +{ + struct expl *e = closure; + e->depth = 0; + memset(&e->null, 0, sizeof e->null); + e->null.type = cJSON_NULL; + e->selection = &e->null; + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + return MUSTACH_OK; +} + +static int compare(void *closure, const char *value) +{ + struct expl *e = closure; + cJSON *o = e->selection; + double d; + + if (cJSON_IsNumber(o)) { + d = o->valuedouble - atof(value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + } else if (cJSON_IsString(o)) { + return strcmp(o->valuestring, value); + } else if (cJSON_IsTrue(o)) { + return strcmp("true", value); + } else if (cJSON_IsFalse(o)) { + return strcmp("false", value); + } else if (cJSON_IsNull(o)) { + return strcmp("null", value); + } else { + return 1; + } +} + +static int sel(void *closure, const char *name) +{ + struct expl *e = closure; + cJSON *o; + int i, r; + + if (name == NULL) { + o = e->stack[e->depth].obj; + r = 1; + } else { + i = e->depth; + while (i >= 0 && !(o = cJSON_GetObjectItemCaseSensitive(e->stack[i].obj, name))) + i--; + if (i >= 0) + r = 1; + else { + o = &e->null; + r = 0; + } + } + e->selection = o; + return r; +} + +static int subsel(void *closure, const char *name) +{ + struct expl *e = closure; + cJSON *o = NULL; + int r = 0; + + if (cJSON_IsObject(e->selection)) { + o = cJSON_GetObjectItemCaseSensitive(e->selection, name); + r = o != NULL; + } + else if (cJSON_IsArray(e->selection) && *name) { + char *end; + int idx = (int)strtol(name, &end, 10); + if (!*end && idx >= 0 && idx < cJSON_GetArraySize(e->selection)) { + o = cJSON_GetArrayItem(e->selection, idx); + r = 1; + } + } + if (r) + e->selection = o; + return r; +} + +static int enter(void *closure, int objiter) +{ + struct expl *e = closure; + cJSON *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + if (objiter) { + if (! cJSON_IsObject(o)) + goto not_entering; + if (o->child == NULL) + goto not_entering; + e->stack[e->depth].obj = o->child; + e->stack[e->depth].next = o->child->next; + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } else if (cJSON_IsArray(o)) { + if (o->child == NULL) + goto not_entering; + e->stack[e->depth].obj = o->child; + e->stack[e->depth].next = o->child->next; + e->stack[e->depth].cont = o; + } else if ((cJSON_IsObject(o) && o->child != NULL) + || cJSON_IsTrue(o) + || (cJSON_IsString(o) && cJSON_GetStringValue(o)[0] != '\0') + || (cJSON_IsNumber(o) && cJSON_GetNumberValue(o) != 0)) { + e->stack[e->depth].obj = o; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].next = NULL; + } else + goto not_entering; + return 1; + +not_entering: + e->depth--; + return 0; +} + +static int next(void *closure) +{ + struct expl *e = closure; + cJSON *o; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + o = e->stack[e->depth].next; + if (o == NULL) + return 0; + + e->stack[e->depth].obj = o; + e->stack[e->depth].next = o->next; + return 1; +} + +static int leave(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + e->depth--; + return 0; +} + +static int get(void *closure, struct mustach_sbuf *sbuf, int key) +{ + struct expl *e = closure; + const char *s; + int d; + + if (key) { + s = ""; + for (d = e->depth ; d >= 0 ; d--) + if (e->stack[d].is_objiter) { + s = e->stack[d].obj->string; + break; + } + } + else if (cJSON_IsString(e->selection)) + s = e->selection->valuestring; + else if (cJSON_IsNull(e->selection)) + s = ""; + else { + s = cJSON_PrintUnformatted(e->selection); + if (s == NULL) + return MUSTACH_ERROR_SYSTEM; + sbuf->freecb = cJSON_free; + } + sbuf->value = s; + return 1; +} + +const struct mustach_wrap_itf mustach_cJSON_wrap_itf = { + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get +}; + +int mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file) +{ + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_cJSON_wrap_itf, &e, flags, file); +} + +int mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd) +{ + struct expl e; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_cJSON_wrap_itf, &e, flags, fd); +} + +int mustach_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size) +{ + struct expl e; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_cJSON_wrap_itf, &e, flags, result, size); +} + +int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_write(template, length, &mustach_cJSON_wrap_itf, &e, flags, writecb, closure); +} + +int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_cJSON_wrap_itf, &e, flags, emitcb, closure); +} + diff --git a/src/templating/mustach-cjson.h b/src/templating/mustach-cjson.h new file mode 100644 index 000000000..e049415f8 --- /dev/null +++ b/src/templating/mustach-cjson.h @@ -0,0 +1,96 @@ +/* + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _mustach_cJSON_h_included_ +#define _mustach_cJSON_h_included_ + +/* + * mustach-cjson is intended to make integration of cJSON + * library by providing integrated functions. + */ + +#include <cjson/cJSON.h> +#include "mustach-wrap.h" + +/** + * Wrap interface used internally by mustach cJSON functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_wrap_itf mustach_cJSON_wrap_itf; + +/** + * mustach_cJSON_file - Renders the mustache 'template' in 'file' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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 mustach_cJSON_file(const char *template, size_t length, cJSON *root, int flags, FILE *file); + +/** + * mustach_cJSON_fd - Renders the mustache 'template' in 'fd' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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 mustach_cJSON_fd(const char *template, size_t length, cJSON *root, int flags, int fd); + + +/** + * mustach_cJSON_mem - Renders the mustache 'template' in 'result' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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_cJSON_mem(const char *template, size_t length, cJSON *root, int flags, char **result, size_t *size); + +/** + * mustach_cJSON_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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. + */ +extern int mustach_cJSON_write(const char *template, size_t length, cJSON *root, int flags, mustach_write_cb_t *writecb, void *closure); + +/** + * mustach_cJSON_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @emitcb: the function that emit 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. + */ +extern int mustach_cJSON_emit(const char *template, size_t length, cJSON *root, int flags, mustach_emit_cb_t *emitcb, void *closure); + +#endif + diff --git a/src/templating/mustach-jansson.c b/src/templating/mustach-jansson.c index 2aed58291..d9b50b57e 100644 --- a/src/templating/mustach-jansson.c +++ b/src/templating/mustach-jansson.c @@ -1,417 +1,271 @@ /* - 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 + SPDX-License-Identifier: ISC +*/ - http://www.apache.org/licenses/LICENSE-2.0 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif - 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 <stdio.h> +#include <string.h> -#include "platform.h" +#include "mustach.h" +#include "mustach-wrap.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; +struct expl { + json_t *root; + json_t *selection; + int depth; + struct { + json_t *cont; + json_t *obj; + void *iter; + int is_objiter; + size_t index, count; + } stack[MUSTACH_MAX_DEPTH]; }; -enum Bang +static int start(void *closure) { - BANG_NONE, - BANG_I18N, - BANG_STRINGIFY, - BANG_AMOUNT_CURRENCY, - BANG_AMOUNT_DECIMAL, -}; + struct expl *e = closure; + e->depth = 0; + e->selection = json_null(); + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + e->stack[0].index = 0; + e->stack[0].count = 1; + return MUSTACH_OK; +} -struct JanssonClosure +static int compare(void *closure, const char *value) { - 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; -}; - + struct expl *e = closure; + json_t *o = e->selection; + double d; + json_int_t i; + + switch (json_typeof(o)) { + case JSON_REAL: + d = json_number_value(o) - atof(value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + case JSON_INTEGER: + i = (json_int_t)json_integer_value(o) - (json_int_t)atoll(value); + return i < 0 ? -1 : i > 0 ? 1 : 0; + case JSON_STRING: + return strcmp(json_string_value(o), value); + case JSON_TRUE: + return strcmp("true", value); + case JSON_FALSE: + return strcmp("false", value); + case JSON_NULL: + return strcmp("null", value); + default: + return 1; + } +} -static json_t * -walk (json_t *obj, const char *path) +static int sel(void *closure, const char *name) { - 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; + struct expl *e = closure; + json_t *o; + int i, r; + + if (name == NULL) { + o = e->stack[e->depth].obj; + r = 1; + } else { + i = e->depth; + while (i >= 0 && !(o = json_object_get(e->stack[i].obj, name))) + i--; + if (i >= 0) + r = 1; + else { + o = json_null(); + r = 0; + } + } + e->selection = o; + return r; } - -static json_t * -find (struct JanssonClosure *e, const char *name) +static int subsel(void *closure, 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; + struct expl *e = closure; + json_t *o = NULL; + int r = 0; + + if (json_is_object(e->selection)) { + o = json_object_get(e->selection, name); + r = o != NULL; + } + else if (json_is_array(e->selection)) { + char *end; + size_t idx = (size_t)strtol(name, &end, 10); + if (!*end && idx < json_array_size(e->selection)) { + o = json_array_get(e->selection, idx); + r = 1; + } + } + if (r) + e->selection = o; + return r; } - -static int -start(void *closure) +static int enter(void *closure, int objiter) { - 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; + struct expl *e = closure; + json_t *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + if (objiter) { + if (!json_is_object(o)) + goto not_entering; + e->stack[e->depth].iter = json_object_iter(o); + if (e->stack[e->depth].iter == NULL) + goto not_entering; + e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } else if (json_is_array(o)) { + e->stack[e->depth].count = json_array_size(o); + if (e->stack[e->depth].count == 0) + goto not_entering; + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_array_get(o, 0); + e->stack[e->depth].index = 0; + } else if ((json_is_object(o) && json_object_size(o)) + || json_is_true(o) + || (json_is_string(o) && json_string_length(o) > 0) + || (json_is_integer(o) && json_integer_value(o) != 0) + || (json_is_real(o) && json_real_value(o) != 0)) { + e->stack[e->depth].count = 1; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].obj = o; + e->stack[e->depth].index = 0; + } else + goto not_entering; + return 1; + +not_entering: + e->depth--; + return 0; } - -static int -emituw (void *closure, const char *buffer, size_t size, int escape, FILE *file) +static int next(void *closure) { - 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; -} + struct expl *e = closure; + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; -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; -} + if (e->stack[e->depth].is_objiter) { + e->stack[e->depth].iter = json_object_iter_next(e->stack[e->depth].cont, e->stack[e->depth].iter); + if (e->stack[e->depth].iter == NULL) + return 0; + e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); + return 1; + } + e->stack[e->depth].index++; + if (e->stack[e->depth].index >= e->stack[e->depth].count) + 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; + e->stack[e->depth].obj = json_array_get(e->stack[e->depth].cont, e->stack[e->depth].index); + return 1; } -static int -leave (void *closure) +static int leave(void *closure) { - struct JanssonClosure *e = closure; - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - e->depth--; - return 0; -} + struct expl *e = closure; -static void -freecb (void *v) -{ - free (v); + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + e->depth--; + return 0; } -static int -get (void *closure, const char *name, struct mustach_sbuf *sbuf) +static int get(void *closure, struct mustach_sbuf *sbuf, int key) { - 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; + struct expl *e = closure; + const char *s; + int d; + + if (key) { + s = ""; + for (d = e->depth ; d >= 0 ; d--) + if (e->stack[d].is_objiter) { + s = json_object_iter_key(e->stack[d].iter); + break; + } + } + else if (json_is_string(e->selection)) + s = json_string_value(e->selection); + else if (json_is_null(e->selection)) + s = ""; + else { + s = json_dumps(e->selection, JSON_ENCODE_ANY | JSON_COMPACT); + if (s == NULL) + return MUSTACH_ERROR_SYSTEM; + sbuf->freecb = free; + } + sbuf->value = s; + return 1; } -static struct mustach_itf itf = { - .start = start, - .put = NULL, - .enter = enter, - .next = next, - .leave = leave, - .partial =NULL, - .get = get, - .emit = NULL, - .stop = NULL +const struct mustach_wrap_itf mustach_jansson_wrap_itf = { + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get }; -static struct mustach_itf itfuw = { - .start = start, - .put = NULL, - .enter = enter, - .next = next, - .leave = leave, - .partial = NULL, - .get = get, - .emit = emituw, - .stop = NULL -}; +int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file) +{ + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_jansson_wrap_itf, &e, flags, file); +} -int fmustach_jansson (const char *template, json_t *root, FILE *file) +int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd) { - struct JanssonClosure e = { 0 }; - e.root = root; - return fmustach(template, &itf, &e, file); + struct expl e; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_jansson_wrap_itf, &e, flags, fd); } -int fdmustach_jansson (const char *template, json_t *root, int fd) +int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size) { - struct JanssonClosure e = { 0 }; - e.root = root; - return fdmustach(template, &itf, &e, fd); + struct expl e; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); } -int mustach_jansson (const char *template, json_t *root, char **result, size_t *size) +int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure) { - struct JanssonClosure e = { 0 }; - e.root = root; - e.writecb = NULL; - return mustach(template, &itf, &e, result, size); + struct expl e; + e.root = root; + return mustach_wrap_write(template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, closure); } -int umustach_jansson (const char *template, json_t *root, mustach_jansson_write_cb writecb, void *closure) +int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure) { - struct JanssonClosure e = { 0 }; - e.root = root; - e.writecb = writecb; - return fmustach(template, &itfuw, &e, closure); + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure); } diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h index 8fe989fa5..8def948e0 100644 --- a/src/templating/mustach-jansson.h +++ b/src/templating/mustach-jansson.h @@ -1,60 +1,60 @@ /* - 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. + SPDX-License-Identifier: ISC */ #ifndef _mustach_jansson_h_included_ #define _mustach_jansson_h_included_ -#include "taler_json_lib.h" -#include "mustach.h" +/* + * mustach-jansson is intended to make integration of jansson + * library by providing integrated functions. + */ + +#include <jansson.h> +#include "mustach-wrap.h" /** - * fmustach_jansson - Renders the mustache 'template' in 'file' for 'root'. + * Wrap interface used internally by mustach jansson functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_wrap_itf mustach_jansson_wrap_itf; + +/** + * mustach_jansson_file - Renders the mustache 'template' in 'file' for 'root'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @root: the root json object to render - * \@file: the file where to write the result + * @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); +extern int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file); /** - * fmustach_jansson - Renders the mustache 'template' in 'fd' for 'root'. + * mustach_jansson_fd - Renders the mustache 'template' in 'fd' for 'root'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @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); +extern int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd); /** - * fmustach_jansson - Renders the mustache 'template' in 'result' for 'root'. + * mustach_jansson_mem - Renders the mustache 'template' in 'result' for 'root'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @root: the root json object to render * @result: the pointer receiving the result when 0 is returned * @size: the size of the returned result @@ -62,13 +62,13 @@ extern int fdmustach_jansson (const char *template, json_t *root, int fd); * 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); +extern int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size); /** - * umustach_jansson - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * mustach_jansson_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @root: the root json object to render * @writecb: the function that write values * @closure: the closure for the write function @@ -76,9 +76,21 @@ extern int mustach_jansson (const char *template, json_t *root, char **result, * 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); +extern int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure); + +/** + * mustach_jansson_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @emitcb: the function that emit 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. + */ +extern int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure); #endif + diff --git a/src/templating/mustach-json-c.c b/src/templating/mustach-json-c.c new file mode 100644 index 000000000..75251c07e --- /dev/null +++ b/src/templating/mustach-json-c.c @@ -0,0 +1,284 @@ +/* + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <string.h> + +#include "mustach.h" +#include "mustach-wrap.h" +#include "mustach-json-c.h" + +struct expl { + struct json_object *root; + struct json_object *selection; + int depth; + struct { + struct json_object *cont; + struct json_object *obj; + struct json_object_iterator iter; + struct json_object_iterator enditer; + int is_objiter; + int index, count; + } stack[MUSTACH_MAX_DEPTH]; +}; + +static int start(void *closure) +{ + struct expl *e = closure; + e->depth = 0; + e->selection = NULL; + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + e->stack[0].index = 0; + e->stack[0].count = 1; + return MUSTACH_OK; +} + +static int compare(void *closure, const char *value) +{ + struct expl *e = closure; + struct json_object *o = e->selection; + double d; + int64_t i; + + switch (json_object_get_type(o)) { + case json_type_double: + d = json_object_get_double(o) - atof(value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + case json_type_int: + i = json_object_get_int64(o) - (int64_t)atoll(value); + return i < 0 ? -1 : i > 0 ? 1 : 0; + default: + return strcmp(json_object_get_string(o), value); + } +} + +static int sel(void *closure, const char *name) +{ + struct expl *e = closure; + struct json_object *o; + int i, r; + + if (name == NULL) { + o = e->stack[e->depth].obj; + r = 1; + } else { + i = e->depth; + while (i >= 0 && !json_object_object_get_ex(e->stack[i].obj, name, &o)) + i--; + if (i >= 0) + r = 1; + else { + o = NULL; + r = 0; + } + } + e->selection = o; + return r; +} + +static int subsel(void *closure, const char *name) +{ + struct expl *e = closure; + struct json_object *o = NULL; + int r = 0; + + if (json_object_is_type(e->selection, json_type_object)) + r = json_object_object_get_ex(e->selection, name, &o); + else if (json_object_is_type(e->selection, json_type_array)) { + char *end; + size_t idx = (size_t)strtol(name, &end, 10); + if (!*end && idx < json_object_array_length(e->selection)) { + o = json_object_array_get_idx(e->selection, idx); + r = 1; + } + } + if (r) + e->selection = o; + return r; +} + +static int enter(void *closure, int objiter) +{ + struct expl *e = closure; + struct json_object *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + if (objiter) { + if (!json_object_is_type(o, json_type_object)) + goto not_entering; + + e->stack[e->depth].iter = json_object_iter_begin(o); + e->stack[e->depth].enditer = json_object_iter_end(o); + if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer)) + goto not_entering; + e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].iter); + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } else if (json_object_is_type(o, json_type_array)) { + e->stack[e->depth].count = json_object_array_length(o); + if (e->stack[e->depth].count == 0) + goto not_entering; + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_object_array_get_idx(o, 0); + e->stack[e->depth].index = 0; + } else if ((json_object_is_type(o, json_type_object) && json_object_object_length(o) > 0) + || json_object_get_boolean(o)) { + e->stack[e->depth].count = 1; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].obj = o; + e->stack[e->depth].index = 0; + } else + goto not_entering; + return 1; + +not_entering: + e->depth--; + return 0; +} + +static int next(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + if (e->stack[e->depth].is_objiter) { + json_object_iter_next(&e->stack[e->depth].iter); + if (json_object_iter_equal(&e->stack[e->depth].iter, &e->stack[e->depth].enditer)) + return 0; + e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].iter); + return 1; + } + + e->stack[e->depth].index++; + if (e->stack[e->depth].index >= e->stack[e->depth].count) + return 0; + + e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index); + return 1; +} + +static int leave(void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + e->depth--; + return 0; +} + +static int get(void *closure, struct mustach_sbuf *sbuf, int key) +{ + struct expl *e = closure; + const char *s; + int d; + + if (key) { + s = ""; + for (d = e->depth ; d >= 0 ; d--) + if (e->stack[d].is_objiter) { + s = json_object_iter_peek_name(&e->stack[d].iter); + break; + } + } + else + switch (json_object_get_type(e->selection)) { + case json_type_string: + s = json_object_get_string(e->selection); + break; + case json_type_null: + s = ""; + break; + default: + s = json_object_to_json_string_ext(e->selection, 0); + break; + } + sbuf->value = s; + return 1; +} + +const struct mustach_wrap_itf mustach_json_c_wrap_itf = { + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get +}; + +int mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file) +{ + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_json_c_wrap_itf, &e, flags, file); +} + +int mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd) +{ + struct expl e; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_json_c_wrap_itf, &e, flags, fd); +} + +int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size) +{ + struct expl e; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_json_c_wrap_itf, &e, flags, result, size); +} + +int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_write(template, length, &mustach_json_c_wrap_itf, &e, flags, writecb, closure); +} + +int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure) +{ + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_json_c_wrap_itf, &e, flags, emitcb, closure); +} + +int fmustach_json_c(const char *template, struct json_object *root, FILE *file) +{ + return mustach_json_c_file(template, 0, root, -1, file); +} + +int fdmustach_json_c(const char *template, struct json_object *root, int fd) +{ + return mustach_json_c_fd(template, 0, root, -1, fd); +} + +int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size) +{ + return mustach_json_c_mem(template, 0, root, -1, result, size); +} + +int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure) +{ + return mustach_json_c_write(template, 0, root, -1, writecb, closure); +} + + diff --git a/src/templating/mustach-json-c.h b/src/templating/mustach-json-c.h new file mode 100644 index 000000000..50846c6cb --- /dev/null +++ b/src/templating/mustach-json-c.h @@ -0,0 +1,160 @@ +/* + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _mustach_json_c_h_included_ +#define _mustach_json_c_h_included_ + +/* + * mustach-json-c is intended to make integration of json-c + * library by providing integrated functions. + */ + +#include <json-c/json.h> +#include "mustach-wrap.h" + +/** + * Wrap interface used internally by mustach json-c functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_wrap_itf mustach_json_c_wrap_itf; + +/** + * mustach_json_c_file - Renders the mustache 'template' in 'file' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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 mustach_json_c_file(const char *template, size_t length, struct json_object *root, int flags, FILE *file); + +/** + * mustach_json_c_fd - Renders the mustache 'template' in 'fd' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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 mustach_json_c_fd(const char *template, size_t length, struct json_object *root, int flags, int fd); + +/** + * mustach_json_c_mem - Renders the mustache 'template' in 'result' for 'root'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_json_c_mem(const char *template, size_t length, struct json_object *root, int flags, char **result, size_t *size); + +/** + * mustach_json_c_write - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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. + */ +extern int mustach_json_c_write(const char *template, size_t length, struct json_object *root, int flags, mustach_write_cb_t *writecb, void *closure); + +/** + * mustach_json_c_emit - Renders the mustache 'template' for 'root' to custom emiter 'emitcb' with 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @root: the root json object to render + * @emitcb: the function that emit 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. + */ +extern int mustach_json_c_emit(const char *template, size_t length, struct json_object *root, int flags, mustach_emit_cb_t *emitcb, void *closure); + +/*************************************************************************** +* compatibility with version before 1.0 +*/ + +/** + * OBSOLETE use mustach_json_c_file + * + * fmustach_json_c - 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. + */ + +DEPRECATED_MUSTACH(extern int fmustach_json_c(const char *template, struct json_object *root, FILE *file)); + +/** + * OBSOLETE use mustach_json_c_fd + * + * fdmustach_json_c - 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. + */ + +DEPRECATED_MUSTACH(extern int fdmustach_json_c(const char *template, struct json_object *root, int fd)); + +/** + * OBSOLETE use mustach_json_c_mem + * + * mustach_json_c - 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. + */ + +DEPRECATED_MUSTACH(extern int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size)); + +/** + * OBSOLETE use mustach_json_c_write + * + * umustach_json_c - 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 mustach_write_cb_t *mustach_json_write_cb; +DEPRECATED_MUSTACH(extern int umustach_json_c(const char *template, struct json_object *root, mustach_write_cb_t *writecb, void *closure)); + +#endif diff --git a/src/templating/mustach-original-Makefile b/src/templating/mustach-original-Makefile new file mode 100644 index 000000000..aee827583 --- /dev/null +++ b/src/templating/mustach-original-Makefile @@ -0,0 +1,305 @@ +# version +MAJOR := 1 +MINOR := 2 +REVIS := 7 + +# installation settings +DESTDIR ?= +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +LIBDIR ?= $(PREFIX)/lib +INCLUDEDIR ?= $(PREFIX)/include +MANDIR ?= $(PREFIX)/share/man +PKGDIR ?= $(LIBDIR)/pkgconfig + +# Tools (sed must be GNU sed) +SED ?= sed +INSTALL ?= install + +# initial settings +VERSION := $(MAJOR).$(MINOR).$(REVIS) +SOVER := .$(MAJOR) +SOVEREV := .$(MAJOR).$(MINOR) + +HEADERS := mustach.h mustach-wrap.h +SPLITLIB := libmustach-core.so$(SOVEREV) +SPLITPC := libmustach-core.pc +COREOBJS := mustach.o mustach-wrap.o +SINGLEOBJS := $(COREOBJS) +SINGLEFLAGS := +SINGLELIBS := +TESTSPECS := +ALL := manuals + +# availability of CJSON +ifneq ($(cjson),no) + cjson_cflags := $(shell pkg-config --silence-errors --cflags libcjson) + cjson_libs := $(shell pkg-config --silence-errors --libs libcjson) + ifdef cjson_libs + cjson := yes + tool ?= cjson + HEADERS += mustach-cjson.h + SPLITLIB += libmustach-cjson.so$(SOVEREV) + SPLITPC += libmustach-cjson.pc + SINGLEOBJS += mustach-cjson.o + SINGLEFLAGS += ${cjson_cflags} + SINGLELIBS += ${cjson_libs} + TESTSPECS += test-specs/test-specs-cjson + else + ifeq ($(cjson),yes) + $(error Can't find required library cjson) + endif + cjson := no + endif +endif + +# availability of JSON-C +ifneq ($(jsonc),no) + jsonc_cflags := $(shell pkg-config --silence-errors --cflags json-c) + jsonc_libs := $(shell pkg-config --silence-errors --libs json-c) + ifdef jsonc_libs + jsonc := yes + tool ?= jsonc + HEADERS += mustach-json-c.h + SPLITLIB += libmustach-json-c.so$(SOVEREV) + SPLITPC += libmustach-json-c.pc + SINGLEOBJS += mustach-json-c.o + SINGLEFLAGS += ${jsonc_cflags} + SINGLELIBS += ${jsonc_libs} + TESTSPECS += test-specs/test-specs-json-c + else + ifeq ($(jsonc),yes) + $(error Can't find required library json-c) + endif + jsonc := no + endif +endif + +# availability of JANSSON +ifneq ($(jansson),no) + jansson_cflags := $(shell pkg-config --silence-errors --cflags jansson) + jansson_libs := $(shell pkg-config --silence-errors --libs jansson) + ifdef jansson_libs + jansson := yes + tool ?= jansson + HEADERS += mustach-jansson.h + SPLITLIB += libmustach-jansson.so$(SOVEREV) + SPLITPC += libmustach-jansson.pc + SINGLEOBJS += mustach-jansson.o + SINGLEFLAGS += ${jansson_cflags} + SINGLELIBS += ${jansson_libs} + TESTSPECS += test-specs/test-specs-jansson + else + ifeq ($(jansson),yes) + $(error Can't find required library jansson) + endif + jansson := no + endif +endif + +# tool +TOOLOBJS = mustach-tool.o $(COREOBJS) +tool ?= none +ifneq ($(tool),none) + ifeq ($(tool),cjson) + TOOLOBJS += mustach-cjson.o + TOOLFLAGS := ${cjson_cflags} -DTOOL=MUSTACH_TOOL_CJSON + TOOLLIBS := ${cjson_libs} + TOOLDEP := mustach-cjson.h + else ifeq ($(tool),jsonc) + TOOLOBJS += mustach-json-c.o + TOOLFLAGS := ${jsonc_cflags} -DTOOL=MUSTACH_TOOL_JSON_C + TOOLLIBS := ${jsonc_libs} + TOOLDEP := mustach-json-c.h + else ifeq ($(tool),jansson) + TOOLOBJS += mustach-jansson.o + TOOLFLAGS := ${jansson_cflags} -DTOOL=MUSTACH_TOOL_JANSSON + TOOLLIBS := ${jansson_libs} + TOOLDEP := mustach-jansson.h + else + $(error Unknown library $(tool) for tool) + endif + ifneq ($($(tool)),yes) + $(error No library found for tool $(tool)) + endif + ALL += mustach +endif + +# compute targets +libs ?= all +ifeq (${libs},split) + ALL += ${SPLITLIB} ${SPLITPC} +else ifeq (${libs},single) + ALL += libmustach.so$(SOVEREV) libmustach.pc +else ifeq (${libs},all) + ALL += libmustach.so$(SOVEREV) libmustach.pc ${SPLITLIB} ${SPLITPC} +else ifneq (${libs},none) + $(error Unknown libs $(libs)) +endif + +# display target +$(info tool = ${tool}) +$(info libs = ${libs}) +$(info jsonc = ${jsonc}) +$(info jansson = ${jansson}) +$(info cjson = ${cjson}) + +# settings + +EFLAGS = -fPIC -Wall -Wextra -DVERSION=${VERSION} + +ifeq ($(shell uname),Darwin) + LDFLAGS_single += -install_name $(LIBDIR)/libmustach.so$(SOVEREV) + LDFLAGS_core += -install_name $(LIBDIR)/libmustach-core.so$(SOVEREV) + LDFLAGS_cjson += -install_name $(LIBDIR)/libmustach-cjson.so$(SOVEREV) + LDFLAGS_jsonc += -install_name $(LIBDIR)/libmustach-json-c.so$(SOVEREV) + LDFLAGS_jansson += -install_name $(LIBDIR)/libmustach-jansson.so$(SOVEREV) +else + LDFLAGS_single += -Wl,-soname,libmustach.so$(SOVER) + LDFLAGS_core += -Wl,-soname,libmustach-core.so$(SOVER) + LDFLAGS_cjson += -Wl,-soname,libmustach-cjson.so$(SOVER) + LDFLAGS_jsonc += -Wl,-soname,libmustach-json-c.so$(SOVER) + LDFLAGS_jansson += -Wl,-soname,libmustach-jansson.so$(SOVER) +endif + +# targets + +.PHONY: all +all: ${ALL} + +mustach: $(TOOLOBJS) + $(CC) $(LDFLAGS) $(TOOLFLAGS) -o mustach $(TOOLOBJS) $(TOOLLIBS) + +libmustach.so$(SOVEREV): $(SINGLEOBJS) + $(CC) -shared $(LDFLAGS) $(LDFLAGS_single) -o $@ $^ $(SINGLELIBS) + +libmustach-core.so$(SOVEREV): $(COREOBJS) + $(CC) -shared $(LDFLAGS) $(LDFLAGS_core) -o $@ $(COREOBJS) $(lib_OBJ) + +libmustach-cjson.so$(SOVEREV): $(COREOBJS) mustach-cjson.o + $(CC) -shared $(LDFLAGS) $(LDFLAGS_cjson) -o $@ $^ $(cjson_libs) + +libmustach-json-c.so$(SOVEREV): $(COREOBJS) mustach-json-c.o + $(CC) -shared $(LDFLAGS) $(LDFLAGS_jsonc) -o $@ $^ $(jsonc_libs) + +libmustach-jansson.so$(SOVEREV): $(COREOBJS) mustach-jansson.o + $(CC) -shared $(LDFLAGS) $(LDFLAGS_jansson) -o $@ $^ $(jansson_libs) + +# pkgconfigs + +%.pc: pkgcfgs + $(SED) -E '/^==.*==$$/{h;d};x;/==$@==/{x;s/VERSION/$(VERSION)/;p;d};x;d' $< > $@ + +# objects + +mustach.o: mustach.c mustach.h + $(CC) -c $(EFLAGS) $(CFLAGS) -o $@ $< + +mustach-wrap.o: mustach-wrap.c mustach.h mustach-wrap.h + $(CC) -c $(EFLAGS) $(CFLAGS) -o $@ $< + +mustach-tool.o: mustach-tool.c mustach.h mustach-json-c.h $(TOOLDEP) + $(CC) -c $(EFLAGS) $(CFLAGS) $(TOOLFLAGS) -o $@ $< + +mustach-cjson.o: mustach-cjson.c mustach.h mustach-wrap.h mustach-cjson.h + $(CC) -c $(EFLAGS) $(CFLAGS) $(cjson_cflags) -o $@ $< + +mustach-json-c.o: mustach-json-c.c mustach.h mustach-wrap.h mustach-json-c.h + $(CC) -c $(EFLAGS) $(CFLAGS) $(jsonc_cflags) -o $@ $< + +mustach-jansson.o: mustach-jansson.c mustach.h mustach-wrap.h mustach-jansson.h + $(CC) -c $(EFLAGS) $(CFLAGS) $(jansson_cflags) -o $@ $< + +# installing +.PHONY: install +install: all + $(INSTALL) -d $(DESTDIR)$(BINDIR) + if test "${tool}" != "none"; then \ + $(INSTALL) -m0755 mustach $(DESTDIR)$(BINDIR)/; \ + fi + $(INSTALL) -d $(DESTDIR)$(INCLUDEDIR)/mustach + $(INSTALL) -m0644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/mustach + $(INSTALL) -d $(DESTDIR)$(LIBDIR) + for x in libmustach*.so$(SOVEREV); do \ + $(INSTALL) -m0755 $$x $(DESTDIR)$(LIBDIR)/ ;\ + ln -sf $$x $(DESTDIR)$(LIBDIR)/$${x%.so.*}.so$(SOVER) ;\ + ln -sf $$x $(DESTDIR)$(LIBDIR)/$${x%.so.*}.so ;\ + done + $(INSTALL) -d $(DESTDIR)/$(PKGDIR) + $(INSTALL) -m0644 libmustach*.pc $(DESTDIR)/$(PKGDIR) + $(INSTALL) -d $(DESTDIR)/$(MANDIR)/man1 + $(INSTALL) -m0644 mustach.1.gz $(DESTDIR)/$(MANDIR)/man1 + +# deinstalling +.PHONY: uninstall +uninstall: + rm -f $(DESTDIR)$(BINDIR)/mustach + rm -f $(DESTDIR)$(LIBDIR)/libmustach*.so* + rm -rf $(DESTDIR)$(INCLUDEDIR)/mustach + +.PHONY: test test-basic test-specs +test: basic-tests spec-tests + +basic-tests: mustach + @$(MAKE) -C test1 test + @$(MAKE) -C test2 test + @$(MAKE) -C test3 test + @$(MAKE) -C test4 test + @$(MAKE) -C test5 test + @$(MAKE) -C test6 test + @$(MAKE) -C test7 test + @$(MAKE) -C test8 test + +spec-tests: $(TESTSPECS) + +test-specs/test-specs-%: test-specs/%-test-specs test-specs/specs + ./$< test-specs/spec/specs/[a-z]*.json > $@.last || true + diff $@.ref $@.last + +test-specs/cjson-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-cjson.h + $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(cjson_cflags) -DTEST=TEST_CJSON -o $@ $< + +test-specs/cjson-test-specs: test-specs/cjson-test-specs.o mustach-cjson.o $(COREOBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(cjson_libs) + +test-specs/json-c-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-json-c.h + $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(jsonc_cflags) -DTEST=TEST_JSON_C -o $@ $< + +test-specs/json-c-test-specs: test-specs/json-c-test-specs.o mustach-json-c.o $(COREOBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(jsonc_libs) + +test-specs/jansson-test-specs.o: test-specs/test-specs.c mustach.h mustach-wrap.h mustach-jansson.h + $(CC) -I. -c $(EFLAGS) $(CFLAGS) $(jansson_cflags) -DTEST=TEST_JANSSON -o $@ $< + +test-specs/jansson-test-specs: test-specs/jansson-test-specs.o mustach-jansson.o $(COREOBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(jansson_libs) + +.PHONY: test-specs/specs +test-specs/specs: + if test -d test-specs/spec; then \ + git -C test-specs/spec pull; \ + else \ + git -C test-specs clone https://github.com/mustache/spec.git; \ + fi + +#cleaning +.PHONY: clean +clean: + rm -f mustach libmustach*.so* *.o *.pc + rm -f test-specs/*-test-specs test-specs/test-specs-*.last + rm -rf *.gcno *.gcda coverage.info gcov-latest + @$(MAKE) -C test1 clean + @$(MAKE) -C test2 clean + @$(MAKE) -C test3 clean + @$(MAKE) -C test4 clean + @$(MAKE) -C test5 clean + @$(MAKE) -C test6 clean + @$(MAKE) -C test7 clean + @$(MAKE) -C test8 clean + +# manpage +.PHONY: manuals +manuals: mustach.1.gz + +mustach.1.gz: mustach.1.scd + if which scdoc >/dev/null 2>&1; then scdoc < mustach.1.scd | gzip > mustach.1.gz; fi diff --git a/src/templating/mustach-tool.c b/src/templating/mustach-tool.c index 364e34a84..5f28c1f58 100644 --- a/src/templating/mustach-tool.c +++ b/src/templating/mustach-tool.c @@ -1,23 +1,14 @@ /* 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. + SPDX-License-Identifier: ISC */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include <stdlib.h> #include <stdio.h> @@ -27,7 +18,7 @@ #include <string.h> #include <libgen.h> -#include "mustach-json-c.h" +#include "mustach-wrap.h" static const size_t BLOCKSIZE = 8192; @@ -43,16 +34,40 @@ static const char *errors[] = { "bad unescape tag", "invalid interface", "item not found", - "partial not found" + "partial not found", + "undefined tag", + "too much template nesting" }; +static const char *errmsg = 0; +static int flags = 0; +static FILE *output = 0; + static void help(char *prog) { - printf("usage: %s json-file mustach-templates...\n", basename(prog)); + char *name = basename(prog); +#define STR_INDIR(x) #x +#define STR(x) STR_INDIR(x) + printf("%s version %s\n", name, STR(VERSION)); +#undef STR +#undef STR_INDIR + printf( + "\n" + "USAGE:\n" + " %s [FLAGS] <json-file> <mustach-templates...>\n" + "\n" + "FLAGS:\n" + " -h, --help Prints help information\n" + " -s, --strict Error when a tag is undefined\n" + "\n" + "ARGS: (if a file is -, read standard input)\n" + " <json-file> JSON file with input data\n" + " <mustach-templates...> Template files to instantiate\n", + name); exit(0); } -static char *readfile(const char *filename) +static char *readfile(const char *filename, size_t *length) { int f; struct stat s; @@ -106,50 +121,138 @@ static char *readfile(const char *filename) } while(rc > 0); close(f); + if (length != NULL) + *length = pos; result[pos] = 0; return result; } +static int load_json(const char *filename); +static int process(const char *content, size_t length); +static void close_json(); + int main(int ac, char **av) { - struct json_object *o; - char *t; + char *t, *f; char *prog = *av; int s; + size_t length; (void)ac; /* unused */ + flags = Mustach_With_AllExtensions; + output = stdout; - if (*++av) { + for( ++av ; av[0] && av[0][0] == '-' && av[0][1] != 0 ; 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]); + if (!strcmp(*av, "-s") || !strcmp(*av, "--strict")) + flags |= Mustach_With_ErrorUndefined; + } + if (*av) { + f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0]; + s = load_json(f); + if (s < 0) { + fprintf(stderr, "Can't load json file %s\n", av[0]); + if(errmsg) + fprintf(stderr, " reason: %s\n", errmsg); exit(1); } while(*++av) { - t = readfile(*av); - s = fmustach_json_c(t, o, stdout); - if (s != 0) { + t = readfile(*av, &length); + s = process(t, length); + free(t); + if (s != MUSTACH_OK) { 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); + close_json(); + } + return 0; +} + +#define MUSTACH_TOOL_JSON_C 1 +#define MUSTACH_TOOL_JANSSON 2 +#define MUSTACH_TOOL_CJSON 3 + +#if TOOL == MUSTACH_TOOL_JSON_C + +#include "mustach-json-c.h" + +static struct json_object *o; +static int load_json(const char *filename) +{ + o = json_object_from_file(filename); +#if JSON_C_VERSION_NUM >= 0x000D00 + errmsg = json_util_get_last_err(); + if (errmsg != NULL) + return -1; +#endif + if (o == NULL) { + errmsg = "null json"; + return -1; + } + return 0; +} +static int process(const char *content, size_t length) +{ + return mustach_json_c_file(content, length, o, flags, output); +} +static void close_json() +{ + json_object_put(o); +} + +#elif TOOL == MUSTACH_TOOL_JANSSON + +#include "mustach-jansson.h" + +static json_t *o; +static json_error_t e; +static int load_json(const char *filename) +{ + o = json_load_file(filename, JSON_DECODE_ANY, &e); + if (o == NULL) { + errmsg = e.text; + return -1; } return 0; } +static int process(const char *content, size_t length) +{ + return mustach_jansson_file(content, length, o, flags, output); +} +static void close_json() +{ + json_decref(o); +} + +#elif TOOL == MUSTACH_TOOL_CJSON +#include "mustach-cjson.h" + +static cJSON *o; +static int load_json(const char *filename) +{ + char *t; + size_t length; + + t = readfile(filename, &length); + o = t ? cJSON_ParseWithLength(t, length) : NULL; + free(t); + return -!o; +} +static int process(const char *content, size_t length) +{ + return mustach_cJSON_file(content, length, o, flags, output); +} +static void close_json() +{ + cJSON_Delete(o); +} + +#else +#error "no defined json library" +#endif diff --git a/src/templating/mustach-wrap.c b/src/templating/mustach-wrap.c new file mode 100644 index 000000000..2cd00db12 --- /dev/null +++ b/src/templating/mustach-wrap.c @@ -0,0 +1,482 @@ +/* + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifdef _WIN32 +#include <malloc.h> +#endif + +#include "mustach.h" +#include "mustach-wrap.h" + +/* +* It was stated that allowing to include files +* through template is not safe when the mustache +* template is open to any value because it could +* create leaks (example: {{>/etc/passwd}}). +*/ +#if MUSTACH_SAFE +# undef MUSTACH_LOAD_TEMPLATE +#elif !defined(MUSTACH_LOAD_TEMPLATE) +# define MUSTACH_LOAD_TEMPLATE 1 +#endif + +#if !defined(INCLUDE_PARTIAL_EXTENSION) +# define INCLUDE_PARTIAL_EXTENSION ".mustache" +#endif + +/* global hook for partials */ +int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf) = NULL; + +/* internal structure for wrapping */ +struct wrap { + /* original interface */ + const struct mustach_wrap_itf *itf; + + /* original closure */ + void *closure; + + /* flags */ + int flags; + + /* emiter callback */ + mustach_emit_cb_t *emitcb; + + /* write callback */ + mustach_write_cb_t *writecb; +}; + +/* length given by masking with 3 */ +enum comp { + C_no = 0, + C_eq = 1, + C_lt = 5, + C_le = 6, + C_gt = 9, + C_ge = 10 +}; + +enum sel { + S_none = 0, + S_ok = 1, + S_objiter = 2, + S_ok_or_objiter = S_ok | S_objiter +}; + +static enum comp getcomp(char *head, int sflags) +{ + return (head[0] == '=' && (sflags & Mustach_With_Equal)) ? C_eq + : (head[0] == '<' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_le : C_lt) + : (head[0] == '>' && (sflags & Mustach_With_Compare)) ? (head[1] == '=' ? C_ge : C_gt) + : C_no; +} + +static char *keyval(char *head, int sflags, enum comp *comp) +{ + char *w, car, escaped; + enum comp k; + + k = C_no; + w = head; + car = *head; + escaped = (sflags & Mustach_With_EscFirstCmp) && (getcomp(head, sflags) != C_no); + while (car && (escaped || (k = getcomp(head, sflags)) == C_no)) { + if (escaped) + escaped = 0; + else + escaped = ((sflags & Mustach_With_JsonPointer) ? car == '~' : car == '\\') + && (getcomp(head + 1, sflags) != C_no); + if (!escaped) + *w++ = car; + head++; + car = *head; + } + *w = 0; + *comp = k; + return k == C_no ? NULL : &head[k & 3]; +} + +static char *getkey(char **head, int sflags) +{ + char *result, *iter, *write, car; + + car = *(iter = *head); + if (!car) + result = NULL; + else { + result = write = iter; + if (sflags & Mustach_With_JsonPointer) + { + while (car && car != '/') { + if (car == '~') + switch (iter[1]) { + case '1': car = '/'; /*@fallthrough@*/ + case '0': iter++; + } + *write++ = car; + car = *++iter; + } + *write = 0; + while (car == '/') + car = *++iter; + } + else + { + while (car && car != '.') { + if (car == '\\' && (iter[1] == '.' || iter[1] == '\\')) + car = *++iter; + *write++ = car; + car = *++iter; + } + *write = 0; + while (car == '.') + car = *++iter; + } + *head = iter; + } + return result; +} + +static enum sel sel(struct wrap *w, const char *name) +{ + enum sel result; + int i, j, sflags, scmp; + char *key, *value; + enum comp k; + + /* make a local writeable copy */ + size_t lenname = 1 + strlen(name); + char buffer[lenname]; + char *copy = buffer; + memcpy(copy, name, lenname); + + /* check if matches json pointer selection */ + sflags = w->flags; + if (sflags & Mustach_With_JsonPointer) { + if (copy[0] == '/') + copy++; + else + sflags ^= Mustach_With_JsonPointer; + } + + /* extract the value, translate the key and get the comparator */ + if (sflags & (Mustach_With_Equal | Mustach_With_Compare)) + value = keyval(copy, sflags, &k); + else { + k = C_no; + value = NULL; + } + + /* case of . alone if Mustach_With_SingleDot? */ + if (copy[0] == '.' && copy[1] == 0 /*&& (sflags & Mustach_With_SingleDot)*/) + /* yes, select current */ + result = w->itf->sel(w->closure, NULL) ? S_ok : S_none; + else + { + /* not the single dot, extract the first key */ + key = getkey(©, sflags); + if (key == NULL) + return 0; + + /* select the root item */ + if (w->itf->sel(w->closure, key)) + result = S_ok; + else if (key[0] == '*' + && !key[1] + && !value + && !*copy + && (w->flags & Mustach_With_ObjectIter) + && w->itf->sel(w->closure, NULL)) + result = S_ok_or_objiter; + else + result = S_none; + if (result == S_ok) { + /* iterate the selection of sub items */ + key = getkey(©, sflags); + while(result == S_ok && key) { + if (w->itf->subsel(w->closure, key)) + /* nothing */; + else if (key[0] == '*' + && !key[1] + && !value + && !*copy + && (w->flags & Mustach_With_ObjectIter)) + result = S_objiter; + else + result = S_none; + key = getkey(©, sflags); + } + } + } + /* should it be compared? */ + if (result == S_ok && value) { + if (!w->itf->compare) + result = S_none; + else { + i = value[0] == '!'; + scmp = w->itf->compare(w->closure, &value[i]); + switch (k) { + case C_eq: j = scmp == 0; break; + case C_lt: j = scmp < 0; break; + case C_le: j = scmp <= 0; break; + case C_gt: j = scmp > 0; break; + case C_ge: j = scmp >= 0; break; + default: j = i; break; + } + if (i == j) + result = S_none; + } + } + return result; +} + +static int start_callback(void *closure) +{ + struct wrap *w = closure; + return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK; +} + +static void stop_callback(void *closure, int status) +{ + struct wrap *w = closure; + if (w->itf->stop) + w->itf->stop(w->closure, status); +} + +static int emit(struct wrap *w, const char *buffer, size_t size, FILE *file) +{ + int r; + + if (w->writecb) + r = w->writecb(file, buffer, size); + else + r = fwrite(buffer, 1, size, file) == size ? MUSTACH_OK : MUSTACH_ERROR_SYSTEM; + return r; +} + +static int emit_callback(void *closure, const char *buffer, size_t size, int escape, FILE *file) +{ + struct wrap *w = closure; + int r; + size_t s, i; + char car; + + if (w->emitcb) + r = w->emitcb(file, buffer, size, escape); + else if (!escape) + r = emit(w, buffer, size, file); + else { + i = 0; + r = MUSTACH_OK; + while(i < size && r == MUSTACH_OK) { + s = i; + while (i < size && (car = buffer[i]) != '<' && car != '>' && car != '&' && car != '"') + i++; + if (i != s) + r = emit(w, &buffer[s], i - s, file); + if (i < size && r == MUSTACH_OK) { + switch(car) { + case '<': r = emit(w, "<", 4, file); break; + case '>': r = emit(w, ">", 4, file); break; + case '&': r = emit(w, "&", 5, file); break; + case '"': r = emit(w, """, 6, file); break; + } + i++; + } + } + } + return r; +} + +static int enter_callback(void *closure, const char *name) +{ + struct wrap *w = closure; + enum sel s = sel(w, name); + return s == S_none ? 0 : w->itf->enter(w->closure, s & S_objiter); +} + +static int next_callback(void *closure) +{ + struct wrap *w = closure; + return w->itf->next(w->closure); +} + +static int leave_callback(void *closure) +{ + struct wrap *w = closure; + return w->itf->leave(w->closure); +} + +static int getoptional(struct wrap *w, const char *name, struct mustach_sbuf *sbuf) +{ + enum sel s = sel(w, name); + if (!(s & S_ok)) + return 0; + return w->itf->get(w->closure, sbuf, s & S_objiter); +} + +static int get_callback(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ + struct wrap *w = closure; + if (getoptional(w, name, sbuf) <= 0) { + if (w->flags & Mustach_With_ErrorUndefined) + return MUSTACH_ERROR_UNDEFINED_TAG; + sbuf->value = ""; + } + return MUSTACH_OK; +} + +#if MUSTACH_LOAD_TEMPLATE +static int get_partial_from_file(const char *name, struct mustach_sbuf *sbuf) +{ + static char extension[] = INCLUDE_PARTIAL_EXTENSION; + size_t s; + long pos; + FILE *file; + char *path, *buffer; + + /* allocate path */ + s = strlen(name); + path = malloc(s + sizeof extension); + if (path == NULL) + return MUSTACH_ERROR_SYSTEM; + + /* try without extension first */ + memcpy(path, name, s + 1); + file = fopen(path, "r"); + if (file == NULL) { + memcpy(&path[s], extension, sizeof extension); + file = fopen(path, "r"); + } + free(path); + + /* if file opened */ + if (file == NULL) + return MUSTACH_ERROR_PARTIAL_NOT_FOUND; + + /* compute file size */ + if (fseek(file, 0, SEEK_END) >= 0 + && (pos = ftell(file)) >= 0 + && fseek(file, 0, SEEK_SET) >= 0) { + /* allocate value */ + s = (size_t)pos; + buffer = malloc(s + 1); + if (buffer != NULL) { + /* read value */ + if (1 == fread(buffer, s, 1, file)) { + /* force zero at end */ + sbuf->value = buffer; + buffer[s] = 0; + sbuf->freecb = free; + fclose(file); + return MUSTACH_OK; + } + free(buffer); + } + } + fclose(file); + return MUSTACH_ERROR_SYSTEM; +} +#endif + +static int partial_callback(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ + struct wrap *w = closure; + int rc; + if (mustach_wrap_get_partial != NULL) { + rc = mustach_wrap_get_partial(name, sbuf); + if (rc != MUSTACH_ERROR_PARTIAL_NOT_FOUND) { + if (rc != MUSTACH_OK) + sbuf->value = ""; + return rc; + } + } +#if MUSTACH_LOAD_TEMPLATE + if (w->flags & Mustach_With_PartialDataFirst) { + if (getoptional(w, name, sbuf) > 0) + rc = MUSTACH_OK; + else + rc = get_partial_from_file(name, sbuf); + } + else { + rc = get_partial_from_file(name, sbuf); + if (rc != MUSTACH_OK && getoptional(w, name, sbuf) > 0) + rc = MUSTACH_OK; + } +#else + rc = getoptional(w, name, sbuf) > 0 ? MUSTACH_OK : MUSTACH_ERROR_PARTIAL_NOT_FOUND; +#endif + if (rc != MUSTACH_OK) + sbuf->value = ""; + return MUSTACH_OK; +} + +const struct mustach_itf mustach_wrap_itf = { + .start = start_callback, + .put = NULL, + .enter = enter_callback, + .next = next_callback, + .leave = leave_callback, + .partial = partial_callback, + .get = get_callback, + .emit = emit_callback, + .stop = stop_callback +}; + +static void wrap_init(struct wrap *wrap, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, mustach_write_cb_t *writecb) +{ + if (flags & Mustach_With_Compare) + flags |= Mustach_With_Equal; + wrap->closure = closure; + wrap->itf = itf; + wrap->flags = flags; + wrap->emitcb = emitcb; + wrap->writecb = writecb; +} + +int mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, NULL); + return mustach_file(template, length, &mustach_wrap_itf, &w, flags, file); +} + +int mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, NULL); + return mustach_fd(template, length, &mustach_wrap_itf, &w, flags, fd); +} + +int mustach_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, NULL); + return mustach_mem(template, length, &mustach_wrap_itf, &w, flags, result, size); +} + +int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, NULL, writecb); + return mustach_file(template, length, &mustach_wrap_itf, &w, flags, writeclosure); +} + +int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure) +{ + struct wrap w; + wrap_init(&w, itf, closure, flags, emitcb, NULL); + return mustach_file(template, length, &mustach_wrap_itf, &w, flags, emitclosure); +} + diff --git a/src/templating/mustach-wrap.h b/src/templating/mustach-wrap.h new file mode 100644 index 000000000..fedcb9191 --- /dev/null +++ b/src/templating/mustach-wrap.h @@ -0,0 +1,235 @@ +/* + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _mustach_wrap_h_included_ +#define _mustach_wrap_h_included_ + +/* + * mustach-wrap is intended to make integration of JSON + * libraries easier by wrapping mustach extensions in a + * single place. + * + * As before, using mustach and only mustach is possible + * (by using only mustach.h) but does not implement high + * level features coming with extensions implemented by + * this high level wrapper. + */ +#include "mustach.h" +/* + * Definition of the writing callbacks for mustach functions + * producing output to callbacks. + * + * Two callback types are defined: + * + * @mustach_write_cb_t: + * + * callback receiving the escaped data to be written as 3 parameters: + * + * 1. the 'closure', the same given to the wmustach_... function + * 2. a pointer to a 'buffer' containing the characters to be written + * 3. the size in bytes of the data pointed by 'buffer' + * + * @mustach_emit_cb_t: + * + * callback receiving the data to be written and a flag indicating + * if escaping should be done or not as 4 parameters: + * + * 1. the 'closure', the same given to the emustach_... function + * 2. a pointer to a 'buffer' containing the characters to be written + * 3. the size in bytes of the data pointed by 'buffer' + * 4. a boolean indicating if 'escape' should be done + */ +#ifndef _mustach_output_callbacks_defined_ +#define _mustach_output_callbacks_defined_ +typedef int mustach_write_cb_t(void *closure, const char *buffer, size_t size); +typedef int mustach_emit_cb_t(void *closure, const char *buffer, size_t size, int escape); +#endif + +/** + * Flags specific to mustach wrap + */ +#define Mustach_With_SingleDot 4 /* obsolete, always set */ +#define Mustach_With_Equal 8 +#define Mustach_With_Compare 16 +#define Mustach_With_JsonPointer 32 +#define Mustach_With_ObjectIter 64 +#define Mustach_With_IncPartial 128 /* obsolete, always set */ +#define Mustach_With_EscFirstCmp 256 +#define Mustach_With_PartialDataFirst 512 +#define Mustach_With_ErrorUndefined 1024 + +#undef Mustach_With_AllExtensions +#define Mustach_With_AllExtensions 1023 /* don't include ErrorUndefined */ + +/** + * mustach_wrap_itf - high level wrap of mustach - interface for callbacks + * + * The functions sel, subsel, enter and next should return 0 or 1. + * + * All other functions should normally return MUSTACH_OK (zero). + * + * If any function returns a negative value, it means an error that + * stop the processing and that is reported to the caller. Mustach + * also has its own error codes. Using the macros MUSTACH_ERROR_USER + * and MUSTACH_IS_ERROR_USER could help to avoid clashes. + * + * @start: If defined (can be NULL), starts the mustach processing + * of the closure, called at the very beginning before any + * mustach processing occurs. + * + * @stop: If defined (can be NULL), stops the mustach processing + * of the closure, called at the very end after all mustach + * processing occurered. The status returned by the processing + * is passed to the stop. + * + * @compare: If defined (can be NULL), compares the value of the + * currently selected item with the given value and returns + * a negative value if current value is lesser, a positive + * value if the current value is greater or zero when + * values are equals. + * If 'compare' is NULL, any comparison in mustach + * is going to fails. + * + * @sel: Selects the item of the given 'name'. If 'name' is NULL + * Selects the current item. Returns 1 if the selection is + * effective or else 0 if the selection failed. + * + * @subsel: Selects from the currently selected object the value of + * the field of given name. Returns 1 if the selection is + * effective or else 0 if the selection failed. + * + * @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 + * + * @get: Returns in 'sbuf' the value of the current selection if 'key' + * is zero. Otherwise, when 'key' is not zero, return in 'sbuf' + * the name of key of the current selection, or if no such key + * exists, the empty string. Must return 1 if possible or + * 0 when not possible or an error code. + */ +struct mustach_wrap_itf { + int (*start)(void *closure); + void (*stop)(void *closure, int status); + int (*compare)(void *closure, const char *value); + int (*sel)(void *closure, const char *name); + int (*subsel)(void *closure, const char *name); + int (*enter)(void *closure, int objiter); + int (*next)(void *closure); + int (*leave)(void *closure); + int (*get)(void *closure, struct mustach_sbuf *sbuf, int key); +}; + +/** + * Mustach interface used internally by mustach wrapper functions. + * Can be used for overriding behaviour. + */ +extern const struct mustach_itf mustach_wrap_itf; + +/** + * Global hook for providing partials. When set to a not NULL value, the pointed + * function replaces the default behaviour and is called to provide the partial + * of the given 'name' in 'sbuf'. + * The function must return MUSTACH_OK when it filled 'sbuf' with value of partial + * or must return an error code if it failed. But if MUSTACH_ERROR_PARTIAL_NOT_FOUND + * is returned, the default behavior is evaluated. + */ +extern int (*mustach_wrap_get_partial)(const char *name, struct mustach_sbuf *sbuf); + +/** + * mustach_wrap_file - Renders the mustache 'template' in 'file' for an abstract + * wrapper of interface 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @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 mustach_wrap_file(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, FILE *file); + +/** + * mustach_wrap_fd - Renders the mustache 'template' in 'fd' for an abstract + * wrapper of interface 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @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 mustach_wrap_fd(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, int fd); + +/** + * mustach_wrap_mem - Renders the mustache 'template' in 'result' for an abstract + * wrapper of interface 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @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_wrap_mem(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, char **result, size_t *size); + +/** + * mustach_wrap_write - Renders the mustache 'template' for an abstract + * wrapper of interface 'itf' and 'closure' to custom writer + * 'writecb' with 'writeclosure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @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. + */ +extern int mustach_wrap_write(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_write_cb_t *writecb, void *writeclosure); + +/** + * mustach_wrap_emit - Renders the mustache 'template' for an abstract + * wrapper of interface 'itf' and 'closure' to custom emiter 'emitcb' + * with 'emitclosure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @itf: the interface of the abstract wrapper + * @closure: the closure of the abstract wrapper + * @emitcb: the function that emit 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. + */ +extern int mustach_wrap_emit(const char *template, size_t length, const struct mustach_wrap_itf *itf, void *closure, int flags, mustach_emit_cb_t *emitcb, void *emitclosure); + +#endif + diff --git a/src/templating/mustach.1.gz b/src/templating/mustach.1.gz Binary files differnew file mode 100644 index 000000000..15b8a9052 --- /dev/null +++ b/src/templating/mustach.1.gz diff --git a/src/templating/mustach.1.scd b/src/templating/mustach.1.scd new file mode 100644 index 000000000..af4f08ef2 --- /dev/null +++ b/src/templating/mustach.1.scd @@ -0,0 +1,60 @@ +mustach(1) + +# NAME + +mustach - Mustache templating command line engine + +# SYNOPSIS + +*mustach* [-s|--strict] JSON TEMPLATE... + +# DESCRIPTION + +Instanciate the TEMPLATE files accordingly to the JSON file. + +If one of the given files is *-*, the standard input is used. + +Option *--strict* make mustach fail if a tag is not found. + +# EXAMPLE + +A typical Mustache template file: *temp.must* + +``` +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +``` + +Given a JSON file: *inst.json* + +``` +{ + "name": "Chris", + "value": 10000, + "taxed_value": 6000, + "in_ca": true +} +``` + +Calling the command *mustach inst.json temp.must* +will produce the following output: + +``` +Hello Chris +You have just won 10000 dollars! +Well, 6000.0 dollars, after taxes. +``` + +# LINK + +Site of *mustach*, the *C* implementation: https://gitlab.com/jobol/mustach + +*Mustache format*: http://mustache.github.io/mustache.5.html + +Main site for *Mustache*: http://mustache.github.io/ + +JSON: https://www.json.org/ + diff --git a/src/templating/mustach.c b/src/templating/mustach.c index caa80dcc9..9f5af131c 100644 --- a/src/templating/mustach.c +++ b/src/templating/mustach.c @@ -1,23 +1,14 @@ /* 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. + SPDX-License-Identifier: ISC */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include <stdlib.h> #include <stdio.h> @@ -27,19 +18,9 @@ #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 */ @@ -51,6 +32,15 @@ struct iwrap { 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 */ + FILE *file; + int flags; + int nesting; +}; + +struct prefix { + size_t len; + const char *start; + struct prefix *prefix; }; #if !defined(NO_OPEN_MEMSTREAM) @@ -135,6 +125,7 @@ static inline void sbuf_reset(struct mustach_sbuf *sbuf) sbuf->value = NULL; sbuf->freecb = NULL; sbuf->closure = NULL; + sbuf->length = 0; } static inline void sbuf_release(struct mustach_sbuf *sbuf) @@ -143,38 +134,47 @@ static inline void sbuf_release(struct mustach_sbuf *sbuf) sbuf->releasecb(sbuf->value, sbuf->closure); } +static inline size_t sbuf_length(struct mustach_sbuf *sbuf) +{ + size_t length = sbuf->length; + if (length == 0 && sbuf->value != NULL) + length = strlen(sbuf->value); + return length; +} + static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) { - size_t i, j; + size_t i, j, r; (void)closure; /* unused */ if (!escape) - return fwrite(buffer, size, 1, file) != 1 ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; + return fwrite(buffer, 1, size, file) != size ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; - i = 0; + r = i = 0; while (i < size) { j = i; - while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&') + while (j < size && buffer[j] != '<' && 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; + r = fwrite("<", 4, 1, file); break; case '>': - if (fwrite(">", 4, 1, file) != 1) - return MUSTACH_ERROR_SYSTEM; + r = fwrite(">", 4, 1, file); break; case '&': - if (fwrite("&", 5, 1, file) != 1) - return MUSTACH_ERROR_SYSTEM; + r = fwrite("&", 5, 1, file); + break; + case '"': + r = fwrite(""", 6, 1, file); break; - default: break; } + if (r != 1) + return MUSTACH_ERROR_SYSTEM; } i = j; } @@ -191,7 +191,7 @@ static int iwrap_put(void *closure, const char *name, int escape, FILE *file) sbuf_reset(&sbuf); rc = iwrap->get(iwrap->closure, name, &sbuf); if (rc >= 0) { - length = strlen(sbuf.value); + length = sbuf_length(&sbuf); if (length) rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file); sbuf_release(&sbuf); @@ -220,55 +220,111 @@ static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *s if (rc == 0) { sbuf->value = result; sbuf->freecb = free; + sbuf->length = size; } } } return rc; } -static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr) +static int emitprefix(struct iwrap *iwrap, struct prefix *prefix) +{ + if (prefix->prefix) { + int rc = emitprefix(iwrap, prefix->prefix); + if (rc < 0) + return rc; + } + return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, iwrap->file) : 0; +} + +static int process(const char *template, size_t length, struct iwrap *iwrap, struct prefix *prefix) { 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]; + char opstr[MUSTACH_MAX_DELIM_LENGTH], clstr[MUSTACH_MAX_DELIM_LENGTH]; + char name[MUSTACH_MAX_LENGTH + 1], c; + const char *beg, *term, *end; + struct { const char *name, *again; size_t length; unsigned enabled: 1, entered: 1; } 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; + int depth, rc, enabled, stdalone; + struct prefix pref; + + pref.prefix = prefix; + end = template + (length ? length : strlen(template)); + opstr[0] = opstr[1] = '{'; + clstr[0] = clstr[1] = '}'; + oplen = cllen = 2; + stdalone = enabled = 1; + depth = pref.len = 0; + for (;;) { + /* search next openning delimiter */ + for (beg = template ; ; beg++) { + c = beg == end ? '\n' : *beg; + if (c == '\n') { + l = (beg != end) + (size_t)(beg - template); + if (stdalone != 2 && enabled) { + if (beg != template /* don't prefix empty lines */) { + rc = emitprefix(iwrap, &pref); + if (rc < 0) + return rc; + } + rc = iwrap->emit(iwrap->closure, template, l, 0, iwrap->file); + if (rc < 0) + return rc; + } + if (beg == end) /* no more mustach */ + return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK; + template += l; + stdalone = 1; + pref.len = 0; + pref.prefix = prefix; + } + else if (!isspace(c)) { + if (stdalone == 2 && enabled) { + rc = emitprefix(iwrap, &pref); + if (rc < 0) + return rc; + pref.len = 0; + stdalone = 0; + pref.prefix = NULL; + } + if (c == *opstr && end - beg >= (ssize_t)oplen) { + for (l = 1 ; l < oplen && beg[l] == opstr[l] ; l++); + if (l == oplen) + break; + } + stdalone = 0; } - 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; } + + pref.start = template; + pref.len = enabled ? (size_t)(beg - template) : 0; beg += oplen; - term = strstr(beg, clstr); - if (term == NULL) - return MUSTACH_ERROR_UNEXPECTED_END; + + /* search next closing delimiter */ + for (term = beg ; ; term++) { + if (term == end) + return MUSTACH_ERROR_UNEXPECTED_END; + if (*term == *clstr && end - term >= (ssize_t)cllen) { + for (l = 1 ; l < cllen && term[l] == clstr[l] ; l++); + if (l == cllen) + break; + } + } template = term + cllen; len = (size_t)(term - beg); c = *beg; switch(c) { + case ':': + stdalone = 0; + if (iwrap->flags & Mustach_With_Colon) + goto exclude_first; + goto get_name; case '!': case '=': break; case '{': - for (l = 0 ; clstr[l] == '}' ; l++); - if (clstr[l]) { + for (l = 0 ; l < cllen && clstr[l] == '}' ; l++); + if (l < cllen) { if (!len || beg[len-1] != '}') return MUSTACH_ERROR_BAD_UNESCAPE_TAG; len--; @@ -279,55 +335,64 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const } c = '&'; /*@fallthrough@*/ + case '&': + stdalone = 0; + /*@fallthrough@*/ case '^': case '#': case '/': - case '&': case '>': -#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH) - case ':': -#endif - beg++; len--; +exclude_first: + beg++; + len--; + goto get_name; default: + stdalone = 0; +get_name: while (len && isspace(beg[0])) { beg++; len--; } while (len && isspace(beg[len-1])) len--; -#if !defined(NO_ALLOW_EMPTY_TAG) - if (len == 0) + if (len == 0 && !(iwrap->flags & Mustach_With_EmptyTag)) 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; } + if (stdalone) + stdalone = 2; + else if (enabled) { + rc = emitprefix(iwrap, &pref); + if (rc < 0) + return rc; + pref.len = 0; + pref.prefix = NULL; + } switch(c) { case '!': /* comment */ /* nothing to do */ break; case '=': - /* defines separators */ + /* defines delimiters */ if (len < 5 || beg[len - 1] != '=') return MUSTACH_ERROR_BAD_SEPARATORS; beg++; len -= 2; + while (len && isspace(*beg)) + beg++, len--; + while (len && isspace(beg[len - 1])) + len--; for (l = 0; l < len && !isspace(beg[l]) ; l++); - if (l == len) + if (l == len || l > MUSTACH_MAX_DELIM_LENGTH) return MUSTACH_ERROR_BAD_SEPARATORS; oplen = l; - tmp = alloca(oplen + 1); - memcpy(tmp, beg, oplen); - tmp[oplen] = 0; - opstr = tmp; + memcpy(opstr, beg, l); while (l < len && isspace(beg[l])) l++; - if (l == len) + if (l == len || len - l > MUSTACH_MAX_DELIM_LENGTH) return MUSTACH_ERROR_BAD_SEPARATORS; cllen = len - l; - tmp = alloca(cllen + 1); - memcpy(tmp, beg + l, cllen); - tmp[cllen] = 0; - clstr = tmp; + memcpy(clstr, beg + l, cllen); break; case '^': case '#': @@ -343,8 +408,8 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const stack[depth].name = beg; stack[depth].again = template; stack[depth].length = len; - stack[depth].enabled = enabled; - stack[depth].entered = rc; + stack[depth].enabled = enabled != 0; + stack[depth].entered = rc != 0; if ((c == '#') == (rc == 0)) enabled = 0; depth++; @@ -367,11 +432,17 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const 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 (iwrap->nesting >= MUSTACH_MAX_NESTING) + rc = MUSTACH_ERROR_TOO_MUCH_NESTING; + else { + sbuf_reset(&sbuf); + rc = iwrap->partial(iwrap->closure_partial, name, &sbuf); + if (rc >= 0) { + iwrap->nesting++; + rc = process(sbuf.value, sbuf_length(&sbuf), iwrap, &pref); + sbuf_release(&sbuf); + iwrap->nesting--; + } } if (rc < 0) return rc; @@ -380,7 +451,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const default: /* replacement */ if (enabled) { - rc = iwrap->put(iwrap->closure_put, name, c != '&', file); + rc = iwrap->put(iwrap->closure_put, name, c != '&', iwrap->file); if (rc < 0) return rc; } @@ -389,7 +460,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const } } -int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file) +int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file) { int rc; struct iwrap iwrap; @@ -422,17 +493,20 @@ int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE iwrap.next = itf->next; iwrap.leave = itf->leave; iwrap.get = itf->get; + iwrap.file = file; + iwrap.flags = flags; + iwrap.nesting = 0; /* process */ rc = itf->start ? itf->start(closure) : 0; if (rc == 0) - rc = process(template, &iwrap, file, "{{", "}}"); + rc = process(template, length, &iwrap, NULL); if (itf->stop) itf->stop(closure, rc); return rc; } -int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd) +int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd) { int rc; FILE *file; @@ -442,13 +516,13 @@ int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int rc = MUSTACH_ERROR_SYSTEM; errno = ENOMEM; } else { - rc = fmustach(template, itf, closure, file); + rc = mustach_file(template, length, itf, closure, flags, file); fclose(file); } return rc; } -int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size) +int mustach_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size) { int rc; FILE *file; @@ -461,7 +535,7 @@ int mustach(const char *template, struct mustach_itf *itf, void *closure, char * if (file == NULL) rc = MUSTACH_ERROR_SYSTEM; else { - rc = fmustach(template, itf, closure, file); + rc = mustach_file(template, length, itf, closure, flags, file); if (rc < 0) memfile_abort(file, result, size); else @@ -470,3 +544,18 @@ int mustach(const char *template, struct mustach_itf *itf, void *closure, char * return rc; } +int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file) +{ + return mustach_file(template, 0, itf, closure, Mustach_With_AllExtensions, file); +} + +int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd) +{ + return mustach_fd(template, 0, itf, closure, Mustach_With_AllExtensions, fd); +} + +int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size) +{ + return mustach_mem(template, 0, itf, closure, Mustach_With_AllExtensions, result, size); +} + diff --git a/src/templating/mustach.h b/src/templating/mustach.h index ad952275c..1b44582d5 100644 --- a/src/templating/mustach.h +++ b/src/templating/mustach.h @@ -1,20 +1,9 @@ /* 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. + SPDX-License-Identifier: ISC */ #ifndef _mustach_h_included_ @@ -25,32 +14,77 @@ struct mustach_sbuf; /* see below */ /** * Current version of mustach and its derivates */ -#define MUSTACH_VERSION 99 +#define MUSTACH_VERSION 102 #define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100) #define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100) /** - * Maximum nested imbrications supported + * Maximum nested section supported */ #define MUSTACH_MAX_DEPTH 256 /** + * Maximum nested template supported + */ +#define MUSTACH_MAX_NESTING 64 + +/** * Maximum length of tags in mustaches {{...}} */ -#define MUSTACH_MAX_LENGTH 1024 +#define MUSTACH_MAX_LENGTH 4096 /** - * mustach_itf - interface for callbacks + * Maximum length of delimitors (2 normally but extended here) + */ +#define MUSTACH_MAX_DELIM_LENGTH 8 + +/** + * Flags specific to mustach core + */ +#define Mustach_With_NoExtensions 0 +#define Mustach_With_Colon 1 +#define Mustach_With_EmptyTag 2 +#define Mustach_With_AllExtensions 3 + +/* + * 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 +#define MUSTACH_ERROR_UNDEFINED_TAG -12 +#define MUSTACH_ERROR_TOO_MUCH_NESTING -13 + +/* + * You can use definition below for user specific error * - * 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 macro MUSTACH_ERROR_USER is involutive so for any value + * value = MUSTACH_ERROR_USER(MUSTACH_ERROR_USER(value)) + */ +#define MUSTACH_ERROR_USER_BASE -100 +#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x)) +#define MUSTACH_IS_ERROR_USER(x) (MUSTACH_ERROR_USER(x) >= 0) + +/** + * mustach_itf - pure abstract mustach - interface for callbacks * * 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. + * + * If any function returns a negative value, it means an error that + * stop the processing and that is reported to the caller. Mustach + * also has its own error codes. Using the macros MUSTACH_ERROR_USER + * and MUSTACH_IS_ERROR_USER could help to avoid clashes. * * @start: If defined (can be NULL), starts the mustach processing * of the closure, called at the very beginning before any @@ -92,18 +126,18 @@ struct mustach_sbuf; /* see below */ * 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'. + * the implementation of 'mustach_json_c_write'. * * @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 + * If 'get' is 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 + * processing occurered. The status returned by the processing * is passed to the stop. * * The array below summarize status of callbacks: @@ -127,7 +161,7 @@ struct mustach_sbuf; /* see below */ * * 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. + * it that way but define 'partial' and let 'get' be NULL. * * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined * but forbids abstract FILE when 'partial' is NULL. @@ -167,6 +201,9 @@ struct mustach_itf { * Can be NULL. * * @closure: The closure to use for 'releasecb'. + * + * @length: Length of the value or zero if unknown and value null terminated. + * To return the empty string, let it to zero and let value to NULL. */ struct mustach_sbuf { const char *value; @@ -175,45 +212,84 @@ struct mustach_sbuf { void (*releasecb)(const char *value, void *closure); }; void *closure; + size_t length; }; -/* - * Definition of error codes returned by mustach +/** + * mustach_file - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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. */ -#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)) +extern int mustach_file(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, FILE *file); /** - * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * mustach_fd - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated * @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 + * @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 fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file); +extern int mustach_fd(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, int fd); /** - * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * mustach_mem - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. * * @template: the template string to instantiate + * @length: length of the template or zero if unknown and template null terminated + * @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_mem(const char *template, size_t length, const struct mustach_itf *itf, void *closure, int flags, char **result, size_t *size); + +/*************************************************************************** +* compatibility with version before 1.0 +*/ +#ifdef __GNUC__ +#define DEPRECATED_MUSTACH(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED_MUSTACH(func) __declspec(deprecated) func +#elif !defined(DEPRECATED_MUSTACH) +#pragma message("WARNING: You need to implement DEPRECATED_MUSTACH for this compiler") +#define DEPRECATED_MUSTACH(func) func +#endif +/** + * OBSOLETE use mustach_file + * + * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * + * @template: the template string to instantiate, null terminated + * @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. + */ +DEPRECATED_MUSTACH(extern int fmustach(const char *template, const struct mustach_itf *itf, void *closure, FILE *file)); + +/** + * OBSOLETE use mustach_fd + * + * fdmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * + * @template: the template string to instantiate, null terminated * @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 @@ -221,12 +297,14 @@ extern int fmustach(const char *template, struct mustach_itf *itf, void *closure * 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); +DEPRECATED_MUSTACH(extern int fdmustach(const char *template, const struct mustach_itf *itf, void *closure, int fd)); /** - * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * OBSOLETE use mustach_mem * - * @template: the template string to instantiate + * mustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * + * @template: the template string to instantiate, null terminated * @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 @@ -235,7 +313,7 @@ extern int fdmustach(const char *template, struct mustach_itf *itf, void *closur * 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); +DEPRECATED_MUSTACH(extern int mustach(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size)); #endif diff --git a/src/templating/pkgcfgs b/src/templating/pkgcfgs new file mode 100644 index 000000000..c3e84dc0e --- /dev/null +++ b/src/templating/pkgcfgs @@ -0,0 +1,35 @@ +==libmustach.pc== +Name: libmustach +Version: VERSION +Description: C Mustach single library +Cflags: -Imustach +Libs: -lmustach + +==libmustach-core.pc== +Name: libmustach-core +Version: VERSION +Description: C Mustach core library +Cflags: -Imustach +Libs: -lmustach-core + +==libmustach-cjson.pc== +Name: libmustach-cjson +Version: VERSION +Description: C Mustach library for cJSON +Cflags: -Imustach +Libs: -lmustach-cjson + +==libmustach-json-c.pc== +Name: libmustach-json-c +Version: VERSION +Description: C Mustach library for json-c +Cflags: -Imustach +Libs: -lmustach-json-c + +==libmustach-jansson.pc== +Name: libmustach-jansson +Version: VERSION +Description: C Mustach library for jansson +Cflags: -Imustach +Libs: -lmustach-jansson + diff --git a/src/templating/run-original-tests.sh b/src/templating/run-original-tests.sh index 9c7d34cdd..21481a286 100755 --- a/src/templating/run-original-tests.sh +++ b/src/templating/run-original-tests.sh @@ -1,10 +1,19 @@ #!/bin/bash -set -eu +# This file is in the public domain. +set -eux + +export CFLAGS="-g" + +echo "Ensuring clean state on entry to upstream tests ..." +make clean + # 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 mustach-original-Makefile mustach mustach-json-c.o || exit 77 +make -f mustach-original-Makefile clean || true +make -f mustach-original-Makefile basic-tests +make -f mustach-original-Makefile clean || true -make -f Makefile.orig mustach || exit 77 -make -f Makefile.orig test -make -f Makefile.orig clean || true +exit 0 diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c index 6384672f3..88a17c682 100644 --- a/src/templating/templating_api.c +++ b/src/templating/templating_api.c @@ -100,7 +100,7 @@ lookup_template (struct MHD_Connection *connection, if (NULL == best) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "No templates found in `%s'\n", + "No templates found for `%s'\n", name); return NULL; } @@ -171,6 +171,33 @@ make_static_url (struct MHD_Connection *con, } +int +TALER_TEMPLATING_fill (const char *tmpl, + const json_t *root, + void **result, + size_t *result_size) +{ + int eno; + + if (0 != + (eno = mustach_jansson_mem (tmpl, + 0, /* length of tmpl */ + (json_t *) root, + Mustach_With_AllExtensions, + (char **) result, + result_size))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "mustach failed on template with error %d\n", + eno); + *result = NULL; + *result_size = 0; + return eno; + } + return eno; +} + + enum GNUNET_GenericReturnValue TALER_TEMPLATING_build (struct MHD_Connection *connection, unsigned int *http_status, @@ -191,9 +218,6 @@ TALER_TEMPLATING_build (struct MHD_Connection *connection, template); if (NULL == tmpl) { - /* FIXME: should this not be an - internal failure? The language - mismatch is not critical here! */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to load template `%s'\n", template); @@ -215,10 +239,12 @@ TALER_TEMPLATING_build (struct MHD_Connection *connection, GNUNET_free (static_url); } if (0 != - (eno = mustach_jansson (tmpl, - (json_t *) root, - &body, - &body_size))) + (eno = mustach_jansson_mem (tmpl, + 0, + (json_t *) root, + Mustach_With_NoExtensions, + &body, + &body_size))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "mustach failed on template `%s' with error %d\n", @@ -334,7 +360,6 @@ load_template (void *cls, (void) cls; if ('.' == filename[0]) return GNUNET_OK; - name = strrchr (filename, '/'); if (NULL == name) @@ -369,7 +394,7 @@ load_template (void *cls, &sb)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "open", + "fstat", filename); GNUNET_break (0 == close (fd)); return GNUNET_OK; @@ -406,6 +431,47 @@ load_template (void *cls, } +MHD_RESULT +TALER_TEMPLATING_reply_error ( + struct MHD_Connection *connection, + const char *template_basename, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *detail) +{ + json_t *data; + enum GNUNET_GenericReturnValue ret; + + data = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("ec", + ec), + GNUNET_JSON_pack_string ("hint", + TALER_ErrorCode_get_hint (ec)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("detail", + detail)) + ); + ret = TALER_TEMPLATING_reply (connection, + http_status, + template_basename, + NULL, + NULL, + data); + json_decref (data); + switch (ret) + { + case GNUNET_OK: + return MHD_YES; + case GNUNET_NO: + return MHD_YES; + case GNUNET_SYSERR: + return MHD_NO; + } + GNUNET_assert (0); + return MHD_NO; +} + + enum GNUNET_GenericReturnValue TALER_TEMPLATING_init (const char *subsystem) { diff --git a/src/templating/test-specs/test-specs-cjson.ref b/src/templating/test-specs/test-specs-cjson.ref new file mode 100644 index 000000000..8897c66cc --- /dev/null +++ b/src/templating/test-specs/test-specs-cjson.ref @@ -0,0 +1,425 @@ + +loading test-specs/spec/specs/comments.json +processing file test-specs/spec/specs/comments.json +[0] Inline + Comment blocks should be removed from the template. + => SUCCESS +[1] Multiline + Multiline comments should be permitted. + => SUCCESS +[2] Standalone + All standalone comment lines should be removed. + => SUCCESS +[3] Indented Standalone + All standalone comment lines should be removed. + => SUCCESS +[4] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[5] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[6] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[7] Multiline Standalone + All standalone comment lines should be removed. + => SUCCESS +[8] Indented Multiline Standalone + All standalone comment lines should be removed. + => SUCCESS +[9] Indented Inline + Inline comments should not strip whitespace + => SUCCESS +[10] Surrounding Whitespace + Comment removal should preserve surrounding whitespace. + => SUCCESS +[11] Variable Name Collision + Comments must never render, even if variable with same name exists. + => SUCCESS + +loading test-specs/spec/specs/delimiters.json +processing file test-specs/spec/specs/delimiters.json +[0] Pair Behavior + The equals sign (used on both sides) should permit delimiter changes. + => SUCCESS +[1] Special Characters + Characters with special meaning regexen should be valid delimiters. + => SUCCESS +[2] Sections + Delimiters set outside sections should persist. + => SUCCESS +[3] Inverted Sections + Delimiters set outside inverted sections should persist. + => SUCCESS +[4] Partial Inheritence + Delimiters set in a parent template should not affect a partial. + => SUCCESS +[5] Post-Partial Behavior + Delimiters set in a partial should not affect the parent template. + => SUCCESS +[6] Surrounding Whitespace + Surrounding whitespace should be left untouched. + => SUCCESS +[7] Outlying Whitespace (Inline) + Whitespace should be left untouched. + => SUCCESS +[8] Standalone Tag + Standalone lines should be removed from the template. + => SUCCESS +[9] Indented Standalone Tag + Indented standalone lines should be removed from the template. + => SUCCESS +[10] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[11] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[12] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[13] Pair with Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/interpolation.json +processing file test-specs/spec/specs/interpolation.json +[0] No Interpolation + Mustache-free templates should render as-is. + => SUCCESS +[1] Basic Interpolation + Unadorned tags should interpolate content into the template. + => SUCCESS +[2] HTML Escaping + Basic interpolation should be HTML escaped. + => SUCCESS +[3] Triple Mustache + Triple mustaches should interpolate without HTML escaping. + => SUCCESS +[4] Ampersand + Ampersand should interpolate without HTML escaping. + => SUCCESS +[5] Basic Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[6] Triple Mustache Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[7] Ampersand Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[8] Basic Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[9] Triple Mustache Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[10] Ampersand Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[11] Basic Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[12] Triple Mustache Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[13] Ampersand Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[14] Basic Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[15] Triple Mustache Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[16] Ampersand Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[17] Dotted Names - Basic Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[18] Dotted Names - Triple Mustache Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[19] Dotted Names - Ampersand Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[20] Dotted Names - Arbitrary Depth + Dotted names should be functional to any level of nesting. + => SUCCESS +[21] Dotted Names - Broken Chains + Any falsey value prior to the last part of the name should yield ''. + => SUCCESS +[22] Dotted Names - Broken Chain Resolution + Each part of a dotted name should resolve only against its parent. + => SUCCESS +[23] Dotted Names - Initial Resolution + The first part of a dotted name should resolve as any other name. + => SUCCESS +[24] Dotted Names - Context Precedence + Dotted names should be resolved against former resolutions. + => SUCCESS +[25] Implicit Iterators - Basic Interpolation + Unadorned tags should interpolate content into the template. + => SUCCESS +[26] Implicit Iterators - HTML Escaping + Basic interpolation should be HTML escaped. + => SUCCESS +[27] Implicit Iterators - Triple Mustache + Triple mustaches should interpolate without HTML escaping. + => SUCCESS +[28] Implicit Iterators - Ampersand + Ampersand should interpolate without HTML escaping. + => SUCCESS +[29] Implicit Iterators - Basic Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[30] Interpolation - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[31] Triple Mustache - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[32] Ampersand - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[33] Interpolation - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[34] Triple Mustache - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[35] Ampersand - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[36] Interpolation With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS +[37] Triple Mustache With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS +[38] Ampersand With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/inverted.json +processing file test-specs/spec/specs/inverted.json +[0] Falsey + Falsey sections should have their contents rendered. + => SUCCESS +[1] Truthy + Truthy sections should have their contents omitted. + => SUCCESS +[2] Null is falsey + Null is falsey. + => SUCCESS +[3] Context + Objects and hashes should behave like truthy values. + => SUCCESS +[4] List + Lists should behave like truthy values. + => SUCCESS +[5] Empty List + Empty lists should behave like falsey values. + => SUCCESS +[6] Doubled + Multiple inverted sections per template should be permitted. + => SUCCESS +[7] Nested (Falsey) + Nested falsey sections should have their contents rendered. + => SUCCESS +[8] Nested (Truthy) + Nested truthy sections should be omitted. + => SUCCESS +[9] Context Misses + Failed context lookups should be considered falsey. + => SUCCESS +[10] Dotted Names - Truthy + Dotted names should be valid for Inverted Section tags. + => SUCCESS +[11] Dotted Names - Falsey + Dotted names should be valid for Inverted Section tags. + => SUCCESS +[12] Dotted Names - Broken Chains + Dotted names that cannot be resolved should be considered falsey. + => SUCCESS +[13] Surrounding Whitespace + Inverted sections should not alter surrounding whitespace. + => SUCCESS +[14] Internal Whitespace + Inverted should not alter internal whitespace. + => SUCCESS +[15] Indented Inline Sections + Single-line sections should not alter surrounding whitespace. + => SUCCESS +[16] Standalone Lines + Standalone lines should be removed from the template. + => SUCCESS +[17] Standalone Indented Lines + Standalone indented lines should be removed from the template. + => SUCCESS +[18] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[19] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[20] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[21] Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/partials.json +processing file test-specs/spec/specs/partials.json +[0] Basic Behavior + The greater-than operator should expand to the named partial. + => SUCCESS +[1] Failed Lookup + The empty string should be used when the named partial is not found. + => SUCCESS +[2] Context + The greater-than operator should operate within the current context. + => SUCCESS +[3] Recursion + The greater-than operator should properly recurse. + => SUCCESS +[4] Nested + The greater-than operator should work from within partials. + => SUCCESS +[5] Surrounding Whitespace + The greater-than operator should not alter surrounding whitespace. + => SUCCESS +[6] Inline Indentation + Whitespace should be left untouched. + => SUCCESS +[7] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[8] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[9] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[10] Standalone Indentation + Each line of the partial should be indented before rendering. + => SUCCESS +[11] Padding Whitespace + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/sections.json +processing file test-specs/spec/specs/sections.json +[0] Truthy + Truthy sections should have their contents rendered. + => SUCCESS +[1] Falsey + Falsey sections should have their contents omitted. + => SUCCESS +[2] Null is falsey + Null is falsey. + => SUCCESS +[3] Context + Objects and hashes should be pushed onto the context stack. + => SUCCESS +[4] Parent contexts + Names missing in the current context are looked up in the stack. + => SUCCESS +[5] Variable test + Non-false sections have their value at the top of context, +accessible as {{.}} or through the parent context. This gives +a simple way to display content conditionally if a variable exists. + + => SUCCESS +[6] List Contexts + All elements on the context stack should be accessible within lists. + => SUCCESS +[7] Deeply Nested Contexts + All elements on the context stack should be accessible. + => SUCCESS +[8] List + Lists should be iterated; list items should visit the context stack. + => SUCCESS +[9] Empty List + Empty lists should behave like falsey values. + => SUCCESS +[10] Doubled + Multiple sections per template should be permitted. + => SUCCESS +[11] Nested (Truthy) + Nested truthy sections should have their contents rendered. + => SUCCESS +[12] Nested (Falsey) + Nested falsey sections should be omitted. + => SUCCESS +[13] Context Misses + Failed context lookups should be considered falsey. + => SUCCESS +[14] Implicit Iterator - String + Implicit iterators should directly interpolate strings. + => SUCCESS +[15] Implicit Iterator - Integer + Implicit iterators should cast integers to strings and interpolate. + => SUCCESS +[16] Implicit Iterator - Decimal + Implicit iterators should cast decimals to strings and interpolate. + => SUCCESS +[17] Implicit Iterator - Array + Implicit iterators should allow iterating over nested arrays. + => SUCCESS +[18] Implicit Iterator - HTML Escaping + Implicit iterators with basic interpolation should be HTML escaped. + => SUCCESS +[19] Implicit Iterator - Triple mustache + Implicit iterators in triple mustache should interpolate without HTML escaping. + => SUCCESS +[20] Implicit Iterator - Ampersand + Implicit iterators in an Ampersand tag should interpolate without HTML escaping. + => SUCCESS +[21] Implicit Iterator - Root-level + Implicit iterators should work on root-level lists. + => SUCCESS +[22] Dotted Names - Truthy + Dotted names should be valid for Section tags. + => SUCCESS +[23] Dotted Names - Falsey + Dotted names should be valid for Section tags. + => SUCCESS +[24] Dotted Names - Broken Chains + Dotted names that cannot be resolved should be considered falsey. + => SUCCESS +[25] Surrounding Whitespace + Sections should not alter surrounding whitespace. + => SUCCESS +[26] Internal Whitespace + Sections should not alter internal whitespace. + => SUCCESS +[27] Indented Inline Sections + Single-line sections should not alter surrounding whitespace. + => SUCCESS +[28] Standalone Lines + Standalone lines should be removed from the template. + => SUCCESS +[29] Indented Standalone Lines + Indented standalone lines should be removed from the template. + => SUCCESS +[30] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[31] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[32] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[33] Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +summary: + error 0 + differ 0 + success 133 diff --git a/src/templating/test-specs/test-specs-jansson.ref b/src/templating/test-specs/test-specs-jansson.ref new file mode 100644 index 000000000..a1cef19c1 --- /dev/null +++ b/src/templating/test-specs/test-specs-jansson.ref @@ -0,0 +1,429 @@ + +loading test-specs/spec/specs/comments.json +processing file test-specs/spec/specs/comments.json +[0] Inline + Comment blocks should be removed from the template. + => SUCCESS +[1] Multiline + Multiline comments should be permitted. + => SUCCESS +[2] Standalone + All standalone comment lines should be removed. + => SUCCESS +[3] Indented Standalone + All standalone comment lines should be removed. + => SUCCESS +[4] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[5] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[6] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[7] Multiline Standalone + All standalone comment lines should be removed. + => SUCCESS +[8] Indented Multiline Standalone + All standalone comment lines should be removed. + => SUCCESS +[9] Indented Inline + Inline comments should not strip whitespace + => SUCCESS +[10] Surrounding Whitespace + Comment removal should preserve surrounding whitespace. + => SUCCESS +[11] Variable Name Collision + Comments must never render, even if variable with same name exists. + => SUCCESS + +loading test-specs/spec/specs/delimiters.json +processing file test-specs/spec/specs/delimiters.json +[0] Pair Behavior + The equals sign (used on both sides) should permit delimiter changes. + => SUCCESS +[1] Special Characters + Characters with special meaning regexen should be valid delimiters. + => SUCCESS +[2] Sections + Delimiters set outside sections should persist. + => SUCCESS +[3] Inverted Sections + Delimiters set outside inverted sections should persist. + => SUCCESS +[4] Partial Inheritence + Delimiters set in a parent template should not affect a partial. + => SUCCESS +[5] Post-Partial Behavior + Delimiters set in a partial should not affect the parent template. + => SUCCESS +[6] Surrounding Whitespace + Surrounding whitespace should be left untouched. + => SUCCESS +[7] Outlying Whitespace (Inline) + Whitespace should be left untouched. + => SUCCESS +[8] Standalone Tag + Standalone lines should be removed from the template. + => SUCCESS +[9] Indented Standalone Tag + Indented standalone lines should be removed from the template. + => SUCCESS +[10] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[11] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[12] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[13] Pair with Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/interpolation.json +processing file test-specs/spec/specs/interpolation.json +[0] No Interpolation + Mustache-free templates should render as-is. + => SUCCESS +[1] Basic Interpolation + Unadorned tags should interpolate content into the template. + => SUCCESS +[2] HTML Escaping + Basic interpolation should be HTML escaped. + => SUCCESS +[3] Triple Mustache + Triple mustaches should interpolate without HTML escaping. + => SUCCESS +[4] Ampersand + Ampersand should interpolate without HTML escaping. + => SUCCESS +[5] Basic Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[6] Triple Mustache Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[7] Ampersand Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[8] Basic Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[9] Triple Mustache Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[10] Ampersand Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[11] Basic Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[12] Triple Mustache Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[13] Ampersand Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[14] Basic Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[15] Triple Mustache Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[16] Ampersand Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[17] Dotted Names - Basic Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[18] Dotted Names - Triple Mustache Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[19] Dotted Names - Ampersand Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[20] Dotted Names - Arbitrary Depth + Dotted names should be functional to any level of nesting. + => SUCCESS +[21] Dotted Names - Broken Chains + Any falsey value prior to the last part of the name should yield ''. + => SUCCESS +[22] Dotted Names - Broken Chain Resolution + Each part of a dotted name should resolve only against its parent. + => SUCCESS +[23] Dotted Names - Initial Resolution + The first part of a dotted name should resolve as any other name. + => SUCCESS +[24] Dotted Names - Context Precedence + Dotted names should be resolved against former resolutions. + => SUCCESS +[25] Implicit Iterators - Basic Interpolation + Unadorned tags should interpolate content into the template. + => SUCCESS +[26] Implicit Iterators - HTML Escaping + Basic interpolation should be HTML escaped. + => SUCCESS +[27] Implicit Iterators - Triple Mustache + Triple mustaches should interpolate without HTML escaping. + => SUCCESS +[28] Implicit Iterators - Ampersand + Ampersand should interpolate without HTML escaping. + => SUCCESS +[29] Implicit Iterators - Basic Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[30] Interpolation - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[31] Triple Mustache - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[32] Ampersand - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[33] Interpolation - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[34] Triple Mustache - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[35] Ampersand - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[36] Interpolation With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS +[37] Triple Mustache With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS +[38] Ampersand With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/inverted.json +processing file test-specs/spec/specs/inverted.json +[0] Falsey + Falsey sections should have their contents rendered. + => SUCCESS +[1] Truthy + Truthy sections should have their contents omitted. + => SUCCESS +[2] Null is falsey + Null is falsey. + => SUCCESS +[3] Context + Objects and hashes should behave like truthy values. + => SUCCESS +[4] List + Lists should behave like truthy values. + => SUCCESS +[5] Empty List + Empty lists should behave like falsey values. + => SUCCESS +[6] Doubled + Multiple inverted sections per template should be permitted. + => SUCCESS +[7] Nested (Falsey) + Nested falsey sections should have their contents rendered. + => SUCCESS +[8] Nested (Truthy) + Nested truthy sections should be omitted. + => SUCCESS +[9] Context Misses + Failed context lookups should be considered falsey. + => SUCCESS +[10] Dotted Names - Truthy + Dotted names should be valid for Inverted Section tags. + => SUCCESS +[11] Dotted Names - Falsey + Dotted names should be valid for Inverted Section tags. + => SUCCESS +[12] Dotted Names - Broken Chains + Dotted names that cannot be resolved should be considered falsey. + => SUCCESS +[13] Surrounding Whitespace + Inverted sections should not alter surrounding whitespace. + => SUCCESS +[14] Internal Whitespace + Inverted should not alter internal whitespace. + => SUCCESS +[15] Indented Inline Sections + Single-line sections should not alter surrounding whitespace. + => SUCCESS +[16] Standalone Lines + Standalone lines should be removed from the template. + => SUCCESS +[17] Standalone Indented Lines + Standalone indented lines should be removed from the template. + => SUCCESS +[18] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[19] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[20] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[21] Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/partials.json +processing file test-specs/spec/specs/partials.json +[0] Basic Behavior + The greater-than operator should expand to the named partial. + => SUCCESS +[1] Failed Lookup + The empty string should be used when the named partial is not found. + => SUCCESS +[2] Context + The greater-than operator should operate within the current context. + => SUCCESS +[3] Recursion + The greater-than operator should properly recurse. + => SUCCESS +[4] Nested + The greater-than operator should work from within partials. + => SUCCESS +[5] Surrounding Whitespace + The greater-than operator should not alter surrounding whitespace. + => SUCCESS +[6] Inline Indentation + Whitespace should be left untouched. + => SUCCESS +[7] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[8] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[9] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[10] Standalone Indentation + Each line of the partial should be indented before rendering. + => SUCCESS +[11] Padding Whitespace + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/sections.json +processing file test-specs/spec/specs/sections.json +[0] Truthy + Truthy sections should have their contents rendered. + => SUCCESS +[1] Falsey + Falsey sections should have their contents omitted. + => SUCCESS +[2] Null is falsey + Null is falsey. + => SUCCESS +[3] Context + Objects and hashes should be pushed onto the context stack. + => SUCCESS +[4] Parent contexts + Names missing in the current context are looked up in the stack. + => SUCCESS +[5] Variable test + Non-false sections have their value at the top of context, +accessible as {{.}} or through the parent context. This gives +a simple way to display content conditionally if a variable exists. + + => SUCCESS +[6] List Contexts + All elements on the context stack should be accessible within lists. + => SUCCESS +[7] Deeply Nested Contexts + All elements on the context stack should be accessible. + => SUCCESS +[8] List + Lists should be iterated; list items should visit the context stack. + => SUCCESS +[9] Empty List + Empty lists should behave like falsey values. + => SUCCESS +[10] Doubled + Multiple sections per template should be permitted. + => SUCCESS +[11] Nested (Truthy) + Nested truthy sections should have their contents rendered. + => SUCCESS +[12] Nested (Falsey) + Nested falsey sections should be omitted. + => SUCCESS +[13] Context Misses + Failed context lookups should be considered falsey. + => SUCCESS +[14] Implicit Iterator - String + Implicit iterators should directly interpolate strings. + => SUCCESS +[15] Implicit Iterator - Integer + Implicit iterators should cast integers to strings and interpolate. + => SUCCESS +[16] Implicit Iterator - Decimal + Implicit iterators should cast decimals to strings and interpolate. + => DIFFERS + .. DATA[{"list":[1.1000000000000001,2.2000000000000002,3.2999999999999998,4.4000000000000004,5.5]}] + .. TEMPLATE["{{#list}}({{.}}){{/list}}"] + .. EXPECTED["(1.1)(2.2)(3.3)(4.4)(5.5)"] + .. GOT["(1.1000000000000001)(2.2000000000000002)(3.2999999999999998)(4.4000000000000004)(5.5)"] +[17] Implicit Iterator - Array + Implicit iterators should allow iterating over nested arrays. + => SUCCESS +[18] Implicit Iterator - HTML Escaping + Implicit iterators with basic interpolation should be HTML escaped. + => SUCCESS +[19] Implicit Iterator - Triple mustache + Implicit iterators in triple mustache should interpolate without HTML escaping. + => SUCCESS +[20] Implicit Iterator - Ampersand + Implicit iterators in an Ampersand tag should interpolate without HTML escaping. + => SUCCESS +[21] Implicit Iterator - Root-level + Implicit iterators should work on root-level lists. + => SUCCESS +[22] Dotted Names - Truthy + Dotted names should be valid for Section tags. + => SUCCESS +[23] Dotted Names - Falsey + Dotted names should be valid for Section tags. + => SUCCESS +[24] Dotted Names - Broken Chains + Dotted names that cannot be resolved should be considered falsey. + => SUCCESS +[25] Surrounding Whitespace + Sections should not alter surrounding whitespace. + => SUCCESS +[26] Internal Whitespace + Sections should not alter internal whitespace. + => SUCCESS +[27] Indented Inline Sections + Single-line sections should not alter surrounding whitespace. + => SUCCESS +[28] Standalone Lines + Standalone lines should be removed from the template. + => SUCCESS +[29] Indented Standalone Lines + Indented standalone lines should be removed from the template. + => SUCCESS +[30] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[31] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[32] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[33] Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +summary: + error 0 + differ 1 + success 132 diff --git a/src/templating/test-specs/test-specs-json-c.ref b/src/templating/test-specs/test-specs-json-c.ref new file mode 100644 index 000000000..8897c66cc --- /dev/null +++ b/src/templating/test-specs/test-specs-json-c.ref @@ -0,0 +1,425 @@ + +loading test-specs/spec/specs/comments.json +processing file test-specs/spec/specs/comments.json +[0] Inline + Comment blocks should be removed from the template. + => SUCCESS +[1] Multiline + Multiline comments should be permitted. + => SUCCESS +[2] Standalone + All standalone comment lines should be removed. + => SUCCESS +[3] Indented Standalone + All standalone comment lines should be removed. + => SUCCESS +[4] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[5] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[6] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[7] Multiline Standalone + All standalone comment lines should be removed. + => SUCCESS +[8] Indented Multiline Standalone + All standalone comment lines should be removed. + => SUCCESS +[9] Indented Inline + Inline comments should not strip whitespace + => SUCCESS +[10] Surrounding Whitespace + Comment removal should preserve surrounding whitespace. + => SUCCESS +[11] Variable Name Collision + Comments must never render, even if variable with same name exists. + => SUCCESS + +loading test-specs/spec/specs/delimiters.json +processing file test-specs/spec/specs/delimiters.json +[0] Pair Behavior + The equals sign (used on both sides) should permit delimiter changes. + => SUCCESS +[1] Special Characters + Characters with special meaning regexen should be valid delimiters. + => SUCCESS +[2] Sections + Delimiters set outside sections should persist. + => SUCCESS +[3] Inverted Sections + Delimiters set outside inverted sections should persist. + => SUCCESS +[4] Partial Inheritence + Delimiters set in a parent template should not affect a partial. + => SUCCESS +[5] Post-Partial Behavior + Delimiters set in a partial should not affect the parent template. + => SUCCESS +[6] Surrounding Whitespace + Surrounding whitespace should be left untouched. + => SUCCESS +[7] Outlying Whitespace (Inline) + Whitespace should be left untouched. + => SUCCESS +[8] Standalone Tag + Standalone lines should be removed from the template. + => SUCCESS +[9] Indented Standalone Tag + Indented standalone lines should be removed from the template. + => SUCCESS +[10] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[11] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[12] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[13] Pair with Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/interpolation.json +processing file test-specs/spec/specs/interpolation.json +[0] No Interpolation + Mustache-free templates should render as-is. + => SUCCESS +[1] Basic Interpolation + Unadorned tags should interpolate content into the template. + => SUCCESS +[2] HTML Escaping + Basic interpolation should be HTML escaped. + => SUCCESS +[3] Triple Mustache + Triple mustaches should interpolate without HTML escaping. + => SUCCESS +[4] Ampersand + Ampersand should interpolate without HTML escaping. + => SUCCESS +[5] Basic Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[6] Triple Mustache Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[7] Ampersand Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[8] Basic Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[9] Triple Mustache Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[10] Ampersand Decimal Interpolation + Decimals should interpolate seamlessly with proper significance. + => SUCCESS +[11] Basic Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[12] Triple Mustache Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[13] Ampersand Null Interpolation + Nulls should interpolate as the empty string. + => SUCCESS +[14] Basic Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[15] Triple Mustache Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[16] Ampersand Context Miss Interpolation + Failed context lookups should default to empty strings. + => SUCCESS +[17] Dotted Names - Basic Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[18] Dotted Names - Triple Mustache Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[19] Dotted Names - Ampersand Interpolation + Dotted names should be considered a form of shorthand for sections. + => SUCCESS +[20] Dotted Names - Arbitrary Depth + Dotted names should be functional to any level of nesting. + => SUCCESS +[21] Dotted Names - Broken Chains + Any falsey value prior to the last part of the name should yield ''. + => SUCCESS +[22] Dotted Names - Broken Chain Resolution + Each part of a dotted name should resolve only against its parent. + => SUCCESS +[23] Dotted Names - Initial Resolution + The first part of a dotted name should resolve as any other name. + => SUCCESS +[24] Dotted Names - Context Precedence + Dotted names should be resolved against former resolutions. + => SUCCESS +[25] Implicit Iterators - Basic Interpolation + Unadorned tags should interpolate content into the template. + => SUCCESS +[26] Implicit Iterators - HTML Escaping + Basic interpolation should be HTML escaped. + => SUCCESS +[27] Implicit Iterators - Triple Mustache + Triple mustaches should interpolate without HTML escaping. + => SUCCESS +[28] Implicit Iterators - Ampersand + Ampersand should interpolate without HTML escaping. + => SUCCESS +[29] Implicit Iterators - Basic Integer Interpolation + Integers should interpolate seamlessly. + => SUCCESS +[30] Interpolation - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[31] Triple Mustache - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[32] Ampersand - Surrounding Whitespace + Interpolation should not alter surrounding whitespace. + => SUCCESS +[33] Interpolation - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[34] Triple Mustache - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[35] Ampersand - Standalone + Standalone interpolation should not alter surrounding whitespace. + => SUCCESS +[36] Interpolation With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS +[37] Triple Mustache With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS +[38] Ampersand With Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/inverted.json +processing file test-specs/spec/specs/inverted.json +[0] Falsey + Falsey sections should have their contents rendered. + => SUCCESS +[1] Truthy + Truthy sections should have their contents omitted. + => SUCCESS +[2] Null is falsey + Null is falsey. + => SUCCESS +[3] Context + Objects and hashes should behave like truthy values. + => SUCCESS +[4] List + Lists should behave like truthy values. + => SUCCESS +[5] Empty List + Empty lists should behave like falsey values. + => SUCCESS +[6] Doubled + Multiple inverted sections per template should be permitted. + => SUCCESS +[7] Nested (Falsey) + Nested falsey sections should have their contents rendered. + => SUCCESS +[8] Nested (Truthy) + Nested truthy sections should be omitted. + => SUCCESS +[9] Context Misses + Failed context lookups should be considered falsey. + => SUCCESS +[10] Dotted Names - Truthy + Dotted names should be valid for Inverted Section tags. + => SUCCESS +[11] Dotted Names - Falsey + Dotted names should be valid for Inverted Section tags. + => SUCCESS +[12] Dotted Names - Broken Chains + Dotted names that cannot be resolved should be considered falsey. + => SUCCESS +[13] Surrounding Whitespace + Inverted sections should not alter surrounding whitespace. + => SUCCESS +[14] Internal Whitespace + Inverted should not alter internal whitespace. + => SUCCESS +[15] Indented Inline Sections + Single-line sections should not alter surrounding whitespace. + => SUCCESS +[16] Standalone Lines + Standalone lines should be removed from the template. + => SUCCESS +[17] Standalone Indented Lines + Standalone indented lines should be removed from the template. + => SUCCESS +[18] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[19] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[20] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[21] Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/partials.json +processing file test-specs/spec/specs/partials.json +[0] Basic Behavior + The greater-than operator should expand to the named partial. + => SUCCESS +[1] Failed Lookup + The empty string should be used when the named partial is not found. + => SUCCESS +[2] Context + The greater-than operator should operate within the current context. + => SUCCESS +[3] Recursion + The greater-than operator should properly recurse. + => SUCCESS +[4] Nested + The greater-than operator should work from within partials. + => SUCCESS +[5] Surrounding Whitespace + The greater-than operator should not alter surrounding whitespace. + => SUCCESS +[6] Inline Indentation + Whitespace should be left untouched. + => SUCCESS +[7] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[8] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[9] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[10] Standalone Indentation + Each line of the partial should be indented before rendering. + => SUCCESS +[11] Padding Whitespace + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +loading test-specs/spec/specs/sections.json +processing file test-specs/spec/specs/sections.json +[0] Truthy + Truthy sections should have their contents rendered. + => SUCCESS +[1] Falsey + Falsey sections should have their contents omitted. + => SUCCESS +[2] Null is falsey + Null is falsey. + => SUCCESS +[3] Context + Objects and hashes should be pushed onto the context stack. + => SUCCESS +[4] Parent contexts + Names missing in the current context are looked up in the stack. + => SUCCESS +[5] Variable test + Non-false sections have their value at the top of context, +accessible as {{.}} or through the parent context. This gives +a simple way to display content conditionally if a variable exists. + + => SUCCESS +[6] List Contexts + All elements on the context stack should be accessible within lists. + => SUCCESS +[7] Deeply Nested Contexts + All elements on the context stack should be accessible. + => SUCCESS +[8] List + Lists should be iterated; list items should visit the context stack. + => SUCCESS +[9] Empty List + Empty lists should behave like falsey values. + => SUCCESS +[10] Doubled + Multiple sections per template should be permitted. + => SUCCESS +[11] Nested (Truthy) + Nested truthy sections should have their contents rendered. + => SUCCESS +[12] Nested (Falsey) + Nested falsey sections should be omitted. + => SUCCESS +[13] Context Misses + Failed context lookups should be considered falsey. + => SUCCESS +[14] Implicit Iterator - String + Implicit iterators should directly interpolate strings. + => SUCCESS +[15] Implicit Iterator - Integer + Implicit iterators should cast integers to strings and interpolate. + => SUCCESS +[16] Implicit Iterator - Decimal + Implicit iterators should cast decimals to strings and interpolate. + => SUCCESS +[17] Implicit Iterator - Array + Implicit iterators should allow iterating over nested arrays. + => SUCCESS +[18] Implicit Iterator - HTML Escaping + Implicit iterators with basic interpolation should be HTML escaped. + => SUCCESS +[19] Implicit Iterator - Triple mustache + Implicit iterators in triple mustache should interpolate without HTML escaping. + => SUCCESS +[20] Implicit Iterator - Ampersand + Implicit iterators in an Ampersand tag should interpolate without HTML escaping. + => SUCCESS +[21] Implicit Iterator - Root-level + Implicit iterators should work on root-level lists. + => SUCCESS +[22] Dotted Names - Truthy + Dotted names should be valid for Section tags. + => SUCCESS +[23] Dotted Names - Falsey + Dotted names should be valid for Section tags. + => SUCCESS +[24] Dotted Names - Broken Chains + Dotted names that cannot be resolved should be considered falsey. + => SUCCESS +[25] Surrounding Whitespace + Sections should not alter surrounding whitespace. + => SUCCESS +[26] Internal Whitespace + Sections should not alter internal whitespace. + => SUCCESS +[27] Indented Inline Sections + Single-line sections should not alter surrounding whitespace. + => SUCCESS +[28] Standalone Lines + Standalone lines should be removed from the template. + => SUCCESS +[29] Indented Standalone Lines + Indented standalone lines should be removed from the template. + => SUCCESS +[30] Standalone Line Endings + "\r\n" should be considered a newline for standalone tags. + => SUCCESS +[31] Standalone Without Previous Line + Standalone tags should not require a newline to precede them. + => SUCCESS +[32] Standalone Without Newline + Standalone tags should not require a newline to follow them. + => SUCCESS +[33] Padding + Superfluous in-tag whitespace should be ignored. + => SUCCESS + +summary: + error 0 + differ 0 + success 133 diff --git a/src/templating/test-specs/test-specs.c b/src/templating/test-specs/test-specs.c new file mode 100644 index 000000000..15c94a80e --- /dev/null +++ b/src/templating/test-specs/test-specs.c @@ -0,0 +1,520 @@ +/* + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> + +#include "mustach-wrap.h" + +#define TEST_JSON_C 1 +#define TEST_JANSSON 2 +#define TEST_CJSON 3 + +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" +}; + +const char *mustach_error_string(int status) +{ + return status >= 0 ? "no error" + : errors[status <= -(int)(sizeof errors / sizeof * errors) ? 0 : -status]; +} + +static const char *errmsg = 0; +static int flags = 0; +static FILE *output = 0; + +static void help(char *prog) +{ + char *name = basename(prog); +#define STR(x) #x + printf("%s version %s\n", name, STR(VERSION)); +#undef STR + printf("usage: %s test-files...\n", name); + exit(0); +} + +#if TEST == TEST_CJSON + +static const size_t BLOCKSIZE = 8192; + +static char *readfile(const char *filename, size_t *length) +{ + 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); + if (length != NULL) + *length = pos; + result[pos] = 0; + return result; +} +#endif + +typedef struct { + unsigned nerror; + unsigned ndiffers; + unsigned nsuccess; + unsigned ninvalid; +} counters; + +static int load_json(const char *filename); +static int process(counters *c); +static void close_json(); +static int get_partial(const char *name, struct mustach_sbuf *sbuf); + +int main(int ac, char **av) +{ + char *f; + char *prog = *av; + int s; + counters c; + + (void)ac; /* unused */ + flags = Mustach_With_SingleDot | Mustach_With_IncPartial; + output = stdout; + mustach_wrap_get_partial = get_partial; + + memset(&c, 0, sizeof c); + while (*++av) { + if (!strcmp(*av, "-h") || !strcmp(*av, "--help")) + help(prog); + f = (av[0][0] == '-' && !av[0][1]) ? "/dev/stdin" : av[0]; + fprintf(output, "\nloading %s\n", f); + s = load_json(f); + if (s < 0) { + fprintf(stderr, "error when loading %s!\n", f); + if(errmsg) + fprintf(stderr, " reason: %s\n", errmsg); + exit(1); + } + fprintf(output, "processing file %s\n", f); + s = process(&c); + if (s < 0) { + fprintf(stderr, "error bad test file %s!\n", f); + exit(1); + } + close_json(); + } + fprintf(output, "\nsummary:\n"); + if (c.ninvalid) + fprintf(output, " invalid %u\n", c.ninvalid); + fprintf(output, " error %u\n", c.nerror); + fprintf(output, " differ %u\n", c.ndiffers); + fprintf(output, " success %u\n", c.nsuccess); + if (c.nerror) + return 2; + if (c.ndiffers) + return 1; + return 0; +} + +void emit(FILE *f, const char *s) +{ + for(;;s++) { + switch(*s) { + case 0: return; + case '\\': fprintf(f, "\\\\"); break; + case '\t': fprintf(f, "\\t"); break; + case '\n': fprintf(f, "\\n"); break; + case '\r': fprintf(f, "\\r"); break; + default: fprintf(f, "%c", *s); break; + } + } +} + +#if TEST == TEST_JSON_C + +#include "mustach-json-c.h" + +static struct json_object *o; + +static struct json_object *partials; +static int get_partial(const char *name, struct mustach_sbuf *sbuf) +{ + struct json_object *x; + if (partials == NULL || !json_object_object_get_ex(partials, name, &x)) + return MUSTACH_ERROR_PARTIAL_NOT_FOUND; + sbuf->value = json_object_get_string(x); + return MUSTACH_OK; +} + +static int load_json(const char *filename) +{ + o = json_object_from_file(filename); +#if JSON_C_VERSION_NUM >= 0x000D00 + errmsg = json_util_get_last_err(); + if (errmsg != NULL) + return -1; +#endif + if (o == NULL) { + errmsg = "null json"; + return -1; + } + return 0; +} +static int process(counters *c) +{ + const char *t, *e; + char *got; + unsigned i, n; + size_t length; + int s; + json_object *tests, *unit, *name, *desc, *data, *template, *expected; + + if (!json_object_object_get_ex(o, "tests", &tests) || json_object_get_type(tests) != json_type_array) + return -1; + + i = 0; + n = (unsigned)json_object_array_length(tests); + while (i < n) { + unit = json_object_array_get_idx(tests, i); + if (json_object_get_type(unit) != json_type_object + || !json_object_object_get_ex(unit, "name", &name) + || !json_object_object_get_ex(unit, "desc", &desc) + || !json_object_object_get_ex(unit, "data", &data) + || !json_object_object_get_ex(unit, "template", &template) + || !json_object_object_get_ex(unit, "expected", &expected) + || json_object_get_type(name) != json_type_string + || json_object_get_type(desc) != json_type_string + || json_object_get_type(template) != json_type_string + || json_object_get_type(expected) != json_type_string) { + fprintf(stderr, "invalid test %u\n", i); + c->ninvalid++; + } + else { + fprintf(output, "[%u] %s\n", i, json_object_get_string(name)); + fprintf(output, "\t%s\n", json_object_get_string(desc)); + if (!json_object_object_get_ex(unit, "partials", &partials)) + partials = NULL; + t = json_object_get_string(template); + e = json_object_get_string(expected); + s = mustach_json_c_mem(t, 0, data, flags, &got, &length); + if (s == 0 && strcmp(got, e) == 0) { + fprintf(output, "\t=> SUCCESS\n"); + c->nsuccess++; + } + else { + if (s < 0) { + fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s)); + c->nerror++; + } + else { + fprintf(output, "\t=> DIFFERS\n"); + c->ndiffers++; + } + if (partials) + fprintf(output, "\t.. PARTIALS[%s]\n", json_object_to_json_string_ext(partials, 0)); + fprintf(output, "\t.. DATA[%s]\n", json_object_to_json_string_ext(data, 0)); + fprintf(output, "\t.. TEMPLATE["); + emit(output, t); + fprintf(output, "]\n"); + fprintf(output, "\t.. EXPECTED["); + emit(output, e); + fprintf(output, "]\n"); + if (s == 0) { + fprintf(output, "\t.. GOT["); + emit(output, got); + fprintf(output, "]\n"); + } + } + free(got); + } + i++; + } + return 0; +} +static void close_json() +{ + json_object_put(o); +} + +#elif TEST == TEST_JANSSON + +#include "mustach-jansson.h" + +static json_t *o; +static json_error_t e; + +static json_t *partials; +static int get_partial(const char *name, struct mustach_sbuf *sbuf) +{ + json_t *x; + if (partials == NULL || !(x = json_object_get(partials, name))) + return MUSTACH_ERROR_PARTIAL_NOT_FOUND; + sbuf->value = json_string_value(x); + return MUSTACH_OK; +} + +static int load_json(const char *filename) +{ + o = json_load_file(filename, JSON_DECODE_ANY, &e); + if (o == NULL) { + errmsg = e.text; + return -1; + } + return 0; +} +static int process(counters *c) +{ + const char *t, *e; + char *got, *tmp; + int i, n; + size_t length; + int s; + json_t *tests, *unit, *name, *desc, *data, *template, *expected; + + tests = json_object_get(o, "tests"); + if (!tests || json_typeof(tests) != JSON_ARRAY) + return -1; + + i = 0; + n = json_array_size(tests); + while (i < n) { + unit = json_array_get(tests, i); + if (!unit || json_typeof(unit) != JSON_OBJECT + || !(name = json_object_get(unit, "name")) + || !(desc = json_object_get(unit, "desc")) + || !(data = json_object_get(unit, "data")) + || !(template = json_object_get(unit, "template")) + || !(expected = json_object_get(unit, "expected")) + || json_typeof(name) != JSON_STRING + || json_typeof(desc) != JSON_STRING + || json_typeof(template) != JSON_STRING + || json_typeof(expected) != JSON_STRING) { + fprintf(stderr, "invalid test %u\n", i); + c->ninvalid++; + } + else { + fprintf(output, "[%u] %s\n", i, json_string_value(name)); + fprintf(output, "\t%s\n", json_string_value(desc)); + partials = json_object_get(unit, "partials"); + t = json_string_value(template); + e = json_string_value(expected); + s = mustach_jansson_mem(t, 0, data, flags, &got, &length); + if (s == 0 && strcmp(got, e) == 0) { + fprintf(output, "\t=> SUCCESS\n"); + c->nsuccess++; + } + else { + if (s < 0) { + fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s)); + c->nerror++; + } + else { + fprintf(output, "\t=> DIFFERS\n"); + c->ndiffers++; + } + if (partials) { + tmp = json_dumps(partials, JSON_ENCODE_ANY | JSON_COMPACT); + fprintf(output, "\t.. PARTIALS[%s]\n", tmp); + free(tmp); + } + tmp = json_dumps(data, JSON_ENCODE_ANY | JSON_COMPACT); + fprintf(output, "\t.. DATA[%s]\n", tmp); + free(tmp); + fprintf(output, "\t.. TEMPLATE["); + emit(output, t); + fprintf(output, "]\n"); + fprintf(output, "\t.. EXPECTED["); + emit(output, e); + fprintf(output, "]\n"); + if (s == 0) { + fprintf(output, "\t.. GOT["); + emit(output, got); + fprintf(output, "]\n"); + } + } + free(got); + } + i++; + } + return 0; +} +static void close_json() +{ + json_decref(o); +} + +#elif TEST == TEST_CJSON + +#include "mustach-cjson.h" + +static cJSON *o; +static cJSON *partials; +static int get_partial(const char *name, struct mustach_sbuf *sbuf) +{ + cJSON *x; + if (partials == NULL || !(x = cJSON_GetObjectItemCaseSensitive(partials, name))) + return MUSTACH_ERROR_PARTIAL_NOT_FOUND; + sbuf->value = x->valuestring; + return MUSTACH_OK; +} + +static int load_json(const char *filename) +{ + char *t; + size_t length; + + t = readfile(filename, &length); + o = t ? cJSON_ParseWithLength(t, length) : NULL; + free(t); + return -!o; +} +static int process(counters *c) +{ + const char *t, *e; + char *got, *tmp; + int i, n; + size_t length; + int s; + cJSON *tests, *unit, *name, *desc, *data, *template, *expected; + + tests = cJSON_GetObjectItemCaseSensitive(o, "tests"); + if (!tests || tests->type != cJSON_Array) + return -1; + + i = 0; + n = cJSON_GetArraySize(tests); + while (i < n) { + unit = cJSON_GetArrayItem(tests, i); + if (!unit || unit->type != cJSON_Object + || !(name = cJSON_GetObjectItemCaseSensitive(unit, "name")) + || !(desc = cJSON_GetObjectItemCaseSensitive(unit, "desc")) + || !(data = cJSON_GetObjectItemCaseSensitive(unit, "data")) + || !(template = cJSON_GetObjectItemCaseSensitive(unit, "template")) + || !(expected = cJSON_GetObjectItemCaseSensitive(unit, "expected")) + || name->type != cJSON_String + || desc->type != cJSON_String + || template->type != cJSON_String + || expected->type != cJSON_String) { + fprintf(stderr, "invalid test %u\n", i); + c->ninvalid++; + } + else { + fprintf(output, "[%u] %s\n", i, name->valuestring); + fprintf(output, "\t%s\n", desc->valuestring); + partials = cJSON_GetObjectItemCaseSensitive(unit, "partials"); + t = template->valuestring; + e = expected->valuestring; + s = mustach_cJSON_mem(t, 0, data, flags, &got, &length); + if (s == 0 && strcmp(got, e) == 0) { + fprintf(output, "\t=> SUCCESS\n"); + c->nsuccess++; + } + else { + if (s < 0) { + fprintf(output, "\t=> ERROR %s\n", mustach_error_string(s)); + c->nerror++; + } + else { + fprintf(output, "\t=> DIFFERS\n"); + c->ndiffers++; + } + if (partials) { + tmp = cJSON_PrintUnformatted(partials); + fprintf(output, "\t.. PARTIALS[%s]\n", tmp); + free(tmp); + } + tmp = cJSON_PrintUnformatted(data); + fprintf(output, "\t.. DATA[%s]\n", tmp); + free(tmp); + fprintf(output, "\t.. TEMPLATE["); + emit(output, t); + fprintf(output, "]\n"); + fprintf(output, "\t.. EXPECTED["); + emit(output, e); + fprintf(output, "]\n"); + if (s == 0) { + fprintf(output, "\t.. GOT["); + emit(output, got); + fprintf(output, "]\n"); + } + } + free(got); + } + i++; + } + return 0; +} +static void close_json() +{ + cJSON_Delete(o); +} + +#else +#error "no defined json library" +#endif diff --git a/src/templating/test1/Makefile b/src/templating/test1/Makefile new file mode 100644 index 000000000..1a3e57914 --- /dev/null +++ b/src/templating/test1/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json must + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test1/json b/src/templating/test1/json index 5b2e3d83a..6562fb064 100644 --- a/src/templating/test1/json +++ b/src/templating/test1/json @@ -5,12 +5,12 @@ "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" } ] } + { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] }, + { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] }, + { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] } ], "person?": { "name": "Jon" }, - "special": "----{{extra}}----", + "special": "----{{extra}}----\n", "extra": 3.14159, "#sharp": "#", "!bang": "!", diff --git a/src/templating/test1/must b/src/templating/test1/must index 723f966c4..92d30b0b2 100644 --- a/src/templating/test1/must +++ b/src/templating/test1/must @@ -12,7 +12,7 @@ Shown. {{/person}} {{#repo}} - <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} + <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}} {{/repo}} {{#person?}} @@ -23,7 +23,7 @@ Shown. ===================================== %(%! gros commentaire %)% %(%#repo%)% - <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% + <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)% %(%/repo%)% ===================================== %(%={{ }}=%)% @@ -41,3 +41,9 @@ end {{:\=equal}} {{::colon}} {{:>greater}} + +{{#repo}} +who 0 {{who.0}} +who 1 {{who.1}} +who 2 {{who.2}} +{{/repo}} diff --git a/src/templating/test1/resu.ref b/src/templating/test1/resu.ref index 545e58579..6cd11bb27 100644 --- a/src/templating/test1/resu.ref +++ b/src/templating/test1/resu.ref @@ -1,38 +1,20 @@ 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 - - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg Hi Jon! - - ===================================== - - - <b>resque</b> reviewers: avrel committers: joe william - - <b>hub</b> reviewers: avrel committers: jack greg - - <b>rip</b> reviewers: joe jack committers: greg - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg ===================================== - ggggggggg ----3.14159---- jjjjjjjjj @@ -47,3 +29,13 @@ end = : > + +who 0 {"commiter":"joe"} +who 1 {"reviewer":"avrel"} +who 2 {"commiter":"william"} +who 0 {"commiter":"jack"} +who 1 {"reviewer":"avrel"} +who 2 {"commiter":"greg"} +who 0 {"reviewer":"joe"} +who 1 {"reviewer":"jack"} +who 2 {"commiter":"greg"} diff --git a/src/templating/test1/vg.ref b/src/templating/test1/vg.ref new file mode 100644 index 000000000..d086e59c5 --- /dev/null +++ b/src/templating/test1/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 111 allocs, 111 frees, 9,702 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test2/Makefile b/src/templating/test2/Makefile new file mode 100644 index 000000000..1a3e57914 --- /dev/null +++ b/src/templating/test2/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json must + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test2/resu.ref b/src/templating/test2/resu.ref index 67d1f547d..5a200a9bf 100644 --- a/src/templating/test2/resu.ref +++ b/src/templating/test2/resu.ref @@ -1,22 +1,7 @@ <h1>Colors</h1> - - - <li><strong>red</strong></li> - - - - - <li><a href="#Green">green</a></li> - - - - <li><a href="#Blue">blue</a></li> - - - diff --git a/src/templating/test2/vg.ref b/src/templating/test2/vg.ref new file mode 100644 index 000000000..e4b4f6d37 --- /dev/null +++ b/src/templating/test2/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 38 allocs, 38 frees, 5,712 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test3/Makefile b/src/templating/test3/Makefile new file mode 100644 index 000000000..1a3e57914 --- /dev/null +++ b/src/templating/test3/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json must + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test3/resu.ref b/src/templating/test3/resu.ref index e89ce9022..ee6dad3fb 100644 --- a/src/templating/test3/resu.ref +++ b/src/templating/test3/resu.ref @@ -3,12 +3,10 @@ * <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 diff --git a/src/templating/test3/vg.ref b/src/templating/test3/vg.ref new file mode 100644 index 000000000..21f7931eb --- /dev/null +++ b/src/templating/test3/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 30 allocs, 30 frees, 5,831 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test4/Makefile b/src/templating/test4/Makefile new file mode 100644 index 000000000..1a3e57914 --- /dev/null +++ b/src/templating/test4/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json must + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test4/resu.ref b/src/templating/test4/resu.ref index 2d48918ac..8a71c4e82 100644 --- a/src/templating/test4/resu.ref +++ b/src/templating/test4/resu.ref @@ -6,95 +6,45 @@ Jon 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 } - + (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" } - + (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" } - + (2) Henry: {"age":27,"lang":"en"} (3) age: 27 - (3) lang: en - - - (2) Amed: { "age": 24, "lang": "fr" } - + (2) Amed: {"age":24,"lang":"fr"} (3) age: 24 - (3) lang: fr - - - diff --git a/src/templating/test4/vg.ref b/src/templating/test4/vg.ref new file mode 100644 index 000000000..922b0676d --- /dev/null +++ b/src/templating/test4/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 121 allocs, 121 frees, 14,608 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test5/Makefile b/src/templating/test5/Makefile new file mode 100644 index 000000000..1a3e57914 --- /dev/null +++ b/src/templating/test5/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json must + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test5/json b/src/templating/test5/json index 5b2e3d83a..6562fb064 100644 --- a/src/templating/test5/json +++ b/src/templating/test5/json @@ -5,12 +5,12 @@ "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" } ] } + { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] }, + { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] }, + { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] } ], "person?": { "name": "Jon" }, - "special": "----{{extra}}----", + "special": "----{{extra}}----\n", "extra": 3.14159, "#sharp": "#", "!bang": "!", diff --git a/src/templating/test5/must3.mustache b/src/templating/test5/must3.mustache index 821aaac33..67eddb1ef 100644 --- a/src/templating/test5/must3.mustache +++ b/src/templating/test5/must3.mustache @@ -1,6 +1,6 @@ must3.mustache == BEGIN {{#repo}} - <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} + <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}} {{/repo}} {{#person?}} @@ -11,7 +11,7 @@ must3.mustache == BEGIN ===================================== %(%! big comment %)% %(%#repo%)% - <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% + <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)% %(%/repo%)% ===================================== must3.mustache == END diff --git a/src/templating/test5/resu.ref b/src/templating/test5/resu.ref index afc396599..f2a608568 100644 --- a/src/templating/test5/resu.ref +++ b/src/templating/test5/resu.ref @@ -3,57 +3,35 @@ 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 - - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg Hi Jon! - - ===================================== - - - <b>resque</b> reviewers: avrel committers: joe william - - <b>hub</b> reviewers: avrel committers: jack greg - - <b>rip</b> reviewers: joe jack committers: greg - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg ===================================== must3.mustache == END - ===================================== Ensure must3 didn't change specials - Hi Jon! - %(%#person?%)% Hi %(%name%)%! %(%/person?%)% diff --git a/src/templating/test5/special b/src/templating/test5/special deleted file mode 100644 index 02d9975c6..000000000 --- a/src/templating/test5/special +++ /dev/null @@ -1 +0,0 @@ -special ==SHOULD NOT BE SEEN== diff --git a/src/templating/test5/special.mustache b/src/templating/test5/special.mustache deleted file mode 100644 index 70a771fd6..000000000 --- a/src/templating/test5/special.mustache +++ /dev/null @@ -1 +0,0 @@ -special.mustache ==SHOULD NOT BE SEEN== diff --git a/src/templating/test5/vg.ref b/src/templating/test5/vg.ref new file mode 100644 index 000000000..89dc21bcb --- /dev/null +++ b/src/templating/test5/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 123 allocs, 123 frees, 20,610 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test6/.gitignore b/src/templating/test6/.gitignore index 62f4d9190..15e6dd5a5 100644 --- a/src/templating/test6/.gitignore +++ b/src/templating/test6/.gitignore @@ -1,4 +1,3 @@ resu.last vg.last test-custom-write -!test-custom-write.c diff --git a/src/templating/test6/Makefile b/src/templating/test6/Makefile new file mode 100644 index 000000000..ea4f86e79 --- /dev/null +++ b/src/templating/test6/Makefile @@ -0,0 +1,12 @@ +.PHONY: test clean + +test-custom-write: test-custom-write.c ../mustach-json-c.h ../mustach-json-c.c ../mustach-wrap.c ../mustach.h ../mustach.c + @echo building test-custom-write + $(CC) $(CFLAGS) $(LDFLAGS) -g -o test-custom-write test-custom-write.c ../mustach.c ../mustach-json-c.c ../mustach-wrap.c -ljson-c + +test: test-custom-write + @mustach=./test-custom-write ../dotest.sh json -U must -l must -x must + +clean: + rm -f resu.last vg.last test-custom-write + diff --git a/src/templating/test6/json b/src/templating/test6/json index 5b2e3d83a..6562fb064 100644 --- a/src/templating/test6/json +++ b/src/templating/test6/json @@ -5,12 +5,12 @@ "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" } ] } + { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] }, + { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] }, + { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] } ], "person?": { "name": "Jon" }, - "special": "----{{extra}}----", + "special": "----{{extra}}----\n", "extra": 3.14159, "#sharp": "#", "!bang": "!", diff --git a/src/templating/test6/must b/src/templating/test6/must index 723f966c4..6df523669 100644 --- a/src/templating/test6/must +++ b/src/templating/test6/must @@ -12,7 +12,7 @@ Shown. {{/person}} {{#repo}} - <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} + <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}} {{/repo}} {{#person?}} @@ -23,7 +23,7 @@ Shown. ===================================== %(%! gros commentaire %)% %(%#repo%)% - <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% + <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)% %(%/repo%)% ===================================== %(%={{ }}=%)% diff --git a/src/templating/test6/resu.ref b/src/templating/test6/resu.ref index 345d3aef6..377eb11af 100644 --- a/src/templating/test6/resu.ref +++ b/src/templating/test6/resu.ref @@ -1,38 +1,20 @@ 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 - - + <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM + <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG + <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG HI JON! - - ===================================== - - - <B>RESQUE</B> REVIEWERS: AVREL COMMITTERS: JOE WILLIAM - - <B>HUB</B> REVIEWERS: AVREL COMMITTERS: JACK GREG - - <B>RIP</B> REVIEWERS: JOE JACK COMMITTERS: GREG - + <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM + <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG + <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG ===================================== - GGGGGGGGG ----3.14159---- JJJJJJJJJ @@ -49,39 +31,21 @@ END > 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 - - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg hi jon! - - ===================================== - - - <b>resque</b> reviewers: avrel committers: joe william - - <b>hub</b> reviewers: avrel committers: jack greg - - <b>rip</b> reviewers: joe jack committers: greg - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg ===================================== - ggggggggg ----3.14159---- jjjjjjjjj @@ -98,39 +62,21 @@ end > 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 - - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg Hi Jon! - - ===================================== - - - <b>resque</b> reviewers: avrel committers: joe william - - <b>hub</b> reviewers: avrel committers: jack greg - - <b>rip</b> reviewers: joe jack committers: greg - + <b>resque</b> reviewers: avrel commiters: joe william + <b>hub</b> reviewers: avrel commiters: jack greg + <b>rip</b> reviewers: joe jack commiters: greg ===================================== - ggggggggg ----3.14159---- jjjjjjjjj diff --git a/src/templating/test6/test-custom-write.c b/src/templating/test6/test-custom-write.c index cc50a47cb..20042c1ed 100644 --- a/src/templating/test6/test-custom-write.c +++ b/src/templating/test6/test-custom-write.c @@ -1,6 +1,5 @@ /* Author: José Bollo <jobol@nonadev.net> - Author: José Bollo <jose.bollo@iot.bzh> https://gitlab.com/jobol/mustach @@ -17,7 +16,9 @@ limitations under the License. */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include <stdlib.h> #include <stdio.h> @@ -36,7 +37,7 @@ static char *readfile(const char *filename) { int f; struct stat s; - char *result; + char *result, *ptr; size_t size, pos; ssize_t rc; @@ -80,7 +81,10 @@ static char *readfile(const char *filename) pos += (size_t)rc; if (pos > size) { size = pos + BLOCKSIZE; - result = realloc(result, size + 1); + ptr = realloc(result, size + 1); + if (!ptr) + free(result); + result = ptr; } } } while(rc > 0); @@ -132,7 +136,7 @@ int main(int ac, char **av) mode = None; else { t = readfile(*av); - s = umustach_json_c(t, o, uwrite, NULL); + s = mustach_json_c_write(t, 0, o, Mustach_With_AllExtensions, uwrite, NULL); if (s != 0) fprintf(stderr, "Template error %d\n", s); free(t); diff --git a/src/templating/test6/vg.ref b/src/templating/test6/vg.ref new file mode 100644 index 000000000..fb0e31bd8 --- /dev/null +++ b/src/templating/test6/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ./test-custom-write json -U must -l must -x must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 174 allocs, 174 frees, 24,250 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test7/Makefile b/src/templating/test7/Makefile new file mode 100644 index 000000000..8e3a3b990 --- /dev/null +++ b/src/templating/test7/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json base.mustache + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test7/base.mustache b/src/templating/test7/base.mustache new file mode 100644 index 000000000..f701e0c65 --- /dev/null +++ b/src/templating/test7/base.mustache @@ -0,0 +1,2 @@ +family: +{{> node}} diff --git a/src/templating/test7/json b/src/templating/test7/json new file mode 100644 index 000000000..c9ee47150 --- /dev/null +++ b/src/templating/test7/json @@ -0,0 +1,8 @@ +{ "data": "grandparent", "children": [ + { "data": "parent", "children": [ + { "data": "child", "children": [] } + ]}, + { "data": "parent2", "children": [ + { "data": "child2", "children": [ { "data": "pet", "children": false } ]} + ]} +]} diff --git a/src/templating/test7/node.mustache b/src/templating/test7/node.mustache new file mode 100644 index 000000000..4154b12ba --- /dev/null +++ b/src/templating/test7/node.mustache @@ -0,0 +1,4 @@ +<{{data}}> +{{#children}} + {{> node}} +{{/children}} diff --git a/src/templating/test7/resu.ref b/src/templating/test7/resu.ref new file mode 100644 index 000000000..d02b24e11 --- /dev/null +++ b/src/templating/test7/resu.ref @@ -0,0 +1,7 @@ +family: +<grandparent> + <parent> + <child> + <parent2> + <child2> + <pet> diff --git a/src/templating/test7/vg.ref b/src/templating/test7/vg.ref new file mode 100644 index 000000000..032e6c447 --- /dev/null +++ b/src/templating/test7/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json base.mustache + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 69 allocs, 69 frees, 36,313 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test8/.gitignore b/src/templating/test8/.gitignore new file mode 100644 index 000000000..4d897daa0 --- /dev/null +++ b/src/templating/test8/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/templating/test8/Makefile b/src/templating/test8/Makefile new file mode 100644 index 000000000..1a3e57914 --- /dev/null +++ b/src/templating/test8/Makefile @@ -0,0 +1,8 @@ +.PHONY: test clean + +test: + @../dotest.sh json must + +clean: + rm -f resu.last vg.last + diff --git a/src/templating/test8/json b/src/templating/test8/json new file mode 100644 index 000000000..04a1e4aaa --- /dev/null +++ b/src/templating/test8/json @@ -0,0 +1,8 @@ +{ +"val1": "", +"val2": 0, +"val3": false, +"val4": null, +"val5": [], +"val6": 0.0 +} diff --git a/src/templating/test8/must b/src/templating/test8/must new file mode 100644 index 000000000..a22374438 --- /dev/null +++ b/src/templating/test8/must @@ -0,0 +1,6 @@ +x{{#val1}} {{.}} {{/val1}}x +x{{#val2}} {{.}} {{/val2}}x +x{{#val3}} {{.}} {{/val3}}x +x{{#val4}} {{.}} {{/val4}}x +x{{#val5}} {{.}} {{/val5}}x +x{{#val6}} {{.}} {{/val6}}x diff --git a/src/templating/test8/resu.ref b/src/templating/test8/resu.ref new file mode 100644 index 000000000..e0c16e49a --- /dev/null +++ b/src/templating/test8/resu.ref @@ -0,0 +1,6 @@ +xx +xx +xx +xx +xx +xx diff --git a/src/templating/test8/vg.ref b/src/templating/test8/vg.ref new file mode 100644 index 000000000..b5b0235f9 --- /dev/null +++ b/src/templating/test8/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 17 allocs, 17 frees, 4,832 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For lists of detected and suppressed errors, rerun with: -s +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/templating/test_mustach_jansson.c b/src/templating/test_mustach_jansson.c index be3db67d2..beb155f6d 100644 --- a/src/templating/test_mustach_jansson.c +++ b/src/templating/test_mustach_jansson.c @@ -24,7 +24,7 @@ */ #include "platform.h" #include "mustach-jansson.h" - +#include <gnunet/gnunet_util_lib.h> static void assert_template (const char *template, @@ -34,10 +34,12 @@ assert_template (const char *template, char *r; size_t sz; - GNUNET_assert (0 == mustach_jansson (template, - root, - &r, - &sz)); + GNUNET_assert (0 == mustach_jansson_mem (template, + 0, + root, + Mustach_With_AllExtensions, + &r, + &sz)); GNUNET_assert (0 == strcmp (r, expected)); GNUNET_free (r); @@ -51,7 +53,6 @@ main (int argc, 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"; @@ -67,24 +68,15 @@ main (int argc, /* 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"; - (void) argc; (void) argv; + GNUNET_log_setup ("test-mustach-jansson", + "INFO", + NULL); GNUNET_assert (NULL != root); GNUNET_assert (NULL != arr); GNUNET_assert (NULL != obj); @@ -122,44 +114,12 @@ main (int argc, 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; } |