1 #include <assert.h>
2 #include <stdio.h>
3 #include <float.h>
4 #include <string.h>
5
6 #ifdef WIN32
7 #include <windows.h>
8 #include <io.h>
9 #include <fileapi.h>
10 #endif
11
12 #include "jv.h"
13 #include "jv_dtoa.h"
14 #include "jv_unicode.h"
15 #include "jv_alloc.h"
16
17 #ifndef MAX_PRINT_DEPTH
18 #define MAX_PRINT_DEPTH (256)
19 #endif
20
21 #define ESC "\033"
22 #define COL(c) (ESC "[" c "m")
23 #define COLRESET (ESC "[0m")
24
25 // Color table. See https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
26 // for how to choose these.
27 static const jv_kind color_kinds[] =
28 {JV_KIND_NULL, JV_KIND_FALSE, JV_KIND_TRUE, JV_KIND_NUMBER,
29 JV_KIND_STRING, JV_KIND_ARRAY, JV_KIND_OBJECT};
30 static char color_bufs[sizeof(color_kinds)/sizeof(color_kinds[0])][16];
31 static const char *color_bufps[8];
32 static const char* def_colors[] =
33 {COL("1;30"), COL("0;39"), COL("0;39"), COL("0;39"),
34 COL("0;32"), COL("1;39"), COL("1;39")};
35 #define FIELD_COLOR COL("34;1")
36
37 static const char **colors = def_colors;
38
39 int
jq_set_colors(const char * c)40 jq_set_colors(const char *c)
41 {
42 const char *e;
43 size_t i;
44
45 if (c == NULL)
46 return 1;
47 colors = def_colors;
48 memset(color_bufs, 0, sizeof(color_bufs));
49 for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]); i++)
50 color_bufps[i] = def_colors[i];
51 for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]) && *c != '\0'; i++, c = e) {
52 if ((e = strchr(c, ':')) == NULL)
53 e = c + strlen(c);
54 if ((size_t)(e - c) > sizeof(color_bufs[i]) - 4 /* ESC [ m NUL */)
55 return 0;
56 color_bufs[i][0] = ESC[0];
57 color_bufs[i][1] = '[';
58 (void) strncpy(&color_bufs[i][2], c, e - c);
59 if (strspn(&color_bufs[i][2], "0123456789;") < strlen(&color_bufs[i][2]))
60 return 0;
61 color_bufs[i][2 + (e - c)] = 'm';
62 color_bufps[i] = color_bufs[i];
63 if (e[0] == ':')
64 e++;
65 }
66 colors = color_bufps;
67 return 1;
68 }
69
put_buf(const char * s,int len,FILE * fout,jv * strout,int is_tty)70 static void put_buf(const char *s, int len, FILE *fout, jv *strout, int is_tty) {
71 if (strout) {
72 *strout = jv_string_append_buf(*strout, s, len);
73 } else {
74 #ifdef WIN32
75 /* See util.h */
76 if (is_tty) {
77 wchar_t *ws;
78 size_t wl;
79 if (len == -1)
80 len = strlen(s);
81 wl = MultiByteToWideChar(CP_UTF8, 0, s, len, NULL, 0);
82 ws = jv_mem_calloc((wl + 1), sizeof(*ws));
83 if (!ws)
84 return;
85 wl = MultiByteToWideChar(CP_UTF8, 0, s, len, ws, wl + 1);
86 ws[wl] = 0;
87 WriteConsoleW((HANDLE)_get_osfhandle(fileno(fout)), ws, wl, NULL, NULL);
88 free(ws);
89 } else
90 fwrite(s, 1, len, fout);
91 #else
92 fwrite(s, 1, len, fout);
93 #endif
94 }
95 }
96
put_char(char c,FILE * fout,jv * strout,int T)97 static void put_char(char c, FILE* fout, jv* strout, int T) {
98 put_buf(&c, 1, fout, strout, T);
99 }
100
put_str(const char * s,FILE * fout,jv * strout,int T)101 static void put_str(const char* s, FILE* fout, jv* strout, int T) {
102 put_buf(s, strlen(s), fout, strout, T);
103 }
104
put_indent(int n,int flags,FILE * fout,jv * strout,int T)105 static void put_indent(int n, int flags, FILE* fout, jv* strout, int T) {
106 if (flags & JV_PRINT_TAB) {
107 while (n--)
108 put_char('\t', fout, strout, T);
109 } else {
110 n *= ((flags & (JV_PRINT_SPACE0 | JV_PRINT_SPACE1 | JV_PRINT_SPACE2)) >> 8);
111 while (n--)
112 put_char(' ', fout, strout, T);
113 }
114 }
115
jvp_dump_string(jv str,int ascii_only,FILE * F,jv * S,int T)116 static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S, int T) {
117 assert(jv_get_kind(str) == JV_KIND_STRING);
118 const char* i = jv_string_value(str);
119 const char* end = i + jv_string_length_bytes(jv_copy(str));
120 const char* cstart;
121 int c = 0;
122 char buf[32];
123 put_char('"', F, S, T);
124 while ((i = jvp_utf8_next((cstart = i), end, &c))) {
125 assert(c != -1);
126 int unicode_escape = 0;
127 if (0x20 <= c && c <= 0x7E) {
128 // printable ASCII
129 if (c == '"' || c == '\\') {
130 put_char('\\', F, S, T);
131 }
132 put_char(c, F, S, T);
133 } else if (c < 0x20 || c == 0x7F) {
134 // ASCII control character
135 switch (c) {
136 case '\b':
137 put_char('\\', F, S, T);
138 put_char('b', F, S, T);
139 break;
140 case '\t':
141 put_char('\\', F, S, T);
142 put_char('t', F, S, T);
143 break;
144 case '\r':
145 put_char('\\', F, S, T);
146 put_char('r', F, S, T);
147 break;
148 case '\n':
149 put_char('\\', F, S, T);
150 put_char('n', F, S, T);
151 break;
152 case '\f':
153 put_char('\\', F, S, T);
154 put_char('f', F, S, T);
155 break;
156 default:
157 unicode_escape = 1;
158 break;
159 }
160 } else {
161 if (ascii_only) {
162 unicode_escape = 1;
163 } else {
164 put_buf(cstart, i - cstart, F, S, T);
165 }
166 }
167 if (unicode_escape) {
168 if (c <= 0xffff) {
169 snprintf(buf, sizeof(buf), "\\u%04x", c);
170 } else {
171 c -= 0x10000;
172 snprintf(buf, sizeof(buf), "\\u%04x\\u%04x",
173 0xD800 | ((c & 0xffc00) >> 10),
174 0xDC00 | (c & 0x003ff));
175 }
176 put_str(buf, F, S, T);
177 }
178 }
179 assert(c != -1);
180 put_char('"', F, S, T);
181 }
182
put_refcnt(struct dtoa_context * C,int refcnt,FILE * F,jv * S,int T)183 static void put_refcnt(struct dtoa_context* C, int refcnt, FILE *F, jv* S, int T){
184 char buf[JVP_DTOA_FMT_MAX_LEN];
185 put_char(' ', F, S, T);
186 put_char('(', F, S, T);
187 put_str(jvp_dtoa_fmt(C, buf, refcnt), F, S, T);
188 put_char(')', F, S, T);
189 }
190
jv_dump_term(struct dtoa_context * C,jv x,int flags,int indent,FILE * F,jv * S)191 static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FILE* F, jv* S) {
192 char buf[JVP_DTOA_FMT_MAX_LEN];
193 const char* color = 0;
194 double refcnt = (flags & JV_PRINT_REFCOUNT) ? jv_get_refcnt(x) - 1 : -1;
195 if (flags & JV_PRINT_COLOR) {
196 for (unsigned i=0; i<sizeof(color_kinds)/sizeof(color_kinds[0]); i++) {
197 if (jv_get_kind(x) == color_kinds[i]) {
198 color = colors[i];
199 put_str(color, F, S, flags & JV_PRINT_ISATTY);
200 break;
201 }
202 }
203 }
204 if (indent > MAX_PRINT_DEPTH) {
205 put_str("<skipped: too deep>", F, S, flags & JV_PRINT_ISATTY);
206 } else switch (jv_get_kind(x)) {
207 default:
208 case JV_KIND_INVALID:
209 if (flags & JV_PRINT_INVALID) {
210 jv msg = jv_invalid_get_msg(jv_copy(x));
211 if (jv_get_kind(msg) == JV_KIND_STRING) {
212 put_str("<invalid:", F, S, flags & JV_PRINT_ISATTY);
213 jvp_dump_string(msg, flags | JV_PRINT_ASCII, F, S, flags & JV_PRINT_ISATTY);
214 put_str(">", F, S, flags & JV_PRINT_ISATTY);
215 } else {
216 put_str("<invalid>", F, S, flags & JV_PRINT_ISATTY);
217 }
218 } else {
219 assert(0 && "Invalid value");
220 }
221 break;
222 case JV_KIND_NULL:
223 put_str("null", F, S, flags & JV_PRINT_ISATTY);
224 break;
225 case JV_KIND_FALSE:
226 put_str("false", F, S, flags & JV_PRINT_ISATTY);
227 break;
228 case JV_KIND_TRUE:
229 put_str("true", F, S, flags & JV_PRINT_ISATTY);
230 break;
231 case JV_KIND_NUMBER: {
232 double d = jv_number_value(x);
233 if (d != d) {
234 // JSON doesn't have NaN, so we'll render it as "null"
235 put_str("null", F, S, flags & JV_PRINT_ISATTY);
236 } else {
237 // Normalise infinities to something we can print in valid JSON
238 if (d > DBL_MAX) d = DBL_MAX;
239 if (d < -DBL_MAX) d = -DBL_MAX;
240 put_str(jvp_dtoa_fmt(C, buf, d), F, S, flags & JV_PRINT_ISATTY);
241 }
242 break;
243 }
244 case JV_KIND_STRING:
245 jvp_dump_string(x, flags & JV_PRINT_ASCII, F, S, flags & JV_PRINT_ISATTY);
246 if (flags & JV_PRINT_REFCOUNT)
247 put_refcnt(C, refcnt, F, S, flags & JV_PRINT_ISATTY);
248 break;
249 case JV_KIND_ARRAY: {
250 if (jv_array_length(jv_copy(x)) == 0) {
251 put_str("[]", F, S, flags & JV_PRINT_ISATTY);
252 break;
253 }
254 put_str("[", F, S, flags & JV_PRINT_ISATTY);
255 if (flags & JV_PRINT_PRETTY) {
256 put_char('\n', F, S, flags & JV_PRINT_ISATTY);
257 put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY);
258 }
259 jv_array_foreach(x, i, elem) {
260 if (i!=0) {
261 if (flags & JV_PRINT_PRETTY) {
262 put_str(",\n", F, S, flags & JV_PRINT_ISATTY);
263 put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY);
264 } else {
265 put_str(",", F, S, flags & JV_PRINT_ISATTY);
266 }
267 }
268 jv_dump_term(C, elem, flags, indent + 1, F, S);
269 if (color) put_str(color, F, S, flags & JV_PRINT_ISATTY);
270 }
271 if (flags & JV_PRINT_PRETTY) {
272 put_char('\n', F, S, flags & JV_PRINT_ISATTY);
273 put_indent(indent, flags, F, S, flags & JV_PRINT_ISATTY);
274 }
275 if (color) put_str(color, F, S, flags & JV_PRINT_ISATTY);
276 put_char(']', F, S, flags & JV_PRINT_ISATTY);
277 if (flags & JV_PRINT_REFCOUNT)
278 put_refcnt(C, refcnt, F, S, flags & JV_PRINT_ISATTY);
279 break;
280 }
281 case JV_KIND_OBJECT: {
282 if (jv_object_length(jv_copy(x)) == 0) {
283 put_str("{}", F, S, flags & JV_PRINT_ISATTY);
284 break;
285 }
286 put_char('{', F, S, flags & JV_PRINT_ISATTY);
287 if (flags & JV_PRINT_PRETTY) {
288 put_char('\n', F, S, flags & JV_PRINT_ISATTY);
289 put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY);
290 }
291 int first = 1;
292 int i = 0;
293 jv keyset = jv_null();
294 while (1) {
295 jv key, value;
296 if (flags & JV_PRINT_SORTED) {
297 if (first) {
298 keyset = jv_keys(jv_copy(x));
299 i = 0;
300 } else {
301 i++;
302 }
303 if (i >= jv_array_length(jv_copy(keyset))) {
304 jv_free(keyset);
305 break;
306 }
307 key = jv_array_get(jv_copy(keyset), i);
308 value = jv_object_get(jv_copy(x), jv_copy(key));
309 } else {
310 if (first) {
311 i = jv_object_iter(x);
312 } else {
313 i = jv_object_iter_next(x, i);
314 }
315 if (!jv_object_iter_valid(x, i)) break;
316 key = jv_object_iter_key(x, i);
317 value = jv_object_iter_value(x, i);
318 }
319
320 if (!first) {
321 if (flags & JV_PRINT_PRETTY){
322 put_str(",\n", F, S, flags & JV_PRINT_ISATTY);
323 put_indent(indent + 1, flags, F, S, flags & JV_PRINT_ISATTY);
324 } else {
325 put_str(",", F, S, flags & JV_PRINT_ISATTY);
326 }
327 }
328 if (color) put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY);
329
330 first = 0;
331 if (color) put_str(FIELD_COLOR, F, S, flags & JV_PRINT_ISATTY);
332 jvp_dump_string(key, flags & JV_PRINT_ASCII, F, S, flags & JV_PRINT_ISATTY);
333 jv_free(key);
334 if (color) put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY);
335
336 if (color) put_str(color, F, S, flags & JV_PRINT_ISATTY);
337 put_str((flags & JV_PRINT_PRETTY) ? ": " : ":", F, S, flags & JV_PRINT_ISATTY);
338 if (color) put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY);
339
340 jv_dump_term(C, value, flags, indent + 1, F, S);
341 if (color) put_str(color, F, S, flags & JV_PRINT_ISATTY);
342 }
343 if (flags & JV_PRINT_PRETTY) {
344 put_char('\n', F, S, flags & JV_PRINT_ISATTY);
345 put_indent(indent, flags, F, S, flags & JV_PRINT_ISATTY);
346 }
347 if (color) put_str(color, F, S, flags & JV_PRINT_ISATTY);
348 put_char('}', F, S, flags & JV_PRINT_ISATTY);
349 if (flags & JV_PRINT_REFCOUNT)
350 put_refcnt(C, refcnt, F, S, flags & JV_PRINT_ISATTY);
351 }
352 }
353 jv_free(x);
354 if (color) {
355 put_str(COLRESET, F, S, flags & JV_PRINT_ISATTY);
356 }
357 }
358
jv_dumpf(jv x,FILE * f,int flags)359 void jv_dumpf(jv x, FILE *f, int flags) {
360 struct dtoa_context C;
361 jvp_dtoa_context_init(&C);
362 jv_dump_term(&C, x, flags, 0, f, 0);
363 jvp_dtoa_context_free(&C);
364 }
365
jv_dump(jv x,int flags)366 void jv_dump(jv x, int flags) {
367 jv_dumpf(x, stdout, flags);
368 }
369
370 /* This one is nice for use in debuggers */
jv_show(jv x,int flags)371 void jv_show(jv x, int flags) {
372 if (flags == -1)
373 flags = JV_PRINT_PRETTY | JV_PRINT_COLOR | JV_PRINT_INDENT_FLAGS(2);
374 jv_dumpf(jv_copy(x), stderr, flags | JV_PRINT_INVALID);
375 fflush(stderr);
376 }
377
jv_dump_string(jv x,int flags)378 jv jv_dump_string(jv x, int flags) {
379 struct dtoa_context C;
380 jvp_dtoa_context_init(&C);
381 jv s = jv_string("");
382 jv_dump_term(&C, x, flags, 0, 0, &s);
383 jvp_dtoa_context_free(&C);
384 return s;
385 }
386
jv_dump_string_trunc(jv x,char * outbuf,size_t bufsize)387 char *jv_dump_string_trunc(jv x, char *outbuf, size_t bufsize) {
388 x = jv_dump_string(x,0);
389 const char* p = jv_string_value(x);
390 const size_t len = strlen(p);
391 strncpy(outbuf, p, bufsize);
392 outbuf[bufsize - 1] = 0;
393 if (len > bufsize - 1 && bufsize >= 4) {
394 // Indicate truncation with '...'
395 outbuf[bufsize - 2]='.';
396 outbuf[bufsize - 3]='.';
397 outbuf[bufsize - 4]='.';
398 }
399 jv_free(x);
400 return outbuf;
401 }
402