Mercurial > sci
comparison scid/page.c @ 26:7e10cace67a3
scid: add basic mustache support
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 02 Aug 2022 13:24:13 +0200 |
parents | 600204c31bf0 |
children |
comparison
equal
deleted
inserted
replaced
25:c40f98360ac9 | 26:7e10cace67a3 |
---|---|
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 */ | 17 */ |
18 | 18 |
19 #include <sys/stat.h> | |
20 #include <assert.h> | |
21 #include <errno.h> | |
22 #include <fcntl.h> | |
23 #include <stdint.h> | |
24 #include <stdio.h> | |
25 #include <stdlib.h> | |
26 #include <string.h> | |
27 #include <unistd.h> | |
28 | |
29 #include <mustache.h> | |
30 | |
31 #include "log.h" | |
19 #include "page.h" | 32 #include "page.h" |
33 #include "scid.h" | |
20 #include "util.h" | 34 #include "util.h" |
35 | |
36 static void | |
37 parse_error(int code, const char *msg, unsigned int line, unsigned int col, void *data) | |
38 { | |
39 (void)code; | |
40 | |
41 const char *path = data; | |
42 | |
43 log_warn("page: %s:%u:%u:%s", path, line, col, msg); | |
44 } | |
45 | |
46 static int | |
47 out_verbatim(const char *output, size_t size, void *data) | |
48 { | |
49 FILE *fp = data; | |
50 | |
51 if (fwrite(output, 1, size, fp) != size) | |
52 return -1; | |
53 | |
54 return 0; | |
55 } | |
56 | |
57 static int | |
58 out_escaped(const char *output, size_t size, void *data) | |
59 { | |
60 FILE *fp = data; | |
61 | |
62 for (size_t i = 0; i < size; ++i) { | |
63 switch (output[i]) { | |
64 case '"': | |
65 if (out_verbatim(""", 6, fp) < 0) | |
66 return -1; | |
67 break; | |
68 case '&': | |
69 if (out_verbatim("&", 5, fp) < 0) | |
70 return -1; | |
71 break; | |
72 case '<': | |
73 if (out_verbatim("<", 4, fp) < 0) | |
74 return -1; | |
75 break; | |
76 case '>': | |
77 if (out_verbatim(">", 4, fp) < 0) | |
78 return -1; | |
79 break; | |
80 default: | |
81 if (out_verbatim(&output[i], 1, fp) <0) | |
82 return -1; | |
83 break; | |
84 } | |
85 } | |
86 | |
87 return 0; | |
88 } | |
89 | |
90 static inline int | |
91 out_int(FILE *fp, intmax_t val) | |
92 { | |
93 char buf[64] = {0}; | |
94 | |
95 snprintf(buf, sizeof (buf), "%jd", val); | |
96 | |
97 return out_verbatim(buf, strlen(buf), fp); | |
98 } | |
99 | |
100 static inline int | |
101 out_double(FILE *fp, double val) | |
102 { | |
103 char buf[64] = {0}; | |
104 | |
105 snprintf(buf, sizeof (buf), "%f", val); | |
106 | |
107 return out_verbatim(buf, strlen(buf), fp); | |
108 } | |
109 | |
110 static int | |
111 dump(void *node, int (*outputter)(const char *, size_t, void *), void *rdata, void *pdata) | |
112 { | |
113 (void)pdata; | |
114 | |
115 FILE *fp = rdata; | |
116 const json_t *value = node; | |
117 | |
118 switch (json_typeof(value)) { | |
119 case JSON_OBJECT: | |
120 case JSON_ARRAY: | |
121 /* This indicates a document construction error. */ | |
122 // TODO: change to error log. | |
123 abort(); | |
124 break; | |
125 case JSON_STRING: | |
126 return outputter(json_string_value(value), json_string_length(value), fp); | |
127 case JSON_INTEGER: | |
128 return out_int(fp, json_integer_value(value)); | |
129 case JSON_REAL: | |
130 return out_double(fp, json_real_value(value)); | |
131 case JSON_TRUE: | |
132 return out_verbatim("true", 4, fp); | |
133 case JSON_FALSE: | |
134 return out_verbatim("false", 5, fp); | |
135 default: | |
136 break; | |
137 } | |
138 | |
139 return 0; | |
140 } | |
141 | |
142 static void * | |
143 get_root(void *pdata) | |
144 { | |
145 return pdata; | |
146 } | |
147 | |
148 static void * | |
149 get_child_by_name(void *node, const char *name, size_t size, void *pdata) | |
150 { | |
151 (void)pdata; | |
152 | |
153 json_t *value = node; | |
154 | |
155 #if 1 | |
156 printf("-> Seeking name '%.*s'\n", (int)size, name); | |
157 printf("-> Type of node: %d (%p)\n", json_typeof(value), value); | |
158 #endif | |
159 | |
160 if (json_is_object(value)) | |
161 return json_object_getn(node, name, size); | |
162 | |
163 return NULL; | |
164 } | |
165 | |
166 static void * | |
167 get_child_by_index(void *node, unsigned int index, void *pdata) | |
168 { | |
169 (void)pdata; | |
170 | |
171 json_t *value = node; | |
172 | |
173 #if 1 | |
174 printf("-> Seeking index '%u'\n", index); | |
175 printf("-> Type of node: %d (%p)\n", json_typeof(value), value); | |
176 #endif | |
177 | |
178 if (json_is_array(value)) | |
179 return json_array_get(node, index); | |
180 | |
181 return NULL; | |
182 } | |
183 | |
184 static MUSTACHE_TEMPLATE * | |
185 get_partial(const char *name, size_t size, void *pdata) | |
186 { | |
187 (void)name; | |
188 (void)size; | |
189 (void)pdata; | |
190 | |
191 return NULL; | |
192 } | |
193 | |
194 static char * | |
195 readall(const char *file) | |
196 { | |
197 int fd; | |
198 char *ret = NULL; | |
199 struct stat st; | |
200 | |
201 if ((fd = open(file, O_RDONLY)) < 0) | |
202 return NULL; | |
203 if (fstat(fd, &st) < 0) | |
204 goto exit; | |
205 if (!(ret = calloc(1, st.st_size + 1))) | |
206 goto exit; | |
207 if (read(fd, ret, st.st_size) != st.st_size) { | |
208 free(ret); | |
209 ret = NULL; | |
210 } | |
211 | |
212 exit: | |
213 close(fd); | |
214 | |
215 return ret; | |
216 } | |
217 | |
218 static int | |
219 process(const char *path, const char *input, FILE *fp, json_t *doc) | |
220 { | |
221 MUSTACHE_PARSER parser = { | |
222 .parse_error = parse_error | |
223 }; | |
224 MUSTACHE_RENDERER rdr = { | |
225 .out_verbatim = out_verbatim, | |
226 .out_escaped = out_escaped | |
227 }; | |
228 MUSTACHE_DATAPROVIDER pv = { | |
229 .dump = dump, | |
230 .get_root = get_root, | |
231 .get_child_by_name = get_child_by_name, | |
232 .get_child_by_index = get_child_by_index, | |
233 .get_partial = get_partial | |
234 }; | |
235 MUSTACHE_TEMPLATE *tmpl; | |
236 int status; | |
237 | |
238 if (!(tmpl = mustache_compile(input, strlen(input), &parser, (void *)path, 0))) | |
239 return -1; | |
240 | |
241 status = mustache_process(tmpl, &rdr, fp, &pv, doc); | |
242 mustache_release(tmpl); | |
243 | |
244 return status ? -1 : 0; | |
245 } | |
246 | |
247 char * | |
248 templatize(const char *path, json_t *doc) | |
249 { | |
250 assert(path); | |
251 | |
252 char *in = NULL, *out = NULL; | |
253 size_t outsz = 0; | |
254 FILE *fp; | |
255 | |
256 if (!(fp = open_memstream(&out, &outsz))) | |
257 goto exit; | |
258 if (!(in = readall(path))) | |
259 goto exit; | |
260 | |
261 /* Process template but don't return a partially written file. */ | |
262 if (process(path, in, fp, doc) < 0) { | |
263 fclose(fp); | |
264 free(out); | |
265 fp = NULL; | |
266 out = NULL; | |
267 errno = EINVAL; | |
268 } | |
269 | |
270 exit: | |
271 if (!out) | |
272 log_warn("page: %s: %s", path, strerror(errno)); | |
273 if (fp) | |
274 fclose(fp); | |
275 | |
276 free(in); | |
277 json_decref(doc); | |
278 | |
279 return out; | |
280 } | |
281 | |
282 static void | |
283 render(struct kreq *req, const char *path, json_t *doc) | |
284 { | |
285 char *content; | |
286 | |
287 if (!(content = templatize(path, doc))) | |
288 log_warn("page: unable to templatize: %s", strerror(errno)); | |
289 if (doc) | |
290 json_decref(doc); | |
291 | |
292 khttp_printf(req, "%s", content); | |
293 | |
294 free(content); | |
295 } | |
21 | 296 |
22 void | 297 void |
23 page(struct kreq *req, | 298 page(struct kreq *req, |
24 const struct ktemplate *tmpl, | |
25 enum khttp status, | 299 enum khttp status, |
26 enum kmime mime, | 300 enum kmime mime, |
27 const char *file) | 301 const char *path, |
28 { | 302 json_t *doc) |
303 { | |
304 assert(req); | |
305 assert(!path || (path && doc)); | |
306 | |
29 khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]); | 307 khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]); |
30 khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]); | 308 khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[status]); |
31 khttp_body(req); | 309 khttp_body(req); |
32 | 310 |
33 if (file) { | 311 if (path) { |
34 khttp_template(req, NULL, util_path("fragments/header.html")); | 312 printf("document is: [%s]\n", json_dumps(doc, JSON_INDENT(2))); |
35 khttp_template(req, tmpl, util_path(file)); | 313 /* TODO: add title in the header. */ |
36 khttp_template(req, NULL, util_path("fragments/footer.html")); | 314 render(req, scid_theme_path("fragments/header.html"), NULL); |
315 render(req, scid_theme_path(path), doc); | |
316 render(req, scid_theme_path("fragments/footer.html"), NULL); | |
37 } | 317 } |
38 | 318 |
39 khttp_free(req); | 319 khttp_free(req); |
40 } | 320 } |