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