Commit a2bf5d4a authored by josé bollo's avatar josé bollo

Initial commit

Change-Id: I1aa8ac3e79e48eb7de116ef7b4f9e682da273bd8 Signed-off-by: josé bollo's avatarJosé Bollo <jose.bollo@iot.bzh>
parents
José Bollo <jobol@nonadev.net>
This diff is collapsed.
mustach: mustach-tool.c mustach.c mustach.h mustach-json-c.c mustach-json-c.h
$(CC) -o mustach mustach-tool.c mustach.c mustach-json-c.c -ljson-c
.PHONY: test clean
test: mustach
@make -C test1 test
clean:
rm -f mustach
@make -C test1 clean
Introduction to mustach
=======================
mustach is a C implementation of [mustache](http://mustache.github.io "main site for mustache").
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**
in your project and use 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.
The tool **mustach** is build using Makefile. Its usage is:
mustach json template [template]...
and it prints the result of applying the templates files to the json file.
/*
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>
#include <json-c/json.h>
#include "mustach.h"
#define MAX_DEPTH 256
struct expl {
struct json_object *root;
int depth;
struct {
struct json_object *cont;
struct json_object *obj;
int index, count;
} stack[MAX_DEPTH];
};
static struct json_object *find(struct expl *e, const char *name)
{
int i;
struct json_object *o;
char *n, *c;
n = strdupa(name);
c = strtok(n, ".");
o = NULL;
i = e->depth;
while (i >= 0 && !json_object_object_get_ex(e->stack[i].obj, c, &o))
i--;
if (i < 0)
return NULL;
c = strtok(NULL, ".");
while(c) {
if (!json_object_object_get_ex(o, c, &o))
return NULL;
c = strtok(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 0;
}
static int put(void *closure, const char *name, int escape, FILE *file)
{
struct expl *e = closure;
struct json_object *o = find(e, name);
if (o)
fprintf(file, "%s", json_object_get_string(o));
return 0;
}
static int enter(void *closure, const char *name)
{
struct expl *e = closure;
struct json_object *o = find(e, name);
if (++e->depth >= MAX_DEPTH)
return MUSTACH_ERROR_TOO_DEPTH;
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;
} 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 {
e->depth--;
return 0;
}
return 1;
}
static int next(void *closure)
{
struct expl *e = closure;
if (e->depth <= 0)
return MUSTACH_ERROR_CLOSING;
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 partial(void *closure, const char *name, char **result)
{
struct expl *e = closure;
struct json_object *o = find(e, name);
*result = strdup(json_object_get_string(o));
return 0;
}
static struct mustach_itf itf = {
.start = start,
.put = put,
.enter = enter,
.next = next,
.leave = leave,
.partial = partial
};
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;
return mustach(template, &itf, &e, result, size);
}
/*
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_
struct json_object;
/**
* 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);
#endif
/*
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 <json-c/json.h>
#include "mustach-json-c.h"
static char *readfile(const char *filename)
{
struct stat s;
int f;
char *result;
f = open(filename, O_RDONLY);
fstat(f, &s);
result = malloc(s.st_size + 1);
read(f, result, s.st_size);
close(f);
result[s.st_size] = 0;
return result;
}
int main(int ac, char **av)
{
struct json_object *o;
char *t;
if (*++av) {
o = json_object_from_file(*av++);
while(o && *av) {
t = readfile(*av++);
fmustach_json_c(t, o, stdout);
free(t);
}
json_object_put(o);
}
return 0;
}
/*
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>
#include "mustach.h"
#define NAME_LENGTH_MAX 1024
#define DEPTH_MAX 256
static int process(const char *template, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr)
{
char name[NAME_LENGTH_MAX + 1], *partial, c;
const char *beg, *term;
struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX];
size_t oplen, cllen, len, l;
int depth, rc, emit;
emit = 1;
oplen = strlen(opstr);
cllen = strlen(clstr);
depth = 0;
for(;;) {
beg = strstr(template, opstr);
if (beg == NULL) {
/* no more mustach */
if (emit)
fwrite(template, strlen(template), 1, file);
return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0;
}
if (emit)
fwrite(template, beg - template, 1, file);
term = strstr(template, clstr);
if (term == NULL)
return MUSTACH_ERROR_UNEXPECTED_END;
template = term + cllen;
beg += oplen;
len = term - beg;
c = *beg;
switch(c) {
case '!':
case '=':
break;
case '^':
case '#':
case '/':
case '&':
case '>':
beg++; len--;
default:
while (len && isspace(beg[0])) { beg++; len--; }
while (len && isspace(beg[len-1])) len--;
if (len == 0)
return MUSTACH_ERROR_EMPTY_TAG;
if (len > NAME_LENGTH_MAX)
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;
opstr = strndupa(beg, l);
while (l < len && isspace(beg[l])) l++;
if (l == len)
return MUSTACH_ERROR_BAD_SEPARATORS;
clstr = strndupa(beg + l, len - l);
oplen = strlen(opstr);
cllen = strlen(clstr);
break;
case '^':
case '#':
/* begin section */
if (rc == DEPTH_MAX)
return MUSTACH_ERROR_TOO_DEPTH;
if (emit) {
rc = itf->enter(closure, name);
if (rc < 0)
return rc;
}
stack[depth].name = beg;
stack[depth].again = template;
stack[depth].length = len;
stack[depth].emit = emit;
stack[depth].entered = rc;
if ((c == '#') == (rc == 0))
emit = 0;
depth++;
break;
case '/':
/* end section */
if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
return MUSTACH_ERROR_CLOSING;
rc = emit && stack[depth].entered ? itf->next(closure) : 0;
if (rc < 0)
return rc;
if (rc) {
template = stack[depth++].again;
} else {
emit = stack[depth].emit;
if (emit && stack[depth].entered)
itf->leave(closure);
}
break;
case '>':
/* partials */
if (emit && itf->partial) {
rc = itf->partial(closure, name, &partial);
if (rc == 0 && partial) {
rc = process(partial, itf, closure, file, opstr, clstr);
free(partial);
}
if (rc < 0)
return rc;
}
break;
default:
/* replacement */
if (emit) {
rc = itf->put(closure, name, c != '&', file);
if (rc < 0)
return rc;
}
break;
}
}
}
int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file)
{
int rc = itf->start(closure);
if (rc == 0)
rc = process(template, itf, closure, file, "{{", "}}");
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;
file = open_memstream(result, size);
if (file == NULL) {
rc = MUSTACH_ERROR_SYSTEM;
errno = ENOMEM;
} else {
*result = NULL;
rc = fmustach(template, itf, closure, file);
if (rc == 0)
fwrite(&rc, 1, 1, file); /* adds terminating null */
fclose(file);
if (rc < 0)
free(*result);
else
(*size)--; /* removes terminating null of the length */
}
return rc;
}
/*
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_
/**
* 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 0.
*
* @start: Starts the mustach processing of the closure
*
* @put: Writes the value of 'name' to 'file' with 'escape' or not
*
* @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: Returns an allocated string for the partial content for 'name'.
* The returned string (in 'result') will be freed by mustach
* 'partial' is optional, it cans be NULL.
*/
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, char **result);
};
#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_DEPTH -6
#define MUSTACH_ERROR_CLOSING -7
/**
* 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
resu.last
vg.last
.PHONY: test clean
test: ../mustach
@echo starting test
@valgrind ../mustach json must > resu.last 2> vg.last
@sed -i 's:^==[0-9]*== ::' vg.last
@diff resu.ref resu.last && echo "result ok" || echo "ERROR! Result differs"
@diff vg.ref vg.last && echo "memory ok" || echo "ERROR! Memory differs"
clean:
rm -f resu.last vg.last
{
"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
}
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?}}
{{=%(% %)%=}}
zzzzzzz
%(%! gros commentaire %)%
aaaaaaaa
%(%={{ }}=%)%
ggggggggg
{{> special}}
jjjjjjjjj
end
Hello Chris
You have just won 10000 dollars!
Well, 6000 dollars, after taxes.
Shown.