cc795057925f95de01a55eb162db5030640d8ac9
[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 struct expl {
33         struct json_object *root;
34         int depth;
35         struct {
36                 struct json_object *cont;
37                 struct json_object *obj;
38                 int index, count;
39         } stack[MAX_DEPTH];
40 };
41
42 /*
43  * Scan a key=val text.
44  * If the sign = is found, drop it and returns a pointer to the value.
45  * If the sign = is not here, returns NULL.
46  * Replace any \= of the key by its unescaped version =.
47  */
48 static char *keyval(char *read, int isptr)
49 {
50         char *write, c;
51
52         c = *(write = read);
53         while (c && c != '=') {
54                 if (isptr) {
55                         if (c == '~' && read[1] == '=') {
56                                 c = *++read;
57                         }
58                 } else {
59                         if (c == '\\') {
60                                 switch (read[1]) {
61                                 case '\\': *write++ = c;
62                                 case '=': c = *++read;
63                                 default: break;
64                                 }
65                         }
66                 }
67                 *write++ = c;
68                 c = *++read;
69         }
70         *write = 0;
71         return c == '=' ? ++read : NULL;
72 }
73
74 /*
75  * Returns the unescaped version of the first component
76  * and update 'name' to point the next components if any.
77  */
78 static char *first(char **name, int isptr)
79 {
80         char *r, *read, *write, c;
81
82         c = *(read = *name);
83         if (!c)
84                 r = NULL;
85         else {
86                 r = write = read;
87                 if (isptr) {
88                         while (c && c != '/') {
89                                 if (c == '~') {
90                                         switch(read[1]) {
91                                         case '1': c = '/';
92                                         case '0': read++;
93                                         default: break;
94                                         }
95                                 }
96                                 *write++ = c;
97                                 c = *++read;
98                         }
99                 } else {
100                         while (c && c != '.') {
101                                 if (c == '\\' && (read[1] == '.' || read[1] == '\\'))
102                                         c = *++read;
103                                 *write++ = c;
104                                 c = *++read;
105                         }
106                 }
107                 *write = 0;
108                 *name = read + !!c;
109         }
110         return r;
111 }
112
113 /*
114  * Replace the last occurence of ':' followed by
115  * any character not being '*' by ':*', the
116  * globalisation of the key.
117  * Returns NULL if no globalisation is done
118  * or else the key globalized.
119  */
120 static char *globalize(char *key)
121 {
122         char *f, *r;
123
124         f = NULL;
125         for (r = key; *r ; r++) {
126                 if (r[0] == ':' && r[1] && r[1] != '*')
127                         f = r;
128         }
129         if (f) {
130                 f[1] = '*';
131                 f[2] = 0;
132                 f = key;
133         }
134         return f;
135 }
136
137 /*
138  * find the object of 'name'
139  */
140 static struct json_object *find(struct expl *e, const char *name)
141 {
142         int i, isptr;
143         struct json_object *o, *r;
144         char *n, *c, *v;
145
146         /* get a local key */
147         n = strdupa(name);
148
149         /* is it a JSON pointer? */
150         isptr = n[0] == '/';
151         n += isptr;
152
153         /* extract its value */
154         v = keyval(n, isptr);
155
156         /* search the first component for each valid globalisation */
157         i = e->depth;
158         c = first(&n, isptr);
159         while (c) {
160                 if (i < 0) {
161                         /* next globalisation */
162                         i = e->depth;
163                         c = globalize(c);
164                 }
165                 else if (json_object_object_get_ex(e->stack[i].obj, c, &o)) {
166
167                         /* found the root, search the subcomponents */
168                         c = first(&n, isptr);
169                         while(c) {
170                                 while (!json_object_object_get_ex(o, c, &r)) {
171                                         c = globalize(c);
172                                         if (!c)
173                                                 return NULL;
174                                 }
175                                 o = r;
176                                 c = first(&n, isptr);
177                         }
178
179                         /* check the value if requested */
180                         if (v) {
181                                 i = v[0] == '!';
182                                 if (i == !strcmp(&v[i], json_object_get_string(o)))
183                                         o = NULL;
184                         }
185                         return o;
186                 }
187                 i--;
188         }
189         return NULL;
190 }
191
192 static int start(void *closure)
193 {
194         struct expl *e = closure;
195         e->depth = 0;
196         e->stack[0].cont = NULL;
197         e->stack[0].obj = e->root;
198         e->stack[0].index = 0;
199         e->stack[0].count = 1;
200         return 0;
201 }
202
203 static void print(FILE *file, const char *string, int escape)
204 {
205         if (!escape)
206                 fputs(string, file);
207         else if (*string)
208                 do {
209                         switch(*string) {
210                         case '%': fputs("%%", file); break;
211                         case '\n': fputs("\\n\\\n", file); break;
212                         default: putc(*string, file); break;
213                         }
214                 } while(*++string);
215 }
216
217 static int put(void *closure, const char *name, int escape, FILE *file)
218 {
219         struct expl *e = closure;
220         struct json_object *o = find(e, name);
221         if (o)
222                 print(file, json_object_get_string(o), escape);
223         return 0;
224 }
225
226 static int enter(void *closure, const char *name)
227 {
228         struct expl *e = closure;
229         struct json_object *o = find(e, name);
230         if (++e->depth >= MAX_DEPTH)
231                 return MUSTACH_ERROR_TOO_DEPTH;
232         if (json_object_is_type(o, json_type_array)) {
233                 e->stack[e->depth].count = json_object_array_length(o);
234                 if (e->stack[e->depth].count == 0) {
235                         e->depth--;
236                         return 0;
237                 }
238                 e->stack[e->depth].cont = o;
239                 e->stack[e->depth].obj = json_object_array_get_idx(o, 0);
240                 e->stack[e->depth].index = 0;
241         } else if (json_object_is_type(o, json_type_object) || json_object_get_boolean(o)) {
242                 e->stack[e->depth].count = 1;
243                 e->stack[e->depth].cont = NULL;
244                 e->stack[e->depth].obj = o;
245                 e->stack[e->depth].index = 0;
246         } else {
247                 e->depth--;
248                 return 0;
249         }
250         return 1;
251 }
252
253 static int next(void *closure)
254 {
255         struct expl *e = closure;
256         if (e->depth <= 0)
257                 return MUSTACH_ERROR_CLOSING;
258         e->stack[e->depth].index++;
259         if (e->stack[e->depth].index >= e->stack[e->depth].count)
260                 return 0;
261         e->stack[e->depth].obj = json_object_array_get_idx(e->stack[e->depth].cont, e->stack[e->depth].index);
262         return 1;
263 }
264
265 static int leave(void *closure)
266 {
267         struct expl *e = closure;
268         if (e->depth <= 0)
269                 return MUSTACH_ERROR_CLOSING;
270         e->depth--;
271         return 0;
272 }
273
274 static struct mustach_itf itf = {
275         .start = start,
276         .put = put,
277         .enter = enter,
278         .next = next,
279         .leave = leave
280 };
281
282 /*
283  * Apply the object 'root' to the mustache 'template'.
284  * In case of success, the function returns 0, the pointer
285  * 'result' receives the allocated instanciation and
286  * the pointer 'size' its size. Note that the real size
287  * is one byte more to effectively store the terminating
288  * null.
289  * In case of error, it returns a negative error code.
290  */
291 int apply_mustach(const char *template, struct json_object *root, char **result, size_t *size)
292 {
293         struct expl e;
294         e.root = root;
295         return mustach(template, &itf, &e, result, size);
296 }
297