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