]> git.cworth.org Git - notmuch/blob - sprinter-json.c
cli/dump: define GZPUTS and use it in notmuch-dump
[notmuch] / sprinter-json.c
1 #include <inttypes.h>
2 #include <stdbool.h>
3 #include <stdio.h>
4 #include <talloc.h>
5 #include "sprinter.h"
6
7 struct sprinter_json {
8     struct sprinter vtable;
9     FILE *stream;
10     /* Top of the state stack, or NULL if the printer is not currently
11      * inside any aggregate types. */
12     struct json_state *state;
13
14     /* A flag to signify that a separator should be inserted in the
15      * output as soon as possible.
16      */
17     bool insert_separator;
18 };
19
20 struct json_state {
21     struct json_state *parent;
22     /* True if nothing has been printed in this aggregate yet.
23      * Suppresses the comma before a value. */
24     bool first;
25     /* The character that closes the current aggregate. */
26     char close;
27 };
28
29 /* Helper function to set up the stream to print a value.  If this
30  * value follows another value, prints a comma. */
31 static struct sprinter_json *
32 json_begin_value (struct sprinter *sp)
33 {
34     struct sprinter_json *spj = (struct sprinter_json *) sp;
35
36     if (spj->state) {
37         if (! spj->state->first) {
38             fputc (',', spj->stream);
39             if (spj->insert_separator) {
40                 fputc ('\n', spj->stream);
41                 spj->insert_separator = false;
42             } else {
43                 fputc (' ', spj->stream);
44             }
45         } else {
46             spj->state->first = false;
47         }
48     }
49     return spj;
50 }
51
52 /* Helper function to begin an aggregate type.  Prints the open
53  * character and pushes a new state frame. */
54 static void
55 json_begin_aggregate (struct sprinter *sp, char open, char close)
56 {
57     struct sprinter_json *spj = json_begin_value (sp);
58     struct json_state *state = talloc (spj, struct json_state);
59
60     fputc (open, spj->stream);
61     state->parent = spj->state;
62     state->first = true;
63     state->close = close;
64     spj->state = state;
65 }
66
67 static void
68 json_begin_map (struct sprinter *sp)
69 {
70     json_begin_aggregate (sp, '{', '}');
71 }
72
73 static void
74 json_begin_list (struct sprinter *sp)
75 {
76     json_begin_aggregate (sp, '[', ']');
77 }
78
79 static void
80 json_end (struct sprinter *sp)
81 {
82     struct sprinter_json *spj = (struct sprinter_json *) sp;
83     struct json_state *state = spj->state;
84
85     fputc (spj->state->close, spj->stream);
86     spj->state = state->parent;
87     talloc_free (state);
88     if (spj->state == NULL)
89         fputc ('\n', spj->stream);
90 }
91
92 /* This implementation supports embedded NULs as allowed by the JSON
93  * specification and Unicode.  Support for *parsing* embedded NULs
94  * varies, but is generally not a problem outside of C-based parsers
95  * (Python's json module and Emacs' json.el take embedded NULs in
96  * stride). */
97 static void
98 json_string_len (struct sprinter *sp, const char *val, size_t len)
99 {
100     static const char *const escapes[] = {
101         ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
102         ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"
103     };
104     struct sprinter_json *spj = json_begin_value (sp);
105
106     fputc ('"', spj->stream);
107     for (; len; ++val, --len) {
108         unsigned char ch = *val;
109         if (ch < ARRAY_SIZE (escapes) && escapes[ch])
110             fputs (escapes[ch], spj->stream);
111         else if (ch >= 32)
112             fputc (ch, spj->stream);
113         else
114             fprintf (spj->stream, "\\u%04x", ch);
115     }
116     fputc ('"', spj->stream);
117 }
118
119 static void
120 json_string (struct sprinter *sp, const char *val)
121 {
122     if (val == NULL)
123         val = "";
124     json_string_len (sp, val, strlen (val));
125 }
126
127 static void
128 json_integer (struct sprinter *sp, int64_t val)
129 {
130     struct sprinter_json *spj = json_begin_value (sp);
131
132     fprintf (spj->stream, "%"PRId64, val);
133 }
134
135 static void
136 json_boolean (struct sprinter *sp, bool val)
137 {
138     struct sprinter_json *spj = json_begin_value (sp);
139
140     fputs (val ? "true" : "false", spj->stream);
141 }
142
143 static void
144 json_null (struct sprinter *sp)
145 {
146     struct sprinter_json *spj = json_begin_value (sp);
147
148     fputs ("null", spj->stream);
149 }
150
151 static void
152 json_map_key (struct sprinter *sp, const char *key)
153 {
154     struct sprinter_json *spj = (struct sprinter_json *) sp;
155
156     json_string (sp, key);
157     fputs (": ", spj->stream);
158     spj->state->first = true;
159 }
160
161 static void
162 json_set_prefix (unused (struct sprinter *sp), unused (const char *name))
163 {
164 }
165
166 static void
167 json_separator (struct sprinter *sp)
168 {
169     struct sprinter_json *spj = (struct sprinter_json *) sp;
170
171     spj->insert_separator = true;
172 }
173
174 struct sprinter *
175 sprinter_json_create (const void *ctx, FILE *stream)
176 {
177     static const struct sprinter_json template = {
178         .vtable = {
179             .begin_map = json_begin_map,
180             .begin_list = json_begin_list,
181             .end = json_end,
182             .string = json_string,
183             .string_len = json_string_len,
184             .integer = json_integer,
185             .boolean = json_boolean,
186             .null = json_null,
187             .map_key = json_map_key,
188             .separator = json_separator,
189             .set_prefix = json_set_prefix,
190             .is_text_printer = false,
191         }
192     };
193     struct sprinter_json *res;
194
195     res = talloc (ctx, struct sprinter_json);
196     if (! res)
197         return NULL;
198
199     *res = template;
200     res->stream = stream;
201     return &res->vtable;
202 }