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("&quot;", 6, fp) < 0)
66 return -1;
67 break;
68 case '&':
69 if (out_verbatim("&amp;", 5, fp) < 0)
70 return -1;
71 break;
72 case '<':
73 if (out_verbatim("&lt;", 4, fp) < 0)
74 return -1;
75 break;
76 case '>':
77 if (out_verbatim("&gt;", 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 }