Mercurial > sci
comparison scid/theme.c @ 29:695637f1d8a7
scid: first index page in javascript
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 04 Aug 2022 14:13:58 +0200 |
parents | 4c16bb25e4f1 |
children | 43333d18e4b8 |
comparison
equal
deleted
inserted
replaced
28:4c16bb25e4f1 | 29:695637f1d8a7 |
---|---|
2 #include <errno.h> | 2 #include <errno.h> |
3 #include <limits.h> | 3 #include <limits.h> |
4 #include <string.h> | 4 #include <string.h> |
5 | 5 |
6 #include <duktape.h> | 6 #include <duktape.h> |
7 #include <jansson.h> | |
8 #include <mustache.h> | |
7 | 9 |
8 #include "log.h" | 10 #include "log.h" |
11 #include "scid.h" | |
9 #include "theme.h" | 12 #include "theme.h" |
10 #include "util.h" | 13 #include "util.h" |
14 | |
15 #define SIGNATURE DUK_HIDDEN_SYMBOL("File") | |
11 | 16 |
12 struct theme { | 17 struct theme { |
13 char base[PATH_MAX]; | 18 char base[PATH_MAX]; |
14 duk_context *ctx; | 19 duk_context *ctx; |
15 }; | 20 }; |
16 | 21 |
22 /* {{{ mustache support */ | |
23 | |
24 static void | |
25 mch_parse_error(int code, const char *msg, unsigned int line, unsigned int col, void *data) | |
26 { | |
27 (void)code; | |
28 | |
29 const char *path = data; | |
30 | |
31 log_warn("theme: %s:%u:%u:%s", path, line, col, msg); | |
32 } | |
33 | |
34 static int | |
35 mch_out_verbatim(const char *output, size_t size, void *data) | |
36 { | |
37 FILE *fp = data; | |
38 | |
39 if (fwrite(output, 1, size, fp) != size) | |
40 return -1; | |
41 | |
42 return 0; | |
43 } | |
44 | |
45 static int | |
46 mch_out_escaped(const char *output, size_t size, void *data) | |
47 { | |
48 FILE *fp = data; | |
49 | |
50 for (size_t i = 0; i < size; ++i) { | |
51 switch (output[i]) { | |
52 case '"': | |
53 if (mch_out_verbatim(""", 6, fp) < 0) | |
54 return -1; | |
55 break; | |
56 case '&': | |
57 if (mch_out_verbatim("&", 5, fp) < 0) | |
58 return -1; | |
59 break; | |
60 case '<': | |
61 if (mch_out_verbatim("<", 4, fp) < 0) | |
62 return -1; | |
63 break; | |
64 case '>': | |
65 if (mch_out_verbatim(">", 4, fp) < 0) | |
66 return -1; | |
67 break; | |
68 default: | |
69 if (mch_out_verbatim(&output[i], 1, fp) <0) | |
70 return -1; | |
71 break; | |
72 } | |
73 } | |
74 | |
75 return 0; | |
76 } | |
77 | |
78 static inline int | |
79 mch_out_int(FILE *fp, intmax_t val) | |
80 { | |
81 char buf[64] = {0}; | |
82 | |
83 snprintf(buf, sizeof (buf), "%jd", val); | |
84 | |
85 return mch_out_verbatim(buf, strlen(buf), fp); | |
86 } | |
87 | |
88 static inline int | |
89 mch_out_double(FILE *fp, double val) | |
90 { | |
91 char buf[64] = {0}; | |
92 | |
93 snprintf(buf, sizeof (buf), "%f", val); | |
94 | |
95 return mch_out_verbatim(buf, strlen(buf), fp); | |
96 } | |
97 | |
98 static int | |
99 mch_dump(void *node, int (*outputter)(const char *, size_t, void *), void *rdata, void *pdata) | |
100 { | |
101 (void)pdata; | |
102 | |
103 FILE *fp = rdata; | |
104 const json_t *value = node; | |
105 | |
106 switch (json_typeof(value)) { | |
107 case JSON_OBJECT: | |
108 case JSON_ARRAY: | |
109 /* This indicates a document construction error. */ | |
110 // TODO: change to error log. | |
111 abort(); | |
112 break; | |
113 case JSON_STRING: | |
114 return outputter(json_string_value(value), json_string_length(value), fp); | |
115 case JSON_INTEGER: | |
116 return mch_out_int(fp, json_integer_value(value)); | |
117 case JSON_REAL: | |
118 return mch_out_double(fp, json_real_value(value)); | |
119 case JSON_TRUE: | |
120 return mch_out_verbatim("true", 4, fp); | |
121 case JSON_FALSE: | |
122 return mch_out_verbatim("false", 5, fp); | |
123 default: | |
124 break; | |
125 } | |
126 | |
127 return 0; | |
128 } | |
129 | |
130 static void * | |
131 mch_get_root(void *pdata) | |
132 { | |
133 return pdata; | |
134 } | |
135 | |
136 static void * | |
137 mch_get_child_by_name(void *node, const char *name, size_t size, void *pdata) | |
138 { | |
139 (void)pdata; | |
140 | |
141 json_t *value = node; | |
142 | |
143 #if 0 | |
144 printf("-> Seeking name '%.*s'\n", (int)size, name); | |
145 printf("-> Type of node: %d (%p)\n", json_typeof(value), value); | |
146 #endif | |
147 | |
148 if (json_is_object(value)) | |
149 return json_object_getn(node, name, size); | |
150 | |
151 return NULL; | |
152 } | |
153 | |
154 static void * | |
155 mch_get_child_by_index(void *node, unsigned int index, void *pdata) | |
156 { | |
157 (void)pdata; | |
158 | |
159 json_t *value = node; | |
160 | |
161 #if 0 | |
162 printf("-> Seeking index '%u'\n", index); | |
163 printf("-> Type of node: %d (%p)\n", json_typeof(value), value); | |
164 #endif | |
165 | |
166 if (json_is_array(value)) | |
167 return json_array_get(node, index); | |
168 | |
169 return NULL; | |
170 } | |
171 | |
172 static MUSTACHE_TEMPLATE * | |
173 mch_get_partial(const char *name, size_t size, void *pdata) | |
174 { | |
175 (void)name; | |
176 (void)size; | |
177 (void)pdata; | |
178 | |
179 return NULL; | |
180 } | |
181 | |
182 static int | |
183 mch_process(const char *path, const char *input, FILE *fp, json_t *doc) | |
184 { | |
185 MUSTACHE_PARSER parser = { | |
186 .parse_error = mch_parse_error | |
187 }; | |
188 MUSTACHE_RENDERER rdr = { | |
189 .out_verbatim = mch_out_verbatim, | |
190 .out_escaped = mch_out_escaped | |
191 }; | |
192 MUSTACHE_DATAPROVIDER pv = { | |
193 .dump = mch_dump, | |
194 .get_root = mch_get_root, | |
195 .get_child_by_name = mch_get_child_by_name, | |
196 .get_child_by_index = mch_get_child_by_index, | |
197 .get_partial = mch_get_partial | |
198 }; | |
199 MUSTACHE_TEMPLATE *tmpl; | |
200 int status; | |
201 | |
202 if (!(tmpl = mustache_compile(input, strlen(input), &parser, (void *)path, 0))) | |
203 return -1; | |
204 | |
205 status = mustache_process(tmpl, &rdr, fp, &pv, doc); | |
206 mustache_release(tmpl); | |
207 | |
208 return status ? -1 : 0; | |
209 } | |
210 | |
211 void | |
212 mch_templatize(FILE *fp, const char *path, json_t *doc) | |
213 { | |
214 assert(path); | |
215 | |
216 char *in = NULL; | |
217 | |
218 if ((in = util_read(path))) { | |
219 /* Process template but don't return a partially written file. */ | |
220 if (mch_process(path, in, fp, doc) < 0) | |
221 log_warn("theme: mustache error %s", path); | |
222 | |
223 free(in); | |
224 } else | |
225 log_warn("theme: %s: %s", path, strerror(errno)); | |
226 } | |
227 | |
228 /* }}} */ | |
229 | |
230 /* {{{ Scid module */ | |
231 | |
232 static duk_ret_t | |
233 Scid_render(duk_context *ctx) | |
234 { | |
235 FILE *fp = duk_require_pointer(ctx, 0); | |
236 const char *path = duk_require_string(ctx, 1); | |
237 const char *json = duk_json_encode(ctx, 2); | |
238 json_t *doc = NULL; | |
239 json_error_t err; | |
240 | |
241 if (json && !(doc = json_loads(json, 0, &err))) | |
242 return duk_error(ctx, DUK_ERR_ERROR, "%d:%d:%s", err.line, err.column, err.text); | |
243 | |
244 mch_templatize(fp, theme_path(scid.theme, path), doc); | |
245 | |
246 if (doc) | |
247 json_decref(doc); | |
248 | |
249 return 0; | |
250 } | |
251 | |
252 static duk_ret_t | |
253 Scid_print(duk_context *ctx) | |
254 { | |
255 puts(duk_require_string(ctx, 0)); | |
256 | |
257 return 0; | |
258 } | |
259 | |
260 static const duk_function_list_entry functions[] = { | |
261 { "render", Scid_render, 3 }, | |
262 { "print", Scid_print, 1 }, | |
263 { NULL, NULL, 0 } | |
264 }; | |
265 | |
266 /* }}} */ | |
267 | |
17 static char * | 268 static char * |
18 render(struct theme *t, const char *json, const char *function) | 269 call(struct theme *t, json_t *json, const char *function) |
19 { | 270 { |
20 char *ret = NULL; | 271 char *out = NULL, *dump = NULL; |
272 size_t outsz = 0; | |
273 FILE *fp = NULL; | |
274 int nargs = 1; | |
21 | 275 |
22 duk_get_global_string(t->ctx, function); | 276 duk_get_global_string(t->ctx, function); |
23 | 277 |
24 if (!duk_is_callable(t->ctx, -1)) { | 278 if (!duk_is_callable(t->ctx, -1)) |
25 duk_pop(t->ctx); | 279 goto over; |
26 return NULL; | 280 if (!(fp = open_memstream(&out, &outsz))) |
281 goto over; | |
282 | |
283 duk_push_pointer(t->ctx, fp); | |
284 | |
285 if (json && (dump = json_dumps(json, JSON_COMPACT))) { | |
286 duk_push_string(t->ctx, dump); | |
287 duk_json_decode(t->ctx, -1); | |
288 nargs++; | |
27 } | 289 } |
28 | 290 |
29 duk_push_string(t->ctx, json); | 291 if (duk_pcall(t->ctx, nargs) != 0) |
30 duk_json_decode(t->ctx, -1); | |
31 | |
32 if (duk_pcall(t->ctx, 1) != 0) { | |
33 log_warn("theme: %s", duk_safe_to_string(t->ctx, -1)); | 292 log_warn("theme: %s", duk_safe_to_string(t->ctx, -1)); |
34 } else if (duk_is_string(t->ctx, -1)) | 293 |
35 ret = util_strdup(duk_get_string(t->ctx, -1)); | 294 over: |
36 | 295 duk_pop(t->ctx); |
37 duk_pop_n(t->ctx, 1); | |
38 | 296 |
39 /* | 297 /* |
40 * For convenience, otherwise all callers have to check for non-NULL | 298 * For convenience, otherwise all callers have to check for non-NULL |
41 * after calling the function. | 299 * after calling the function. |
42 */ | 300 */ |
43 if (!ret) | 301 free(dump); |
44 ret = util_strdup(""); | 302 |
45 | 303 if (fp) |
46 return ret; | 304 fclose(fp); |
305 if (!out) | |
306 out = util_strdup(""); | |
307 | |
308 return out; | |
47 } | 309 } |
48 | 310 |
49 const char * | 311 const char * |
50 theme_path(struct theme *t, const char *filename) | 312 theme_path(struct theme *t, const char *filename) |
51 { | 313 { |
79 else { | 341 else { |
80 if (duk_peval_string(t->ctx, data) != 0) | 342 if (duk_peval_string(t->ctx, data) != 0) |
81 log_warn("theme: %s", duk_safe_to_string(t->ctx, -1)); | 343 log_warn("theme: %s", duk_safe_to_string(t->ctx, -1)); |
82 | 344 |
83 duk_pop(t->ctx); | 345 duk_pop(t->ctx); |
346 duk_push_object(t->ctx); | |
347 duk_put_function_list(t->ctx, -1, functions); | |
348 duk_put_global_string(t->ctx, "Scid"); | |
349 free(data); | |
84 } | 350 } |
85 | 351 |
86 return t; | 352 return t; |
87 } | 353 } |
88 | 354 |
89 char * | 355 char * |
90 theme_render_index(struct theme *t, const char *json) | 356 theme_page_index(struct theme *t, json_t *json) |
91 { | 357 { |
92 assert(t); | 358 assert(t); |
93 assert(json); | 359 |
94 | 360 return call(t, json, "onPageIndex"); |
95 return render(t, json, "index"); | 361 } |
362 | |
363 char * | |
364 theme_page_status(struct theme *t, enum khttp status) | |
365 { | |
366 assert(t); | |
367 | |
368 (void)t; | |
369 (void)status; | |
370 | |
371 #if 0 | |
372 return call(t, json, "onPageStatus"); | |
373 #endif | |
374 return "ERROR"; | |
96 } | 375 } |
97 | 376 |
98 void | 377 void |
99 theme_free(struct theme *t) | 378 theme_free(struct theme *t) |
100 { | 379 { |