X-Git-Url: https://git.ozlabs.org/?a=blobdiff_plain;f=ccan%2Fjson_out%2Fjson_out.c;fp=ccan%2Fjson_out%2Fjson_out.c;h=b8c798fc0dae7527cbf5986da619cdf7cb8ed437;hb=2fd44331077429da94f8f7eb43513068257742f8;hp=0000000000000000000000000000000000000000;hpb=3ceb24bf19adbe59bf8aeda9cc1426b4ba2244c2;p=ccan diff --git a/ccan/json_out/json_out.c b/ccan/json_out/json_out.c new file mode 100644 index 00000000..b8c798fc --- /dev/null +++ b/ccan/json_out/json_out.c @@ -0,0 +1,292 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#include +#include +#include +#include +#include + +struct json_out { + /* Callback if we reallocate. */ + void (*move_cb)(struct json_out *jout, ptrdiff_t delta, void *arg); + void *cb_arg; + +#ifdef CCAN_JSON_OUT_DEBUG + /* tal_arr of types ( or [ we're enclosed in. */ + char *wrapping; +#endif + /* True if we haven't yet put an element in current wrapping */ + bool empty; + + /* Output. */ + MEMBUF(char) outbuf; +}; + +/* Realloc helper for tal membufs */ +static void *membuf_tal_realloc(struct membuf *mb, + void *rawelems, size_t newsize) +{ + char *p = rawelems; + + tal_resize(&p, newsize); + return p; +} + +struct json_out *json_out_new(const tal_t *ctx) +{ + struct json_out *jout = tal(ctx, struct json_out); + + membuf_init(&jout->outbuf, + tal_arr(jout, char, 64), 64, membuf_tal_realloc); +#ifdef CCAN_JSON_OUT_DEBUG + jout->wrapping = tal_arr(jout, char, 0); +#endif + jout->empty = true; + jout->move_cb = NULL; + return jout; +} + +void json_out_call_on_move_(struct json_out *jout, + void (*cb)(struct json_out *jout, ptrdiff_t delta, + void *arg), + void *arg) +{ + if (cb) + assert(!jout->move_cb); + jout->move_cb = cb; + jout->cb_arg = arg; +} + +struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src) +{ + size_t num_elems = membuf_num_elems(&src->outbuf); + char *elems = membuf_elems(&src->outbuf); + struct json_out *jout = tal_dup(ctx, struct json_out, src); + membuf_init(&jout->outbuf, + tal_dup_arr(jout, char, elems, num_elems, 0), + num_elems, membuf_tal_realloc); + membuf_added(&jout->outbuf, num_elems); +#ifdef CCAN_JSON_OUT_DEBUG + jout->wrapping = tal_dup_arr(jout, char, + jout->wrapping, tal_count(jout->wrapping), + 0); +#endif + return jout; +} + +static void indent(struct json_out *jout, char type) +{ +#ifdef CCAN_JSON_OUT_DEBUG + size_t n = tal_count(jout->wrapping); + tal_resize(&jout->wrapping, n+1); + jout->wrapping[n] = type; +#endif + jout->empty = true; +} + +static void unindent(struct json_out *jout, char type) +{ +#ifdef CCAN_JSON_OUT_DEBUG + size_t indent = tal_count(jout->wrapping); + assert(indent > 0); + /* Both [ and ] and { and } are two apart in ASCII */ + assert(jout->wrapping[indent-1] == type - 2); + tal_resize(&jout->wrapping, indent-1); +#endif + jout->empty = false; +} + +/* Make sure jout->outbuf has room for len: return pointer */ +static char *mkroom(struct json_out *jout, size_t len) +{ + ptrdiff_t delta = membuf_prepare_space(&jout->outbuf, len); + + if (delta && jout->move_cb) + jout->move_cb(jout, delta, jout->cb_arg); + + return membuf_space(&jout->outbuf); +} + +static void check_fieldname(const struct json_out *jout, + const char *fieldname) +{ +#ifdef CCAN_JSON_OUT_DEBUG + size_t n = tal_count(jout->wrapping); + if (n == 0) + /* Can't have a fieldname if not in anything! */ + assert(!fieldname); + else if (jout->wrapping[n-1] == '[') + /* No fieldnames in arrays. */ + assert(!fieldname); + else { + /* Must have fieldnames in objects. */ + assert(fieldname); + /* We don't escape this for you */ + assert(!json_escape_needed(fieldname, strlen(fieldname))); + } +#endif +} + +char *json_out_member_direct(struct json_out *jout, + const char *fieldname, size_t extra) +{ + char *dest; + + /* Prepend comma if required. */ + if (!jout->empty) + extra++; + + check_fieldname(jout, fieldname); + if (fieldname) + extra += 1 + strlen(fieldname) + 2; + + if (!extra) { + dest = NULL; + goto out; + } + + dest = mkroom(jout, extra); + + if (!jout->empty) + *(dest++) = ','; + if (fieldname) { + *(dest++) = '"'; + memcpy(dest, fieldname, strlen(fieldname)); + dest += strlen(fieldname); + *(dest++) = '"'; + *(dest++) = ':'; + } + membuf_added(&jout->outbuf, extra); + +out: + jout->empty = false; + return dest; +} + +void json_out_start(struct json_out *jout, const char *fieldname, char type) +{ + assert(type == '[' || type == '{'); + json_out_member_direct(jout, fieldname, 1)[0] = type; + indent(jout, type); +} + +void json_out_end(struct json_out *jout, char type) +{ + assert(type == '}' || type == ']'); + json_out_direct(jout, 1)[0] = type; + unindent(jout, type); +} + +void json_out_addv(struct json_out *jout, + const char *fieldname, + bool quote, + const char *fmt, + va_list ap) +{ + size_t fmtlen, avail; + va_list ap2; + char *dst; + + json_out_member_direct(jout, fieldname, 0); + + /* Make a copy in case we need it below. */ + va_copy(ap2, ap); + + /* We can use any additional space, but need room for ". */ + avail = membuf_num_space(&jout->outbuf); + if (quote) { + if (avail < 2) + avail = 0; + else + avail -= 2; + } + + /* Try printing in place first. */ + dst = membuf_space(&jout->outbuf); + fmtlen = vsnprintf(dst + quote, avail, fmt, ap); + + /* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means + * chopping off the last character. So if fmtlen == + * membuf_num_space(&jout->outbuf), the result was truncated! */ + if (fmtlen + (int)quote*2 >= membuf_num_space(&jout->outbuf)) { + /* Make room for NUL terminator, even though we don't want it */ + dst = mkroom(jout, fmtlen + 1 + (int)quote*2); + vsprintf(dst + quote, fmt, ap2); + } + + /* Of course, if we need to escape it, we have to redo it all. */ + if (json_escape_needed(dst + quote, fmtlen)) { + struct json_escape *e; + e = json_escape_len(NULL, dst + quote, fmtlen); + fmtlen = strlen(e->s); + dst = mkroom(jout, fmtlen + (int)quote*2); + memcpy(dst + quote, e, fmtlen); + tal_free(e); + } + + if (quote) { + dst[0] = '"'; + dst[fmtlen+1] = '"'; + } + membuf_added(&jout->outbuf, fmtlen + (int)quote*2); + va_end(ap2); +} + +void json_out_add(struct json_out *jout, + const char *fieldname, + bool quote, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + json_out_addv(jout, fieldname, quote, fmt, ap); + va_end(ap); +} + +void json_out_addstr(struct json_out *jout, + const char *fieldname, + const char *str) +{ + size_t len = strlen(str); + char *p; + + p = json_out_member_direct(jout, fieldname, len + 2); + p[0] = p[1+len] = '"'; + memcpy(p+1, str, len); +} + +void json_out_add_splice(struct json_out *jout, + const char *fieldname, + const struct json_out *src) +{ + const char *p; + size_t len; + + p = json_out_contents(src, &len); + memcpy(json_out_member_direct(jout, fieldname, len), p, len); +} + +char *json_out_direct(struct json_out *jout, size_t len) +{ + char *p = mkroom(jout, len); + membuf_added(&jout->outbuf, len); + return p; +} + +void json_out_finished(const struct json_out *jout) +{ +#ifdef CCAN_JSON_OUT_DEBUG + assert(tal_count(jout->wrapping) == 0); +#endif +} + +const char *json_out_contents(const struct json_out *jout, size_t *len) +{ + *len = membuf_num_elems(&jout->outbuf); + return *len ? membuf_elems(&jout->outbuf) : NULL; +} + +void json_out_consume(struct json_out *jout, size_t len) +{ + membuf_consume(&jout->outbuf, len); +}