diff options
Diffstat (limited to 'src/mustach')
48 files changed, 2848 insertions, 0 deletions
diff --git a/src/mustach/AUTHORS b/src/mustach/AUTHORS new file mode 100644 index 00000000..2fcc6043 --- /dev/null +++ b/src/mustach/AUTHORS @@ -0,0 +1,23 @@ +Main author: + José Bollo <jobol@nonadev.net> + +Contributors: + Abhishek Mishra + Atlas + Harold L Marzan + Lailton Fernando Mariano + Sami Kerola + Sijmen J. Mulder + Tomasz Sieprawski + +Packagers: + pkgsrc: Sijmen J. Mulder + alpine linux: Lucas Ramage + +Thanks to issue submitters: + Dante Torres + @fabbe + Johann Oskarsson + Mark Bucciarelli + Paul Wisehart + Thierry Fournier diff --git a/src/mustach/LICENSE-2.0.txt b/src/mustach/LICENSE-2.0.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/src/mustach/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/mustach/Makefile.am b/src/mustach/Makefile.am new file mode 100644 index 00000000..a39a28d0 --- /dev/null +++ b/src/mustach/Makefile.am @@ -0,0 +1,19 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + + +lib_LIBRARIES = \ + libmustach.a + +libmustach_a_SOURCES = \ + mustach.c mustach.h + +check_SCRIPTS = \ + run-original-tests.sh + +TESTS = $(check_SCRIPTS) diff --git a/src/mustach/Makefile.orig b/src/mustach/Makefile.orig new file mode 100644 index 00000000..3e0fd17d --- /dev/null +++ b/src/mustach/Makefile.orig @@ -0,0 +1,66 @@ +DESTDIR ?= +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +LIBDIR ?= $(PREFIX)/lib +INCLUDEDIR ?= $(PREFIX)/include +SOVER = .0 +SOVEREV = .0.99 + +CFLAGS += -fPIC -Wall -Wextra +LDLIBS += -ljson-c + +lib_OBJ = mustach.o mustach-json-c.o +tool_OBJ = mustach.o mustach-json-c.o mustach-tool.o +HEADERS = mustach.h mustach-json-c.h + +lib_LDFLAGS += -shared +ifeq ($(shell uname),Darwin) + lib_LDFLAGS += -install_name $(LIBDIR)/libmustach.so$(SOVEREV) +endif + +all: mustach libmustach.so$(SOVEREV) + +install: all + install -d $(DESTDIR)$(BINDIR) + install -d $(DESTDIR)$(LIBDIR) + install -d $(DESTDIR)$(INCLUDEDIR)/mustach + install -m0755 mustach $(DESTDIR)$(BINDIR)/ + install -m0644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/mustach + install -m0755 libmustach.so* $(DESTDIR)$(LIBDIR)/ + ln -sf libmustach.so$(SOVEREV) $(DESTDIR)$(LIBDIR)/libmustach.so$(SOVER) + ln -sf libmustach.so$(SOVEREV) $(DESTDIR)$(LIBDIR)/libmustach.so + + +uninstall: + rm -f $(DESTDIR)$(BINDIR)/mustach + rm -f $(DESTDIR)$(LIBDIR)/libmustach.so* + rm -rf $(DESTDIR)$(INCLUDEDIR)/mustach + +mustach: $(tool_OBJ) + $(CC) $(LDFLAGS) -o mustach $(tool_OBJ) $(LDLIBS) + +libmustach.so$(SOVEREV): $(lib_OBJ) + $(CC) $(LDFLAGS) $(lib_LDFLAGS) -o libmustach.so$(SOVEREV) $(lib_OBJ) $(LDLIBS) + +mustach.o: mustach.h +mustach-json.o: mustach.h mustach-json-c.h +mustach-tool.o: mustach.h mustach-json-c.h + +test: mustach + @$(MAKE) -C test1 test + @$(MAKE) -C test2 test + @$(MAKE) -C test3 test + @$(MAKE) -C test4 test + @$(MAKE) -C test5 test + @$(MAKE) -C test6 test + +clean: + rm -f mustach libmustach.so$(SOVEREV) *.o + @$(MAKE) -C test1 clean + @$(MAKE) -C test2 clean + @$(MAKE) -C test3 clean + @$(MAKE) -C test4 clean + @$(MAKE) -C test5 clean + @$(MAKE) -C test6 clean + +.PHONY: test clean install uninstall diff --git a/src/mustach/ORIGIN b/src/mustach/ORIGIN new file mode 100644 index 00000000..fafb0ae7 --- /dev/null +++ b/src/mustach/ORIGIN @@ -0,0 +1,9 @@ +Cloned originally from https://gitlab.com/jobol/mustach/ + +Changes: +======== + +Renamed original Makefile to Makefile.orig and wrote Makefile.am for us. + +Added run-original-tests.sh shell script as a wrapper around Makefile.org +to us the original build process for the test suite. diff --git a/src/mustach/README.md b/src/mustach/README.md new file mode 100644 index 00000000..1ad57262 --- /dev/null +++ b/src/mustach/README.md @@ -0,0 +1,214 @@ +# Introduction to Mustach 0.99 + +`mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache") +template specification. + +The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach). + +The best way to use mustach is to copy the files **mustach.h** and **mustach.c** +directly into your project and use it. + +Alternatively, make and meson files are provided for building `mustach` and +`libmustach.so` shared library. + +## Distributions offering mustach package + +### Alpine Linux + +```sh +apk add mustach +apk add mustach-lib +apk add mustach-dev +``` + +### NetBSD + +```sh +cd devel/mustach +make +``` + +See http://pkgsrc.se/devel/mustach + +## Using Mustach from sources + +The file **mustach.h** is the main documentation. Look at it. + +The current source files are: + +- **mustach.c** core implementation of mustache in C +- **mustach.h** header file for core definitions +- **mustach-json-c.c** tiny json wrapper of mustach using [json-c](https://github.com/json-c/json-c) +- **mustach-json-c.h** header file for using the tiny JSON wrapper +- **mustach-tool.c** simple tool for applying template files to a JSON file + +The file **mustach-json-c.c** is the main example of use of **mustach** core +and it is also a practical implementation that can be used. It uses the library +json-c. (NOTE for Mac OS: available through homebrew). + +HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...). + +The tool **mustach** is build using `make`, its usage is: + + mustach json template [template]... + +It then outputs the result of applying the templates files to the JSON file. + +### Portability + +Some system does not provide *open_memstream*. In that case, tell your +prefered compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**. +Example: + + gcc -DNO_OPEN_MEMSTREAM + +### Integration + +The file **mustach.h** is the main documentation. Look at it. + +The file **mustach-json-c.c** provides a good example of integration. + +If you intend to use basic HTML/XML escaping and standard C FILE, the callbacks +of the interface **mustach_itf** that you have to implement are: +`enter`, `next`, `leave`, `get`. + +If you intend to use specific escaping and/or specific output, the callbacks +of the interface **mustach_itf** that you have to implement are: +`enter`, `next`, `leave`, `get` and `emit`. + +### Extensions + +By default, the current implementation provides the following extensions: + +#### Explicit Substitution + +This is a core extension implemented in file **mustach.c**. + +In somecases the name of the key used for 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 are a tool extension implemented in file **mustach-json-c.c**. + +These extensions allows you to test the value of the selected key. +They allow to write `key=value` (matching test) or `key=!value` +(not matching test) in any query. + +The specific comparison extension also allows to compare if greater, +lesser, etc.. than a value. It allows to write `key>value`. + +It the comparator sign appears in the first column it is ignored +as if it was escaped. + +#### Access to current value + +The value of the current field can be accessed using single dot like +in `{{#key}}{{.}}{{/key}}` that applied to `{"key":3.14}` produces `3.14` +and `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces +` 1 2`. + +#### Iteration on objects + +Using the pattern `{{#X.*}}...{{/X.*}}` it is possible to iterate on +fields of `X`. Example: `{{s.*}} {{*}}:{{.}}{{/s.*}}` applied on +`{"s":{"a":1,"b":true}}` produces ` a:1 b:true`. Here the single star +`{{*}}` is replaced by the iterated key and the single dot `{{.}}` is +replaced by its value. + +### Removing Extensions + +When compiling mustach.c or mustach-json-c.c, +extensions can be removed by defining macros +using option -D. + +The possible macros are of 3 categories, the global, +the mustach core specific and the mustach-json-c example +of implementation specific. + +#### Global macros + +- `NO_EXTENSION_FOR_MUSTACH` + + This macro disables any current or future + extensions for the core or the example. + +#### Macros for the core mustach engine (mustach.c) + +- `NO_COLON_EXTENSION_FOR_MUSTACH` + + This macro remove the ability to use colon (:) + as explicit command for variable substituion. + This extension allows to have name starting + with one of the mustach character `:#^/&{=>` + +- `NO_ALLOW_EMPTY_TAG` + + Generate the error MUSTACH_ERROR_EMPTY_TAG automatically + when an empty tag is encountered. + +#### Macros for the implementation example (mustach-json-c.c) + +- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH` + + This macro allows the program to check whether + the actual value is equal to an expected value. + This is useful in `{{#key=val}}` or `{{^key=val}}` + with the corresponding `{{/key=val}}`. + It can also be used in `{{key=val}}` but this + doesn't seem to be useful. + +- `NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH` + + This macro allows the program to compare the actual + value with an expected value. The comparison operators + are `=`, `>`, `<`, `>=`, `<=`. The meaning of the + comparison numeric or alphabetic depends on the type + of the inspected value. Also the result of the comparison + can be inverted if the value starts with `!`. + Example of use: `{{key>=val}}`, or `{{#key>=val}}` and + `{{^key>=val}}` with their matching `{{/key>=val}}`. + +- `NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH` + + This macro fordids automatic escaping of coparison + sign appearing at first column. + +- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH` + + This macro removes the possible use of JSON pointers. + JSON pointers are defined in IETF RFC 6901. + If not set, any key starting with "/" is a JSON pointer. + This implies to use the colon to introduce keys. + So `NO_COLON_EXTENSION_FOR_MUSTACH` implies + `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`. + A special escaping is used for `=`, `<`, `>` signs when + values comparisons are enabled: `~=` gives `=` in the key. + +- `NO_OBJECT_ITERATION_FOR_MUSTACH` + + Disable the object iteration extension. That extension allows + to iterate over the keys of an object. The iteration on object + is selected by using the selector `{{#key.*}}`. In the context + of iterating over object keys, the single key `{{*}}` returns the + key and `{{.}}` returns the value. + +- `NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH` + + Disable access to current object value using single dot + like in `{{.}}`. + +- `NO_INCLUDE_PARTIAL_FALLBACK` + + Disable include of file by partial pattern like `{{> name}}`. + By default if a such pattern is found, **mustach** search + for `name` in the current json context. This what is done + historically and when `NO_INCLUDE_PARTIAL_FALLBACK` is defined. + When `NO_INCLUDE_PARTIAL_FALLBACK` is defined, if the value is + found in the json context, the files `name` and `name.mustache` + are searched in that order and the first file found is used + as partial content. The macro `INCLUDE_PARTIAL_EXTENSION` can + be use for changing the extension added. diff --git a/src/mustach/meson.build b/src/mustach/meson.build new file mode 100644 index 00000000..c7ecc8df --- /dev/null +++ b/src/mustach/meson.build @@ -0,0 +1,12 @@ +project('mustach', 'c', + version: '1.0.0' +) + +mustach_inc = include_directories('.') +mustach_lib = shared_library('mustach', + 'mustach.c', + include_directories: mustach_inc +) + +mustach_dep = declare_dependency(link_with: mustach_lib, + include_directories: mustach_inc) diff --git a/src/mustach/mustach-json-c.c b/src/mustach/mustach-json-c.c new file mode 100644 index 00000000..df901dcd --- /dev/null +++ b/src/mustach/mustach-json-c.c @@ -0,0 +1,526 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> +#ifdef _WIN32 +#include <malloc.h> +#endif +#ifdef __sun +# include <alloca.h> +#endif + +#include "mustach.h" +#include "mustach-json-c.h" + +#if defined(NO_EXTENSION_FOR_MUSTACH) +# undef NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH +# define NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH +# undef NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH +# define NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH +# undef NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH +# define NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH +# undef NO_JSON_POINTER_EXTENSION_FOR_MUSTACH +# define NO_JSON_POINTER_EXTENSION_FOR_MUSTACH +# undef NO_OBJECT_ITERATION_FOR_MUSTACH +# define NO_OBJECT_ITERATION_FOR_MUSTACH +# undef NO_INCLUDE_PARTIAL_FALLBACK +# define NO_INCLUDE_PARTIAL_FALLBACK +#endif + +#if !defined(NO_INCLUDE_PARTIAL_FALLBACK) \ + && !defined(INCLUDE_PARTIAL_EXTENSION) +# define INCLUDE_PARTIAL_EXTENSION ".mustache" +#endif + +#if !defined(NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH) +# undef NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH +#endif + +struct expl { + struct json_object *root; + mustach_json_c_write_cb writecb; + int depth; +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + int found_objiter; +#endif + struct { + struct json_object *cont; + struct json_object *obj; +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + struct json_object_iterator biter, eiter; + int is_objiter; +#endif + int index, count; + } stack[MUSTACH_MAX_DEPTH]; +}; + +enum comp { + C_no = 0, + C_eq = 1, + C_lt = 5, + C_le = 6, + C_gt = 9, + C_ge = 10 +}; + +#if !defined(NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH) +static enum comp getcomp(char *head) +{ + return head[0] == '=' ? C_eq +#if !defined(NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH) + : head[0] == '<' ? (head[1] == '=' ? C_le : C_lt) + : head[0] == '>' ? (head[1] == '=' ? C_ge : C_gt) +#endif + : C_no; +} + +static char *keyval(char *head, int isptr, enum comp *comp) +{ + char *w, c, s; + enum comp k; + + k = C_no; +#if !defined(NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH) + s = getcomp(head) != C_no; +#else + s = 0; +#endif + c = *(w = head); + while (c && (s || (k = getcomp(head)) == C_no)) { + if (s) + s = 0; + else + s = (isptr ? c == '~' : c == '\\') + && (getcomp(head + 1) != C_no); + if (!s) + *w++ = c; + c = *++head; + } + *w = 0; + *comp = k; + return k == C_no ? NULL : &head[k & 3]; +} + +static int compare(struct json_object *o, const char *value) +{ + switch (json_object_get_type(o)) { + case json_type_double: + return json_object_get_double(o) - atof(value); + case json_type_int: + return json_object_get_int64(o) - (int64_t)atoll(value); + default: + return strcmp(json_object_get_string(o), value); + } +} + +static int evalcomp(struct json_object *o, char *value, enum comp k) +{ + int r, c; + + c = compare(o, value); + switch (k) { + case C_eq: r = c == 0; break; +#if !defined(NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH) + case C_lt: r = c < 0; break; + case C_le: r = c <= 0; break; + case C_gt: r = c > 0; break; + case C_ge: r = c >= 0; break; +#endif + default: r = 0; break; + } + return r; +} +#else +static inline char *keyval(char *head, int isptr, enum comp *comp) +{ + *comp = C_no; + return NULL; +} +static inline int compare(struct json_object *o, char *value, enum comp k) +{ + return 0; +} +#endif + +static char *key(char **head, int isptr) +{ + char *r, *i, *w, c; + + c = *(i = *head); + if (!c) + r = NULL; + else { + r = w = i; +#if !defined(NO_JSON_POINTER_EXTENSION_FOR_MUSTACH) + if (isptr) + while (c && c != '/') { + if (c == '~') + switch (i[1]) { + case '1': c = '/'; /*@fallthrough@*/ + case '0': i++; + } + *w++ = c; + c = *++i; + } + else +#endif + while (c && c != '.') { + if (c == '\\' && (i[1] == '.' || i[1] == '\\')) + c = *++i; + *w++ = c; + c = *++i; + } + *w = 0; + *head = i + !!c; + } + return r; +} + +static struct json_object *find(struct expl *e, const char *name) +{ + int i, isptr; + struct json_object *o, *no; + char *n, *c, *v; + enum comp k; + + n = alloca(1 + strlen(name)); + strcpy(n, name); + isptr = 0; +#if !defined(NO_JSON_POINTER_EXTENSION_FOR_MUSTACH) + isptr = n[0] == '/'; + n += isptr; +#endif + + v = keyval(n, isptr, &k); +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + e->found_objiter = 0; +#endif +#if !defined(NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH) + if (n[0] == '.' && !n[1]) { + /* case of . alone */ + o = e->stack[e->depth].obj; + } else +#endif + { + c = key(&n, isptr); + if (c == NULL) + return NULL; + o = NULL; + i = e->depth; + while (i >= 0 && !json_object_object_get_ex(e->stack[i].obj, c, &o)) + i--; + if (i < 0) { +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + o = e->stack[e->depth].obj; + if (c[0] == '*' && !c[1] && !v && !key(&n, isptr) && json_object_is_type(o, json_type_object)) { + e->found_objiter = 1; + return o; + } +#endif + return NULL; + } + c = key(&n, isptr); + while(c) { + if (!json_object_object_get_ex(o, c, &no)) { +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + if (c[0] == '*' && !c[1] && !v && !key(&n, isptr) && json_object_is_type(o, json_type_object)) { + e->found_objiter = 1; + return o; + } +#endif + return NULL; + } + o = no; + c = key(&n, isptr); + } + } + if (v) { + i = v[0] == '!'; + if (i == evalcomp(o, &v[i], k)) + o = NULL; + } + return o; +} + +static int start(void *closure) +{ + struct expl *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; + return MUSTACH_OK; +} + +static int write(struct expl *e, const char *buffer, size_t size, FILE *file) +{ + return e->writecb(file, buffer, size); +} + +static int emituw(void *closure, const char *buffer, size_t size, int escape, FILE *file) +{ + struct expl *e = closure; + if (!escape) + write(e, buffer, size, file); + else + do { + switch(*buffer) { + case '<': write(e, "<", 4, file); break; + case '>': write(e, ">", 4, file); break; + case '&': write(e, "&", 5, file); break; + default: write(e, buffer, 1, file); break; + } + buffer++; + } while(--size); + return MUSTACH_OK; +} + +static const char *item(struct expl *e, const char *name) +{ + struct json_object *o; + const char *s; + +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + if (name[0] == '*' && !name[1] && e->stack[e->depth].is_objiter) + s = json_object_iter_peek_name(&e->stack[e->depth].biter); + else + s = (o = find(e, name)) && !e->found_objiter ? json_object_get_string(o) : NULL; +#else + s = (o = find(e, name)) ? json_object_get_string(o) : NULL; +#endif + return s; +} + +static int enter(void *closure, const char *name) +{ + struct expl *e = closure; + struct json_object *o = find(e, name); + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + 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) { + e->depth--; + return 0; + } + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_object_array_get_idx(o, 0); + e->stack[e->depth].index = 0; +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + e->stack[e->depth].is_objiter = 0; + } else if (json_object_is_type(o, json_type_object) && e->found_objiter) { + e->stack[e->depth].biter = json_object_iter_begin(o); + e->stack[e->depth].eiter = json_object_iter_end(o); + if (json_object_iter_equal(&e->stack[e->depth].biter, &e->stack[e->depth].eiter)) { + e->depth--; + return 0; + } + e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].biter); + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; +#endif + } 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; +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + e->stack[e->depth].is_objiter = 0; +#endif + } else { + e->depth--; + return 0; + } + return 1; +} + +static int next(void *closure) +{ + struct expl *e = closure; + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; +#if !defined(NO_OBJECT_ITERATION_FOR_MUSTACH) + if (e->stack[e->depth].is_objiter) { + json_object_iter_next(&e->stack[e->depth].biter); + if (json_object_iter_equal(&e->stack[e->depth].biter, &e->stack[e->depth].eiter)) + return 0; + e->stack[e->depth].obj = json_object_iter_peek_value(&e->stack[e->depth].biter); + return 1; + } +#endif + 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; +} + +#if !defined(NO_INCLUDE_PARTIAL_FALLBACK) +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) { + /* 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 expl *e = closure; + const char *s; + + s = item(e, name); + if (s) + sbuf->value = s; + else if (get_partial_from_file(name, sbuf) < 0) + sbuf->value = ""; + return MUSTACH_OK; +} +#endif + +static int get(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ + struct expl *e = closure; + const char *s; + + s = item(e, name); + if (s) + sbuf->value = s; + else + sbuf->value = ""; + return MUSTACH_OK; +} + +static struct mustach_itf itf = { + .start = start, + .put = NULL, + .enter = enter, + .next = next, + .leave = leave, +#if !defined(NO_INCLUDE_PARTIAL_FALLBACK) + .partial = partial, +#else + .partial =NULL, +#endif + .get = get, + .emit = NULL, + .stop = NULL +}; + +static struct mustach_itf itfuw = { + .start = start, + .put = NULL, + .enter = enter, + .next = next, + .leave = leave, +#if !defined(NO_INCLUDE_PARTIAL_FALLBACK) + .partial = partial, +#else + .partial =NULL, +#endif + .get = get, + .emit = emituw, + .stop = NULL +}; + +int fmustach_json_c(const char *template, struct json_object *root, FILE *file) +{ + struct expl e; + e.root = root; + return fmustach(template, &itf, &e, file); +} + +int fdmustach_json_c(const char *template, struct json_object *root, int fd) +{ + struct expl e; + e.root = root; + return fdmustach(template, &itf, &e, fd); +} + +int mustach_json_c(const char *template, struct json_object *root, char **result, size_t *size) +{ + struct expl e; + e.root = root; + e.writecb = NULL; + return mustach(template, &itf, &e, result, size); +} + +int umustach_json_c(const char *template, struct json_object *root, mustach_json_c_write_cb writecb, void *closure) +{ + struct expl e; + e.root = root; + e.writecb = writecb; + return fmustach(template, &itfuw, &e, closure); +} + diff --git a/src/mustach/mustach-json-c.h b/src/mustach/mustach-json-c.h new file mode 100644 index 00000000..3dfa228b --- /dev/null +++ b/src/mustach/mustach-json-c.h @@ -0,0 +1,78 @@ +/* + Author: José Bollo <jose.bollo@iot.bzh> + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _mustach_json_c_h_included_ +#define _mustach_json_c_h_included_ + +#include <json-c/json.h> + +/** + * fmustach_json_c - Renders the mustache 'template' in 'file' for 'root'. + * + * @template: the template string to instanciate + * @root: the root json object to render + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fmustach_json_c(const char *template, struct json_object *root, FILE *file); + +/** + * fmustach_json_c - Renders the mustache 'template' in 'fd' for 'root'. + * + * @template: the template string to instanciate + * @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_json_c(const char *template, struct json_object *root, int fd); + + +/** + * fmustach_json_c - Renders the mustache 'template' in 'result' for 'root'. + * + * @template: the template string to instanciate + * @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(const char *template, struct json_object *root, char **result, size_t *size); + +/** + * umustach_json_c - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * + * @template: the template string to instanciate + * @root: the root json object to render + * @writecb: the function that write values + * @closure: the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +typedef int (*mustach_json_c_write_cb)(void*closure, const char*buffer, size_t size); +extern int umustach_json_c(const char *template, struct json_object *root, mustach_json_c_write_cb writecb, void *closure); + +#endif + diff --git a/src/mustach/mustach-tool.c b/src/mustach/mustach-tool.c new file mode 100644 index 00000000..364e34a8 --- /dev/null +++ b/src/mustach/mustach-tool.c @@ -0,0 +1,155 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <libgen.h> + +#include "mustach-json-c.h" + +static const size_t BLOCKSIZE = 8192; + +static const char *errors[] = { + "??? unreferenced ???", + "system", + "unexpected end", + "empty tag", + "tag too long", + "bad separators", + "too depth", + "closing", + "bad unescape tag", + "invalid interface", + "item not found", + "partial not found" +}; + +static void help(char *prog) +{ + printf("usage: %s json-file mustach-templates...\n", basename(prog)); + exit(0); +} + +static char *readfile(const char *filename) +{ + int f; + struct stat s; + char *result; + size_t size, pos; + ssize_t rc; + + result = NULL; + if (filename[0] == '-' && filename[1] == 0) + f = dup(0); + else + f = open(filename, O_RDONLY); + if (f < 0) { + fprintf(stderr, "Can't open file: %s\n", filename); + exit(1); + } + + fstat(f, &s); + switch (s.st_mode & S_IFMT) { + case S_IFREG: + size = s.st_size; + break; + case S_IFSOCK: + case S_IFIFO: + size = BLOCKSIZE; + break; + default: + fprintf(stderr, "Bad file: %s\n", filename); + exit(1); + } + + pos = 0; + result = malloc(size + 1); + do { + if (result == NULL) { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + rc = read(f, &result[pos], (size - pos) + 1); + if (rc < 0) { + fprintf(stderr, "Error while reading %s\n", filename); + exit(1); + } + if (rc > 0) { + pos += (size_t)rc; + if (pos > size) { + size = pos + BLOCKSIZE; + result = realloc(result, size + 1); + } + } + } while(rc > 0); + + close(f); + result[pos] = 0; + return result; +} + +int main(int ac, char **av) +{ + struct json_object *o; + char *t; + char *prog = *av; + int s; + + (void)ac; /* unused */ + + if (*++av) { + if (!strcmp(*av, "-h") || !strcmp(*av, "--help")) + help(prog); + if (av[0][0] == '-' && !av[0][1]) + o = json_object_from_fd(0); + else + o = json_object_from_file(av[0]); +#if JSON_C_VERSION_NUM >= 0x000D00 + if (json_util_get_last_err() != NULL) { + fprintf(stderr, "Bad json: %s (file %s)\n", json_util_get_last_err(), av[0]); + exit(1); + } + else +#endif + if (o == NULL) { + fprintf(stderr, "Aborted: null json (file %s)\n", av[0]); + exit(1); + } + while(*++av) { + t = readfile(*av); + s = fmustach_json_c(t, o, stdout); + if (s != 0) { + s = -s; + if (s < 1 || s >= (int)(sizeof errors / sizeof * errors)) + s = 0; + fprintf(stderr, "Template error %s (file %s)\n", errors[s], *av); + } + free(t); + } + json_object_put(o); + } + return 0; +} + diff --git a/src/mustach/mustach.c b/src/mustach/mustach.c new file mode 100644 index 00000000..caa80dcc --- /dev/null +++ b/src/mustach/mustach.c @@ -0,0 +1,472 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#ifdef _WIN32 +#include <malloc.h> +#endif +#ifdef __sun +# include <alloca.h> +#endif + +#include "mustach.h" + +#if defined(NO_EXTENSION_FOR_MUSTACH) +# undef NO_COLON_EXTENSION_FOR_MUSTACH +# define NO_COLON_EXTENSION_FOR_MUSTACH +# undef NO_ALLOW_EMPTY_TAG +# define NO_ALLOW_EMPTY_TAG +#endif + +struct iwrap { + int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); + void *closure; /* closure for: enter, next, leave, emit, get */ + int (*put)(void *closure, const char *name, int escape, FILE *file); + void *closure_put; /* closure for put */ + int (*enter)(void *closure, const char *name); + int (*next)(void *closure); + int (*leave)(void *closure); + int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); + int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); + void *closure_partial; /* closure for partial */ +}; + +#if !defined(NO_OPEN_MEMSTREAM) +static FILE *memfile_open(char **buffer, size_t *size) +{ + return open_memstream(buffer, size); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ + fclose(file); + free(*buffer); + *buffer = NULL; + *size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ + int rc; + + /* adds terminating null */ + rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0; + fclose(file); + if (rc == 0) + /* removes terminating null of the length */ + (*size)--; + else { + free(*buffer); + *buffer = NULL; + *size = 0; + } + return rc; +} +#else +static FILE *memfile_open(char **buffer, size_t *size) +{ + /* + * We can't provide *buffer and *size as open_memstream does but + * at least clear them so the caller won't get bad data. + */ + *buffer = NULL; + *size = 0; + + return tmpfile(); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ + fclose(file); + *buffer = NULL; + *size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ + int rc; + size_t s; + char *b; + + s = (size_t)ftell(file); + b = malloc(s + 1); + if (b == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + s = 0; + } else { + rewind(file); + if (1 == fread(b, s, 1, file)) { + rc = 0; + b[s] = 0; + } else { + rc = MUSTACH_ERROR_SYSTEM; + free(b); + b = NULL; + s = 0; + } + } + *buffer = b; + *size = s; + return rc; +} +#endif + +static inline void sbuf_reset(struct mustach_sbuf *sbuf) +{ + sbuf->value = NULL; + sbuf->freecb = NULL; + sbuf->closure = NULL; +} + +static inline void sbuf_release(struct mustach_sbuf *sbuf) +{ + if (sbuf->releasecb) + sbuf->releasecb(sbuf->value, sbuf->closure); +} + +static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) +{ + size_t i, j; + + (void)closure; /* unused */ + + if (!escape) + return fwrite(buffer, size, 1, file) != 1 ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; + + i = 0; + while (i < size) { + j = i; + while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&') + j++; + if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + if (j < size) { + switch(buffer[j++]) { + case '<': + if (fwrite("<", 4, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + break; + case '>': + if (fwrite(">", 4, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + break; + case '&': + if (fwrite("&", 5, 1, file) != 1) + return MUSTACH_ERROR_SYSTEM; + break; + default: break; + } + } + i = j; + } + return MUSTACH_OK; +} + +static int iwrap_put(void *closure, const char *name, int escape, FILE *file) +{ + struct iwrap *iwrap = closure; + int rc; + struct mustach_sbuf sbuf; + size_t length; + + sbuf_reset(&sbuf); + rc = iwrap->get(iwrap->closure, name, &sbuf); + if (rc >= 0) { + length = strlen(sbuf.value); + if (length) + rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file); + sbuf_release(&sbuf); + } + return rc; +} + +static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ + struct iwrap *iwrap = closure; + int rc; + FILE *file; + size_t size; + char *result; + + result = NULL; + file = memfile_open(&result, &size); + if (file == NULL) + rc = MUSTACH_ERROR_SYSTEM; + else { + rc = iwrap->put(iwrap->closure_put, name, 0, file); + if (rc < 0) + memfile_abort(file, &result, &size); + else { + rc = memfile_close(file, &result, &size); + if (rc == 0) { + sbuf->value = result; + sbuf->freecb = free; + } + } + } + return rc; +} + +static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr) +{ + struct mustach_sbuf sbuf; + char name[MUSTACH_MAX_LENGTH + 1], c, *tmp; + const char *beg, *term; + struct { const char *name, *again; size_t length; int enabled, entered; } stack[MUSTACH_MAX_DEPTH]; + size_t oplen, cllen, len, l; + int depth, rc, enabled; + + enabled = 1; + oplen = strlen(opstr); + cllen = strlen(clstr); + depth = 0; + for(;;) { + beg = strstr(template, opstr); + if (beg == NULL) { + /* no more mustach */ + if (enabled && template[0]) { + rc = iwrap->emit(iwrap->closure, template, strlen(template), 0, file); + if (rc < 0) + return rc; + } + return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK; + } + if (enabled && beg != template) { + rc = iwrap->emit(iwrap->closure, template, (size_t)(beg - template), 0, file); + if (rc < 0) + return rc; + } + beg += oplen; + term = strstr(beg, clstr); + if (term == NULL) + return MUSTACH_ERROR_UNEXPECTED_END; + template = term + cllen; + len = (size_t)(term - beg); + c = *beg; + switch(c) { + case '!': + case '=': + break; + case '{': + for (l = 0 ; clstr[l] == '}' ; l++); + if (clstr[l]) { + if (!len || beg[len-1] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + len--; + } else { + if (term[l] != '}') + return MUSTACH_ERROR_BAD_UNESCAPE_TAG; + template++; + } + c = '&'; + /*@fallthrough@*/ + case '^': + case '#': + case '/': + case '&': + case '>': +#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH) + case ':': +#endif + beg++; len--; + default: + while (len && isspace(beg[0])) { beg++; len--; } + while (len && isspace(beg[len-1])) len--; +#if !defined(NO_ALLOW_EMPTY_TAG) + if (len == 0) + return MUSTACH_ERROR_EMPTY_TAG; +#endif + if (len > MUSTACH_MAX_LENGTH) + return MUSTACH_ERROR_TAG_TOO_LONG; + memcpy(name, beg, len); + name[len] = 0; + break; + } + switch(c) { + case '!': + /* comment */ + /* nothing to do */ + break; + case '=': + /* defines separators */ + if (len < 5 || beg[len - 1] != '=') + return MUSTACH_ERROR_BAD_SEPARATORS; + beg++; + len -= 2; + for (l = 0; l < len && !isspace(beg[l]) ; l++); + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + oplen = l; + tmp = alloca(oplen + 1); + memcpy(tmp, beg, oplen); + tmp[oplen] = 0; + opstr = tmp; + while (l < len && isspace(beg[l])) l++; + if (l == len) + return MUSTACH_ERROR_BAD_SEPARATORS; + cllen = len - l; + tmp = alloca(cllen + 1); + memcpy(tmp, beg + l, cllen); + tmp[cllen] = 0; + clstr = tmp; + break; + case '^': + case '#': + /* begin section */ + if (depth == MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + rc = enabled; + if (rc) { + rc = iwrap->enter(iwrap->closure, name); + if (rc < 0) + return rc; + } + stack[depth].name = beg; + stack[depth].again = template; + stack[depth].length = len; + stack[depth].enabled = enabled; + stack[depth].entered = rc; + if ((c == '#') == (rc == 0)) + enabled = 0; + depth++; + break; + case '/': + /* end section */ + if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len)) + return MUSTACH_ERROR_CLOSING; + rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0; + if (rc < 0) + return rc; + if (rc) { + template = stack[depth++].again; + } else { + enabled = stack[depth].enabled; + if (enabled && stack[depth].entered) + iwrap->leave(iwrap->closure); + } + break; + case '>': + /* partials */ + if (enabled) { + sbuf_reset(&sbuf); + rc = iwrap->partial(iwrap->closure_partial, name, &sbuf); + if (rc >= 0) { + rc = process(sbuf.value, iwrap, file, opstr, clstr); + sbuf_release(&sbuf); + } + if (rc < 0) + return rc; + } + break; + default: + /* replacement */ + if (enabled) { + rc = iwrap->put(iwrap->closure_put, name, c != '&', file); + if (rc < 0) + return rc; + } + break; + } + } +} + +int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file) +{ + int rc; + struct iwrap iwrap; + + /* check validity */ + if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get)) + return MUSTACH_ERROR_INVALID_ITF; + + /* init wrap structure */ + iwrap.closure = closure; + if (itf->put) { + iwrap.put = itf->put; + iwrap.closure_put = closure; + } else { + iwrap.put = iwrap_put; + iwrap.closure_put = &iwrap; + } + if (itf->partial) { + iwrap.partial = itf->partial; + iwrap.closure_partial = closure; + } else if (itf->get) { + iwrap.partial = itf->get; + iwrap.closure_partial = closure; + } else { + iwrap.partial = iwrap_partial; + iwrap.closure_partial = &iwrap; + } + iwrap.emit = itf->emit ? itf->emit : iwrap_emit; + iwrap.enter = itf->enter; + iwrap.next = itf->next; + iwrap.leave = itf->leave; + iwrap.get = itf->get; + + /* process */ + rc = itf->start ? itf->start(closure) : 0; + if (rc == 0) + rc = process(template, &iwrap, file, "{{", "}}"); + if (itf->stop) + itf->stop(closure, rc); + return rc; +} + +int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd) +{ + int rc; + FILE *file; + + file = fdopen(fd, "w"); + if (file == NULL) { + rc = MUSTACH_ERROR_SYSTEM; + errno = ENOMEM; + } else { + rc = fmustach(template, itf, closure, file); + fclose(file); + } + return rc; +} + +int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size) +{ + int rc; + FILE *file; + size_t s; + + *result = NULL; + if (size == NULL) + size = &s; + file = memfile_open(result, size); + if (file == NULL) + rc = MUSTACH_ERROR_SYSTEM; + else { + rc = fmustach(template, itf, closure, file); + if (rc < 0) + memfile_abort(file, result, size); + else + rc = memfile_close(file, result, size); + } + return rc; +} + diff --git a/src/mustach/mustach.h b/src/mustach/mustach.h new file mode 100644 index 00000000..b2e5f8f2 --- /dev/null +++ b/src/mustach/mustach.h @@ -0,0 +1,241 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _mustach_h_included_ +#define _mustach_h_included_ + +struct mustach_sbuf; /* see below */ + +/** + * Current version of mustach and its derivates + */ +#define MUSTACH_VERSION 99 +#define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100) +#define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100) + +/** + * Maximum nested imbrications supported + */ +#define MUSTACH_MAX_DEPTH 256 + +/** + * Maximum length of tags in mustaches {{...}} + */ +#define MUSTACH_MAX_LENGTH 1024 + +/** + * mustach_itf - interface for callbacks + * + * All of this function should return a negative value to stop + * the mustache processing. The returned negative value will be + * then returned to the caller of mustach as is. + * + * The functions enter and next should return 0 or 1. + * + * All other functions should normally return MUSTACH_OK (zero). + * If it returns a negative value, it means an error that stop + * the process and that is reported to the caller. + * + * @start: If defined (can be NULL), starts the mustach processing + * of the closure, called at the very beginning before any + * mustach processing occurs. + * + * @put: If defined (can be NULL), writes the value of 'name' + * to 'file' with 'escape' or not. + * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be + * the empty string. In that later case an implementation can + * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. + * If NULL and 'get' NULL the error MUSTACH_ERROR_INVALID_ITF + * is returned. + * + * @enter: Enters the section of 'name' if possible. + * Musts return 1 if entered or 0 if not entered. + * When 1 is returned, the function 'leave' will always be called. + * Conversely 'leave' is never called when enter returns 0 or + * a negative value. + * When 1 is returned, the function must activate the first + * item of the section. + * + * @next: Activates the next item of the section if it exists. + * Musts return 1 when the next item is activated. + * Musts return 0 when there is no item to activate. + * + * @leave: Leaves the last entered section + * + * @partial: If defined (can be NULL), returns in 'sbuf' the content of the + * partial of 'name'. @see mustach_sbuf + * If NULL but 'get' not NULL, 'get' is used instead of partial. + * If NULL and 'get' NULL and 'put' not NULL, 'put' is called with + * a true FILE. + * + * @emit: If defined (can be NULL), writes the 'buffer' of 'size' with 'escape'. + * If NULL the standard function 'fwrite' is used with a true FILE. + * If not NULL that function is called instead of 'fwrite' to output + * text. + * It implies that if you define either 'partial' or 'get' callback, + * the meaning of 'FILE *file' is abstract for mustach's process and + * then you can use 'FILE*file' pass any kind of pointer (including NULL) + * to the function 'fmustach'. An example of a such behaviour is given by + * the implementation of 'umustach_json_c'. + * + * @get: If defined (can be NULL), returns in 'sbuf' the value of 'name'. + * As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be + * the empty string. In that later case an implementation can + * return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. + * If NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF + * is returned. + * + * @stop: If defined (can be NULL), stops the mustach processing + * of the closure, called at the very end after all mustach + * processing occurerd. The status returned by the processing + * is passed to the stop. + * + * The array below summarize status of callbacks: + * + * FULLY OPTIONAL: start partial + * MANDATORY: enter next leave + * COMBINATORIAL: put emit get + * + * Not definig a MANDATORY callback returns error MUSTACH_ERROR_INVALID_ITF. + * + * For COMBINATORIAL callbacks the array below summarize possible combinations: + * + * combination : put : emit : get : abstract FILE + * -------------+---------+---------+---------+----------------------- + * HISTORIC : defined : NULL : NULL : NO: standard FILE + * MINIMAL : NULL : NULL : defined : NO: standard FILE + * CUSTOM : NULL : defined : defined : YES: abstract FILE + * DUCK : defined : NULL : defined : NO: standard FILE + * DANGEROUS : defined : defined : any : YES or NO, depends on 'partial' + * INVALID : NULL : any : NULL : - + * + * The DUCK case runs on one leg. 'get' is not used if 'partial' is defined + * but is used for 'partial' if 'partial' is NULL. Thus for clarity, do not use + * it that way but define 'partial' and let 'get' NULL. + * + * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined + * but forbids abstract FILE when 'partial' is NULL. + * + * The INVALID case returns error MUSTACH_ERROR_INVALID_ITF. + */ +struct mustach_itf { + int (*start)(void *closure); + int (*put)(void *closure, const char *name, int escape, FILE *file); + int (*enter)(void *closure, const char *name); + int (*next)(void *closure); + int (*leave)(void *closure); + int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); + int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); + int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); + void (*stop)(void *closure, int status); +}; + +/** + * mustach_sbuf - Interface for handling zero terminated strings + * + * That structure is used for returning zero terminated strings -in 'value'- + * to mustach. The callee can provide a function for releasing the returned + * 'value'. Three methods for releasing the string are possible. + * + * 1. no release: set either 'freecb' or 'releasecb' with NULL (done by default) + * 2. release without closure: set 'freecb' to its expected value + * 3. release with closure: set 'releasecb' and 'closure' to their expected values + * + * @value: The value of the string. That value is not changed by mustach -const-. + * + * @freecb: The function to call for freeing the value without closure. + * For convenience, signature of that callback is compatible with 'free'. + * Can be NULL. + * + * @releasecb: The function to release with closure. + * Can be NULL. + * + * @closure: The closure to use for 'releasecb'. + */ +struct mustach_sbuf { + const char *value; + union { + void (*freecb)(void*); + void (*releasecb)(const char *value, void *closure); + }; + void *closure; +}; + +/* + * Definition of error codes returned by mustach + */ +#define MUSTACH_OK 0 +#define MUSTACH_ERROR_SYSTEM -1 +#define MUSTACH_ERROR_UNEXPECTED_END -2 +#define MUSTACH_ERROR_EMPTY_TAG -3 +#define MUSTACH_ERROR_TAG_TOO_LONG -4 +#define MUSTACH_ERROR_BAD_SEPARATORS -5 +#define MUSTACH_ERROR_TOO_DEEP -6 +#define MUSTACH_ERROR_CLOSING -7 +#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8 +#define MUSTACH_ERROR_INVALID_ITF -9 +#define MUSTACH_ERROR_ITEM_NOT_FOUND -10 +#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11 + +/* You can use definition below for user specific error */ +#define MUSTACH_ERROR_USER_BASE -100 +#define MUSTACH_ERROR_USER(x) (MUSTACH_ERROR_USER_BASE-(x)) + +/** + * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @file: the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file); + +/** + * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @fd: the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd); + +/** + * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * + * @template: the template string to instanciate + * @itf: the interface to the functions that mustach calls + * @closure: the closure to pass to functions called + * @result: the pointer receiving the result when 0 is returned + * @size: the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size); + +#endif + diff --git a/src/mustach/run-original-tests.sh b/src/mustach/run-original-tests.sh new file mode 100755 index 00000000..9c7d34cd --- /dev/null +++ b/src/mustach/run-original-tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -eu +# The build fails if libjson-c-dev is not installed. +# That's OK, we don't otherwise need it and don't +# even bother testing for it in configure.ac. +# However, in that case, skip the test suite. + +make -f Makefile.orig mustach || exit 77 +make -f Makefile.orig test +make -f Makefile.orig clean || true diff --git a/src/mustach/test1/.gitignore b/src/mustach/test1/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/mustach/test1/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/mustach/test1/json b/src/mustach/test1/json new file mode 100644 index 00000000..bf8e37a1 --- /dev/null +++ b/src/mustach/test1/json @@ -0,0 +1,23 @@ +{ + "name": "Chris", + "value": 10000, + "taxed_value": 6000, + "in_ca": true, + "person": false, + "repo": [ + { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] }, + { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] }, + { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] } + ], + "person?": { "name": "Jon" }, + "special": "----{{extra}}----", + "extra": 3.14159, + "#sharp": "#", + "!bang": "!", + "/slash": "/", + "^circ": "^", + "=equal": "=", + ":colon": ":", + ">greater": ">", + "~tilde": "~" +} diff --git a/src/mustach/test1/must b/src/mustach/test1/must new file mode 100644 index 00000000..6df52366 --- /dev/null +++ b/src/mustach/test1/must @@ -0,0 +1,43 @@ +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +Shown. +{{#person}} + Never shown! +{{/person}} +{{^person}} + No person +{{/person}} + +{{#repo}} + <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}} +{{/repo}} + +{{#person?}} + Hi {{name}}! +{{/person?}} + +{{=%(% %)%=}} +===================================== +%(%! gros commentaire %)% +%(%#repo%)% + <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)% +%(%/repo%)% +===================================== +%(%={{ }}=%)% +ggggggggg +{{> special}} +jjjjjjjjj +end + +{{:#sharp}} +{{:!bang}} +{{:~tilde}} +{{:/~0tilde}} +{{:/~1slash}} see json pointers IETF RFC 6901 +{{:^circ}} +{{:\=equal}} +{{::colon}} +{{:>greater}} diff --git a/src/mustach/test1/resu.ref b/src/mustach/test1/resu.ref new file mode 100644 index 00000000..5d4ce862 --- /dev/null +++ b/src/mustach/test1/resu.ref @@ -0,0 +1,49 @@ +Hello Chris +You have just won 10000 dollars! + +Well, 6000 dollars, after taxes. + +Shown. + + + No person + + + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + + + + Hi Jon! + + + +===================================== + + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + +===================================== + +ggggggggg +----3.14159---- +jjjjjjjjj +end + +# +! +~ +~ +/ see json pointers IETF RFC 6901 +^ += +: +> diff --git a/src/mustach/test1/vg.ref b/src/mustach/test1/vg.ref new file mode 100644 index 00000000..92e39d4c --- /dev/null +++ b/src/mustach/test1/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 169 allocs, 169 frees, 24,725 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For counts of detected and suppressed errors, rerun with: -v +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/mustach/test2/.gitignore b/src/mustach/test2/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/mustach/test2/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/mustach/test2/json b/src/mustach/test2/json new file mode 100644 index 00000000..8c668b3b --- /dev/null +++ b/src/mustach/test2/json @@ -0,0 +1,9 @@ +{ + "header": "Colors", + "items": [ + {"name": "red", "first": true, "url": "#Red"}, + {"name": "green", "link": true, "url": "#Green"}, + {"name": "blue", "link": true, "url": "#Blue"} + ], + "empty": false +} diff --git a/src/mustach/test2/must b/src/mustach/test2/must new file mode 100644 index 00000000..aa6da707 --- /dev/null +++ b/src/mustach/test2/must @@ -0,0 +1,17 @@ +<h1>{{header}}</h1> +{{#bug}} +{{/bug}} + +{{#items}} + {{#first}} + <li><strong>{{name}}</strong></li> + {{/first}} + {{#link}} + <li><a href="{{url}}">{{name}}</a></li> + {{/link}} +{{/items}} + +{{#empty}} + <p>The list is empty.</p> +{{/empty}} + diff --git a/src/mustach/test2/resu.ref b/src/mustach/test2/resu.ref new file mode 100644 index 00000000..67d1f547 --- /dev/null +++ b/src/mustach/test2/resu.ref @@ -0,0 +1,22 @@ +<h1>Colors</h1> + + + + + <li><strong>red</strong></li> + + + + + + <li><a href="#Green">green</a></li> + + + + + <li><a href="#Blue">blue</a></li> + + + + + diff --git a/src/mustach/test2/vg.ref b/src/mustach/test2/vg.ref new file mode 100644 index 00000000..1218d583 --- /dev/null +++ b/src/mustach/test2/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 62 allocs, 62 frees, 11,187 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For counts of detected and suppressed errors, rerun with: -v +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/mustach/test3/.gitignore b/src/mustach/test3/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/mustach/test3/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/mustach/test3/json b/src/mustach/test3/json new file mode 100644 index 00000000..79278817 --- /dev/null +++ b/src/mustach/test3/json @@ -0,0 +1,7 @@ +{ + "name": "Chris", + "company": "<b>GitHub & Co</b>", + "names": ["Chris", "Kross"], + "skills": ["JavaScript", "PHP", "Java"], + "age": 18 +} diff --git a/src/mustach/test3/must b/src/mustach/test3/must new file mode 100644 index 00000000..5c490469 --- /dev/null +++ b/src/mustach/test3/must @@ -0,0 +1,15 @@ +* {{name}} +* {{age}} +* {{company}} +* {{&company}} +* {{{company}}} +{{=<% %>=}} +* <%company%> +* <%&company%> +* <%{company}%> + +<%={{ }}=%> +* <ul>{{#names}}<li>{{.}}</li>{{/names}}</ul> +* skills: <ul>{{#skills}}<li>{{.}}</li>{{/skills}}</ul> +{{#age}}* age: {{.}}{{/age}} + diff --git a/src/mustach/test3/resu.ref b/src/mustach/test3/resu.ref new file mode 100644 index 00000000..e89ce902 --- /dev/null +++ b/src/mustach/test3/resu.ref @@ -0,0 +1,15 @@ +* Chris +* 18 +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> + +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> + + +* <ul><li>Chris</li><li>Kross</li></ul> +* skills: <ul><li>JavaScript</li><li>PHP</li><li>Java</li></ul> +* age: 18 + diff --git a/src/mustach/test3/vg.ref b/src/mustach/test3/vg.ref new file mode 100644 index 00000000..4d0d4f7a --- /dev/null +++ b/src/mustach/test3/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 41 allocs, 41 frees, 8,834 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For counts of detected and suppressed errors, rerun with: -v +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/mustach/test4/.gitignore b/src/mustach/test4/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/mustach/test4/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/mustach/test4/json b/src/mustach/test4/json new file mode 100644 index 00000000..a1083607 --- /dev/null +++ b/src/mustach/test4/json @@ -0,0 +1,13 @@ +{ + "person": { "name": "Jon", "age": 25 }, + "person.name": "Fred", + "person.name=Fred": "The other Fred.", + "persons": [ + { "name": "Jon", "age": 25, "lang": "en" }, + { "name": "Henry", "age": 27, "lang": "en" }, + { "name": "Amed", "age": 24, "lang": "fr" } ], + "fellows": { + "Jon": { "age": 25, "lang": "en" }, + "Henry": { "age": 27, "lang": "en" }, + "Amed": { "age": 24, "lang": "fr" } } +} diff --git a/src/mustach/test4/must b/src/mustach/test4/must new file mode 100644 index 00000000..003b9366 --- /dev/null +++ b/src/mustach/test4/must @@ -0,0 +1,58 @@ +This are extensions!! + +{{person.name}} +{{person.age}} + +{{person\.name}} +{{person\.name\=Fred}} + +{{#person.name=Jon}} +Hello Jon +{{/person.name=Jon}} + +{{^person.name=Jon}} +No Jon? Hey Jon... +{{/person.name=Jon}} + +{{^person.name=Harry}} +No Harry? Hey Calahan... +{{/person.name=Harry}} + +{{#person\.name=Fred}} +Hello Fred +{{/person\.name=Fred}} + +{{^person\.name=Fred}} +No Fred? Hey Fred... +{{/person\.name=Fred}} + +{{#person\.name\=Fred=The other Fred.}} +Hello Fred#2 +{{/person\.name\=Fred=The other Fred.}} + +{{^person\.name\=Fred=The other Fred.}} +No Fred#2? Hey Fred#2... +{{/person\.name\=Fred=The other Fred.}} + +{{#persons}} +{{#lang=!fr}}Hello {{name}}, {{age}} years{{/lang=!fr}} +{{#lang=fr}}Salut {{name}}, {{age}} ans{{/lang=fr}} +{{/persons}} + +{{#persons}} +{{name}}: {{age=24}}/{{age}}/{{age=!27}} +{{/persons}} + +{{#fellows.*}} +{{*}}: {{age=24}}/{{age}}/{{age=!27}} +{{/fellows.*}} + +{{#*}} + (1) {{*}}: {{.}} + {{#*}} + (2) {{*}}: {{.}} + {{#*}} + (3) {{*}}: {{.}} + {{/*}} + {{/*}} +{{/*}} diff --git a/src/mustach/test4/resu.ref b/src/mustach/test4/resu.ref new file mode 100644 index 00000000..2d48918a --- /dev/null +++ b/src/mustach/test4/resu.ref @@ -0,0 +1,100 @@ +This are extensions!! + +Jon +25 + +Fred +The other Fred. + + +Hello Jon + + + + + +No Harry? Hey Calahan... + + + +Hello Fred + + + + + +Hello Fred#2 + + + + + +Hello Jon, 25 years + + +Hello Henry, 27 years + + + +Salut Amed, 24 ans + + + +Jon: /25/25 + +Henry: /27/ + +Amed: 24/24/24 + + + +Jon: /25/25 + +Henry: /27/ + +Amed: 24/24/24 + + + + (1) person: { "name": "Jon", "age": 25 } + + (2) name: Jon + + + (2) age: 25 + + + + (1) person.name: Fred + + + (1) person.name=Fred: The other Fred. + + + (1) persons: [ { "name": "Jon", "age": 25, "lang": "en" }, { "name": "Henry", "age": 27, "lang": "en" }, { "name": "Amed", "age": 24, "lang": "fr" } ] + + + (1) fellows: { "Jon": { "age": 25, "lang": "en" }, "Henry": { "age": 27, "lang": "en" }, "Amed": { "age": 24, "lang": "fr" } } + + (2) Jon: { "age": 25, "lang": "en" } + + (3) age: 25 + + (3) lang: en + + + (2) Henry: { "age": 27, "lang": "en" } + + (3) age: 27 + + (3) lang: en + + + (2) Amed: { "age": 24, "lang": "fr" } + + (3) age: 24 + + (3) lang: fr + + + diff --git a/src/mustach/test4/vg.ref b/src/mustach/test4/vg.ref new file mode 100644 index 00000000..0aa92dc7 --- /dev/null +++ b/src/mustach/test4/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 142 allocs, 142 frees, 18,276 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For counts of detected and suppressed errors, rerun with: -v +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/mustach/test5/.gitignore b/src/mustach/test5/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/mustach/test5/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/mustach/test5/json b/src/mustach/test5/json new file mode 100644 index 00000000..bf8e37a1 --- /dev/null +++ b/src/mustach/test5/json @@ -0,0 +1,23 @@ +{ + "name": "Chris", + "value": 10000, + "taxed_value": 6000, + "in_ca": true, + "person": false, + "repo": [ + { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] }, + { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] }, + { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] } + ], + "person?": { "name": "Jon" }, + "special": "----{{extra}}----", + "extra": 3.14159, + "#sharp": "#", + "!bang": "!", + "/slash": "/", + "^circ": "^", + "=equal": "=", + ":colon": ":", + ">greater": ">", + "~tilde": "~" +} diff --git a/src/mustach/test5/must b/src/mustach/test5/must new file mode 100644 index 00000000..44305df2 --- /dev/null +++ b/src/mustach/test5/must @@ -0,0 +1,23 @@ +===================================== +from json +{{> special}} +===================================== +not found +{{> notfound}} +===================================== +without extension first +{{> must2 }} +===================================== +last with extension +{{> must3 }} +===================================== +Ensure must3 didn't change specials + +{{#person?}} + Hi {{name}}! +{{/person?}} + +%(%#person?%)% + Hi %(%name%)%! +%(%/person?%)% + diff --git a/src/mustach/test5/must2 b/src/mustach/test5/must2 new file mode 100644 index 00000000..d4a1d378 --- /dev/null +++ b/src/mustach/test5/must2 @@ -0,0 +1,14 @@ +must2 == BEGIN +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +Shown. +{{#person}} + Never shown! +{{/person}} +{{^person}} + No person +{{/person}} +must2 == END diff --git a/src/mustach/test5/must2.mustache b/src/mustach/test5/must2.mustache new file mode 100644 index 00000000..33f1ead3 --- /dev/null +++ b/src/mustach/test5/must2.mustache @@ -0,0 +1 @@ +must2.mustache ==SHOULD NOT BE SEEN== diff --git a/src/mustach/test5/must3.mustache b/src/mustach/test5/must3.mustache new file mode 100644 index 00000000..67eddb1e --- /dev/null +++ b/src/mustach/test5/must3.mustache @@ -0,0 +1,17 @@ +must3.mustache == BEGIN +{{#repo}} + <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}} +{{/repo}} + +{{#person?}} + Hi {{name}}! +{{/person?}} + +{{=%(% %)%=}} +===================================== +%(%! big comment %)% +%(%#repo%)% + <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)% +%(%/repo%)% +===================================== +must3.mustache == END diff --git a/src/mustach/test5/resu.ref b/src/mustach/test5/resu.ref new file mode 100644 index 00000000..117499fc --- /dev/null +++ b/src/mustach/test5/resu.ref @@ -0,0 +1,60 @@ +===================================== +from json +----3.14159---- +===================================== +not found + +===================================== +without extension first +must2 == BEGIN +Hello Chris +You have just won 10000 dollars! + +Well, 6000 dollars, after taxes. + +Shown. + + + No person + +must2 == END + +===================================== +last with extension +must3.mustache == BEGIN + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + + + + Hi Jon! + + + +===================================== + + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + +===================================== +must3.mustache == END + +===================================== +Ensure must3 didn't change specials + + + Hi Jon! + + +%(%#person?%)% + Hi %(%name%)%! +%(%/person?%)% + diff --git a/src/mustach/test5/special b/src/mustach/test5/special new file mode 100644 index 00000000..02d9975c --- /dev/null +++ b/src/mustach/test5/special @@ -0,0 +1 @@ +special ==SHOULD NOT BE SEEN== diff --git a/src/mustach/test5/special.mustache b/src/mustach/test5/special.mustache new file mode 100644 index 00000000..70a771fd --- /dev/null +++ b/src/mustach/test5/special.mustache @@ -0,0 +1 @@ +special.mustache ==SHOULD NOT BE SEEN== diff --git a/src/mustach/test5/vg.ref b/src/mustach/test5/vg.ref new file mode 100644 index 00000000..83f48b62 --- /dev/null +++ b/src/mustach/test5/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info +Command: ../mustach json must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 181 allocs, 181 frees, 36,033 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For counts of detected and suppressed errors, rerun with: -v +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) diff --git a/src/mustach/test6/.gitignore b/src/mustach/test6/.gitignore new file mode 100644 index 00000000..15e6dd5a --- /dev/null +++ b/src/mustach/test6/.gitignore @@ -0,0 +1,3 @@ +resu.last +vg.last +test-custom-write diff --git a/src/mustach/test6/json b/src/mustach/test6/json new file mode 100644 index 00000000..bf8e37a1 --- /dev/null +++ b/src/mustach/test6/json @@ -0,0 +1,23 @@ +{ + "name": "Chris", + "value": 10000, + "taxed_value": 6000, + "in_ca": true, + "person": false, + "repo": [ + { "name": "resque", "who": [ { "commiter": "joe" }, { "reviewer": "avrel" }, { "commiter": "william" } ] }, + { "name": "hub", "who": [ { "commiter": "jack" }, { "reviewer": "avrel" }, { "commiter": "greg" } ] }, + { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "commiter": "greg" } ] } + ], + "person?": { "name": "Jon" }, + "special": "----{{extra}}----", + "extra": 3.14159, + "#sharp": "#", + "!bang": "!", + "/slash": "/", + "^circ": "^", + "=equal": "=", + ":colon": ":", + ">greater": ">", + "~tilde": "~" +} diff --git a/src/mustach/test6/must b/src/mustach/test6/must new file mode 100644 index 00000000..6df52366 --- /dev/null +++ b/src/mustach/test6/must @@ -0,0 +1,43 @@ +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +Shown. +{{#person}} + Never shown! +{{/person}} +{{^person}} + No person +{{/person}} + +{{#repo}} + <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} commiters:{{#who}} {{commiter}}{{/who}} +{{/repo}} + +{{#person?}} + Hi {{name}}! +{{/person?}} + +{{=%(% %)%=}} +===================================== +%(%! gros commentaire %)% +%(%#repo%)% + <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% commiters:%(%#who%)% %(%commiter%)%%(%/who%)% +%(%/repo%)% +===================================== +%(%={{ }}=%)% +ggggggggg +{{> special}} +jjjjjjjjj +end + +{{:#sharp}} +{{:!bang}} +{{:~tilde}} +{{:/~0tilde}} +{{:/~1slash}} see json pointers IETF RFC 6901 +{{:^circ}} +{{:\=equal}} +{{::colon}} +{{:>greater}} diff --git a/src/mustach/test6/resu.ref b/src/mustach/test6/resu.ref new file mode 100644 index 00000000..e983fee6 --- /dev/null +++ b/src/mustach/test6/resu.ref @@ -0,0 +1,147 @@ +HELLO CHRIS +YOU HAVE JUST WON 10000 DOLLARS! + +WELL, 6000 DOLLARS, AFTER TAXES. + +SHOWN. + + + NO PERSON + + + + <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM + + <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG + + <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG + + + + HI JON! + + + +===================================== + + + <B>RESQUE</B> REVIEWERS: AVREL COMMITERS: JOE WILLIAM + + <B>HUB</B> REVIEWERS: AVREL COMMITERS: JACK GREG + + <B>RIP</B> REVIEWERS: JOE JACK COMMITERS: GREG + +===================================== + +GGGGGGGGG +----3.14159---- +JJJJJJJJJ +END + +# +! +~ +~ +/ SEE JSON POINTERS IETF RFC 6901 +^ += +: +> +hello chris +you have just won 10000 dollars! + +well, 6000 dollars, after taxes. + +shown. + + + no person + + + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + + + + hi jon! + + + +===================================== + + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + +===================================== + +ggggggggg +----3.14159---- +jjjjjjjjj +end + +# +! +~ +~ +/ see json pointers ietf rfc 6901 +^ += +: +> +Hello Chris +You have just won 10000 dollars! + +Well, 6000 dollars, after taxes. + +Shown. + + + No person + + + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + + + + Hi Jon! + + + +===================================== + + + <b>resque</b> reviewers: avrel commiters: joe william + + <b>hub</b> reviewers: avrel commiters: jack greg + + <b>rip</b> reviewers: joe jack commiters: greg + +===================================== + +ggggggggg +----3.14159---- +jjjjjjjjj +end + +# +! +~ +~ +/ see json pointers IETF RFC 6901 +^ += +: +> diff --git a/src/mustach/test6/vg.ref b/src/mustach/test6/vg.ref new file mode 100644 index 00000000..2ad202f8 --- /dev/null +++ b/src/mustach/test6/vg.ref @@ -0,0 +1,14 @@ +Memcheck, a memory error detector +Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. +Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info +Command: ./test-custom-write json -U must -l must -x must + + +HEAP SUMMARY: + in use at exit: 0 bytes in 0 blocks + total heap usage: 171 allocs, 171 frees, 26,351 bytes allocated + +All heap blocks were freed -- no leaks are possible + +For counts of detected and suppressed errors, rerun with: -v +ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) |