wgt-json: Add comments
[src/app-framework-main.git] / src / 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 <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <ctype.h>
27
28 #include "mustach.h"
29
30 #define NAME_LENGTH_MAX   1024
31 #define DEPTH_MAX         256
32
33 static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result)
34 {
35         int rc;
36         FILE *file;
37         size_t size;
38
39         *result = NULL;
40         file = open_memstream(result, &size);
41         if (file == NULL)
42                 rc = MUSTACH_ERROR_SYSTEM;
43         else {
44                 rc = itf->put(closure, name, 0, file);
45                 if (rc == 0)
46                         /* adds terminating null */
47                         rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
48                 fclose(file);
49                 if (rc < 0) {
50                         free(*result);
51                         *result = NULL;
52                 }
53         }
54         return rc;
55 }
56
57 static int process(const char *template, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr)
58 {
59         char name[NAME_LENGTH_MAX + 1], *partial, c;
60         const char *beg, *term;
61         struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX];
62         size_t oplen, cllen, len, l;
63         int depth, rc, emit;
64
65         emit = 1;
66         oplen = strlen(opstr);
67         cllen = strlen(clstr);
68         depth = 0;
69         for(;;) {
70                 beg = strstr(template, opstr);
71                 if (beg == NULL) {
72                         /* no more mustach */
73                         if (emit)
74                                 fwrite(template, strlen(template), 1, file);
75                         return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0;
76                 }
77                 if (emit)
78                         fwrite(template, (size_t)(beg - template), 1, file);
79                 beg += oplen;
80                 term = strstr(beg, clstr);
81                 if (term == NULL)
82                         return MUSTACH_ERROR_UNEXPECTED_END;
83                 template = term + cllen;
84                 len = (size_t)(term - beg);
85                 c = *beg;
86                 switch(c) {
87                 case '!':
88                 case '=':
89                         break;
90                 case '{':
91                         for (l = 0 ; clstr[l] == '}' ; l++);
92                         if (clstr[l]) {
93                                 if (!len || beg[len-1] != '}')
94                                         return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
95                                 len--;
96                         } else {
97                                 if (term[l] != '}')
98                                         return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
99                                 template++;
100                         }
101                         c = '&';
102                         /*@fallthrough@*/
103                 case '^':
104                 case '#':
105                 case '/':
106                 case '&':
107                 case '>':
108 #if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
109                 case ':':
110 #endif
111                         beg++; len--;
112                 default:
113                         while (len && isspace(beg[0])) { beg++; len--; }
114                         while (len && isspace(beg[len-1])) len--;
115                         if (len == 0)
116                                 return MUSTACH_ERROR_EMPTY_TAG;
117                         if (len > NAME_LENGTH_MAX)
118                                 return MUSTACH_ERROR_TAG_TOO_LONG;
119                         memcpy(name, beg, len);
120                         name[len] = 0;
121                         break;
122                 }
123                 switch(c) {
124                 case '!':
125                         /* comment */
126                         /* nothing to do */
127                         break;
128                 case '=':
129                         /* defines separators */
130                         if (len < 5 || beg[len - 1] != '=')
131                                 return MUSTACH_ERROR_BAD_SEPARATORS;
132                         beg++;
133                         len -= 2;
134                         for (l = 0; l < len && !isspace(beg[l]) ; l++);
135                         if (l == len)
136                                 return MUSTACH_ERROR_BAD_SEPARATORS;
137                         opstr = strndupa(beg, l);
138                         while (l < len && isspace(beg[l])) l++;
139                         if (l == len)
140                                 return MUSTACH_ERROR_BAD_SEPARATORS;
141                         clstr = strndupa(beg + l, len - l);
142                         oplen = strlen(opstr);
143                         cllen = strlen(clstr);
144                         break;
145                 case '^':
146                 case '#':
147                         /* begin section */
148                         if (depth == DEPTH_MAX)
149                                 return MUSTACH_ERROR_TOO_DEPTH;
150                         rc = emit;
151                         if (rc) {
152                                 rc = itf->enter(closure, name);
153                                 if (rc < 0)
154                                         return rc;
155                         }
156                         stack[depth].name = beg;
157                         stack[depth].again = template;
158                         stack[depth].length = len;
159                         stack[depth].emit = emit;
160                         stack[depth].entered = rc;
161                         if ((c == '#') == (rc == 0))
162                                 emit = 0;
163                         depth++;
164                         break;
165                 case '/':
166                         /* end section */
167                         if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
168                                 return MUSTACH_ERROR_CLOSING;
169                         rc = emit && stack[depth].entered ? itf->next(closure) : 0;
170                         if (rc < 0)
171                                 return rc;
172                         if (rc) {
173                                 template = stack[depth++].again;
174                         } else {
175                                 emit = stack[depth].emit;
176                                 if (emit && stack[depth].entered)
177                                         itf->leave(closure);
178                         }
179                         break;
180                 case '>':
181                         /* partials */
182                         if (emit) {
183                                 rc = getpartial(itf, closure, name, &partial);
184                                 if (rc == 0) {
185                                         rc = process(partial, itf, closure, file, opstr, clstr);
186                                         free(partial);
187                                 }
188                                 if (rc < 0)
189                                         return rc;
190                         }
191                         break;
192                 default:
193                         /* replacement */
194                         if (emit) {
195                                 rc = itf->put(closure, name, c != '&', file);
196                                 if (rc < 0)
197                                         return rc;
198                         }
199                         break;
200                 }
201         }
202 }
203
204 int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file)
205 {
206         int rc = itf->start ? itf->start(closure) : 0;
207         if (rc == 0)
208                 rc = process(template, itf, closure, file, "{{", "}}");
209         return rc;
210 }
211
212 int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd)
213 {
214         int rc;
215         FILE *file;
216
217         file = fdopen(fd, "w");
218         if (file == NULL) {
219                 rc = MUSTACH_ERROR_SYSTEM;
220                 errno = ENOMEM;
221         } else {
222                 rc = fmustach(template, itf, closure, file);
223                 fclose(file);
224         }
225         return rc;
226 }
227
228 int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size)
229 {
230         int rc;
231         FILE *file;
232         size_t s;
233
234         *result = NULL;
235         if (size == NULL)
236                 size = &s;
237         file = open_memstream(result, size);
238         if (file == NULL) {
239                 rc = MUSTACH_ERROR_SYSTEM;
240                 errno = ENOMEM;
241         } else {
242                 rc = fmustach(template, itf, closure, file);
243                 if (rc == 0)
244                         /* adds terminating null */
245                         rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
246                 fclose(file);
247                 if (rc >= 0)
248                         /* removes terminating null of the length */
249                         (*size)--;
250                 else {
251                         free(*result);
252                         *result = NULL;
253                         *size = 0;
254                 }
255         }
256         return rc;
257 }
258