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