json_writer: new routines to create JSON data
Add "struct json_writer" and a series of jw_ routines to compose JSON data into a string buffer. The resulting string may then be printed by commands wanting to support a JSON-like output format. The json_writer is limited to correctly formatting structured data for output. It does not attempt to build an object model of the JSON data. We say "JSON-like" because we do not enforce the Unicode (usually UTF-8) requirement on string fields. Internally, Git does not necessarily have Unicode/UTF-8 data for most fields, so it is currently unclear the best way to enforce that requirement. For example, on Linux pathnames can contain arbitrary 8-bit character data, so a command like "status" would not know how to encode the reported pathnames. We may want to revisit this (or double encode such strings) in the future. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: René Scharfe <l.s.r@web.de> Helped-by: Wink Saville <wink@saville.com> Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint
							parent
							
								
									b2453d3449
								
							
						
					
					
						commit
						75459410ed
					
				
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								Makefile
								
								
								
								
							|  | @ -709,6 +709,7 @@ TEST_BUILTINS_OBJS += test-example-decorate.o | |||
| TEST_BUILTINS_OBJS += test-genrandom.o | ||||
| TEST_BUILTINS_OBJS += test-hashmap.o | ||||
| TEST_BUILTINS_OBJS += test-index-version.o | ||||
| TEST_BUILTINS_OBJS += test-json-writer.o | ||||
| TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o | ||||
| TEST_BUILTINS_OBJS += test-match-trees.o | ||||
| TEST_BUILTINS_OBJS += test-mergesort.o | ||||
|  | @ -871,6 +872,7 @@ LIB_OBJS += hashmap.o | |||
| LIB_OBJS += help.o | ||||
| LIB_OBJS += hex.o | ||||
| LIB_OBJS += ident.o | ||||
| LIB_OBJS += json-writer.o | ||||
| LIB_OBJS += kwset.o | ||||
| LIB_OBJS += levenshtein.o | ||||
| LIB_OBJS += line-log.o | ||||
|  |  | |||
|  | @ -0,0 +1,414 @@ | |||
| #include "cache.h" | ||||
| #include "json-writer.h" | ||||
|  | ||||
| void jw_init(struct json_writer *jw) | ||||
| { | ||||
| 	strbuf_init(&jw->json, 0); | ||||
| 	strbuf_init(&jw->open_stack, 0); | ||||
| 	jw->need_comma = 0; | ||||
| 	jw->pretty = 0; | ||||
| } | ||||
|  | ||||
| void jw_release(struct json_writer *jw) | ||||
| { | ||||
| 	strbuf_release(&jw->json); | ||||
| 	strbuf_release(&jw->open_stack); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Append JSON-quoted version of the given string to 'out'. | ||||
|  */ | ||||
| static void append_quoted_string(struct strbuf *out, const char *in) | ||||
| { | ||||
| 	unsigned char c; | ||||
|  | ||||
| 	strbuf_addch(out, '"'); | ||||
| 	while ((c = *in++) != '\0') { | ||||
| 		if (c == '"') | ||||
| 			strbuf_addstr(out, "\\\""); | ||||
| 		else if (c == '\\') | ||||
| 			strbuf_addstr(out, "\\\\"); | ||||
| 		else if (c == '\n') | ||||
| 			strbuf_addstr(out, "\\n"); | ||||
| 		else if (c == '\r') | ||||
| 			strbuf_addstr(out, "\\r"); | ||||
| 		else if (c == '\t') | ||||
| 			strbuf_addstr(out, "\\t"); | ||||
| 		else if (c == '\f') | ||||
| 			strbuf_addstr(out, "\\f"); | ||||
| 		else if (c == '\b') | ||||
| 			strbuf_addstr(out, "\\b"); | ||||
| 		else if (c < 0x20) | ||||
| 			strbuf_addf(out, "\\u%04x", c); | ||||
| 		else | ||||
| 			strbuf_addch(out, c); | ||||
| 	} | ||||
| 	strbuf_addch(out, '"'); | ||||
| } | ||||
|  | ||||
| static void indent_pretty(struct json_writer *jw) | ||||
| { | ||||
| 	int k; | ||||
|  | ||||
| 	for (k = 0; k < jw->open_stack.len; k++) | ||||
| 		strbuf_addstr(&jw->json, "  "); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Begin an object or array (either top-level or nested within the currently | ||||
|  * open object or array). | ||||
|  */ | ||||
| static void begin(struct json_writer *jw, char ch_open, int pretty) | ||||
| { | ||||
| 	jw->pretty = pretty; | ||||
|  | ||||
| 	strbuf_addch(&jw->json, ch_open); | ||||
|  | ||||
| 	strbuf_addch(&jw->open_stack, ch_open); | ||||
| 	jw->need_comma = 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Assert that the top of the open-stack is an object. | ||||
|  */ | ||||
| static void assert_in_object(const struct json_writer *jw, const char *key) | ||||
| { | ||||
| 	if (!jw->open_stack.len) | ||||
| 		BUG("json-writer: object: missing jw_object_begin(): '%s'", key); | ||||
| 	if (jw->open_stack.buf[jw->open_stack.len - 1] != '{') | ||||
| 		BUG("json-writer: object: not in object: '%s'", key); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Assert that the top of the open-stack is an array. | ||||
|  */ | ||||
| static void assert_in_array(const struct json_writer *jw) | ||||
| { | ||||
| 	if (!jw->open_stack.len) | ||||
| 		BUG("json-writer: array: missing jw_array_begin()"); | ||||
| 	if (jw->open_stack.buf[jw->open_stack.len - 1] != '[') | ||||
| 		BUG("json-writer: array: not in array"); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Add comma if we have already seen a member at this level. | ||||
|  */ | ||||
| static void maybe_add_comma(struct json_writer *jw) | ||||
| { | ||||
| 	if (jw->need_comma) | ||||
| 		strbuf_addch(&jw->json, ','); | ||||
| 	else | ||||
| 		jw->need_comma = 1; | ||||
| } | ||||
|  | ||||
| static void fmt_double(struct json_writer *jw, int precision, | ||||
| 			      double value) | ||||
| { | ||||
| 	if (precision < 0) { | ||||
| 		strbuf_addf(&jw->json, "%f", value); | ||||
| 	} else { | ||||
| 		struct strbuf fmt = STRBUF_INIT; | ||||
| 		strbuf_addf(&fmt, "%%.%df", precision); | ||||
| 		strbuf_addf(&jw->json, fmt.buf, value); | ||||
| 		strbuf_release(&fmt); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void object_common(struct json_writer *jw, const char *key) | ||||
| { | ||||
| 	assert_in_object(jw, key); | ||||
| 	maybe_add_comma(jw); | ||||
|  | ||||
| 	if (jw->pretty) { | ||||
| 		strbuf_addch(&jw->json, '\n'); | ||||
| 		indent_pretty(jw); | ||||
| 	} | ||||
|  | ||||
| 	append_quoted_string(&jw->json, key); | ||||
| 	strbuf_addch(&jw->json, ':'); | ||||
| 	if (jw->pretty) | ||||
| 		strbuf_addch(&jw->json, ' '); | ||||
| } | ||||
|  | ||||
| static void array_common(struct json_writer *jw) | ||||
| { | ||||
| 	assert_in_array(jw); | ||||
| 	maybe_add_comma(jw); | ||||
|  | ||||
| 	if (jw->pretty) { | ||||
| 		strbuf_addch(&jw->json, '\n'); | ||||
| 		indent_pretty(jw); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Assert that the given JSON object or JSON array has been properly | ||||
|  * terminated.  (Has closing bracket.) | ||||
|  */ | ||||
| static void assert_is_terminated(const struct json_writer *jw) | ||||
| { | ||||
| 	if (jw->open_stack.len) | ||||
| 		BUG("json-writer: object: missing jw_end(): '%s'", | ||||
| 		    jw->json.buf); | ||||
| } | ||||
|  | ||||
| void jw_object_begin(struct json_writer *jw, int pretty) | ||||
| { | ||||
| 	begin(jw, '{', pretty); | ||||
| } | ||||
|  | ||||
| void jw_object_string(struct json_writer *jw, const char *key, const char *value) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
| 	append_quoted_string(&jw->json, value); | ||||
| } | ||||
|  | ||||
| void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
| 	strbuf_addf(&jw->json, "%"PRIdMAX, value); | ||||
| } | ||||
|  | ||||
| void jw_object_double(struct json_writer *jw, const char *key, int precision, | ||||
| 		      double value) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
| 	fmt_double(jw, precision, value); | ||||
| } | ||||
|  | ||||
| void jw_object_true(struct json_writer *jw, const char *key) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
| 	strbuf_addstr(&jw->json, "true"); | ||||
| } | ||||
|  | ||||
| void jw_object_false(struct json_writer *jw, const char *key) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
| 	strbuf_addstr(&jw->json, "false"); | ||||
| } | ||||
|  | ||||
| void jw_object_bool(struct json_writer *jw, const char *key, int value) | ||||
| { | ||||
| 	if (value) | ||||
| 		jw_object_true(jw, key); | ||||
| 	else | ||||
| 		jw_object_false(jw, key); | ||||
| } | ||||
|  | ||||
| void jw_object_null(struct json_writer *jw, const char *key) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
| 	strbuf_addstr(&jw->json, "null"); | ||||
| } | ||||
|  | ||||
| static void increase_indent(struct strbuf *sb, | ||||
| 			    const struct json_writer *jw, | ||||
| 			    int indent) | ||||
| { | ||||
| 	int k; | ||||
|  | ||||
| 	strbuf_reset(sb); | ||||
| 	for (k = 0; k < jw->json.len; k++) { | ||||
| 		char ch = jw->json.buf[k]; | ||||
| 		strbuf_addch(sb, ch); | ||||
| 		if (ch == '\n') | ||||
| 			strbuf_addchars(sb, ' ', indent); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void kill_indent(struct strbuf *sb, | ||||
| 			const struct json_writer *jw) | ||||
| { | ||||
| 	int k; | ||||
| 	int eat_it = 0; | ||||
|  | ||||
| 	strbuf_reset(sb); | ||||
| 	for (k = 0; k < jw->json.len; k++) { | ||||
| 		char ch = jw->json.buf[k]; | ||||
| 		if (eat_it && ch == ' ') | ||||
| 			continue; | ||||
| 		if (ch == '\n') { | ||||
| 			eat_it = 1; | ||||
| 			continue; | ||||
| 		} | ||||
| 		eat_it = 0; | ||||
| 		strbuf_addch(sb, ch); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void append_sub_jw(struct json_writer *jw, | ||||
| 			  const struct json_writer *value) | ||||
| { | ||||
| 	/* | ||||
| 	 * If both are pretty, increase the indentation of the sub_jw | ||||
| 	 * to better fit under the super. | ||||
| 	 * | ||||
| 	 * If the super is pretty, but the sub_jw is compact, leave the | ||||
| 	 * sub_jw compact.  (We don't want to parse and rebuild the sub_jw | ||||
| 	 * for this debug-ish feature.) | ||||
| 	 * | ||||
| 	 * If the super is compact, and the sub_jw is pretty, convert | ||||
| 	 * the sub_jw to compact. | ||||
| 	 * | ||||
| 	 * If both are compact, keep the sub_jw compact. | ||||
| 	 */ | ||||
| 	if (jw->pretty && jw->open_stack.len && value->pretty) { | ||||
| 		struct strbuf sb = STRBUF_INIT; | ||||
| 		increase_indent(&sb, value, jw->open_stack.len * 2); | ||||
| 		strbuf_addbuf(&jw->json, &sb); | ||||
| 		strbuf_release(&sb); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (!jw->pretty && value->pretty) { | ||||
| 		struct strbuf sb = STRBUF_INIT; | ||||
| 		kill_indent(&sb, value); | ||||
| 		strbuf_addbuf(&jw->json, &sb); | ||||
| 		strbuf_release(&sb); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	strbuf_addbuf(&jw->json, &value->json); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Append existing (properly terminated) JSON sub-data (object or array) | ||||
|  * as-is onto the given JSON data. | ||||
|  */ | ||||
| void jw_object_sub_jw(struct json_writer *jw, const char *key, | ||||
| 		      const struct json_writer *value) | ||||
| { | ||||
| 	assert_is_terminated(value); | ||||
|  | ||||
| 	object_common(jw, key); | ||||
| 	append_sub_jw(jw, value); | ||||
| } | ||||
|  | ||||
| void jw_object_inline_begin_object(struct json_writer *jw, const char *key) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
|  | ||||
| 	jw_object_begin(jw, jw->pretty); | ||||
| } | ||||
|  | ||||
| void jw_object_inline_begin_array(struct json_writer *jw, const char *key) | ||||
| { | ||||
| 	object_common(jw, key); | ||||
|  | ||||
| 	jw_array_begin(jw, jw->pretty); | ||||
| } | ||||
|  | ||||
| void jw_array_begin(struct json_writer *jw, int pretty) | ||||
| { | ||||
| 	begin(jw, '[', pretty); | ||||
| } | ||||
|  | ||||
| void jw_array_string(struct json_writer *jw, const char *value) | ||||
| { | ||||
| 	array_common(jw); | ||||
| 	append_quoted_string(&jw->json, value); | ||||
| } | ||||
|  | ||||
| void jw_array_intmax(struct json_writer *jw, intmax_t value) | ||||
| { | ||||
| 	array_common(jw); | ||||
| 	strbuf_addf(&jw->json, "%"PRIdMAX, value); | ||||
| } | ||||
|  | ||||
| void jw_array_double(struct json_writer *jw, int precision, double value) | ||||
| { | ||||
| 	array_common(jw); | ||||
| 	fmt_double(jw, precision, value); | ||||
| } | ||||
|  | ||||
| void jw_array_true(struct json_writer *jw) | ||||
| { | ||||
| 	array_common(jw); | ||||
| 	strbuf_addstr(&jw->json, "true"); | ||||
| } | ||||
|  | ||||
| void jw_array_false(struct json_writer *jw) | ||||
| { | ||||
| 	array_common(jw); | ||||
| 	strbuf_addstr(&jw->json, "false"); | ||||
| } | ||||
|  | ||||
| void jw_array_bool(struct json_writer *jw, int value) | ||||
| { | ||||
| 	if (value) | ||||
| 		jw_array_true(jw); | ||||
| 	else | ||||
| 		jw_array_false(jw); | ||||
| } | ||||
|  | ||||
| void jw_array_null(struct json_writer *jw) | ||||
| { | ||||
| 	array_common(jw); | ||||
| 	strbuf_addstr(&jw->json, "null"); | ||||
| } | ||||
|  | ||||
| void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value) | ||||
| { | ||||
| 	assert_is_terminated(value); | ||||
|  | ||||
| 	array_common(jw); | ||||
| 	append_sub_jw(jw, value); | ||||
| } | ||||
|  | ||||
| void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv) | ||||
| { | ||||
| 	int k; | ||||
|  | ||||
| 	for (k = 0; k < argc; k++) | ||||
| 		jw_array_string(jw, argv[k]); | ||||
| } | ||||
|  | ||||
| void jw_array_argv(struct json_writer *jw, const char **argv) | ||||
| { | ||||
| 	while (*argv) | ||||
| 		jw_array_string(jw, *argv++); | ||||
| } | ||||
|  | ||||
| void jw_array_inline_begin_object(struct json_writer *jw) | ||||
| { | ||||
| 	array_common(jw); | ||||
|  | ||||
| 	jw_object_begin(jw, jw->pretty); | ||||
| } | ||||
|  | ||||
| void jw_array_inline_begin_array(struct json_writer *jw) | ||||
| { | ||||
| 	array_common(jw); | ||||
|  | ||||
| 	jw_array_begin(jw, jw->pretty); | ||||
| } | ||||
|  | ||||
| int jw_is_terminated(const struct json_writer *jw) | ||||
| { | ||||
| 	return !jw->open_stack.len; | ||||
| } | ||||
|  | ||||
| void jw_end(struct json_writer *jw) | ||||
| { | ||||
| 	char ch_open; | ||||
| 	int len; | ||||
|  | ||||
| 	if (!jw->open_stack.len) | ||||
| 		BUG("json-writer: too many jw_end(): '%s'", jw->json.buf); | ||||
|  | ||||
| 	len = jw->open_stack.len - 1; | ||||
| 	ch_open = jw->open_stack.buf[len]; | ||||
|  | ||||
| 	strbuf_setlen(&jw->open_stack, len); | ||||
| 	jw->need_comma = 1; | ||||
|  | ||||
| 	if (jw->pretty) { | ||||
| 		strbuf_addch(&jw->json, '\n'); | ||||
| 		indent_pretty(jw); | ||||
| 	} | ||||
|  | ||||
| 	if (ch_open == '{') | ||||
| 		strbuf_addch(&jw->json, '}'); | ||||
| 	else | ||||
| 		strbuf_addch(&jw->json, ']'); | ||||
| } | ||||
|  | @ -0,0 +1,105 @@ | |||
| #ifndef JSON_WRITER_H | ||||
| #define JSON_WRITER_H | ||||
|  | ||||
| /* | ||||
|  * JSON data structures are defined at: | ||||
|  * [1] http://www.ietf.org/rfc/rfc7159.txt | ||||
|  * [2] http://json.org/ | ||||
|  * | ||||
|  * The JSON-writer API allows one to build JSON data structures using a | ||||
|  * simple wrapper around a "struct strbuf" buffer.  It is intended as a | ||||
|  * simple API to build output strings; it is not intended to be a general | ||||
|  * object model for JSON data.  In particular, it does not re-order keys | ||||
|  * in an object (dictionary), it does not de-dup keys in an object, and | ||||
|  * it does not allow lookup or parsing of JSON data. | ||||
|  * | ||||
|  * All string values (both keys and string r-values) are properly quoted | ||||
|  * and escaped if they contain special characters. | ||||
|  * | ||||
|  * These routines create compact JSON data (with no unnecessary whitespace, | ||||
|  * newlines, or indenting).  If you get an unexpected response, verify | ||||
|  * that you're not expecting a pretty JSON string. | ||||
|  * | ||||
|  * Both "JSON objects" (aka sets of k/v pairs) and "JSON array" can be | ||||
|  * constructed using a 'begin append* end' model. | ||||
|  * | ||||
|  * Nested objects and arrays can either be constructed bottom up (by | ||||
|  * creating sub object/arrays first and appending them to the super | ||||
|  * object/array) -or- by building them inline in one pass.  This is a | ||||
|  * personal style and/or data shape choice. | ||||
|  * | ||||
|  * See t/helper/test-json-writer.c for various usage examples. | ||||
|  * | ||||
|  * LIMITATIONS: | ||||
|  * ============ | ||||
|  * | ||||
|  * The JSON specification [1,2] defines string values as Unicode data | ||||
|  * and probably UTF-8 encoded.  The current json-writer API does not | ||||
|  * enforce this and will write any string as received.  However, it will | ||||
|  * properly quote and backslash-escape them as necessary.  It is up to | ||||
|  * the caller to UTF-8 encode their strings *before* passing them to this | ||||
|  * API.  This layer should not have to try to guess the encoding or locale | ||||
|  * of the given strings. | ||||
|  */ | ||||
|  | ||||
| struct json_writer | ||||
| { | ||||
| 	/* | ||||
| 	 * Buffer of the in-progress JSON currently being composed. | ||||
| 	 */ | ||||
| 	struct strbuf json; | ||||
|  | ||||
| 	/* | ||||
| 	 * Simple stack of the currently open array and object forms. | ||||
| 	 * This is a string of '{' and '[' characters indicating the | ||||
| 	 * currently unterminated forms.  This is used to ensure the | ||||
| 	 * properly closing character is used when popping a level and | ||||
| 	 * to know when the JSON is completely closed. | ||||
| 	 */ | ||||
| 	struct strbuf open_stack; | ||||
|  | ||||
| 	unsigned int need_comma:1; | ||||
| 	unsigned int pretty:1; | ||||
| }; | ||||
|  | ||||
| #define JSON_WRITER_INIT { STRBUF_INIT, STRBUF_INIT, 0, 0 } | ||||
|  | ||||
| void jw_init(struct json_writer *jw); | ||||
| void jw_release(struct json_writer *jw); | ||||
|  | ||||
| void jw_object_begin(struct json_writer *jw, int pretty); | ||||
| void jw_array_begin(struct json_writer *jw, int pretty); | ||||
|  | ||||
| void jw_object_string(struct json_writer *jw, const char *key, | ||||
| 		      const char *value); | ||||
| void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value); | ||||
| void jw_object_double(struct json_writer *jw, const char *key, int precision, | ||||
| 		      double value); | ||||
| void jw_object_true(struct json_writer *jw, const char *key); | ||||
| void jw_object_false(struct json_writer *jw, const char *key); | ||||
| void jw_object_bool(struct json_writer *jw, const char *key, int value); | ||||
| void jw_object_null(struct json_writer *jw, const char *key); | ||||
| void jw_object_sub_jw(struct json_writer *jw, const char *key, | ||||
| 		      const struct json_writer *value); | ||||
|  | ||||
| void jw_object_inline_begin_object(struct json_writer *jw, const char *key); | ||||
| void jw_object_inline_begin_array(struct json_writer *jw, const char *key); | ||||
|  | ||||
| void jw_array_string(struct json_writer *jw, const char *value); | ||||
| void jw_array_intmax(struct json_writer *jw, intmax_t value); | ||||
| void jw_array_double(struct json_writer *jw, int precision, double value); | ||||
| void jw_array_true(struct json_writer *jw); | ||||
| void jw_array_false(struct json_writer *jw); | ||||
| void jw_array_bool(struct json_writer *jw, int value); | ||||
| void jw_array_null(struct json_writer *jw); | ||||
| void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value); | ||||
| void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv); | ||||
| void jw_array_argv(struct json_writer *jw, const char **argv); | ||||
|  | ||||
| void jw_array_inline_begin_object(struct json_writer *jw); | ||||
| void jw_array_inline_begin_array(struct json_writer *jw); | ||||
|  | ||||
| int jw_is_terminated(const struct json_writer *jw); | ||||
| void jw_end(struct json_writer *jw); | ||||
|  | ||||
| #endif /* JSON_WRITER_H */ | ||||
|  | @ -0,0 +1,565 @@ | |||
| #include "test-tool.h" | ||||
| #include "cache.h" | ||||
| #include "json-writer.h" | ||||
|  | ||||
| static const char *expect_obj1 = "{\"a\":\"abc\",\"b\":42,\"c\":true}"; | ||||
| static const char *expect_obj2 = "{\"a\":-1,\"b\":2147483647,\"c\":0}"; | ||||
| static const char *expect_obj3 = "{\"a\":0,\"b\":4294967295,\"c\":9223372036854775807}"; | ||||
| static const char *expect_obj4 = "{\"t\":true,\"f\":false,\"n\":null}"; | ||||
| static const char *expect_obj5 = "{\"abc\\tdef\":\"abc\\\\def\"}"; | ||||
| static const char *expect_obj6 = "{\"a\":3.14}"; | ||||
|  | ||||
| static const char *pretty_obj1 = ("{\n" | ||||
| 				  "  \"a\": \"abc\",\n" | ||||
| 				  "  \"b\": 42,\n" | ||||
| 				  "  \"c\": true\n" | ||||
| 				  "}"); | ||||
| static const char *pretty_obj2 = ("{\n" | ||||
| 				  "  \"a\": -1,\n" | ||||
| 				  "  \"b\": 2147483647,\n" | ||||
| 				  "  \"c\": 0\n" | ||||
| 				  "}"); | ||||
| static const char *pretty_obj3 = ("{\n" | ||||
| 				  "  \"a\": 0,\n" | ||||
| 				  "  \"b\": 4294967295,\n" | ||||
| 				  "  \"c\": 9223372036854775807\n" | ||||
| 				  "}"); | ||||
| static const char *pretty_obj4 = ("{\n" | ||||
| 				  "  \"t\": true,\n" | ||||
| 				  "  \"f\": false,\n" | ||||
| 				  "  \"n\": null\n" | ||||
| 				  "}"); | ||||
|  | ||||
| static struct json_writer obj1 = JSON_WRITER_INIT; | ||||
| static struct json_writer obj2 = JSON_WRITER_INIT; | ||||
| static struct json_writer obj3 = JSON_WRITER_INIT; | ||||
| static struct json_writer obj4 = JSON_WRITER_INIT; | ||||
| static struct json_writer obj5 = JSON_WRITER_INIT; | ||||
| static struct json_writer obj6 = JSON_WRITER_INIT; | ||||
|  | ||||
| static void make_obj1(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&obj1, pretty); | ||||
| 	{ | ||||
| 		jw_object_string(&obj1, "a", "abc"); | ||||
| 		jw_object_intmax(&obj1, "b", 42); | ||||
| 		jw_object_true(&obj1, "c"); | ||||
| 	} | ||||
| 	jw_end(&obj1); | ||||
| } | ||||
|  | ||||
| static void make_obj2(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&obj2, pretty); | ||||
| 	{ | ||||
| 		jw_object_intmax(&obj2, "a", -1); | ||||
| 		jw_object_intmax(&obj2, "b", 0x7fffffff); | ||||
| 		jw_object_intmax(&obj2, "c", 0); | ||||
| 	} | ||||
| 	jw_end(&obj2); | ||||
| } | ||||
|  | ||||
| static void make_obj3(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&obj3, pretty); | ||||
| 	{ | ||||
| 		jw_object_intmax(&obj3, "a", 0); | ||||
| 		jw_object_intmax(&obj3, "b", 0xffffffff); | ||||
| 		jw_object_intmax(&obj3, "c", 0x7fffffffffffffffULL); | ||||
| 	} | ||||
| 	jw_end(&obj3); | ||||
| } | ||||
|  | ||||
| static void make_obj4(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&obj4, pretty); | ||||
| 	{ | ||||
| 		jw_object_true(&obj4, "t"); | ||||
| 		jw_object_false(&obj4, "f"); | ||||
| 		jw_object_null(&obj4, "n"); | ||||
| 	} | ||||
| 	jw_end(&obj4); | ||||
| } | ||||
|  | ||||
| static void make_obj5(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&obj5, pretty); | ||||
| 	{ | ||||
| 		jw_object_string(&obj5, "abc" "\x09" "def", "abc" "\\" "def"); | ||||
| 	} | ||||
| 	jw_end(&obj5); | ||||
| } | ||||
|  | ||||
| static void make_obj6(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&obj6, pretty); | ||||
| 	{ | ||||
| 		jw_object_double(&obj6, "a", 2, 3.14159); | ||||
| 	} | ||||
| 	jw_end(&obj6); | ||||
| } | ||||
|  | ||||
| static const char *expect_arr1 = "[\"abc\",42,true]"; | ||||
| static const char *expect_arr2 = "[-1,2147483647,0]"; | ||||
| static const char *expect_arr3 = "[0,4294967295,9223372036854775807]"; | ||||
| static const char *expect_arr4 = "[true,false,null]"; | ||||
|  | ||||
| static const char *pretty_arr1 = ("[\n" | ||||
| 				  "  \"abc\",\n" | ||||
| 				  "  42,\n" | ||||
| 				  "  true\n" | ||||
| 				  "]"); | ||||
| static const char *pretty_arr2 = ("[\n" | ||||
| 				  "  -1,\n" | ||||
| 				  "  2147483647,\n" | ||||
| 				  "  0\n" | ||||
| 				  "]"); | ||||
| static const char *pretty_arr3 = ("[\n" | ||||
| 				  "  0,\n" | ||||
| 				  "  4294967295,\n" | ||||
| 				  "  9223372036854775807\n" | ||||
| 				  "]"); | ||||
| static const char *pretty_arr4 = ("[\n" | ||||
| 				  "  true,\n" | ||||
| 				  "  false,\n" | ||||
| 				  "  null\n" | ||||
| 				  "]"); | ||||
|  | ||||
| static struct json_writer arr1 = JSON_WRITER_INIT; | ||||
| static struct json_writer arr2 = JSON_WRITER_INIT; | ||||
| static struct json_writer arr3 = JSON_WRITER_INIT; | ||||
| static struct json_writer arr4 = JSON_WRITER_INIT; | ||||
|  | ||||
| static void make_arr1(int pretty) | ||||
| { | ||||
| 	jw_array_begin(&arr1, pretty); | ||||
| 	{ | ||||
| 		jw_array_string(&arr1, "abc"); | ||||
| 		jw_array_intmax(&arr1, 42); | ||||
| 		jw_array_true(&arr1); | ||||
| 	} | ||||
| 	jw_end(&arr1); | ||||
| } | ||||
|  | ||||
| static void make_arr2(int pretty) | ||||
| { | ||||
| 	jw_array_begin(&arr2, pretty); | ||||
| 	{ | ||||
| 		jw_array_intmax(&arr2, -1); | ||||
| 		jw_array_intmax(&arr2, 0x7fffffff); | ||||
| 		jw_array_intmax(&arr2, 0); | ||||
| 	} | ||||
| 	jw_end(&arr2); | ||||
| } | ||||
|  | ||||
| static void make_arr3(int pretty) | ||||
| { | ||||
| 	jw_array_begin(&arr3, pretty); | ||||
| 	{ | ||||
| 		jw_array_intmax(&arr3, 0); | ||||
| 		jw_array_intmax(&arr3, 0xffffffff); | ||||
| 		jw_array_intmax(&arr3, 0x7fffffffffffffffULL); | ||||
| 	} | ||||
| 	jw_end(&arr3); | ||||
| } | ||||
|  | ||||
| static void make_arr4(int pretty) | ||||
| { | ||||
| 	jw_array_begin(&arr4, pretty); | ||||
| 	{ | ||||
| 		jw_array_true(&arr4); | ||||
| 		jw_array_false(&arr4); | ||||
| 		jw_array_null(&arr4); | ||||
| 	} | ||||
| 	jw_end(&arr4); | ||||
| } | ||||
|  | ||||
| static char *expect_nest1 = | ||||
| 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; | ||||
|  | ||||
| static struct json_writer nest1 = JSON_WRITER_INIT; | ||||
|  | ||||
| static void make_nest1(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&nest1, pretty); | ||||
| 	{ | ||||
| 		jw_object_sub_jw(&nest1, "obj1", &obj1); | ||||
| 		jw_object_sub_jw(&nest1, "arr1", &arr1); | ||||
| 	} | ||||
| 	jw_end(&nest1); | ||||
| } | ||||
|  | ||||
| static char *expect_inline1 = | ||||
| 	"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; | ||||
|  | ||||
| static char *pretty_inline1 = | ||||
| 	("{\n" | ||||
| 	 "  \"obj1\": {\n" | ||||
| 	 "    \"a\": \"abc\",\n" | ||||
| 	 "    \"b\": 42,\n" | ||||
| 	 "    \"c\": true\n" | ||||
| 	 "  },\n" | ||||
| 	 "  \"arr1\": [\n" | ||||
| 	 "    \"abc\",\n" | ||||
| 	 "    42,\n" | ||||
| 	 "    true\n" | ||||
| 	 "  ]\n" | ||||
| 	 "}"); | ||||
|  | ||||
| static struct json_writer inline1 = JSON_WRITER_INIT; | ||||
|  | ||||
| static void make_inline1(int pretty) | ||||
| { | ||||
| 	jw_object_begin(&inline1, pretty); | ||||
| 	{ | ||||
| 		jw_object_inline_begin_object(&inline1, "obj1"); | ||||
| 		{ | ||||
| 			jw_object_string(&inline1, "a", "abc"); | ||||
| 			jw_object_intmax(&inline1, "b", 42); | ||||
| 			jw_object_true(&inline1, "c"); | ||||
| 		} | ||||
| 		jw_end(&inline1); | ||||
| 		jw_object_inline_begin_array(&inline1, "arr1"); | ||||
| 		{ | ||||
| 			jw_array_string(&inline1, "abc"); | ||||
| 			jw_array_intmax(&inline1, 42); | ||||
| 			jw_array_true(&inline1); | ||||
| 		} | ||||
| 		jw_end(&inline1); | ||||
| 	} | ||||
| 	jw_end(&inline1); | ||||
| } | ||||
|  | ||||
| static char *expect_inline2 = | ||||
| 	"[[1,2],[3,4],{\"a\":\"abc\"}]"; | ||||
|  | ||||
| static char *pretty_inline2 = | ||||
| 	("[\n" | ||||
| 	 "  [\n" | ||||
| 	 "    1,\n" | ||||
| 	 "    2\n" | ||||
| 	 "  ],\n" | ||||
| 	 "  [\n" | ||||
| 	 "    3,\n" | ||||
| 	 "    4\n" | ||||
| 	 "  ],\n" | ||||
| 	 "  {\n" | ||||
| 	 "    \"a\": \"abc\"\n" | ||||
| 	 "  }\n" | ||||
| 	 "]"); | ||||
|  | ||||
| static struct json_writer inline2 = JSON_WRITER_INIT; | ||||
|  | ||||
| static void make_inline2(int pretty) | ||||
| { | ||||
| 	jw_array_begin(&inline2, pretty); | ||||
| 	{ | ||||
| 		jw_array_inline_begin_array(&inline2); | ||||
| 		{ | ||||
| 			jw_array_intmax(&inline2, 1); | ||||
| 			jw_array_intmax(&inline2, 2); | ||||
| 		} | ||||
| 		jw_end(&inline2); | ||||
| 		jw_array_inline_begin_array(&inline2); | ||||
| 		{ | ||||
| 			jw_array_intmax(&inline2, 3); | ||||
| 			jw_array_intmax(&inline2, 4); | ||||
| 		} | ||||
| 		jw_end(&inline2); | ||||
| 		jw_array_inline_begin_object(&inline2); | ||||
| 		{ | ||||
| 			jw_object_string(&inline2, "a", "abc"); | ||||
| 		} | ||||
| 		jw_end(&inline2); | ||||
| 	} | ||||
| 	jw_end(&inline2); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * When super is compact, we expect subs to be compacted (even if originally | ||||
|  * pretty). | ||||
|  */ | ||||
| static const char *expect_mixed1 = | ||||
| 	("{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true}," | ||||
| 	 "\"arr1\":[\"abc\",42,true]}"); | ||||
|  | ||||
| /* | ||||
|  * When super is pretty, a compact sub (obj1) is kept compact and a pretty | ||||
|  * sub (arr1) is re-indented. | ||||
|  */ | ||||
| static const char *pretty_mixed1 = | ||||
| 	("{\n" | ||||
| 	 "  \"obj1\": {\"a\":\"abc\",\"b\":42,\"c\":true},\n" | ||||
| 	 "  \"arr1\": [\n" | ||||
| 	 "    \"abc\",\n" | ||||
| 	 "    42,\n" | ||||
| 	 "    true\n" | ||||
| 	 "  ]\n" | ||||
| 	 "}"); | ||||
|  | ||||
| static struct json_writer mixed1 = JSON_WRITER_INIT; | ||||
|  | ||||
| static void make_mixed1(int pretty) | ||||
| { | ||||
| 	jw_init(&obj1); | ||||
| 	jw_init(&arr1); | ||||
|  | ||||
| 	make_obj1(0); /* obj1 is compact */ | ||||
| 	make_arr1(1); /* arr1 is pretty */ | ||||
|  | ||||
| 	jw_object_begin(&mixed1, pretty); | ||||
| 	{ | ||||
| 		jw_object_sub_jw(&mixed1, "obj1", &obj1); | ||||
| 		jw_object_sub_jw(&mixed1, "arr1", &arr1); | ||||
| 	} | ||||
| 	jw_end(&mixed1); | ||||
| } | ||||
|  | ||||
| static void cmp(const char *test, const struct json_writer *jw, const char *exp) | ||||
| { | ||||
| 	if (!strcmp(jw->json.buf, exp)) | ||||
| 		return; | ||||
|  | ||||
| 	printf("error[%s]: observed '%s' expected '%s'\n", | ||||
| 	       test, jw->json.buf, exp); | ||||
| 	exit(1); | ||||
| } | ||||
|  | ||||
| #define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0) | ||||
| #define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0) | ||||
|  | ||||
| /* | ||||
|  * Run some basic regression tests with some known patterns. | ||||
|  * These tests also demonstrate how to use the jw_ API. | ||||
|  */ | ||||
| static int unit_tests(void) | ||||
| { | ||||
| 	/* comptact (canonical) forms */ | ||||
| 	t(obj1); | ||||
| 	t(obj2); | ||||
| 	t(obj3); | ||||
| 	t(obj4); | ||||
| 	t(obj5); | ||||
| 	t(obj6); | ||||
|  | ||||
| 	t(arr1); | ||||
| 	t(arr2); | ||||
| 	t(arr3); | ||||
| 	t(arr4); | ||||
|  | ||||
| 	t(nest1); | ||||
|  | ||||
| 	t(inline1); | ||||
| 	t(inline2); | ||||
|  | ||||
| 	jw_init(&obj1); | ||||
| 	jw_init(&obj2); | ||||
| 	jw_init(&obj3); | ||||
| 	jw_init(&obj4); | ||||
|  | ||||
| 	jw_init(&arr1); | ||||
| 	jw_init(&arr2); | ||||
| 	jw_init(&arr3); | ||||
| 	jw_init(&arr4); | ||||
|  | ||||
| 	jw_init(&inline1); | ||||
| 	jw_init(&inline2); | ||||
|  | ||||
| 	/* pretty forms */ | ||||
| 	p(obj1); | ||||
| 	p(obj2); | ||||
| 	p(obj3); | ||||
| 	p(obj4); | ||||
|  | ||||
| 	p(arr1); | ||||
| 	p(arr2); | ||||
| 	p(arr3); | ||||
| 	p(arr4); | ||||
|  | ||||
| 	p(inline1); | ||||
| 	p(inline2); | ||||
|  | ||||
| 	/* mixed forms */ | ||||
| 	t(mixed1); | ||||
| 	jw_init(&mixed1); | ||||
| 	p(mixed1); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static void get_s(int line_nr, char **s_in) | ||||
| { | ||||
| 	*s_in = strtok(NULL, " "); | ||||
| 	if (!*s_in) | ||||
| 		die("line[%d]: expected: <s>", line_nr); | ||||
| } | ||||
|  | ||||
| static void get_i(int line_nr, intmax_t *s_in) | ||||
| { | ||||
| 	char *s; | ||||
| 	char *endptr; | ||||
|  | ||||
| 	get_s(line_nr, &s); | ||||
|  | ||||
| 	*s_in = strtol(s, &endptr, 10); | ||||
| 	if (*endptr || errno == ERANGE) | ||||
| 		die("line[%d]: invalid integer value", line_nr); | ||||
| } | ||||
|  | ||||
| static void get_d(int line_nr, double *s_in) | ||||
| { | ||||
| 	char *s; | ||||
| 	char *endptr; | ||||
|  | ||||
| 	get_s(line_nr, &s); | ||||
|  | ||||
| 	*s_in = strtod(s, &endptr); | ||||
| 	if (*endptr || errno == ERANGE) | ||||
| 		die("line[%d]: invalid float value", line_nr); | ||||
| } | ||||
|  | ||||
| static int pretty; | ||||
|  | ||||
| #define MAX_LINE_LENGTH (64 * 1024) | ||||
|  | ||||
| static char *get_trimmed_line(char *buf, int buf_size) | ||||
| { | ||||
| 	int len; | ||||
|  | ||||
| 	if (!fgets(buf, buf_size, stdin)) | ||||
| 		return NULL; | ||||
|  | ||||
| 	len = strlen(buf); | ||||
| 	while (len > 0) { | ||||
| 		char c = buf[len - 1]; | ||||
| 		if (c == '\n' || c == '\r' || c == ' ' || c == '\t') | ||||
| 			buf[--len] = 0; | ||||
| 		else | ||||
| 			break; | ||||
| 	} | ||||
|  | ||||
| 	while (*buf == ' ' || *buf == '\t') | ||||
| 		buf++; | ||||
|  | ||||
| 	return buf; | ||||
| } | ||||
|  | ||||
| static int scripted(void) | ||||
| { | ||||
| 	struct json_writer jw = JSON_WRITER_INIT; | ||||
| 	char buf[MAX_LINE_LENGTH]; | ||||
| 	char *line; | ||||
| 	int line_nr = 0; | ||||
|  | ||||
| 	line = get_trimmed_line(buf, MAX_LINE_LENGTH); | ||||
| 	if (!line) | ||||
| 		return 0; | ||||
|  | ||||
| 	if (!strcmp(line, "object")) | ||||
| 		jw_object_begin(&jw, pretty); | ||||
| 	else if (!strcmp(line, "array")) | ||||
| 		jw_array_begin(&jw, pretty); | ||||
| 	else | ||||
| 		die("expected first line to be 'object' or 'array'"); | ||||
|  | ||||
| 	while ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) { | ||||
| 		char *verb; | ||||
| 		char *key; | ||||
| 		char *s_value; | ||||
| 		intmax_t i_value; | ||||
| 		double d_value; | ||||
|  | ||||
| 		line_nr++; | ||||
|  | ||||
| 		verb = strtok(line, " "); | ||||
|  | ||||
| 		if (!strcmp(verb, "end")) { | ||||
| 			jw_end(&jw); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-string")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			get_s(line_nr, &s_value); | ||||
| 			jw_object_string(&jw, key, s_value); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-int")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			get_i(line_nr, &i_value); | ||||
| 			jw_object_intmax(&jw, key, i_value); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-double")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			get_i(line_nr, &i_value); | ||||
| 			get_d(line_nr, &d_value); | ||||
| 			jw_object_double(&jw, key, i_value, d_value); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-true")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			jw_object_true(&jw, key); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-false")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			jw_object_false(&jw, key); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-null")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			jw_object_null(&jw, key); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-object")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			jw_object_inline_begin_object(&jw, key); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "object-array")) { | ||||
| 			get_s(line_nr, &key); | ||||
| 			jw_object_inline_begin_array(&jw, key); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "array-string")) { | ||||
| 			get_s(line_nr, &s_value); | ||||
| 			jw_array_string(&jw, s_value); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "array-int")) { | ||||
| 			get_i(line_nr, &i_value); | ||||
| 			jw_array_intmax(&jw, i_value); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "array-double")) { | ||||
| 			get_i(line_nr, &i_value); | ||||
| 			get_d(line_nr, &d_value); | ||||
| 			jw_array_double(&jw, i_value, d_value); | ||||
| 		} | ||||
| 		else if (!strcmp(verb, "array-true")) | ||||
| 			jw_array_true(&jw); | ||||
| 		else if (!strcmp(verb, "array-false")) | ||||
| 			jw_array_false(&jw); | ||||
| 		else if (!strcmp(verb, "array-null")) | ||||
| 			jw_array_null(&jw); | ||||
| 		else if (!strcmp(verb, "array-object")) | ||||
| 			jw_array_inline_begin_object(&jw); | ||||
| 		else if (!strcmp(verb, "array-array")) | ||||
| 			jw_array_inline_begin_array(&jw); | ||||
| 		else | ||||
| 			die("unrecognized token: '%s'", verb); | ||||
| 	} | ||||
|  | ||||
| 	if (!jw_is_terminated(&jw)) | ||||
| 		die("json not terminated: '%s'", jw.json.buf); | ||||
|  | ||||
| 	printf("%s\n", jw.json.buf); | ||||
|  | ||||
| 	strbuf_release(&jw.json); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int cmd__json_writer(int argc, const char **argv) | ||||
| { | ||||
| 	argc--; /* skip over "json-writer" arg */ | ||||
| 	argv++; | ||||
|  | ||||
| 	if (argc > 0 && argv[0][0] == '-') { | ||||
| 		if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--unit")) | ||||
| 			return unit_tests(); | ||||
|  | ||||
| 		if (!strcmp(argv[0], "-p") || !strcmp(argv[0], "--pretty")) | ||||
| 			pretty = 1; | ||||
| 	} | ||||
|  | ||||
| 	return scripted(); | ||||
| } | ||||
|  | @ -19,6 +19,7 @@ static struct test_cmd cmds[] = { | |||
| 	{ "genrandom", cmd__genrandom }, | ||||
| 	{ "hashmap", cmd__hashmap }, | ||||
| 	{ "index-version", cmd__index_version }, | ||||
| 	{ "json-writer", cmd__json_writer }, | ||||
| 	{ "lazy-init-name-hash", cmd__lazy_init_name_hash }, | ||||
| 	{ "match-trees", cmd__match_trees }, | ||||
| 	{ "mergesort", cmd__mergesort }, | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ int cmd__example_decorate(int argc, const char **argv); | |||
| int cmd__genrandom(int argc, const char **argv); | ||||
| int cmd__hashmap(int argc, const char **argv); | ||||
| int cmd__index_version(int argc, const char **argv); | ||||
| int cmd__json_writer(int argc, const char **argv); | ||||
| int cmd__lazy_init_name_hash(int argc, const char **argv); | ||||
| int cmd__match_trees(int argc, const char **argv); | ||||
| int cmd__mergesort(int argc, const char **argv); | ||||
|  |  | |||
|  | @ -0,0 +1,331 @@ | |||
| #!/bin/sh | ||||
|  | ||||
| test_description='test json-writer JSON generation' | ||||
| . ./test-lib.sh | ||||
|  | ||||
| test_expect_success 'unit test of json-writer routines' ' | ||||
| 	test-tool json-writer -u | ||||
| ' | ||||
|  | ||||
| test_expect_success 'trivial object' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'trivial array' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	[] | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	array | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'simple object' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc","b":42,"c":3.14,"d":true,"e":false,"f":null} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-int b 42 | ||||
| 		object-double c 2 3.140 | ||||
| 		object-true d | ||||
| 		object-false e | ||||
| 		object-null f | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'simple array' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	["abc",42,3.14,true,false,null] | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	array | ||||
| 		array-string abc | ||||
| 		array-int 42 | ||||
| 		array-double 2 3.140 | ||||
| 		array-true | ||||
| 		array-false | ||||
| 		array-null | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'escape quoting string' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc\\def"} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc\def | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'escape quoting string 2' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc\"def"} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc"def | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'nested inline object' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":{"e":false,"f":null}}} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-int b 42 | ||||
| 		object-object sub1 | ||||
| 			object-double c 2 3.140 | ||||
| 			object-true d | ||||
| 			object-object sub2 | ||||
| 				object-false e | ||||
| 				object-null f | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'nested inline array' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	["abc",42,[3.14,true,[false,null]]] | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	array | ||||
| 		array-string abc | ||||
| 		array-int 42 | ||||
| 		array-array | ||||
| 			array-double 2 3.140 | ||||
| 			array-true | ||||
| 			array-array | ||||
| 				array-false | ||||
| 				array-null | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'nested inline object and array' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,null]}} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-int b 42 | ||||
| 		object-object sub1 | ||||
| 			object-double c 2 3.140 | ||||
| 			object-true d | ||||
| 			object-array sub2 | ||||
| 				array-false | ||||
| 				array-null | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'nested inline object and array 2' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,{"g":0,"h":1},null]}} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-int b 42 | ||||
| 		object-object sub1 | ||||
| 			object-double c 2 3.140 | ||||
| 			object-true d | ||||
| 			object-array sub2 | ||||
| 				array-false | ||||
| 				array-object | ||||
| 					object-int g 0 | ||||
| 					object-int h 1 | ||||
| 				end | ||||
| 				array-null | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'pretty nested inline object and array 2' ' | ||||
| 	sed -e "s/^|//" >expect <<-\EOF && | ||||
| 	|{ | ||||
| 	|  "a": "abc", | ||||
| 	|  "b": 42, | ||||
| 	|  "sub1": { | ||||
| 	|    "c": 3.14, | ||||
| 	|    "d": true, | ||||
| 	|    "sub2": [ | ||||
| 	|      false, | ||||
| 	|      { | ||||
| 	|        "g": 0, | ||||
| 	|        "h": 1 | ||||
| 	|      }, | ||||
| 	|      null | ||||
| 	|    ] | ||||
| 	|  } | ||||
| 	|} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-int b 42 | ||||
| 		object-object sub1 | ||||
| 			object-double c 2 3.140 | ||||
| 			object-true d | ||||
| 			object-array sub2 | ||||
| 				array-false | ||||
| 				array-object | ||||
| 					object-int g 0 | ||||
| 					object-int h 1 | ||||
| 				end | ||||
| 				array-null | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer -p <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'inline object with no members' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc","empty":{},"b":42} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-object empty | ||||
| 		end | ||||
| 		object-int b 42 | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'inline array with no members' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc","empty":[],"b":42} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-array empty | ||||
| 		end | ||||
| 		object-int b 42 | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_expect_success 'larger empty example' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	{"a":"abc","empty":[{},{},{},[],{}],"b":42} | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-array empty | ||||
| 			array-object | ||||
| 			end | ||||
| 			array-object | ||||
| 			end | ||||
| 			array-object | ||||
| 			end | ||||
| 			array-array | ||||
| 			end | ||||
| 			array-object | ||||
| 			end | ||||
| 		end | ||||
| 		object-int b 42 | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_lazy_prereq PERLJSON ' | ||||
| 	perl -MJSON -e "exit 0" | ||||
| ' | ||||
|  | ||||
| # As a sanity check, ask Perl to parse our generated JSON and recursively | ||||
| # dump the resulting data in sorted order.  Confirm that that matches our | ||||
| # expectations. | ||||
| test_expect_success PERLJSON 'parse JSON using Perl' ' | ||||
| 	cat >expect <<-\EOF && | ||||
| 	row[0].a abc | ||||
| 	row[0].b 42 | ||||
| 	row[0].sub1 hash | ||||
| 	row[0].sub1.c 3.14 | ||||
| 	row[0].sub1.d 1 | ||||
| 	row[0].sub1.sub2 array | ||||
| 	row[0].sub1.sub2[0] 0 | ||||
| 	row[0].sub1.sub2[1] hash | ||||
| 	row[0].sub1.sub2[1].g 0 | ||||
| 	row[0].sub1.sub2[1].h 1 | ||||
| 	row[0].sub1.sub2[2] null | ||||
| 	EOF | ||||
| 	cat >input <<-\EOF && | ||||
| 	object | ||||
| 		object-string a abc | ||||
| 		object-int b 42 | ||||
| 		object-object sub1 | ||||
| 			object-double c 2 3.140 | ||||
| 			object-true d | ||||
| 			object-array sub2 | ||||
| 				array-false | ||||
| 				array-object | ||||
| 					object-int g 0 | ||||
| 					object-int h 1 | ||||
| 				end | ||||
| 				array-null | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	EOF | ||||
| 	test-tool json-writer <input >output.json && | ||||
| 	perl "$TEST_DIRECTORY"/t0019/parse_json.perl <output.json >actual && | ||||
| 	test_cmp expect actual | ||||
| ' | ||||
|  | ||||
| test_done | ||||
|  | @ -0,0 +1,52 @@ | |||
| #!/usr/bin/perl | ||||
| use strict; | ||||
| use warnings; | ||||
| use JSON; | ||||
|  | ||||
| sub dump_array { | ||||
|     my ($label_in, $ary_ref) = @_; | ||||
|     my @ary = @$ary_ref; | ||||
|  | ||||
|     for ( my $i = 0; $i <= $#{ $ary_ref }; $i++ ) | ||||
|     { | ||||
| 	my $label = "$label_in\[$i\]"; | ||||
| 	dump_item($label, $ary[$i]); | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub dump_hash { | ||||
|     my ($label_in, $obj_ref) = @_; | ||||
|     my %obj = %$obj_ref; | ||||
|  | ||||
|     foreach my $k (sort keys %obj) { | ||||
| 	my $label = (length($label_in) > 0) ? "$label_in.$k" : "$k"; | ||||
| 	my $value = $obj{$k}; | ||||
|  | ||||
| 	dump_item($label, $value); | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub dump_item { | ||||
|     my ($label_in, $value) = @_; | ||||
|     if (ref($value) eq 'ARRAY') { | ||||
| 	print "$label_in array\n"; | ||||
| 	dump_array($label_in, $value); | ||||
|     } elsif (ref($value) eq 'HASH') { | ||||
| 	print "$label_in hash\n"; | ||||
| 	dump_hash($label_in, $value); | ||||
|     } elsif (defined $value) { | ||||
| 	print "$label_in $value\n"; | ||||
|     } else { | ||||
| 	print "$label_in null\n"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| my $row = 0; | ||||
| while (<>) { | ||||
|     my $data = decode_json( $_ ); | ||||
|     my $label = "row[$row]"; | ||||
|  | ||||
|     dump_hash($label, $data); | ||||
|     $row++; | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	 Jeff Hostetler
						Jeff Hostetler