diff --git a/src/templating/.gitignore b/src/templating/.gitignore index b2bf6ef99..ac9c37f45 100644 --- a/src/templating/.gitignore +++ b/src/templating/.gitignore @@ -1 +1,2 @@ test_mustach_jansson +taler-mustach-tool diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS index 2fcc60437..b440c24ed 100644 --- a/src/templating/AUTHORS +++ b/src/templating/AUTHORS @@ -4,8 +4,15 @@ Main author: Contributors: Abhishek Mishra Atlas + Ben Beasley + Gabriel Zachmann Harold L Marzan + Kurt Jung Lailton Fernando Mariano + Lucas Ramage + Paramtamtam + RekGRpth + Ryan Fox Sami Kerola Sijmen J. Mulder Tomasz Sieprawski @@ -13,6 +20,7 @@ Contributors: Packagers: pkgsrc: Sijmen J. Mulder alpine linux: Lucas Ramage + RPM & DEB: Marcus Hardt Thanks to issue submitters: Dante Torres @@ -21,3 +29,4 @@ Thanks to issue submitters: Mark Bucciarelli Paul Wisehart Thierry Fournier + SASU OKFT 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 2501151a8..fcdc22cd4 100644 --- a/src/templating/Makefile.am +++ b/src/templating/Makefile.am @@ -6,6 +6,15 @@ 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 lib_LTLIBRARIES = \ libtalertemplating.la @@ -15,6 +24,7 @@ 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 = \ diff --git a/src/templating/README.md b/src/templating/README.md index a6df19f64..6699b0e89 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 simpliest 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 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). +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). -HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...). +Since version 1.0, the project also provide integration of other JSON libraries: +**cJSON** and **jansson**. + +*If you integrate a new library with* **mustach**, *your contribution will be +welcome here*. The tool **mustach** is build using `make`, its usage is: @@ -57,14 +85,14 @@ It then outputs the result of applying the templates files to the JSON file. ### Portability Some system does not provide *open_memstream*. In that case, tell your -preferred compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**. +prefered 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,211 @@ 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 -In somecases the name of the key used for substitution begins with a +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. + +## 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 substition 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 substition 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**. -This are a tool extension implemented in file **mustach-json-c.c**. +### Empty Tag Allowed (Mustach_With_EmptyTag) -These extensions allows you to test the value of the selected key. -They allow to write `key=value` (matching test) or `key=!value` +When an empty tag is found, instead of automatically raising the error +MUSTACH\_ERROR\_EMPTY\_TAG pass it. + +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` signs when +values comparisons are enabled: `~=` gives `=` in the key. -### Removing Extensions +This is a wrap extension implemented in file **mustach-wrap.c**. -When compiling mustach.c or mustach-json-c.c, -extensions can be removed by defining macros -using option -D. +### Iteration On Objects (Mustach_With_ObjectIter) -The possible macros are of 3 categories, the global, -the mustach core specific and the mustach-json-c example -of implementation specific. +With this extension, using the pattern `{{#X.*}}...{{/X.*}}` +allows to iterate on fields of `X`. -#### Global macros +Example: -- `NO_EXTENSION_FOR_MUSTACH` +- `{{s.*}} {{*}}:{{.}}{{/s.*}}` applied on `{"s":{"a":1,"b":true}}` produces ` a:1 b:true` - This macro disables any current or future - extensions for the core or the example. +Here the single star `{{*}}` is replaced by the iterated key +and the single dot `{{.}}` is replaced by its value. -#### Macros for the core mustach engine (mustach.c) +This is a wrap extension implemented in file **mustach-wrap.c**. -- `NO_COLON_EXTENSION_FOR_MUSTACH` +### Error when a requested tag is undefined (Mustach_With_ErrorUndefined) - 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 `:#^/&{=>` +Report the error MUSTACH_ERROR_UNDEFINED_TAG when a requested tag +is not defined. -- `NO_ALLOW_EMPTY_TAG` +This is a wrap extension implemented in file **mustach-wrap.c**. - Generate the error MUSTACH_ERROR_EMPTY_TAG automatically - when an empty tag is encountered. +### Access To Current Value -#### Macros for the implementation example (mustach-json-c.c) +*this was an extension but is now always enforced* -- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH` +The value of the current field can be accessed using single dot. - 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. +Examples: -- `NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH` +- `{{#key}}{{.}}{{/key}}` applied to `{"key":3.14}` produces `3.14` +- `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces ` 1 2`. - 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}}`. +This is a wrap extension implemented in file **mustach-wrap.c**. -- `NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH` +### Partial Data First - This macro fordids automatic escaping of coparison - sign appearing at first column. +*this was an extension but is now always enforced* -- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH` +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`. - 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. +By default, the order of the search is (1) as a file, +and if not found, (2) in the current json context. -- `NO_OBJECT_ITERATION_FOR_MUSTACH` +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. - 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. +That option is useful to keep the compatibility with +versions of *mustach* anteriors to 1.2.0. -- `NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH` +This is a wrap extension implemented in file **mustach-wrap.c**. - Disable access to current object value using single dot - like in `{{.}}`. +### Escape First Compare -- `NO_INCLUDE_PARTIAL_FALLBACK` +This extension automatically escapes comparisons appears as +first characters. + +This is a wrap extension implemented in file **mustach-wrap.c**. + +## Difference with version 0.99 and previous + +### Extensions + +The extensions can no more be removed at compile time, use +flags to select your required extension on need. + +### Name of functions + +Names of functions were improved. Old names remain but are obsolete +and legacy. Their removal in far future versions is possible. + +The table below summarize the changes. + + 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 - Disable include of file by partial pattern like `{{> name}}`. - By default if a such pattern is found, **mustach** search - for `name` in the current json context. This what is done - historically and when `NO_INCLUDE_PARTIAL_FALLBACK` is defined. - When `NO_INCLUDE_PARTIAL_FALLBACK` is defined, if the value is - found in the json context, the files `name` and `name.mustache` - are searched in that order and the first file found is used - as partial content. The macro `INCLUDE_PARTIAL_EXTENSION` can - be use for changing the extension added. diff --git a/src/templating/mustach-cjson.c b/src/templating/mustach-cjson.c new file mode 100644 index 000000000..48b97e7df --- /dev/null +++ b/src/templating/mustach-cjson.c @@ -0,0 +1,239 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#define _GNU_SOURCE + +#include +#include +#include + +#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; + int r; + + o = cJSON_GetObjectItemCaseSensitive(e->selection, name); + r = o != NULL; + 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_IsFalse(o) && ! cJSON_IsNull(o))) { + 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; + + if (key) { + s = e->stack[e->depth].is_objiter + ? e->stack[e->depth].obj->string + : ""; + } + 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..ae0d818cb --- /dev/null +++ b/src/templating/mustach-cjson.h @@ -0,0 +1,96 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#ifndef _mustach_cJSON_h_included_ +#define _mustach_cJSON_h_included_ + +/* + * mustach-json-c is intended to make integration of cJSON + * library by providing integrated functions. + */ + +#include +#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 c65fe2b01..3ce0e0a84 100644 --- a/src/templating/mustach-jansson.c +++ b/src/templating/mustach-jansson.c @@ -1,429 +1,251 @@ /* - Copyright (C) 2020 Taler Systems SA - - Original license: Author: José Bollo - Author: José Bollo 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 */ -#include "platform.h" +#define _GNU_SOURCE + +#include +#include + +#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; +} + +static int compare(void *closure, const char *value) +{ + 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 int sel(void *closure, const char *name) +{ + 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 int subsel(void *closure, const char *name) +{ + struct expl *e = closure; + json_t *o; + int r; + + o = json_object_get(e->selection, name); + r = o != NULL; + if (r) + e->selection = o; + return r; +} + +static int enter(void *closure, int objiter) +{ + 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(0)) || (!json_is_false(o) && !json_is_null(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) { + 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; + + 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) +{ + 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; + + if (key) { + s = e->stack[e->depth].is_objiter + ? json_object_iter_key(e->stack[e->depth].iter) + : ""; + } + 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; +} + +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 }; -struct JanssonClosure +int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file) { - json_t *root; - mustach_jansson_write_cb writecb; - int depth; - - /** - * Did the last find(..) call result in an iterable? - */ - struct Context stack[MUSTACH_MAX_DEPTH]; - - /** - * The last object we found should be iterated over. - */ - bool found_iter; - - /** - * Last bang we found. - */ - enum Bang found_bang; - - /** - * Language for i18n lookups. - */ - const char *lang; -}; - - -static json_t * -walk (json_t *obj, const char *path) -{ - char *saveptr = NULL; - char *sp = GNUNET_strdup (path); - char *p = sp; - while (true) - { - char *tok = strtok_r (p, ".", &saveptr); - if (tok == NULL) - break; - obj = json_object_get (obj, tok); - if (obj == NULL) - break; - p = NULL; - } - GNUNET_free (sp); - return obj; + struct expl e; + e.root = root; + return mustach_wrap_file(template, length, &mustach_jansson_wrap_itf, &e, flags, file); } - -static json_t * -find (struct JanssonClosure *e, const char *name) +int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd) { - 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; + e.root = root; + return mustach_wrap_fd(template, length, &mustach_jansson_wrap_itf, &e, flags, fd); } - -static int -start (void *closure) +int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size) { - 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; + e.root = root; + return mustach_wrap_mem(template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); } - -static int -emituw (void *closure, const char *buffer, size_t size, int escape, FILE *file) +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 = 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; + e.root = root; + return mustach_wrap_write(template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, closure); } - -static int -enter (void *closure, const char *name) +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 = 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; + struct expl e; + e.root = root; + return mustach_wrap_emit(template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure); } - -static int -next (void *closure) -{ - struct JanssonClosure *e = closure; - struct Context *ctx; - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - ctx = &e->stack[e->depth]; - if (ctx->is_objiter) - { - ctx->iter = json_object_iter_next (ctx->obj, ctx->iter); - if (NULL == ctx->iter) - return 0; - ctx->obj = json_object_iter_value (ctx->iter); - return 1; - } - ctx->index++; - if (ctx->index >= ctx->count) - return 0; - ctx->obj = json_array_get (ctx->cont, ctx->index); - return 1; -} - - -static int -leave (void *closure) -{ - struct JanssonClosure *e = closure; - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - e->depth--; - return 0; -} - - -static void -freecb (void *v) -{ - free (v); -} - - -static int -get (void *closure, const char *name, struct mustach_sbuf *sbuf) -{ - struct JanssonClosure *e = closure; - json_t *obj; - - if ( (0 == strcmp (name, "*") ) && - (e->stack[e->depth].is_objiter) ) - { - sbuf->value = json_object_iter_key (e->stack[e->depth].iter); - return MUSTACH_OK; - } - obj = find (e, name); - if (NULL != obj) - { - switch (e->found_bang) - { - case BANG_I18N: - case BANG_NONE: - { - const char *s = json_string_value (obj); - if (NULL != s) - { - sbuf->value = s; - return MUSTACH_OK; - } - } - break; - case BANG_STRINGIFY: - sbuf->value = json_dumps (obj, JSON_INDENT (2)); - sbuf->freecb = freecb; - return MUSTACH_OK; - case BANG_AMOUNT_DECIMAL: - { - char *s; - char *c; - if (! json_is_string (obj)) - break; - s = GNUNET_strdup (json_string_value (obj)); - c = strchr (s, ':'); - if (NULL != c) - *c = 0; - sbuf->value = s; - sbuf->freecb = freecb; - return MUSTACH_OK; - } - break; - case BANG_AMOUNT_CURRENCY: - { - const char *s; - if (! json_is_string (obj)) - break; - s = json_string_value (obj); - s = strchr (s, ':'); - if (NULL == s) - break; - sbuf->value = s + 1; - return MUSTACH_OK; - } - break; - default: - break; - } - } - sbuf->value = ""; - return MUSTACH_OK; -} - - -static struct mustach_itf itf = { - .start = start, - .put = NULL, - .enter = enter, - .next = next, - .leave = leave, - .partial = NULL, - .get = get, - .emit = NULL, - .stop = NULL -}; - -static struct mustach_itf itfuw = { - .start = start, - .put = NULL, - .enter = enter, - .next = next, - .leave = leave, - .partial = NULL, - .get = get, - .emit = emituw, - .stop = NULL -}; - -int -fmustach_jansson (const char *template, json_t *root, FILE *file) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - return fmustach (template, &itf, &e, file); -} - - -int -fdmustach_jansson (const char *template, json_t *root, int fd) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - return fdmustach (template, &itf, &e, fd); -} - - -int -mustach_jansson (const char *template, json_t *root, char **result, - size_t *size) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - e.writecb = NULL; - return mustach (template, &itf, &e, result, size); -} - - -int -umustach_jansson (const char *template, json_t *root, mustach_jansson_write_cb - writecb, void *closure) -{ - struct JanssonClosure e = { 0 }; - e.root = root; - e.writecb = writecb; - return fmustach (template, &itfuw, &e, closure); -} diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h 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 Author: José Bollo 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 +#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..a21a113fb --- /dev/null +++ b/src/templating/mustach-json-c.c @@ -0,0 +1,267 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#define _GNU_SOURCE + +#include +#include + +#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; + int r; + + r = json_object_object_get_ex(e->selection, name, &o); + 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_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; + + if (key) + s = e->stack[e->depth].is_objiter + ? json_object_iter_peek_name(&e->stack[e->depth].iter) + : ""; + 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 + + 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 +#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-tool.c b/src/templating/mustach-tool.c index 364e34a84..0c8f44070 100644 --- a/src/templating/mustach-tool.c +++ b/src/templating/mustach-tool.c @@ -1,20 +1,9 @@ /* Author: José Bollo - Author: José Bollo 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 */ #define _GNU_SOURCE @@ -27,7 +16,7 @@ #include #include -#include "mustach-json-c.h" +#include "mustach-wrap.h" static const size_t BLOCKSIZE = 8192; @@ -43,16 +32,39 @@ static const char *errors[] = { "bad unescape tag", "invalid interface", "item not found", - "partial not found" + "partial not found", + "undefined tag" }; +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] \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 with input data\n" + " 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 +118,140 @@ 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 + +#define TOOL MUSTACH_TOOL_JANSSON + +#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..75cc9d1f6 --- /dev/null +++ b/src/templating/mustach-wrap.c @@ -0,0 +1,456 @@ +/* + Author: José Bollo + + https://gitlab.com/jobol/mustach + + SPDX-License-Identifier: ISC +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +#include "mustach.h" +#include "mustach-wrap.h" + +#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(void *closure) +{ + struct wrap *w = closure; + return w->itf->start ? w->itf->start(w->closure) : MUSTACH_OK; +} + +static void stop(void *closure, int status) +{ + struct wrap *w = closure; + if (w->itf->stop) + w->itf->stop(w->closure, status); +} + +static int write(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(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 = write(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 = write(w, &buffer[s], i - s, file); + if (i < size && r == MUSTACH_OK) { + switch(car) { + case '<': r = write(w, "<", 4, file); break; + case '>': r = write(w, ">", 4, file); break; + case '&': r = write(w, "&", 5, file); break; + case '"': r = write(w, """, 6, file); break; + } + i++; + } + } + } + return r; +} + +static int enter(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(void *closure) +{ + struct wrap *w = closure; + return w->itf->next(w->closure); +} + +static int leave(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(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; +} + +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; +} + +static int partial(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); + else 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; + } + if (rc != MUSTACH_OK) + sbuf->value = ""; + return MUSTACH_OK; +} + +const struct mustach_itf mustach_wrap_itf = { + .start = start, + .put = NULL, + .enter = enter, + .next = next, + .leave = leave, + .partial = partial, + .get = get, + .emit = emit, + .stop = stop +}; + +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..37e6ff6cf --- /dev/null +++ b/src/templating/mustach-wrap.h @@ -0,0 +1,234 @@ +/* + Author: José Bollo + + 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. + */ +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.c b/src/templating/mustach.c index caa80dcc9..548c38224 100644 --- a/src/templating/mustach.c +++ b/src/templating/mustach.c @@ -1,20 +1,9 @@ /* Author: José Bollo - Author: José Bollo 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 */ #define _GNU_SOURCE @@ -27,19 +16,9 @@ #ifdef _WIN32 #include #endif -#ifdef __sun -# include -#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 +30,13 @@ 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 */ + int flags; +}; + +struct prefix { + size_t len; + const char *start; + struct prefix *prefix; }; #if !defined(NO_OPEN_MEMSTREAM) @@ -135,6 +121,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 +130,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 +187,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 +216,109 @@ 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, FILE *file, struct prefix *prefix) +{ + if (prefix->prefix) { + int rc = emitprefix(iwrap, file, prefix->prefix); + if (rc < 0) + return rc; + } + return prefix->len ? iwrap->emit(iwrap->closure, prefix->start, prefix->len, 0, file) : 0; +} + +static int process(const char *template, size_t length, struct iwrap *iwrap, FILE *file, 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; + int depth, rc, enabled, stdalone; + struct prefix pref; - 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; + 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, file, &pref); + if (rc < 0) + return rc; + } + rc = iwrap->emit(iwrap->closure, template, l, 0, 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; + } + else if (!isspace(c)) { + if (stdalone == 2 && enabled) { + rc = emitprefix(iwrap, file, &pref); + if (rc < 0) + return rc; + pref.len = 0; + stdalone = 0; + } + 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 +329,63 @@ 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, file, &pref); + if (rc < 0) + return rc; + pref.len = 0; + } 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 +401,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++; @@ -370,7 +428,7 @@ static int process(const char *template, struct iwrap *iwrap, FILE *file, const sbuf_reset(&sbuf); rc = iwrap->partial(iwrap->closure_partial, name, &sbuf); if (rc >= 0) { - rc = process(sbuf.value, iwrap, file, opstr, clstr); + rc = process(sbuf.value, sbuf_length(&sbuf), iwrap, file, &pref); sbuf_release(&sbuf); } if (rc < 0) @@ -389,7 +447,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 +480,18 @@ 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.flags = flags; /* process */ rc = itf->start ? itf->start(closure) : 0; if (rc == 0) - rc = process(template, &iwrap, file, "{{", "}}"); + rc = process(template, length, &iwrap, file, 0); 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 +501,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 +520,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 +529,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..8c4a43f10 100644 --- a/src/templating/mustach.h +++ b/src/templating/mustach.h @@ -1,20 +1,9 @@ /* Author: José Bollo - Author: José Bollo 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,7 +14,7 @@ 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) @@ -37,20 +26,59 @@ struct mustach_sbuf; /* see below */ /** * 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 + +/* + * 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 +120,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 +155,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 +195,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 +206,28 @@ struct mustach_sbuf { void (*releasecb)(const char *value, void *closure); }; void *closure; + size_t length; }; -/* - * Definition of error codes returned by mustach - */ -#define MUSTACH_OK 0 -#define MUSTACH_ERROR_SYSTEM -1 -#define MUSTACH_ERROR_UNEXPECTED_END -2 -#define MUSTACH_ERROR_EMPTY_TAG -3 -#define MUSTACH_ERROR_TAG_TOO_LONG -4 -#define MUSTACH_ERROR_BAD_SEPARATORS -5 -#define MUSTACH_ERROR_TOO_DEEP -6 -#define MUSTACH_ERROR_CLOSING -7 -#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8 -#define MUSTACH_ERROR_INVALID_ITF -9 -#define MUSTACH_ERROR_ITEM_NOT_FOUND -10 -#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11 - -/* You can use definition below for user specific error */ -#define MUSTACH_ERROR_USER_BASE -100 -#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x)) - /** - * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * 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 + * @file: the file where to write the result * * Returns 0 in case of success, -1 with errno set in case of system error * a other negative value in case of error. */ -extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file); +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 'fd' 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 * @fd: the file descriptor number where to write the result @@ -221,12 +235,13 @@ 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); +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 'result' 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 @@ -235,7 +250,64 @@ 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); +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 + * + * 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(const char *template, const struct mustach_itf *itf, void *closure, int fd)); + +/** + * OBSOLETE use mustach_mem + * + * 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 + * @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(const char *template, const struct mustach_itf *itf, void *closure, char **result, size_t *size)); #endif diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c index 4bd7c5fe7..9261bde79 100644 --- a/src/templating/templating_api.c +++ b/src/templating/templating_api.c @@ -180,10 +180,12 @@ TALER_TEMPLATING_fill (const char *tmpl, int eno; if (0 != - (eno = mustach_jansson (tmpl, - (json_t *) root, - (char **) result, - result_size))) + (eno = mustach_jansson_mem (tmpl, + 0, /* length of tmpl */ + (json_t *) root, + Mustach_With_NoExtensions, + (char **) result, + result_size))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "mustach failed on template with error %d\n", @@ -237,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",