implement POSIX regexp support
[project/jsonpath.git] / main.c
1 /*
2 * Copyright (C) 2013-2014 Jo-Philipp Wich <jo@mein.io>
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdio.h>
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <unistd.h>
21 #include <errno.h>
22
23 #ifdef JSONC
24 #include <json.h>
25 #else
26 #include <json-c/json.h>
27 #endif
28
29 #include <libubox/list.h>
30
31 #include "lexer.h"
32 #include "parser.h"
33 #include "matcher.h"
34
35
36 struct match_item {
37 struct json_object *jsobj;
38 struct list_head list;
39 };
40
41 static void
42 print_usage(char *app)
43 {
44 printf(
45 "== Usage ==\n\n"
46 " # %s [-a] [-i <file> | -s \"json...\"] {-t <pattern> | -e <pattern>}\n"
47 " -q Quiet, no errors are printed\n"
48 " -h, --help Print this help\n"
49 " -a Implicitely treat input as array, useful for JSON logs\n"
50 " -i path Specify a JSON file to parse\n"
51 " -s \"json\" Specify a JSON string to parse\n"
52 " -l limit Specify max number of results to show\n"
53 " -F separator Specify a field separator when using export\n"
54 " -t <pattern> Print the type of values matched by pattern\n"
55 " -e <pattern> Print the values matched by pattern\n"
56 " -e VAR=<pat> Serialize matched value for shell \"eval\"\n\n"
57 "== Patterns ==\n\n"
58 " Patterns are JsonPath: http://goessner.net/articles/JsonPath/\n"
59 " This tool implements $, @, [], * and the union operator ','\n"
60 " plus the usual expressions and literals.\n"
61 " It does not support the recursive child search operator '..' or\n"
62 " the '?()' and '()' filter expressions as those would require a\n"
63 " complete JavaScript engine to support them.\n\n"
64 "== Examples ==\n\n"
65 " Display the first IPv4 address on lan:\n"
66 " # ifstatus lan | %s -e '@[\"ipv4-address\"][0].address'\n\n"
67 " Extract the release string from the board information:\n"
68 " # ubus call system board | %s -e '@.release.description'\n\n"
69 " Find all interfaces which are up:\n"
70 " # ubus call network.interface dump | \\\n"
71 " %s -e '@.interface[@.up=true].interface'\n\n"
72 " Export br-lan traffic counters for shell eval:\n"
73 " # devstatus br-lan | %s -e 'RX=@.statistics.rx_bytes' \\\n"
74 " -e 'TX=@.statistics.tx_bytes'\n",
75 app, app, app, app, app);
76 }
77
78 static struct json_object *
79 parse_json_chunk(struct json_tokener *tok, struct json_object *array,
80 const char *buf, size_t len, enum json_tokener_error *err)
81 {
82 struct json_object *obj = NULL;
83
84 while (len)
85 {
86 obj = json_tokener_parse_ex(tok, buf, len);
87 *err = json_tokener_get_error(tok);
88
89 if (*err == json_tokener_success)
90 {
91 if (array)
92 {
93 json_object_array_add(array, obj);
94 }
95 else
96 {
97 break;
98 }
99 }
100 else if (*err != json_tokener_continue)
101 {
102 break;
103 }
104
105 buf += tok->char_offset;
106 len -= tok->char_offset;
107 }
108
109 return obj;
110 }
111
112 static struct json_object *
113 parse_json(FILE *fd, const char *source, const char **error, bool array_mode)
114 {
115 size_t len;
116 char buf[256];
117 struct json_object *obj = NULL, *array = NULL;
118 struct json_tokener *tok = json_tokener_new();
119 enum json_tokener_error err = json_tokener_continue;
120
121 if (!tok)
122 {
123 *error = "Out of memory";
124 return NULL;
125 }
126
127 if (array_mode)
128 {
129 array = json_object_new_array();
130
131 if (!array)
132 {
133 json_tokener_free(tok);
134 *error = "Out of memory";
135 return NULL;
136 }
137 }
138
139 if (source)
140 {
141 obj = parse_json_chunk(tok, array, source, strlen(source), &err);
142 }
143 else
144 {
145 while ((len = fread(buf, 1, sizeof(buf), fd)) > 0)
146 {
147 obj = parse_json_chunk(tok, array, buf, len, &err);
148
149 if (err == json_tokener_success && !array)
150 break;
151
152 if (err != json_tokener_continue)
153 break;
154 }
155 }
156
157 json_tokener_free(tok);
158
159 if (err)
160 {
161 if (err == json_tokener_continue)
162 err = json_tokener_error_parse_eof;
163
164 *error = json_tokener_error_desc(err);
165 return NULL;
166 }
167
168 return array ? array : obj;
169 }
170
171 static void
172 print_string(const char *s)
173 {
174 const char *p;
175
176 printf("'");
177
178 for (p = s; *p; p++)
179 {
180 if (*p == '\'')
181 printf("'\"'\"'");
182 else
183 printf("%c", *p);
184 }
185
186 printf("'");
187 }
188
189 static void
190 print_separator(const char *sep, int *sc, int sl)
191 {
192 if (*sc > 0)
193 {
194 switch (sep[(*sc - 1) % sl])
195 {
196 case '"':
197 printf("'\"'");
198 break;
199
200 case '\'':
201 printf("\"'\"");
202 break;
203
204 case ' ':
205 printf("\\ ");
206 break;
207
208 default:
209 printf("%c", sep[(*sc - 1) % sl]);
210 }
211 }
212
213 (*sc)++;
214 }
215
216 static void
217 export_value(struct list_head *matches, const char *prefix, const char *sep,
218 int limit)
219 {
220 int n, len;
221 int sc = 0, sl = strlen(sep);
222 struct match_item *item;
223
224 if (list_empty(matches))
225 return;
226
227 if (prefix)
228 {
229 printf("export %s=", prefix);
230
231 list_for_each_entry(item, matches, list)
232 {
233 if (limit-- <= 0)
234 break;
235
236 switch (json_object_get_type(item->jsobj))
237 {
238 case json_type_object:
239 ; /* a label can only be part of a statement */
240 json_object_object_foreach(item->jsobj, key, val)
241 {
242 if (!val)
243 continue;
244
245 print_separator(sep, &sc, sl);
246 print_string(key);
247 }
248 break;
249
250 case json_type_array:
251 for (n = 0, len = json_object_array_length(item->jsobj);
252 n < len; n++)
253 {
254 print_separator(sep, &sc, sl);
255 printf("%d", n);
256 }
257 break;
258
259 case json_type_boolean:
260 print_separator(sep, &sc, sl);
261 printf("%d", json_object_get_boolean(item->jsobj));
262 break;
263
264 case json_type_int:
265 print_separator(sep, &sc, sl);
266 printf("%" PRId64, json_object_get_int64(item->jsobj));
267 break;
268
269 case json_type_double:
270 print_separator(sep, &sc, sl);
271 printf("%f", json_object_get_double(item->jsobj));
272 break;
273
274 case json_type_string:
275 print_separator(sep, &sc, sl);
276 print_string(json_object_get_string(item->jsobj));
277 break;
278
279 case json_type_null:
280 break;
281 }
282 }
283
284 printf("; ");
285 }
286 else
287 {
288 list_for_each_entry(item, matches, list)
289 {
290 if (limit-- <= 0)
291 break;
292
293 switch (json_object_get_type(item->jsobj))
294 {
295 case json_type_object:
296 case json_type_array:
297 case json_type_boolean:
298 case json_type_int:
299 case json_type_double:
300 printf("%s\n", json_object_to_json_string(item->jsobj));
301 break;
302
303 case json_type_string:
304 printf("%s\n", json_object_get_string(item->jsobj));
305 break;
306
307 case json_type_null:
308 break;
309 }
310 }
311 }
312 }
313
314 static void
315 export_type(struct list_head *matches, const char *prefix, int limit)
316 {
317 bool first = true;
318 struct match_item *item;
319 const char *types[] = {
320 "null",
321 "boolean",
322 "double",
323 "int",
324 "object",
325 "array",
326 "string"
327 };
328
329 if (list_empty(matches))
330 return;
331
332 if (prefix)
333 printf("export %s=", prefix);
334
335 list_for_each_entry(item, matches, list)
336 {
337 if (!first)
338 printf("\\ ");
339
340 if (limit-- <= 0)
341 break;
342
343 printf("%s", types[json_object_get_type(item->jsobj)]);
344 first = false;
345 }
346
347 if (prefix)
348 printf("; ");
349 else
350 printf("\n");
351 }
352
353 static void
354 match_cb(struct json_object *res, void *priv)
355 {
356 struct list_head *h = priv;
357 struct match_item *i = calloc(1, sizeof(*i));
358
359 if (i)
360 {
361 i->jsobj = res;
362 list_add_tail(&i->list, h);
363 }
364 }
365
366 static void
367 print_error(struct jp_state *state, char *expr)
368 {
369 int i;
370 bool first = true;
371
372 fprintf(stderr, "Syntax error: ");
373
374 switch (state->error_code)
375 {
376 case -4:
377 fprintf(stderr, "Unexpected character\n");
378 break;
379
380 case -3:
381 fprintf(stderr, "String or label literal too long\n");
382 break;
383
384 case -2:
385 fprintf(stderr, "Invalid escape sequence\n");
386 break;
387
388 case -1:
389 fprintf(stderr, "Unterminated string\n");
390 break;
391
392 default:
393 for (i = 0; i < sizeof(state->error_code) * 8; i++)
394 {
395 if (state->error_code & (1 << i))
396 {
397 fprintf(stderr,
398 first ? "Expecting %s" : " or %s", tokennames[i]);
399
400 first = false;
401 }
402 }
403
404 fprintf(stderr, "\n");
405 break;
406 }
407
408 fprintf(stderr, "In expression %s\n", expr);
409 fprintf(stderr, "Near here ----");
410
411 for (i = 0; i < state->error_pos; i++)
412 fprintf(stderr, "-");
413
414 fprintf(stderr, "^\n");
415 }
416
417 static bool
418 filter_json(int opt, struct json_object *jsobj, char *expr, const char *sep,
419 int limit)
420 {
421 struct jp_state *state;
422 const char *prefix = NULL;
423 struct list_head matches;
424 struct match_item *item, *tmp;
425 struct json_object *res = NULL;
426
427 state = jp_parse(expr);
428
429 if (!state)
430 {
431 fprintf(stderr, "Out of memory\n");
432 goto out;
433 }
434 else if (state->error_code)
435 {
436 print_error(state, expr);
437 goto out;
438 }
439
440 INIT_LIST_HEAD(&matches);
441
442 res = jp_match(state->path, jsobj, match_cb, &matches);
443 prefix = (state->path->type == T_LABEL) ? state->path->str : NULL;
444
445 switch (opt)
446 {
447 case 't':
448 export_type(&matches, prefix, limit);
449 break;
450
451 default:
452 export_value(&matches, prefix, sep, limit);
453 break;
454 }
455
456 list_for_each_entry_safe(item, tmp, &matches, list)
457 free(item);
458
459 out:
460 if (state)
461 jp_free(state);
462
463 return !!res;
464 }
465
466 int main(int argc, char **argv)
467 {
468 bool array_mode = false;
469 int opt, rv = 0, limit = 0x7FFFFFFF;
470 FILE *input = stdin;
471 struct json_object *jsobj = NULL;
472 const char *jserr = NULL, *source = NULL, *separator = " ";
473
474 if (argc == 1)
475 {
476 print_usage(argv[0]);
477 goto out;
478 }
479
480 while ((opt = getopt(argc, argv, "ahi:s:e:t:F:l:q")) != -1)
481 {
482 switch (opt)
483 {
484 case 'a':
485 array_mode = true;
486 break;
487
488 case 'h':
489 print_usage(argv[0]);
490 goto out;
491
492 case 'i':
493 input = fopen(optarg, "r");
494
495 if (!input)
496 {
497 fprintf(stderr, "Failed to open %s: %s\n",
498 optarg, strerror(errno));
499
500 rv = 125;
501 goto out;
502 }
503
504 break;
505
506 case 's':
507 source = optarg;
508 break;
509
510 case 'F':
511 if (optarg && *optarg)
512 separator = optarg;
513 break;
514
515 case 'l':
516 limit = atoi(optarg);
517 break;
518
519 case 't':
520 case 'e':
521 if (!jsobj)
522 {
523 jsobj = parse_json(input, source, &jserr, array_mode);
524
525 if (!jsobj)
526 {
527 fprintf(stderr, "Failed to parse json data: %s\n",
528 jserr);
529
530 rv = 126;
531 goto out;
532 }
533 }
534
535 if (!filter_json(opt, jsobj, optarg, separator, limit))
536 rv = 1;
537
538 break;
539
540 case 'q':
541 fclose(stderr);
542 break;
543 }
544 }
545
546 out:
547 if (jsobj)
548 json_object_put(jsobj);
549
550 if (input && input != stdin)
551 fclose(input);
552
553 return rv;
554 }