Allows or in mustache instanciation
[src/app-framework-main.git] / src / wgtpkg-mustach.c
1 /*
2  Author: José Bollo <jobol@nonadev.net>
3  Author: José Bollo <jose.bollo@iot.bzh>
4
5  https://gitlab.com/jobol/mustach
6
7  Licensed under the Apache License, Version 2.0 (the "License");
8  you may not use this file except in compliance with the License.
9  You may obtain a copy of the License at
10
11      http://www.apache.org/licenses/LICENSE-2.0
12
13  Unless required by applicable law or agreed to in writing, software
14  distributed under the License is distributed on an "AS IS" BASIS,
15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  See the License for the specific language governing permissions and
17  limitations under the License.
18 */
19
20 #define _GNU_SOURCE
21
22 #include <stdio.h>
23 #include <string.h>
24
25 #include <json-c/json.h>
26
27 #include "mustach.h"
28
29 #define MAX_DEPTH 256
30
31 /*
32  * exploration state when instanciating mustache
33  */
34 struct expl {
35         struct json_object *root;
36         int depth;
37         struct {
38                 struct json_object *cont;
39                 struct json_object *obj;
40                 int index, count;
41         } stack[MAX_DEPTH];
42 };
43
44 /*
45  * Scan a key=val text.
46  * If the sign = is found, drop it and returns a pointer to the value.
47  * If the sign = is not here, returns NULL.
48  * Replace any \= of the key by its unescaped version =.
49  */
50 static char *keyval(char *read, int isptr)
51 {
52         char *write, c;
53
54         c = *(write = read);
55         while (c && c != '=') {
56                 if (isptr) {
57                         if (c == '~' && read[1] == '=') {
58                                 c = *++read;
59                         }
60                 } else {
61                         if (c == '\\') {
62                                 switch (read[1]) {
63                                 case '\\': *write++ = c;
64                                 case '=': c = *++read;
65                                 default: break;
66                                 }
67                         }
68                 }
69                 *write++ = c;
70                 c = *++read;
71         }
72         *write = 0;
73         return c == '=' ? ++read : NULL;
74 }
75
76 /*
77  * Returns the unescaped version of the first component
78  * and update 'name' to point the next components if any.
79  */
80 static char *first(char **name, int isptr)
81 {
82         char *r, *read, *write, c;
83
84         c = *(read = *name);
85         if (!c)
86                 r = NULL;
87         else {
88                 r = write = read;
89                 if (isptr) {
90                         while (c && c != '/') {
91                                 if (c == '~') {
92                                         switch(read[1]) {
93                                         case '1': c = '/';
94                                         case '0': read++;
95                                         default: break;
96                                         }
97                                 }
98                                 *write++ = c;
99                                 c = *++read;
100                         }
101                 } else {
102                         while (c && c != '.') {
103                                 if (c == '\\' && (read[1] == '.' || read[1] == '\\'))
104                                         c = *++read;
105                                 *write++ = c;
106                                 c = *++read;
107                         }
108                 }
109                 *write = 0;
110                 *name = read + !!c;
111         }
112         return r;
113 }
114
115 /*
116  * Returns the unescaped version of the first value
117  * and update 'val' to point the next value if any.
118  */
119 static char *value(char **val)
120 {
121         char *r, *read, *write, c;
122
123         c = *(read = *val);
124         if (!c)
125                 r = NULL;
126         else {
127                 r = write = read;
128                 while (c && c != '|') {
129                         if (c == '\\' && (read[1] == '|' || read[1] == '\\'))
130                                 c = *++read;
131                         *write++ = c;
132                         c = *++read;
133                 }
134                 *write = 0;
135                 *val = read + !!c;
136         }
137         return r;
138 }
139
140 /*
141  * Replace the last occurence of ':' followed by
142  * any character not being '*' by ':*', the
143  * globalisation of the key.
144  * Returns NULL if no globalisation is done
145  * or else the key globalized.
146  */
147 static char *globalize(char *key)
148 {
149         char *f, *r;
150
151         f = NULL;
152         for (r = key; *r ; r++) {
153                 if (r[0] == ':' && r[1] && r[1] != '*')
154                         f = r;
155         }
156         if (f) {
157                 f[1] = '*';
158                 f[2] = 0;
159                 f = key;
160         }
161         return f;
162 }
163
164 /*
165  * find the object of 'name'
166  */
167 static struct json_object *find(struct expl *e, const char *name)
168 {
169         int i, isptr;
170         struct json_object *o, *r;
171         char *n, *c, *v;
172
173         /* get a local key */
174         n = strdupa(name);
175
176         /* is it a JSON pointer? */
177         isptr = n[0] == '/';
178         n += isptr;
179
180         /* extract its value */
181         v = keyval(n, isptr);
182
183         /* search the first component for each valid globalisation */
184         i = e->depth;
185         c = first(&n, isptr);
186         while (c) {
187                 if (i < 0) {
188                         /* next globalisation */
189                         i = e->depth;
190                         c = globalize(c);
191                 }
192                 else if (json_object_object_get_ex(e->stack[i].obj, c, &o)) {
193
194                         /* found the root, search the subcomponents */
195                         c = first(&n, isptr);
196                         while(c) {
197                                 while (!json_object_object_get_ex(o, c, &r)) {
198                                         c = globalize(c);
199                                         if (!c)
200                                                 return NULL;
201                                 }
202                                 o = r;
203                                 c = first(&n, isptr);
204                         }
205
206                         /* check the value if requested */
207                         if (v) {
208                                 i = v[0] == '!';
209                                 v += i;
210                                 do {
211                                         c = value(&v);
212                                 } while (c && strcmp(c, json_object_get_string(o)));
213                                 if (i != !c)
214                                         o = NULL;
215                         }
216                         return o;
217                 }
218                 i--;
219         }
220         return NULL;
221 }
222
223 static int start(void *closure)
224 {
225         struct expl *e = closure;
226         e->depth = 0;
227         e->stack[0].cont = NULL;
228         e->stack[0].obj = e->root;
229         e->stack[0].index = 0;
230         e->stack[0].count = 1;
231         return 0;
232 }
233
234 static void print(FILE *file, const char *string, int escape)
235 {
236         if (!escape)
237                 fputs(string, file);
238         else if (*string)
239                 do {
240                         switch(*string) {
241                         case '%': fputs("%%", file); break;
242                         case '\n': fputs("\\n\\\n", file); break;
243                         default: putc(*string, file); break;
244                         }
245                 } while(*++string);
246 }
247
248 static int put(void *closure, const char *name, int escape, FILE *file)
249 {
250         struct expl *e = closure;
251         struct json_object *o = find(e, name);
252         if (o)
253                 print(file, json_object_get_string(o), escape);
254         return 0;
255 }
256
257 static int enter(void *closure, const char *name)
258 {
259         struct expl *e = closure;
260         struct json_object *o = find(e, name);
261         if (++e->depth >= MAX_DEPTH)
262                 return MUSTACH_ERROR_TOO_DEPTH;
263         if (json_object_is_type(o, json_type_array)) {
264                 e->stack[e->depth].count = json_object_array_length(o);
265                 if (e->stack[e->depth].count == 0) {
266                         e->depth--;
267                         return 0;
268                 }
269                 e->stack[e->depth].cont = o;
270                 e->stack[e->depth].obj = json_object_array_get_idx(o, 0);
271                 e->stack[e->depth].index = 0;
272         } else if (json_object_is_type(o, json_type_object) || json_object_get_boolean(o)) {
273                 e->stack[e->depth].count = 1;
274                 e->stack[e->depth].cont = NULL;
275                 e->stack[e->depth].obj = o;
276                 e->stack[e->depth].index = 0;
277         } else {
278                 e->depth--;
279                 return 0;
280         }
281         return 1;
282 }
283
284 static int next(void *closure)
285 {
286         struct expl *e = closure;
287         if (e->depth <= 0)
288                 return MUSTACH_ERROR_CLOSING;
289         e->stack[e->depth].index++;
290         if (e->stack[e->depth].index >= e->stack[e->depth].count)
291                 return 0;
292         e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index);
293         return 1;
294 }
295
296 static int leave(void *closure)
297 {
298         struct expl *e = closure;
299         if (e->depth <= 0)
300                 return MUSTACH_ERROR_CLOSING;
301         e->depth--;
302         return 0;
303 }
304
305 static struct mustach_itf itf = {
306         .start = start,
307         .put = put,
308         .enter = enter,
309         .next = next,
310         .leave = leave
311 };
312
313 /*
314  * Apply the object 'root' to the mustache 'template'.
315  * In case of success, the function returns 0, the pointer
316  * 'result' receives the allocated instanciation and
317  * the pointer 'size' its size. Note that the real size
318  * is one byte more to effectively store the terminating
319  * null.
320  * In case of error, it returns a negative error code.
321  */
322 int apply_mustach(const char *template, struct json_object *root, char **result, size_t *size)
323 {
324         struct expl e;
325         e.root = root;
326         return mustach(template, &itf, &e, result, size);
327 }
328