{"thread":{"id":"48704","subject":"[PATCH v10] json_writer V10","startedAt":"2018-06-13T13:14:46Z","lastAt":"2018-06-13T13:14:48Z","messageCount":2,"participants":["git@jeffhostetler.com"],"isPatch":true,"patchVersion":10,"patchTotal":null},"messages":[{"id":"350054","messageId":"20180613131430.13674-1-git@jeffhostetler.com","threadId":"48704","inReplyTo":null,"subject":"[PATCH v10] json_writer V10","fromName":"","fromEmail":"git@jeffhostetler.com","sentAt":"2018-06-13T13:14:29Z","receivedAt":"2018-06-13T13:14:46Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nHere is V10 of my json-writer patch.  I fixed the DEVELOPER=1 warnings\nthat Eric pointed out and refactored the indent_pretty() code as Junio\nsuggested.\n\nJeff Hostetler (1):\n  json_writer: new routines to create JSON data\n\n Makefile                    |   2 +\n json-writer.c               | 414 ++++++++++++++++++++++++++++++++\n json-writer.h               | 105 ++++++++\n t/helper/test-json-writer.c | 565 ++++++++++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c        |   1 +\n t/helper/test-tool.h        |   1 +\n t/t0019-json-writer.sh      | 331 ++++++++++++++++++++++++++\n t/t0019/parse_json.perl     |  52 ++++\n 8 files changed, 1471 insertions(+)\n create mode 100644 json-writer.c\n create mode 100644 json-writer.h\n create mode 100644 t/helper/test-json-writer.c\n create mode 100755 t/t0019-json-writer.sh\n create mode 100644 t/t0019/parse_json.perl\n\n-- \n2.9.3\n\n"},{"id":"350055","messageId":"20180613131430.13674-2-git@jeffhostetler.com","threadId":"48704","inReplyTo":"20180613131430.13674-1-git@jeffhostetler.com","subject":"[PATCH v10] json_writer: new routines to create JSON data","fromName":"","fromEmail":"git@jeffhostetler.com","sentAt":"2018-06-13T13:14:30Z","receivedAt":"2018-06-13T13:14:48Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd \"struct json_writer\" and a series of jw_ routines to compose JSON\ndata into a string buffer.  The resulting string may then be printed by\ncommands wanting to support a JSON-like output format.\n\nThe json_writer is limited to correctly formatting structured data for\noutput.  It does not attempt to build an object model of the JSON data.\n\nWe say \"JSON-like\" because we do not enforce the Unicode (usually UTF-8)\nrequirement on string fields.  Internally, Git does not necessarily have\nUnicode/UTF-8 data for most fields, so it is currently unclear the best\nway to enforce that requirement.  For example, on Linux pathnames can\ncontain arbitrary 8-bit character data, so a command like \"status\" would\nnot know how to encode the reported pathnames.  We may want to revisit\nthis (or double encode such strings) in the future.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: René Scharfe <l.s.r@web.de>\nHelped-by: Wink Saville <wink@saville.com>\nHelped-by: Ramsay Jones <ramsay@ramsayjones.plus.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                    |   2 +\n json-writer.c               | 414 ++++++++++++++++++++++++++++++++\n json-writer.h               | 105 ++++++++\n t/helper/test-json-writer.c | 565 ++++++++++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c        |   1 +\n t/helper/test-tool.h        |   1 +\n t/t0019-json-writer.sh      | 331 ++++++++++++++++++++++++++\n t/t0019/parse_json.perl     |  52 ++++\n 8 files changed, 1471 insertions(+)\n create mode 100644 json-writer.c\n create mode 100644 json-writer.h\n create mode 100644 t/helper/test-json-writer.c\n create mode 100755 t/t0019-json-writer.sh\n create mode 100644 t/t0019/parse_json.perl\n\ndiff --git a/Makefile b/Makefile\nindex 1d27f36..5a781e2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -709,6 +709,7 @@ TEST_BUILTINS_OBJS += test-example-decorate.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-hashmap.o\n TEST_BUILTINS_OBJS += test-index-version.o\n+TEST_BUILTINS_OBJS += test-json-writer.o\n TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o\n TEST_BUILTINS_OBJS += test-match-trees.o\n TEST_BUILTINS_OBJS += test-mergesort.o\n@@ -871,6 +872,7 @@ LIB_OBJS += hashmap.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\n+LIB_OBJS += json-writer.o\n LIB_OBJS += kwset.o\n LIB_OBJS += levenshtein.o\n LIB_OBJS += line-log.o\ndiff --git a/json-writer.c b/json-writer.c\nnew file mode 100644\nindex 0000000..115de3e\n--- /dev/null\n+++ b/json-writer.c\n@@ -0,0 +1,414 @@\n+#include \"cache.h\"\n+#include \"json-writer.h\"\n+\n+void jw_init(struct json_writer *jw)\n+{\n+\tstrbuf_reset(&jw->json);\n+\tstrbuf_reset(&jw->open_stack);\n+\tjw->need_comma = 0;\n+\tjw->pretty = 0;\n+}\n+\n+void jw_release(struct json_writer *jw)\n+{\n+\tstrbuf_release(&jw->json);\n+\tstrbuf_release(&jw->open_stack);\n+}\n+\n+/*\n+ * Append JSON-quoted version of the given string to 'out'.\n+ */\n+static void append_quoted_string(struct strbuf *out, const char *in)\n+{\n+\tunsigned char c;\n+\n+\tstrbuf_addch(out, '\"');\n+\twhile ((c = *in++) != '\\0') {\n+\t\tif (c == '\"')\n+\t\t\tstrbuf_addstr(out, \"\\\\\\\"\");\n+\t\telse if (c == '\\\\')\n+\t\t\tstrbuf_addstr(out, \"\\\\\\\\\");\n+\t\telse if (c == '\\n')\n+\t\t\tstrbuf_addstr(out, \"\\\\n\");\n+\t\telse if (c == '\\r')\n+\t\t\tstrbuf_addstr(out, \"\\\\r\");\n+\t\telse if (c == '\\t')\n+\t\t\tstrbuf_addstr(out, \"\\\\t\");\n+\t\telse if (c == '\\f')\n+\t\t\tstrbuf_addstr(out, \"\\\\f\");\n+\t\telse if (c == '\\b')\n+\t\t\tstrbuf_addstr(out, \"\\\\b\");\n+\t\telse if (c < 0x20)\n+\t\t\tstrbuf_addf(out, \"\\\\u%04x\", c);\n+\t\telse\n+\t\t\tstrbuf_addch(out, c);\n+\t}\n+\tstrbuf_addch(out, '\"');\n+}\n+\n+static void indent_pretty(struct json_writer *jw)\n+{\n+\tint k;\n+\n+\tfor (k = 0; k < jw->open_stack.len; k++)\n+\t\tstrbuf_addstr(&jw->json, \"  \");\n+}\n+\n+/*\n+ * Begin an object or array (either top-level or nested within the currently\n+ * open object or array).\n+ */\n+static void begin(struct json_writer *jw, char ch_open, int pretty)\n+{\n+\tjw->pretty = pretty;\n+\n+\tstrbuf_addch(&jw->json, ch_open);\n+\n+\tstrbuf_addch(&jw->open_stack, ch_open);\n+\tjw->need_comma = 0;\n+}\n+\n+/*\n+ * Assert that the top of the open-stack is an object.\n+ */\n+static void assert_in_object(const struct json_writer *jw, const char *key)\n+{\n+\tif (!jw->open_stack.len)\n+\t\tBUG(\"json-writer: object: missing jw_object_begin(): '%s'\", key);\n+\tif (jw->open_stack.buf[jw->open_stack.len - 1] != '{')\n+\t\tBUG(\"json-writer: object: not in object: '%s'\", key);\n+}\n+\n+/*\n+ * Assert that the top of the open-stack is an array.\n+ */\n+static void assert_in_array(const struct json_writer *jw)\n+{\n+\tif (!jw->open_stack.len)\n+\t\tBUG(\"json-writer: array: missing jw_array_begin()\");\n+\tif (jw->open_stack.buf[jw->open_stack.len - 1] != '[')\n+\t\tBUG(\"json-writer: array: not in array\");\n+}\n+\n+/*\n+ * Add comma if we have already seen a member at this level.\n+ */\n+static void maybe_add_comma(struct json_writer *jw)\n+{\n+\tif (jw->need_comma)\n+\t\tstrbuf_addch(&jw->json, ',');\n+\telse\n+\t\tjw->need_comma = 1;\n+}\n+\n+static void fmt_double(struct json_writer *jw, int precision,\n+\t\t\t      double value)\n+{\n+\tif (precision < 0) {\n+\t\tstrbuf_addf(&jw->json, \"%f\", value);\n+\t} else {\n+\t\tstruct strbuf fmt = STRBUF_INIT;\n+\t\tstrbuf_addf(&fmt, \"%%.%df\", precision);\n+\t\tstrbuf_addf(&jw->json, fmt.buf, value);\n+\t\tstrbuf_release(&fmt);\n+\t}\n+}\n+\n+static void object_common(struct json_writer *jw, const char *key)\n+{\n+\tassert_in_object(jw, key);\n+\tmaybe_add_comma(jw);\n+\n+\tif (jw->pretty) {\n+\t\tstrbuf_addch(&jw->json, '\\n');\n+\t\tindent_pretty(jw);\n+\t}\n+\n+\tappend_quoted_string(&jw->json, key);\n+\tstrbuf_addch(&jw->json, ':');\n+\tif (jw->pretty)\n+\t\tstrbuf_addch(&jw->json, ' ');\n+}\n+\n+static void array_common(struct json_writer *jw)\n+{\n+\tassert_in_array(jw);\n+\tmaybe_add_comma(jw);\n+\n+\tif (jw->pretty) {\n+\t\tstrbuf_addch(&jw->json, '\\n');\n+\t\tindent_pretty(jw);\n+\t}\n+}\n+\n+/*\n+ * Assert that the given JSON object or JSON array has been properly\n+ * terminated.  (Has closing bracket.)\n+ */\n+static void assert_is_terminated(const struct json_writer *jw)\n+{\n+\tif (jw->open_stack.len)\n+\t\tBUG(\"json-writer: object: missing jw_end(): '%s'\",\n+\t\t    jw->json.buf);\n+}\n+\n+void jw_object_begin(struct json_writer *jw, int pretty)\n+{\n+\tbegin(jw, '{', pretty);\n+}\n+\n+void jw_object_string(struct json_writer *jw, const char *key, const char *value)\n+{\n+\tobject_common(jw, key);\n+\tappend_quoted_string(&jw->json, value);\n+}\n+\n+void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addf(&jw->json, \"%\"PRIdMAX, value);\n+}\n+\n+void jw_object_double(struct json_writer *jw, const char *key, int precision,\n+\t\t      double value)\n+{\n+\tobject_common(jw, key);\n+\tfmt_double(jw, precision, value);\n+}\n+\n+void jw_object_true(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addstr(&jw->json, \"true\");\n+}\n+\n+void jw_object_false(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addstr(&jw->json, \"false\");\n+}\n+\n+void jw_object_bool(struct json_writer *jw, const char *key, int value)\n+{\n+\tif (value)\n+\t\tjw_object_true(jw, key);\n+\telse\n+\t\tjw_object_false(jw, key);\n+}\n+\n+void jw_object_null(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addstr(&jw->json, \"null\");\n+}\n+\n+static void increase_indent(struct strbuf *sb,\n+\t\t\t    const struct json_writer *jw,\n+\t\t\t    int indent)\n+{\n+\tint k;\n+\n+\tstrbuf_reset(sb);\n+\tfor (k = 0; k < jw->json.len; k++) {\n+\t\tchar ch = jw->json.buf[k];\n+\t\tstrbuf_addch(sb, ch);\n+\t\tif (ch == '\\n')\n+\t\t\tstrbuf_addchars(sb, ' ', indent);\n+\t}\n+}\n+\n+static void kill_indent(struct strbuf *sb,\n+\t\t\tconst struct json_writer *jw)\n+{\n+\tint k;\n+\tint eat_it = 0;\n+\n+\tstrbuf_reset(sb);\n+\tfor (k = 0; k < jw->json.len; k++) {\n+\t\tchar ch = jw->json.buf[k];\n+\t\tif (eat_it && ch == ' ')\n+\t\t\tcontinue;\n+\t\tif (ch == '\\n') {\n+\t\t\teat_it = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\teat_it = 0;\n+\t\tstrbuf_addch(sb, ch);\n+\t}\n+}\n+\n+static void append_sub_jw(struct json_writer *jw,\n+\t\t\t  const struct json_writer *value)\n+{\n+\t/*\n+\t * If both are pretty, increase the indentation of the sub_jw\n+\t * to better fit under the super.\n+\t *\n+\t * If the super is pretty, but the sub_jw is compact, leave the\n+\t * sub_jw compact.  (We don't want to parse and rebuild the sub_jw\n+\t * for this debug-ish feature.)\n+\t *\n+\t * If the super is compact, and the sub_jw is pretty, convert\n+\t * the sub_jw to compact.\n+\t *\n+\t * If both are compact, keep the sub_jw compact.\n+\t */\n+\tif (jw->pretty && jw->open_stack.len && value->pretty) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tincrease_indent(&sb, value, jw->open_stack.len * 2);\n+\t\tstrbuf_addbuf(&jw->json, &sb);\n+\t\tstrbuf_release(&sb);\n+\t\treturn;\n+\t}\n+\tif (!jw->pretty && value->pretty) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tkill_indent(&sb, value);\n+\t\tstrbuf_addbuf(&jw->json, &sb);\n+\t\tstrbuf_release(&sb);\n+\t\treturn;\n+\t}\n+\n+\tstrbuf_addbuf(&jw->json, &value->json);\n+}\n+\n+/*\n+ * Append existing (properly terminated) JSON sub-data (object or array)\n+ * as-is onto the given JSON data.\n+ */\n+void jw_object_sub_jw(struct json_writer *jw, const char *key,\n+\t\t      const struct json_writer *value)\n+{\n+\tassert_is_terminated(value);\n+\n+\tobject_common(jw, key);\n+\tappend_sub_jw(jw, value);\n+}\n+\n+void jw_object_inline_begin_object(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\n+\tjw_object_begin(jw, jw->pretty);\n+}\n+\n+void jw_object_inline_begin_array(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\n+\tjw_array_begin(jw, jw->pretty);\n+}\n+\n+void jw_array_begin(struct json_writer *jw, int pretty)\n+{\n+\tbegin(jw, '[', pretty);\n+}\n+\n+void jw_array_string(struct json_writer *jw, const char *value)\n+{\n+\tarray_common(jw);\n+\tappend_quoted_string(&jw->json, value);\n+}\n+\n+void jw_array_intmax(struct json_writer *jw, intmax_t value)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addf(&jw->json, \"%\"PRIdMAX, value);\n+}\n+\n+void jw_array_double(struct json_writer *jw, int precision, double value)\n+{\n+\tarray_common(jw);\n+\tfmt_double(jw, precision, value);\n+}\n+\n+void jw_array_true(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addstr(&jw->json, \"true\");\n+}\n+\n+void jw_array_false(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addstr(&jw->json, \"false\");\n+}\n+\n+void jw_array_bool(struct json_writer *jw, int value)\n+{\n+\tif (value)\n+\t\tjw_array_true(jw);\n+\telse\n+\t\tjw_array_false(jw);\n+}\n+\n+void jw_array_null(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addstr(&jw->json, \"null\");\n+}\n+\n+void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value)\n+{\n+\tassert_is_terminated(value);\n+\n+\tarray_common(jw);\n+\tappend_sub_jw(jw, value);\n+}\n+\n+void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\tjw_array_string(jw, argv[k]);\n+}\n+\n+void jw_array_argv(struct json_writer *jw, const char **argv)\n+{\n+\twhile (*argv)\n+\t\tjw_array_string(jw, *argv++);\n+}\n+\n+void jw_array_inline_begin_object(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\n+\tjw_object_begin(jw, jw->pretty);\n+}\n+\n+void jw_array_inline_begin_array(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\n+\tjw_array_begin(jw, jw->pretty);\n+}\n+\n+int jw_is_terminated(const struct json_writer *jw)\n+{\n+\treturn !jw->open_stack.len;\n+}\n+\n+void jw_end(struct json_writer *jw)\n+{\n+\tchar ch_open;\n+\tint len;\n+\n+\tif (!jw->open_stack.len)\n+\t\tBUG(\"json-writer: too many jw_end(): '%s'\", jw->json.buf);\n+\n+\tlen = jw->open_stack.len - 1;\n+\tch_open = jw->open_stack.buf[len];\n+\n+\tstrbuf_setlen(&jw->open_stack, len);\n+\tjw->need_comma = 1;\n+\n+\tif (jw->pretty) {\n+\t\tstrbuf_addch(&jw->json, '\\n');\n+\t\tindent_pretty(jw);\n+\t}\n+\n+\tif (ch_open == '{')\n+\t\tstrbuf_addch(&jw->json, '}');\n+\telse\n+\t\tstrbuf_addch(&jw->json, ']');\n+}\ndiff --git a/json-writer.h b/json-writer.h\nnew file mode 100644\nindex 0000000..fc18acc\n--- /dev/null\n+++ b/json-writer.h\n@@ -0,0 +1,105 @@\n+#ifndef JSON_WRITER_H\n+#define JSON_WRITER_H\n+\n+/*\n+ * JSON data structures are defined at:\n+ * [1] http://www.ietf.org/rfc/rfc7159.txt\n+ * [2] http://json.org/\n+ *\n+ * The JSON-writer API allows one to build JSON data structures using a\n+ * simple wrapper around a \"struct strbuf\" buffer.  It is intended as a\n+ * simple API to build output strings; it is not intended to be a general\n+ * object model for JSON data.  In particular, it does not re-order keys\n+ * in an object (dictionary), it does not de-dup keys in an object, and\n+ * it does not allow lookup or parsing of JSON data.\n+ *\n+ * All string values (both keys and string r-values) are properly quoted\n+ * and escaped if they contain special characters.\n+ *\n+ * These routines create compact JSON data (with no unnecessary whitespace,\n+ * newlines, or indenting).  If you get an unexpected response, verify\n+ * that you're not expecting a pretty JSON string.\n+ *\n+ * Both \"JSON objects\" (aka sets of k/v pairs) and \"JSON array\" can be\n+ * constructed using a 'begin append* end' model.\n+ *\n+ * Nested objects and arrays can either be constructed bottom up (by\n+ * creating sub object/arrays first and appending them to the super\n+ * object/array) -or- by building them inline in one pass.  This is a\n+ * personal style and/or data shape choice.\n+ *\n+ * See t/helper/test-json-writer.c for various usage examples.\n+ *\n+ * LIMITATIONS:\n+ * ============\n+ *\n+ * The JSON specification [1,2] defines string values as Unicode data\n+ * and probably UTF-8 encoded.  The current json-writer API does not\n+ * enforce this and will write any string as received.  However, it will\n+ * properly quote and backslash-escape them as necessary.  It is up to\n+ * the caller to UTF-8 encode their strings *before* passing them to this\n+ * API.  This layer should not have to try to guess the encoding or locale\n+ * of the given strings.\n+ */\n+\n+struct json_writer\n+{\n+\t/*\n+\t * Buffer of the in-progress JSON currently being composed.\n+\t */\n+\tstruct strbuf json;\n+\n+\t/*\n+\t * Simple stack of the currently open array and object forms.\n+\t * This is a string of '{' and '[' characters indicating the\n+\t * currently unterminated forms.  This is used to ensure the\n+\t * properly closing character is used when popping a level and\n+\t * to know when the JSON is completely closed.\n+\t */\n+\tstruct strbuf open_stack;\n+\n+\tunsigned int need_comma:1;\n+\tunsigned int pretty:1;\n+};\n+\n+#define JSON_WRITER_INIT { STRBUF_INIT, STRBUF_INIT, 0, 0 }\n+\n+void jw_init(struct json_writer *jw);\n+void jw_release(struct json_writer *jw);\n+\n+void jw_object_begin(struct json_writer *jw, int pretty);\n+void jw_array_begin(struct json_writer *jw, int pretty);\n+\n+void jw_object_string(struct json_writer *jw, const char *key,\n+\t\t      const char *value);\n+void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value);\n+void jw_object_double(struct json_writer *jw, const char *key, int precision,\n+\t\t      double value);\n+void jw_object_true(struct json_writer *jw, const char *key);\n+void jw_object_false(struct json_writer *jw, const char *key);\n+void jw_object_bool(struct json_writer *jw, const char *key, int value);\n+void jw_object_null(struct json_writer *jw, const char *key);\n+void jw_object_sub_jw(struct json_writer *jw, const char *key,\n+\t\t      const struct json_writer *value);\n+\n+void jw_object_inline_begin_object(struct json_writer *jw, const char *key);\n+void jw_object_inline_begin_array(struct json_writer *jw, const char *key);\n+\n+void jw_array_string(struct json_writer *jw, const char *value);\n+void jw_array_intmax(struct json_writer *jw, intmax_t value);\n+void jw_array_double(struct json_writer *jw, int precision, double value);\n+void jw_array_true(struct json_writer *jw);\n+void jw_array_false(struct json_writer *jw);\n+void jw_array_bool(struct json_writer *jw, int value);\n+void jw_array_null(struct json_writer *jw);\n+void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value);\n+void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv);\n+void jw_array_argv(struct json_writer *jw, const char **argv);\n+\n+void jw_array_inline_begin_object(struct json_writer *jw);\n+void jw_array_inline_begin_array(struct json_writer *jw);\n+\n+int jw_is_terminated(const struct json_writer *jw);\n+void jw_end(struct json_writer *jw);\n+\n+#endif /* JSON_WRITER_H */\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nnew file mode 100644\nindex 0000000..37c4525\n--- /dev/null\n+++ b/t/helper/test-json-writer.c\n@@ -0,0 +1,565 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"json-writer.h\"\n+\n+static const char *expect_obj1 = \"{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true}\";\n+static const char *expect_obj2 = \"{\\\"a\\\":-1,\\\"b\\\":2147483647,\\\"c\\\":0}\";\n+static const char *expect_obj3 = \"{\\\"a\\\":0,\\\"b\\\":4294967295,\\\"c\\\":9223372036854775807}\";\n+static const char *expect_obj4 = \"{\\\"t\\\":true,\\\"f\\\":false,\\\"n\\\":null}\";\n+static const char *expect_obj5 = \"{\\\"abc\\\\tdef\\\":\\\"abc\\\\\\\\def\\\"}\";\n+static const char *expect_obj6 = \"{\\\"a\\\":3.14}\";\n+\n+static const char *pretty_obj1 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"a\\\": \\\"abc\\\",\\n\"\n+\t\t\t\t  \"  \\\"b\\\": 42,\\n\"\n+\t\t\t\t  \"  \\\"c\\\": true\\n\"\n+\t\t\t\t  \"}\");\n+static const char *pretty_obj2 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"a\\\": -1,\\n\"\n+\t\t\t\t  \"  \\\"b\\\": 2147483647,\\n\"\n+\t\t\t\t  \"  \\\"c\\\": 0\\n\"\n+\t\t\t\t  \"}\");\n+static const char *pretty_obj3 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"a\\\": 0,\\n\"\n+\t\t\t\t  \"  \\\"b\\\": 4294967295,\\n\"\n+\t\t\t\t  \"  \\\"c\\\": 9223372036854775807\\n\"\n+\t\t\t\t  \"}\");\n+static const char *pretty_obj4 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"t\\\": true,\\n\"\n+\t\t\t\t  \"  \\\"f\\\": false,\\n\"\n+\t\t\t\t  \"  \\\"n\\\": null\\n\"\n+\t\t\t\t  \"}\");\n+\n+static struct json_writer obj1 = JSON_WRITER_INIT;\n+static struct json_writer obj2 = JSON_WRITER_INIT;\n+static struct json_writer obj3 = JSON_WRITER_INIT;\n+static struct json_writer obj4 = JSON_WRITER_INIT;\n+static struct json_writer obj5 = JSON_WRITER_INIT;\n+static struct json_writer obj6 = JSON_WRITER_INIT;\n+\n+static void make_obj1(int pretty)\n+{\n+\tjw_object_begin(&obj1, pretty);\n+\t{\n+\t\tjw_object_string(&obj1, \"a\", \"abc\");\n+\t\tjw_object_intmax(&obj1, \"b\", 42);\n+\t\tjw_object_true(&obj1, \"c\");\n+\t}\n+\tjw_end(&obj1);\n+}\n+\n+static void make_obj2(int pretty)\n+{\n+\tjw_object_begin(&obj2, pretty);\n+\t{\n+\t\tjw_object_intmax(&obj2, \"a\", -1);\n+\t\tjw_object_intmax(&obj2, \"b\", 0x7fffffff);\n+\t\tjw_object_intmax(&obj2, \"c\", 0);\n+\t}\n+\tjw_end(&obj2);\n+}\n+\n+static void make_obj3(int pretty)\n+{\n+\tjw_object_begin(&obj3, pretty);\n+\t{\n+\t\tjw_object_intmax(&obj3, \"a\", 0);\n+\t\tjw_object_intmax(&obj3, \"b\", 0xffffffff);\n+\t\tjw_object_intmax(&obj3, \"c\", 0x7fffffffffffffffULL);\n+\t}\n+\tjw_end(&obj3);\n+}\n+\n+static void make_obj4(int pretty)\n+{\n+\tjw_object_begin(&obj4, pretty);\n+\t{\n+\t\tjw_object_true(&obj4, \"t\");\n+\t\tjw_object_false(&obj4, \"f\");\n+\t\tjw_object_null(&obj4, \"n\");\n+\t}\n+\tjw_end(&obj4);\n+}\n+\n+static void make_obj5(int pretty)\n+{\n+\tjw_object_begin(&obj5, pretty);\n+\t{\n+\t\tjw_object_string(&obj5, \"abc\" \"\\x09\" \"def\", \"abc\" \"\\\\\" \"def\");\n+\t}\n+\tjw_end(&obj5);\n+}\n+\n+static void make_obj6(int pretty)\n+{\n+\tjw_object_begin(&obj6, pretty);\n+\t{\n+\t\tjw_object_double(&obj6, \"a\", 2, 3.14159);\n+\t}\n+\tjw_end(&obj6);\n+}\n+\n+static const char *expect_arr1 = \"[\\\"abc\\\",42,true]\";\n+static const char *expect_arr2 = \"[-1,2147483647,0]\";\n+static const char *expect_arr3 = \"[0,4294967295,9223372036854775807]\";\n+static const char *expect_arr4 = \"[true,false,null]\";\n+\n+static const char *pretty_arr1 = (\"[\\n\"\n+\t\t\t\t  \"  \\\"abc\\\",\\n\"\n+\t\t\t\t  \"  42,\\n\"\n+\t\t\t\t  \"  true\\n\"\n+\t\t\t\t  \"]\");\n+static const char *pretty_arr2 = (\"[\\n\"\n+\t\t\t\t  \"  -1,\\n\"\n+\t\t\t\t  \"  2147483647,\\n\"\n+\t\t\t\t  \"  0\\n\"\n+\t\t\t\t  \"]\");\n+static const char *pretty_arr3 = (\"[\\n\"\n+\t\t\t\t  \"  0,\\n\"\n+\t\t\t\t  \"  4294967295,\\n\"\n+\t\t\t\t  \"  9223372036854775807\\n\"\n+\t\t\t\t  \"]\");\n+static const char *pretty_arr4 = (\"[\\n\"\n+\t\t\t\t  \"  true,\\n\"\n+\t\t\t\t  \"  false,\\n\"\n+\t\t\t\t  \"  null\\n\"\n+\t\t\t\t  \"]\");\n+\n+static struct json_writer arr1 = JSON_WRITER_INIT;\n+static struct json_writer arr2 = JSON_WRITER_INIT;\n+static struct json_writer arr3 = JSON_WRITER_INIT;\n+static struct json_writer arr4 = JSON_WRITER_INIT;\n+\n+static void make_arr1(int pretty)\n+{\n+\tjw_array_begin(&arr1, pretty);\n+\t{\n+\t\tjw_array_string(&arr1, \"abc\");\n+\t\tjw_array_intmax(&arr1, 42);\n+\t\tjw_array_true(&arr1);\n+\t}\n+\tjw_end(&arr1);\n+}\n+\n+static void make_arr2(int pretty)\n+{\n+\tjw_array_begin(&arr2, pretty);\n+\t{\n+\t\tjw_array_intmax(&arr2, -1);\n+\t\tjw_array_intmax(&arr2, 0x7fffffff);\n+\t\tjw_array_intmax(&arr2, 0);\n+\t}\n+\tjw_end(&arr2);\n+}\n+\n+static void make_arr3(int pretty)\n+{\n+\tjw_array_begin(&arr3, pretty);\n+\t{\n+\t\tjw_array_intmax(&arr3, 0);\n+\t\tjw_array_intmax(&arr3, 0xffffffff);\n+\t\tjw_array_intmax(&arr3, 0x7fffffffffffffffULL);\n+\t}\n+\tjw_end(&arr3);\n+}\n+\n+static void make_arr4(int pretty)\n+{\n+\tjw_array_begin(&arr4, pretty);\n+\t{\n+\t\tjw_array_true(&arr4);\n+\t\tjw_array_false(&arr4);\n+\t\tjw_array_null(&arr4);\n+\t}\n+\tjw_end(&arr4);\n+}\n+\n+static char *expect_nest1 =\n+\t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n+\n+static struct json_writer nest1 = JSON_WRITER_INIT;\n+\n+static void make_nest1(int pretty)\n+{\n+\tjw_object_begin(&nest1, pretty);\n+\t{\n+\t\tjw_object_sub_jw(&nest1, \"obj1\", &obj1);\n+\t\tjw_object_sub_jw(&nest1, \"arr1\", &arr1);\n+\t}\n+\tjw_end(&nest1);\n+}\n+\n+static char *expect_inline1 =\n+\t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n+\n+static char *pretty_inline1 =\n+\t(\"{\\n\"\n+\t \"  \\\"obj1\\\": {\\n\"\n+\t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n+\t \"    \\\"b\\\": 42,\\n\"\n+\t \"    \\\"c\\\": true\\n\"\n+\t \"  },\\n\"\n+\t \"  \\\"arr1\\\": [\\n\"\n+\t \"    \\\"abc\\\",\\n\"\n+\t \"    42,\\n\"\n+\t \"    true\\n\"\n+\t \"  ]\\n\"\n+\t \"}\");\n+\n+static struct json_writer inline1 = JSON_WRITER_INIT;\n+\n+static void make_inline1(int pretty)\n+{\n+\tjw_object_begin(&inline1, pretty);\n+\t{\n+\t\tjw_object_inline_begin_object(&inline1, \"obj1\");\n+\t\t{\n+\t\t\tjw_object_string(&inline1, \"a\", \"abc\");\n+\t\t\tjw_object_intmax(&inline1, \"b\", 42);\n+\t\t\tjw_object_true(&inline1, \"c\");\n+\t\t}\n+\t\tjw_end(&inline1);\n+\t\tjw_object_inline_begin_array(&inline1, \"arr1\");\n+\t\t{\n+\t\t\tjw_array_string(&inline1, \"abc\");\n+\t\t\tjw_array_intmax(&inline1, 42);\n+\t\t\tjw_array_true(&inline1);\n+\t\t}\n+\t\tjw_end(&inline1);\n+\t}\n+\tjw_end(&inline1);\n+}\n+\n+static char *expect_inline2 =\n+\t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n+\n+static char *pretty_inline2 =\n+\t(\"[\\n\"\n+\t \"  [\\n\"\n+\t \"    1,\\n\"\n+\t \"    2\\n\"\n+\t \"  ],\\n\"\n+\t \"  [\\n\"\n+\t \"    3,\\n\"\n+\t \"    4\\n\"\n+\t \"  ],\\n\"\n+\t \"  {\\n\"\n+\t \"    \\\"a\\\": \\\"abc\\\"\\n\"\n+\t \"  }\\n\"\n+\t \"]\");\n+\n+static struct json_writer inline2 = JSON_WRITER_INIT;\n+\n+static void make_inline2(int pretty)\n+{\n+\tjw_array_begin(&inline2, pretty);\n+\t{\n+\t\tjw_array_inline_begin_array(&inline2);\n+\t\t{\n+\t\t\tjw_array_intmax(&inline2, 1);\n+\t\t\tjw_array_intmax(&inline2, 2);\n+\t\t}\n+\t\tjw_end(&inline2);\n+\t\tjw_array_inline_begin_array(&inline2);\n+\t\t{\n+\t\t\tjw_array_intmax(&inline2, 3);\n+\t\t\tjw_array_intmax(&inline2, 4);\n+\t\t}\n+\t\tjw_end(&inline2);\n+\t\tjw_array_inline_begin_object(&inline2);\n+\t\t{\n+\t\t\tjw_object_string(&inline2, \"a\", \"abc\");\n+\t\t}\n+\t\tjw_end(&inline2);\n+\t}\n+\tjw_end(&inline2);\n+}\n+\n+/*\n+ * When super is compact, we expect subs to be compacted (even if originally\n+ * pretty).\n+ */\n+static const char *expect_mixed1 =\n+\t(\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\"\n+\t \"\\\"arr1\\\":[\\\"abc\\\",42,true]}\");\n+\n+/*\n+ * When super is pretty, a compact sub (obj1) is kept compact and a pretty\n+ * sub (arr1) is re-indented.\n+ */\n+static const char *pretty_mixed1 =\n+\t(\"{\\n\"\n+\t \"  \\\"obj1\\\": {\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\n\"\n+\t \"  \\\"arr1\\\": [\\n\"\n+\t \"    \\\"abc\\\",\\n\"\n+\t \"    42,\\n\"\n+\t \"    true\\n\"\n+\t \"  ]\\n\"\n+\t \"}\");\n+\n+static struct json_writer mixed1 = JSON_WRITER_INIT;\n+\n+static void make_mixed1(int pretty)\n+{\n+\tjw_init(&obj1);\n+\tjw_init(&arr1);\n+\n+\tmake_obj1(0); /* obj1 is compact */\n+\tmake_arr1(1); /* arr1 is pretty */\n+\n+\tjw_object_begin(&mixed1, pretty);\n+\t{\n+\t\tjw_object_sub_jw(&mixed1, \"obj1\", &obj1);\n+\t\tjw_object_sub_jw(&mixed1, \"arr1\", &arr1);\n+\t}\n+\tjw_end(&mixed1);\n+}\n+\n+static void cmp(const char *test, const struct json_writer *jw, const char *exp)\n+{\n+\tif (!strcmp(jw->json.buf, exp))\n+\t\treturn;\n+\n+\tprintf(\"error[%s]: observed '%s' expected '%s'\\n\",\n+\t       test, jw->json.buf, exp);\n+\texit(1);\n+}\n+\n+#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0)\n+#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0)\n+\n+/*\n+ * Run some basic regression tests with some known patterns.\n+ * These tests also demonstrate how to use the jw_ API.\n+ */\n+static int unit_tests(void)\n+{\n+\t/* comptact (canonical) forms */\n+\tt(obj1);\n+\tt(obj2);\n+\tt(obj3);\n+\tt(obj4);\n+\tt(obj5);\n+\tt(obj6);\n+\n+\tt(arr1);\n+\tt(arr2);\n+\tt(arr3);\n+\tt(arr4);\n+\n+\tt(nest1);\n+\n+\tt(inline1);\n+\tt(inline2);\n+\n+\tjw_init(&obj1);\n+\tjw_init(&obj2);\n+\tjw_init(&obj3);\n+\tjw_init(&obj4);\n+\n+\tjw_init(&arr1);\n+\tjw_init(&arr2);\n+\tjw_init(&arr3);\n+\tjw_init(&arr4);\n+\n+\tjw_init(&inline1);\n+\tjw_init(&inline2);\n+\n+\t/* pretty forms */\n+\tp(obj1);\n+\tp(obj2);\n+\tp(obj3);\n+\tp(obj4);\n+\n+\tp(arr1);\n+\tp(arr2);\n+\tp(arr3);\n+\tp(arr4);\n+\n+\tp(inline1);\n+\tp(inline2);\n+\n+\t/* mixed forms */\n+\tt(mixed1);\n+\tjw_init(&mixed1);\n+\tp(mixed1);\n+\n+\treturn 0;\n+}\n+\n+static void get_s(int line_nr, char **s_in)\n+{\n+\t*s_in = strtok(NULL, \" \");\n+\tif (!*s_in)\n+\t\tdie(\"line[%d]: expected: <s>\", line_nr);\n+}\n+\n+static void get_i(int line_nr, intmax_t *s_in)\n+{\n+\tchar *s;\n+\tchar *endptr;\n+\n+\tget_s(line_nr, &s);\n+\n+\t*s_in = strtol(s, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\tdie(\"line[%d]: invalid integer value\", line_nr);\n+}\n+\n+static void get_d(int line_nr, double *s_in)\n+{\n+\tchar *s;\n+\tchar *endptr;\n+\n+\tget_s(line_nr, &s);\n+\n+\t*s_in = strtod(s, &endptr);\n+\tif (*endptr || errno == ERANGE)\n+\t\tdie(\"line[%d]: invalid float value\", line_nr);\n+}\n+\n+static int pretty;\n+\n+#define MAX_LINE_LENGTH (64 * 1024)\n+\n+static char *get_trimmed_line(char *buf, int buf_size)\n+{\n+\tint len;\n+\n+\tif (!fgets(buf, buf_size, stdin))\n+\t\treturn NULL;\n+\n+\tlen = strlen(buf);\n+\twhile (len > 0) {\n+\t\tchar c = buf[len - 1];\n+\t\tif (c == '\\n' || c == '\\r' || c == ' ' || c == '\\t')\n+\t\t\tbuf[--len] = 0;\n+\t\telse\n+\t\t\tbreak;\n+\t}\n+\n+\twhile (*buf == ' ' || *buf == '\\t')\n+\t\tbuf++;\n+\n+\treturn buf;\n+}\n+\n+static int scripted(void)\n+{\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tchar buf[MAX_LINE_LENGTH];\n+\tchar *line;\n+\tint line_nr = 0;\n+\n+\tline = get_trimmed_line(buf, MAX_LINE_LENGTH);\n+\tif (!line)\n+\t\treturn 0;\n+\n+\tif (!strcmp(line, \"object\"))\n+\t\tjw_object_begin(&jw, pretty);\n+\telse if (!strcmp(line, \"array\"))\n+\t\tjw_array_begin(&jw, pretty);\n+\telse\n+\t\tdie(\"expected first line to be 'object' or 'array'\");\n+\n+\twhile ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) {\n+\t\tchar *verb;\n+\t\tchar *key;\n+\t\tchar *s_value;\n+\t\tintmax_t i_value;\n+\t\tdouble d_value;\n+\n+\t\tline_nr++;\n+\n+\t\tverb = strtok(line, \" \");\n+\n+\t\tif (!strcmp(verb, \"end\")) {\n+\t\t\tjw_end(&jw);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-string\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tget_s(line_nr, &s_value);\n+\t\t\tjw_object_string(&jw, key, s_value);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-int\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tget_i(line_nr, &i_value);\n+\t\t\tjw_object_intmax(&jw, key, i_value);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-double\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tget_i(line_nr, &i_value);\n+\t\t\tget_d(line_nr, &d_value);\n+\t\t\tjw_object_double(&jw, key, i_value, d_value);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-true\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tjw_object_true(&jw, key);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-false\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tjw_object_false(&jw, key);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-null\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tjw_object_null(&jw, key);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-object\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tjw_object_inline_begin_object(&jw, key);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"object-array\")) {\n+\t\t\tget_s(line_nr, &key);\n+\t\t\tjw_object_inline_begin_array(&jw, key);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"array-string\")) {\n+\t\t\tget_s(line_nr, &s_value);\n+\t\t\tjw_array_string(&jw, s_value);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"array-int\")) {\n+\t\t\tget_i(line_nr, &i_value);\n+\t\t\tjw_array_intmax(&jw, i_value);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"array-double\")) {\n+\t\t\tget_i(line_nr, &i_value);\n+\t\t\tget_d(line_nr, &d_value);\n+\t\t\tjw_array_double(&jw, i_value, d_value);\n+\t\t}\n+\t\telse if (!strcmp(verb, \"array-true\"))\n+\t\t\tjw_array_true(&jw);\n+\t\telse if (!strcmp(verb, \"array-false\"))\n+\t\t\tjw_array_false(&jw);\n+\t\telse if (!strcmp(verb, \"array-null\"))\n+\t\t\tjw_array_null(&jw);\n+\t\telse if (!strcmp(verb, \"array-object\"))\n+\t\t\tjw_array_inline_begin_object(&jw);\n+\t\telse if (!strcmp(verb, \"array-array\"))\n+\t\t\tjw_array_inline_begin_array(&jw);\n+\t\telse\n+\t\t\tdie(\"unrecognized token: '%s'\", verb);\n+\t}\n+\n+\tif (!jw_is_terminated(&jw))\n+\t\tdie(\"json not terminated: '%s'\", jw.json.buf);\n+\n+\tprintf(\"%s\\n\", jw.json.buf);\n+\n+\tstrbuf_release(&jw.json);\n+\treturn 0;\n+}\n+\n+int cmd__json_writer(int argc, const char **argv)\n+{\n+\targc--; /* skip over \"json-writer\" arg */\n+\targv++;\n+\n+\tif (argc > 0 && argv[0][0] == '-') {\n+\t\tif (!strcmp(argv[0], \"-u\") || !strcmp(argv[0], \"--unit\"))\n+\t\t\treturn unit_tests();\n+\n+\t\tif (!strcmp(argv[0], \"-p\") || !strcmp(argv[0], \"--pretty\"))\n+\t\t\tpretty = 1;\n+\t}\n+\n+\treturn scripted();\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 805a45d..707499e 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -19,6 +19,7 @@ static struct test_cmd cmds[] = {\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"hashmap\", cmd__hashmap },\n \t{ \"index-version\", cmd__index_version },\n+\t{ \"json-writer\", cmd__json_writer },\n \t{ \"lazy-init-name-hash\", cmd__lazy_init_name_hash },\n \t{ \"match-trees\", cmd__match_trees },\n \t{ \"mergesort\", cmd__mergesort },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 7116ddf..a058c31 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -13,6 +13,7 @@ int cmd__example_decorate(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__hashmap(int argc, const char **argv);\n int cmd__index_version(int argc, const char **argv);\n+int cmd__json_writer(int argc, const char **argv);\n int cmd__lazy_init_name_hash(int argc, const char **argv);\n int cmd__match_trees(int argc, const char **argv);\n int cmd__mergesort(int argc, const char **argv);\ndiff --git a/t/t0019-json-writer.sh b/t/t0019-json-writer.sh\nnew file mode 100755\nindex 0000000..3b0c336\n--- /dev/null\n+++ b/t/t0019-json-writer.sh\n@@ -0,0 +1,331 @@\n+#!/bin/sh\n+\n+test_description='test json-writer JSON generation'\n+. ./test-lib.sh\n+\n+test_expect_success 'unit test of json-writer routines' '\n+\ttest-tool json-writer -u\n+'\n+\n+test_expect_success 'trivial object' '\n+\tcat >expect <<-\\EOF &&\n+\t{}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'trivial array' '\n+\tcat >expect <<-\\EOF &&\n+\t[]\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tarray\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'simple object' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"c\":3.14,\"d\":true,\"e\":false,\"f\":null}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-int b 42\n+\t\tobject-double c 2 3.140\n+\t\tobject-true d\n+\t\tobject-false e\n+\t\tobject-null f\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'simple array' '\n+\tcat >expect <<-\\EOF &&\n+\t[\"abc\",42,3.14,true,false,null]\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tarray\n+\t\tarray-string abc\n+\t\tarray-int 42\n+\t\tarray-double 2 3.140\n+\t\tarray-true\n+\t\tarray-false\n+\t\tarray-null\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'escape quoting string' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\\\\def\"}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\\def\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'escape quoting string 2' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\\\"def\"}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\"def\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline object' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"sub1\":{\"c\":3.14,\"d\":true,\"sub2\":{\"e\":false,\"f\":null}}}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-int b 42\n+\t\tobject-object sub1\n+\t\t\tobject-double c 2 3.140\n+\t\t\tobject-true d\n+\t\t\tobject-object sub2\n+\t\t\t\tobject-false e\n+\t\t\t\tobject-null f\n+\t\t\tend\n+\t\tend\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline array' '\n+\tcat >expect <<-\\EOF &&\n+\t[\"abc\",42,[3.14,true,[false,null]]]\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tarray\n+\t\tarray-string abc\n+\t\tarray-int 42\n+\t\tarray-array\n+\t\t\tarray-double 2 3.140\n+\t\t\tarray-true\n+\t\t\tarray-array\n+\t\t\t\tarray-false\n+\t\t\t\tarray-null\n+\t\t\tend\n+\t\tend\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline object and array' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"sub1\":{\"c\":3.14,\"d\":true,\"sub2\":[false,null]}}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-int b 42\n+\t\tobject-object sub1\n+\t\t\tobject-double c 2 3.140\n+\t\t\tobject-true d\n+\t\t\tobject-array sub2\n+\t\t\t\tarray-false\n+\t\t\t\tarray-null\n+\t\t\tend\n+\t\tend\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline object and array 2' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"sub1\":{\"c\":3.14,\"d\":true,\"sub2\":[false,{\"g\":0,\"h\":1},null]}}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-int b 42\n+\t\tobject-object sub1\n+\t\t\tobject-double c 2 3.140\n+\t\t\tobject-true d\n+\t\t\tobject-array sub2\n+\t\t\t\tarray-false\n+\t\t\t\tarray-object\n+\t\t\t\t\tobject-int g 0\n+\t\t\t\t\tobject-int h 1\n+\t\t\t\tend\n+\t\t\t\tarray-null\n+\t\t\tend\n+\t\tend\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'pretty nested inline object and array 2' '\n+\tsed -e \"s/^|//\" >expect <<-\\EOF &&\n+\t|{\n+\t|  \"a\": \"abc\",\n+\t|  \"b\": 42,\n+\t|  \"sub1\": {\n+\t|    \"c\": 3.14,\n+\t|    \"d\": true,\n+\t|    \"sub2\": [\n+\t|      false,\n+\t|      {\n+\t|        \"g\": 0,\n+\t|        \"h\": 1\n+\t|      },\n+\t|      null\n+\t|    ]\n+\t|  }\n+\t|}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-int b 42\n+\t\tobject-object sub1\n+\t\t\tobject-double c 2 3.140\n+\t\t\tobject-true d\n+\t\t\tobject-array sub2\n+\t\t\t\tarray-false\n+\t\t\t\tarray-object\n+\t\t\t\t\tobject-int g 0\n+\t\t\t\t\tobject-int h 1\n+\t\t\t\tend\n+\t\t\t\tarray-null\n+\t\t\tend\n+\t\tend\n+\tend\n+\tEOF\n+\ttest-tool json-writer -p <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'inline object with no members' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"empty\":{},\"b\":42}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-object empty\n+\t\tend\n+\t\tobject-int b 42\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'inline array with no members' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"empty\":[],\"b\":42}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-array empty\n+\t\tend\n+\t\tobject-int b 42\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'larger empty example' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"empty\":[{},{},{},[],{}],\"b\":42}\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-array empty\n+\t\t\tarray-object\n+\t\t\tend\n+\t\t\tarray-object\n+\t\t\tend\n+\t\t\tarray-object\n+\t\t\tend\n+\t\t\tarray-array\n+\t\t\tend\n+\t\t\tarray-object\n+\t\t\tend\n+\t\tend\n+\t\tobject-int b 42\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_lazy_prereq PERLJSON '\n+\tperl -MJSON -e \"exit 0\"\n+'\n+\n+# As a sanity check, ask Perl to parse our generated JSON and recursively\n+# dump the resulting data in sorted order.  Confirm that that matches our\n+# expectations.\n+test_expect_success PERLJSON 'parse JSON using Perl' '\n+\tcat >expect <<-\\EOF &&\n+\trow[0].a abc\n+\trow[0].b 42\n+\trow[0].sub1 hash\n+\trow[0].sub1.c 3.14\n+\trow[0].sub1.d 1\n+\trow[0].sub1.sub2 array\n+\trow[0].sub1.sub2[0] 0\n+\trow[0].sub1.sub2[1] hash\n+\trow[0].sub1.sub2[1].g 0\n+\trow[0].sub1.sub2[1].h 1\n+\trow[0].sub1.sub2[2] null\n+\tEOF\n+\tcat >input <<-\\EOF &&\n+\tobject\n+\t\tobject-string a abc\n+\t\tobject-int b 42\n+\t\tobject-object sub1\n+\t\t\tobject-double c 2 3.140\n+\t\t\tobject-true d\n+\t\t\tobject-array sub2\n+\t\t\t\tarray-false\n+\t\t\t\tarray-object\n+\t\t\t\t\tobject-int g 0\n+\t\t\t\t\tobject-int h 1\n+\t\t\t\tend\n+\t\t\t\tarray-null\n+\t\t\tend\n+\t\tend\n+\tend\n+\tEOF\n+\ttest-tool json-writer <input >output.json &&\n+\tperl \"$TEST_DIRECTORY\"/t0019/parse_json.perl <output.json >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0019/parse_json.perl b/t/t0019/parse_json.perl\nnew file mode 100644\nindex 0000000..ca4e5bf\n--- /dev/null\n+++ b/t/t0019/parse_json.perl\n@@ -0,0 +1,52 @@\n+#!/usr/bin/perl\n+use strict;\n+use warnings;\n+use JSON;\n+\n+sub dump_array {\n+    my ($label_in, $ary_ref) = @_;\n+    my @ary = @$ary_ref;\n+\n+    for ( my $i = 0; $i <= $#{ $ary_ref }; $i++ )\n+    {\n+\tmy $label = \"$label_in\\[$i\\]\";\n+\tdump_item($label, $ary[$i]);\n+    }\n+}\n+\n+sub dump_hash {\n+    my ($label_in, $obj_ref) = @_;\n+    my %obj = %$obj_ref;\n+\n+    foreach my $k (sort keys %obj) {\n+\tmy $label = (length($label_in) > 0) ? \"$label_in.$k\" : \"$k\";\n+\tmy $value = $obj{$k};\n+\n+\tdump_item($label, $value);\n+    }\n+}\n+\n+sub dump_item {\n+    my ($label_in, $value) = @_;\n+    if (ref($value) eq 'ARRAY') {\n+\tprint \"$label_in array\\n\";\n+\tdump_array($label_in, $value);\n+    } elsif (ref($value) eq 'HASH') {\n+\tprint \"$label_in hash\\n\";\n+\tdump_hash($label_in, $value);\n+    } elsif (defined $value) {\n+\tprint \"$label_in $value\\n\";\n+    } else {\n+\tprint \"$label_in null\\n\";\n+    }\n+}\n+\n+my $row = 0;\n+while (<>) {\n+    my $data = decode_json( $_ );\n+    my $label = \"row[$row]\";\n+\n+    dump_hash($label, $data);\n+    $row++;\n+}\n+\n-- \n2.9.3\n\n"}]}