]>
Commit | Line | Data |
---|---|---|
75459410 JH |
1 | #include "cache.h" |
2 | #include "json-writer.h" | |
3 | ||
4 | void jw_init(struct json_writer *jw) | |
5 | { | |
6 | strbuf_init(&jw->json, 0); | |
7 | strbuf_init(&jw->open_stack, 0); | |
8 | jw->need_comma = 0; | |
9 | jw->pretty = 0; | |
10 | } | |
11 | ||
12 | void jw_release(struct json_writer *jw) | |
13 | { | |
14 | strbuf_release(&jw->json); | |
15 | strbuf_release(&jw->open_stack); | |
16 | } | |
17 | ||
18 | /* | |
19 | * Append JSON-quoted version of the given string to 'out'. | |
20 | */ | |
21 | static void append_quoted_string(struct strbuf *out, const char *in) | |
22 | { | |
23 | unsigned char c; | |
24 | ||
25 | strbuf_addch(out, '"'); | |
26 | while ((c = *in++) != '\0') { | |
27 | if (c == '"') | |
28 | strbuf_addstr(out, "\\\""); | |
29 | else if (c == '\\') | |
30 | strbuf_addstr(out, "\\\\"); | |
31 | else if (c == '\n') | |
32 | strbuf_addstr(out, "\\n"); | |
33 | else if (c == '\r') | |
34 | strbuf_addstr(out, "\\r"); | |
35 | else if (c == '\t') | |
36 | strbuf_addstr(out, "\\t"); | |
37 | else if (c == '\f') | |
38 | strbuf_addstr(out, "\\f"); | |
39 | else if (c == '\b') | |
40 | strbuf_addstr(out, "\\b"); | |
41 | else if (c < 0x20) | |
42 | strbuf_addf(out, "\\u%04x", c); | |
43 | else | |
44 | strbuf_addch(out, c); | |
45 | } | |
46 | strbuf_addch(out, '"'); | |
47 | } | |
48 | ||
49 | static void indent_pretty(struct json_writer *jw) | |
50 | { | |
51 | int k; | |
52 | ||
53 | for (k = 0; k < jw->open_stack.len; k++) | |
54 | strbuf_addstr(&jw->json, " "); | |
55 | } | |
56 | ||
57 | /* | |
58 | * Begin an object or array (either top-level or nested within the currently | |
59 | * open object or array). | |
60 | */ | |
61 | static void begin(struct json_writer *jw, char ch_open, int pretty) | |
62 | { | |
63 | jw->pretty = pretty; | |
64 | ||
65 | strbuf_addch(&jw->json, ch_open); | |
66 | ||
67 | strbuf_addch(&jw->open_stack, ch_open); | |
68 | jw->need_comma = 0; | |
69 | } | |
70 | ||
71 | /* | |
72 | * Assert that the top of the open-stack is an object. | |
73 | */ | |
74 | static void assert_in_object(const struct json_writer *jw, const char *key) | |
75 | { | |
76 | if (!jw->open_stack.len) | |
77 | BUG("json-writer: object: missing jw_object_begin(): '%s'", key); | |
78 | if (jw->open_stack.buf[jw->open_stack.len - 1] != '{') | |
79 | BUG("json-writer: object: not in object: '%s'", key); | |
80 | } | |
81 | ||
82 | /* | |
83 | * Assert that the top of the open-stack is an array. | |
84 | */ | |
85 | static void assert_in_array(const struct json_writer *jw) | |
86 | { | |
87 | if (!jw->open_stack.len) | |
88 | BUG("json-writer: array: missing jw_array_begin()"); | |
89 | if (jw->open_stack.buf[jw->open_stack.len - 1] != '[') | |
90 | BUG("json-writer: array: not in array"); | |
91 | } | |
92 | ||
93 | /* | |
94 | * Add comma if we have already seen a member at this level. | |
95 | */ | |
96 | static void maybe_add_comma(struct json_writer *jw) | |
97 | { | |
98 | if (jw->need_comma) | |
99 | strbuf_addch(&jw->json, ','); | |
100 | else | |
101 | jw->need_comma = 1; | |
102 | } | |
103 | ||
104 | static void fmt_double(struct json_writer *jw, int precision, | |
105 | double value) | |
106 | { | |
107 | if (precision < 0) { | |
108 | strbuf_addf(&jw->json, "%f", value); | |
109 | } else { | |
110 | struct strbuf fmt = STRBUF_INIT; | |
111 | strbuf_addf(&fmt, "%%.%df", precision); | |
112 | strbuf_addf(&jw->json, fmt.buf, value); | |
113 | strbuf_release(&fmt); | |
114 | } | |
115 | } | |
116 | ||
117 | static void object_common(struct json_writer *jw, const char *key) | |
118 | { | |
119 | assert_in_object(jw, key); | |
120 | maybe_add_comma(jw); | |
121 | ||
122 | if (jw->pretty) { | |
123 | strbuf_addch(&jw->json, '\n'); | |
124 | indent_pretty(jw); | |
125 | } | |
126 | ||
127 | append_quoted_string(&jw->json, key); | |
128 | strbuf_addch(&jw->json, ':'); | |
129 | if (jw->pretty) | |
130 | strbuf_addch(&jw->json, ' '); | |
131 | } | |
132 | ||
133 | static void array_common(struct json_writer *jw) | |
134 | { | |
135 | assert_in_array(jw); | |
136 | maybe_add_comma(jw); | |
137 | ||
138 | if (jw->pretty) { | |
139 | strbuf_addch(&jw->json, '\n'); | |
140 | indent_pretty(jw); | |
141 | } | |
142 | } | |
143 | ||
144 | /* | |
145 | * Assert that the given JSON object or JSON array has been properly | |
146 | * terminated. (Has closing bracket.) | |
147 | */ | |
148 | static void assert_is_terminated(const struct json_writer *jw) | |
149 | { | |
150 | if (jw->open_stack.len) | |
151 | BUG("json-writer: object: missing jw_end(): '%s'", | |
152 | jw->json.buf); | |
153 | } | |
154 | ||
155 | void jw_object_begin(struct json_writer *jw, int pretty) | |
156 | { | |
157 | begin(jw, '{', pretty); | |
158 | } | |
159 | ||
160 | void jw_object_string(struct json_writer *jw, const char *key, const char *value) | |
161 | { | |
162 | object_common(jw, key); | |
163 | append_quoted_string(&jw->json, value); | |
164 | } | |
165 | ||
166 | void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value) | |
167 | { | |
168 | object_common(jw, key); | |
169 | strbuf_addf(&jw->json, "%"PRIdMAX, value); | |
170 | } | |
171 | ||
172 | void jw_object_double(struct json_writer *jw, const char *key, int precision, | |
173 | double value) | |
174 | { | |
175 | object_common(jw, key); | |
176 | fmt_double(jw, precision, value); | |
177 | } | |
178 | ||
179 | void jw_object_true(struct json_writer *jw, const char *key) | |
180 | { | |
181 | object_common(jw, key); | |
182 | strbuf_addstr(&jw->json, "true"); | |
183 | } | |
184 | ||
185 | void jw_object_false(struct json_writer *jw, const char *key) | |
186 | { | |
187 | object_common(jw, key); | |
188 | strbuf_addstr(&jw->json, "false"); | |
189 | } | |
190 | ||
191 | void jw_object_bool(struct json_writer *jw, const char *key, int value) | |
192 | { | |
193 | if (value) | |
194 | jw_object_true(jw, key); | |
195 | else | |
196 | jw_object_false(jw, key); | |
197 | } | |
198 | ||
199 | void jw_object_null(struct json_writer *jw, const char *key) | |
200 | { | |
201 | object_common(jw, key); | |
202 | strbuf_addstr(&jw->json, "null"); | |
203 | } | |
204 | ||
205 | static void increase_indent(struct strbuf *sb, | |
206 | const struct json_writer *jw, | |
207 | int indent) | |
208 | { | |
209 | int k; | |
210 | ||
211 | strbuf_reset(sb); | |
212 | for (k = 0; k < jw->json.len; k++) { | |
213 | char ch = jw->json.buf[k]; | |
214 | strbuf_addch(sb, ch); | |
215 | if (ch == '\n') | |
216 | strbuf_addchars(sb, ' ', indent); | |
217 | } | |
218 | } | |
219 | ||
220 | static void kill_indent(struct strbuf *sb, | |
221 | const struct json_writer *jw) | |
222 | { | |
223 | int k; | |
224 | int eat_it = 0; | |
225 | ||
226 | strbuf_reset(sb); | |
227 | for (k = 0; k < jw->json.len; k++) { | |
228 | char ch = jw->json.buf[k]; | |
229 | if (eat_it && ch == ' ') | |
230 | continue; | |
231 | if (ch == '\n') { | |
232 | eat_it = 1; | |
233 | continue; | |
234 | } | |
235 | eat_it = 0; | |
236 | strbuf_addch(sb, ch); | |
237 | } | |
238 | } | |
239 | ||
240 | static void append_sub_jw(struct json_writer *jw, | |
241 | const struct json_writer *value) | |
242 | { | |
243 | /* | |
244 | * If both are pretty, increase the indentation of the sub_jw | |
245 | * to better fit under the super. | |
246 | * | |
247 | * If the super is pretty, but the sub_jw is compact, leave the | |
248 | * sub_jw compact. (We don't want to parse and rebuild the sub_jw | |
249 | * for this debug-ish feature.) | |
250 | * | |
251 | * If the super is compact, and the sub_jw is pretty, convert | |
252 | * the sub_jw to compact. | |
253 | * | |
254 | * If both are compact, keep the sub_jw compact. | |
255 | */ | |
256 | if (jw->pretty && jw->open_stack.len && value->pretty) { | |
257 | struct strbuf sb = STRBUF_INIT; | |
258 | increase_indent(&sb, value, jw->open_stack.len * 2); | |
259 | strbuf_addbuf(&jw->json, &sb); | |
260 | strbuf_release(&sb); | |
261 | return; | |
262 | } | |
263 | if (!jw->pretty && value->pretty) { | |
264 | struct strbuf sb = STRBUF_INIT; | |
265 | kill_indent(&sb, value); | |
266 | strbuf_addbuf(&jw->json, &sb); | |
267 | strbuf_release(&sb); | |
268 | return; | |
269 | } | |
270 | ||
271 | strbuf_addbuf(&jw->json, &value->json); | |
272 | } | |
273 | ||
274 | /* | |
275 | * Append existing (properly terminated) JSON sub-data (object or array) | |
276 | * as-is onto the given JSON data. | |
277 | */ | |
278 | void jw_object_sub_jw(struct json_writer *jw, const char *key, | |
279 | const struct json_writer *value) | |
280 | { | |
281 | assert_is_terminated(value); | |
282 | ||
283 | object_common(jw, key); | |
284 | append_sub_jw(jw, value); | |
285 | } | |
286 | ||
287 | void jw_object_inline_begin_object(struct json_writer *jw, const char *key) | |
288 | { | |
289 | object_common(jw, key); | |
290 | ||
291 | jw_object_begin(jw, jw->pretty); | |
292 | } | |
293 | ||
294 | void jw_object_inline_begin_array(struct json_writer *jw, const char *key) | |
295 | { | |
296 | object_common(jw, key); | |
297 | ||
298 | jw_array_begin(jw, jw->pretty); | |
299 | } | |
300 | ||
301 | void jw_array_begin(struct json_writer *jw, int pretty) | |
302 | { | |
303 | begin(jw, '[', pretty); | |
304 | } | |
305 | ||
306 | void jw_array_string(struct json_writer *jw, const char *value) | |
307 | { | |
308 | array_common(jw); | |
309 | append_quoted_string(&jw->json, value); | |
310 | } | |
311 | ||
312 | void jw_array_intmax(struct json_writer *jw, intmax_t value) | |
313 | { | |
314 | array_common(jw); | |
315 | strbuf_addf(&jw->json, "%"PRIdMAX, value); | |
316 | } | |
317 | ||
318 | void jw_array_double(struct json_writer *jw, int precision, double value) | |
319 | { | |
320 | array_common(jw); | |
321 | fmt_double(jw, precision, value); | |
322 | } | |
323 | ||
324 | void jw_array_true(struct json_writer *jw) | |
325 | { | |
326 | array_common(jw); | |
327 | strbuf_addstr(&jw->json, "true"); | |
328 | } | |
329 | ||
330 | void jw_array_false(struct json_writer *jw) | |
331 | { | |
332 | array_common(jw); | |
333 | strbuf_addstr(&jw->json, "false"); | |
334 | } | |
335 | ||
336 | void jw_array_bool(struct json_writer *jw, int value) | |
337 | { | |
338 | if (value) | |
339 | jw_array_true(jw); | |
340 | else | |
341 | jw_array_false(jw); | |
342 | } | |
343 | ||
344 | void jw_array_null(struct json_writer *jw) | |
345 | { | |
346 | array_common(jw); | |
347 | strbuf_addstr(&jw->json, "null"); | |
348 | } | |
349 | ||
350 | void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value) | |
351 | { | |
352 | assert_is_terminated(value); | |
353 | ||
354 | array_common(jw); | |
355 | append_sub_jw(jw, value); | |
356 | } | |
357 | ||
358 | void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv) | |
359 | { | |
360 | int k; | |
361 | ||
362 | for (k = 0; k < argc; k++) | |
363 | jw_array_string(jw, argv[k]); | |
364 | } | |
365 | ||
366 | void jw_array_argv(struct json_writer *jw, const char **argv) | |
367 | { | |
368 | while (*argv) | |
369 | jw_array_string(jw, *argv++); | |
370 | } | |
371 | ||
372 | void jw_array_inline_begin_object(struct json_writer *jw) | |
373 | { | |
374 | array_common(jw); | |
375 | ||
376 | jw_object_begin(jw, jw->pretty); | |
377 | } | |
378 | ||
379 | void jw_array_inline_begin_array(struct json_writer *jw) | |
380 | { | |
381 | array_common(jw); | |
382 | ||
383 | jw_array_begin(jw, jw->pretty); | |
384 | } | |
385 | ||
386 | int jw_is_terminated(const struct json_writer *jw) | |
387 | { | |
388 | return !jw->open_stack.len; | |
389 | } | |
390 | ||
391 | void jw_end(struct json_writer *jw) | |
392 | { | |
393 | char ch_open; | |
394 | int len; | |
395 | ||
396 | if (!jw->open_stack.len) | |
397 | BUG("json-writer: too many jw_end(): '%s'", jw->json.buf); | |
398 | ||
399 | len = jw->open_stack.len - 1; | |
400 | ch_open = jw->open_stack.buf[len]; | |
401 | ||
402 | strbuf_setlen(&jw->open_stack, len); | |
403 | jw->need_comma = 1; | |
404 | ||
405 | if (jw->pretty) { | |
406 | strbuf_addch(&jw->json, '\n'); | |
407 | indent_pretty(jw); | |
408 | } | |
409 | ||
410 | if (ch_open == '{') | |
411 | strbuf_addch(&jw->json, '}'); | |
412 | else | |
413 | strbuf_addch(&jw->json, ']'); | |
414 | } |