cli: properly format 64bit values
[oweals/jsonpath.git] / main.c
1 /*
2  * Copyright (C) 2013-2014 Jo-Philipp Wich <jow@openwrt.org>
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 [-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         "  -i path      Specify a JSON file to parse\n"
50         "  -s \"json\"  Specify a JSON string to parse\n"
51         "  -l limit     Specify max number of results to show\n"
52         "  -F separator Specify a field separator when using export\n"
53         "  -t <pattern> Print the type of values matched by pattern\n"
54         "  -e <pattern> Print the values matched by pattern\n"
55         "  -e VAR=<pat> Serialize matched value for shell \"eval\"\n\n"
56         "== Patterns ==\n\n"
57         "  Patterns are JsonPath: http://goessner.net/articles/JsonPath/\n"
58         "  This tool implements $, @, [], * and the union operator ','\n"
59         "  plus the usual expressions and literals.\n"
60         "  It does not support the recursive child search operator '..' or\n"
61         "  the '?()' and '()' filter expressions as those would require a\n"
62         "  complete JavaScript engine to support them.\n\n"
63         "== Examples ==\n\n"
64         "  Display the first IPv4 address on lan:\n"
65         "  # ifstatus lan | %s -e '@[\"ipv4-address\"][0].address'\n\n"
66         "  Extract the release string from the board information:\n"
67         "  # ubus call system board | %s -e '@.release.description'\n\n"
68         "  Find all interfaces which are up:\n"
69         "  # ubus call network.interface dump | \\\n"
70         "       %s -e '@.interface[@.up=true].interface'\n\n"
71         "  Export br-lan traffic counters for shell eval:\n"
72         "  # devstatus br-lan | %s -e 'RX=@.statistics.rx_bytes' \\\n"
73         "       -e 'TX=@.statistics.tx_bytes'\n",
74                 app, app, app, app, app);
75 }
76
77 static struct json_object *
78 parse_json(FILE *fd, const char *source, const char **error)
79 {
80         int len;
81         char buf[256];
82         struct json_object *obj = NULL;
83         struct json_tokener *tok = json_tokener_new();
84         enum json_tokener_error err = json_tokener_continue;
85
86         if (!tok)
87                 return NULL;
88
89         if (source)
90         {
91                 obj = json_tokener_parse_ex(tok, source, strlen(source));
92                 err = json_tokener_get_error(tok);
93         }
94         else
95         {
96                 while ((len = fread(buf, 1, sizeof(buf), fd)) > 0)
97                 {
98                         obj = json_tokener_parse_ex(tok, buf, len);
99                         err = json_tokener_get_error(tok);
100
101                         if (!err || err != json_tokener_continue)
102                                 break;
103                 }
104         }
105
106         json_tokener_free(tok);
107
108         if (err)
109         {
110                 if (err == json_tokener_continue)
111                         err = json_tokener_error_parse_eof;
112
113                 *error = json_tokener_error_desc(err);
114                 return NULL;
115         }
116
117         return obj;
118 }
119
120 static void
121 print_string(const char *s)
122 {
123         const char *p;
124
125         printf("'");
126
127         for (p = s; *p; p++)
128         {
129                 if (*p == '\'')
130                         printf("'\"'\"'");
131                 else
132                         printf("%c", *p);
133         }
134
135         printf("'");
136 }
137
138 static void
139 print_separator(const char *sep, int *sc, int sl)
140 {
141         if (*sc > 0)
142         {
143                 switch (sep[(*sc - 1) % sl])
144                 {
145                 case '"':
146                         printf("'\"'");
147                         break;
148
149                 case '\'':
150                         printf("\"'\"");
151                         break;
152
153                 case ' ':
154                         printf("\\ ");
155                         break;
156
157                 default:
158                         printf("%c", sep[(*sc - 1) % sl]);
159                 }
160         }
161
162         (*sc)++;
163 }
164
165 static void
166 export_value(struct list_head *matches, const char *prefix, const char *sep,
167              int limit)
168 {
169         int n, len;
170         int sc = 0, sl = strlen(sep);
171         struct match_item *item;
172
173         if (list_empty(matches))
174                 return;
175
176         if (prefix)
177         {
178                 printf("export %s=", prefix);
179
180                 list_for_each_entry(item, matches, list)
181                 {
182                         if (limit-- <= 0)
183                                 break;
184
185                         switch (json_object_get_type(item->jsobj))
186                         {
187                         case json_type_object:
188                                 ; /* a label can only be part of a statement */
189                                 json_object_object_foreach(item->jsobj, key, val)
190                                 {
191                                         if (!val)
192                                                 continue;
193
194                                         print_separator(sep, &sc, sl);
195                                         print_string(key);
196                                 }
197                                 break;
198
199                         case json_type_array:
200                                 for (n = 0, len = json_object_array_length(item->jsobj);
201                                      n < len; n++)
202                                 {
203                                         print_separator(sep, &sc, sl);
204                                         printf("%d", n);
205                                 }
206                                 break;
207
208                         case json_type_boolean:
209                                 print_separator(sep, &sc, sl);
210                                 printf("%d", json_object_get_boolean(item->jsobj));
211                                 break;
212
213                         case json_type_int:
214                                 print_separator(sep, &sc, sl);
215                                 printf("%" PRId64, json_object_get_int64(item->jsobj));
216                                 break;
217
218                         case json_type_double:
219                                 print_separator(sep, &sc, sl);
220                                 printf("%f", json_object_get_double(item->jsobj));
221                                 break;
222
223                         case json_type_string:
224                                 print_separator(sep, &sc, sl);
225                                 print_string(json_object_get_string(item->jsobj));
226                                 break;
227
228                         case json_type_null:
229                                 break;
230                         }
231                 }
232
233                 printf("; ");
234         }
235         else
236         {
237                 list_for_each_entry(item, matches, list)
238                 {
239                         if (limit-- <= 0)
240                                 break;
241
242                         switch (json_object_get_type(item->jsobj))
243                         {
244                         case json_type_object:
245                         case json_type_array:
246                         case json_type_boolean:
247                         case json_type_int:
248                         case json_type_double:
249                                 printf("%s\n", json_object_to_json_string(item->jsobj));
250                                 break;
251
252                         case json_type_string:
253                                 printf("%s\n", json_object_get_string(item->jsobj));
254                                 break;
255
256                         case json_type_null:
257                                 break;
258                         }
259                 }
260         }
261 }
262
263 static void
264 export_type(struct list_head *matches, const char *prefix, int limit)
265 {
266         bool first = true;
267         struct match_item *item;
268         const char *types[] = {
269                 "null",
270                 "boolean",
271                 "double",
272                 "int",
273                 "object",
274                 "array",
275                 "string"
276         };
277
278         if (list_empty(matches))
279                 return;
280
281         if (prefix)
282                 printf("export %s=", prefix);
283
284         list_for_each_entry(item, matches, list)
285         {
286                 if (!first)
287                         printf("\\ ");
288
289                 if (limit-- <= 0)
290                         break;
291
292                 printf("%s", types[json_object_get_type(item->jsobj)]);
293                 first = false;
294         }
295
296         if (prefix)
297                 printf("; ");
298         else
299                 printf("\n");
300 }
301
302 static void
303 match_cb(struct json_object *res, void *priv)
304 {
305         struct list_head *h = priv;
306         struct match_item *i = calloc(1, sizeof(*i));
307
308         if (i)
309         {
310                 i->jsobj = res;
311                 list_add_tail(&i->list, h);
312         }
313 }
314
315 static void
316 print_error(struct jp_state *state, char *expr)
317 {
318         int i;
319         bool first = true;
320
321         fprintf(stderr, "Syntax error: ");
322
323         switch (state->error_code)
324         {
325         case -4:
326                 fprintf(stderr, "Unexpected character\n");
327                 break;
328
329         case -3:
330                 fprintf(stderr, "String or label literal too long\n");
331                 break;
332
333         case -2:
334                 fprintf(stderr, "Invalid escape sequence\n");
335                 break;
336
337         case -1:
338                 fprintf(stderr, "Unterminated string\n");
339                 break;
340
341         default:
342                 for (i = 0; i < sizeof(state->error_code) * 8; i++)
343                 {
344                         if (state->error_code & (1 << i))
345                         {
346                                 fprintf(stderr,
347                                         first ? "Expecting %s" : " or %s", tokennames[i]);
348
349                                 first = false;
350                         }
351                 }
352
353                 fprintf(stderr, "\n");
354                 break;
355         }
356
357         fprintf(stderr, "In expression %s\n", expr);
358         fprintf(stderr, "Near here ----");
359
360         for (i = 0; i < state->error_pos; i++)
361                 fprintf(stderr, "-");
362
363         fprintf(stderr, "^\n");
364 }
365
366 static bool
367 filter_json(int opt, struct json_object *jsobj, char *expr, const char *sep,
368             int limit)
369 {
370         struct jp_state *state;
371         const char *prefix = NULL;
372         struct list_head matches;
373         struct match_item *item, *tmp;
374         struct json_object *res = NULL;
375
376         state = jp_parse(expr);
377
378         if (!state)
379         {
380                 fprintf(stderr, "Out of memory\n");
381                 goto out;
382         }
383         else if (state->error_code)
384         {
385                 print_error(state, expr);
386                 goto out;
387         }
388
389         INIT_LIST_HEAD(&matches);
390
391         res = jp_match(state->path, jsobj, match_cb, &matches);
392         prefix = (state->path->type == T_LABEL) ? state->path->str : NULL;
393
394         switch (opt)
395         {
396         case 't':
397                 export_type(&matches, prefix, limit);
398                 break;
399
400         default:
401                 export_value(&matches, prefix, sep, limit);
402                 break;
403         }
404
405         list_for_each_entry_safe(item, tmp, &matches, list)
406                 free(item);
407
408 out:
409         if (state)
410                 jp_free(state);
411
412         return !!res;
413 }
414
415 int main(int argc, char **argv)
416 {
417         int opt, rv = 0, limit = 0x7FFFFFFF;
418         FILE *input = stdin;
419         struct json_object *jsobj = NULL;
420         const char *jserr = NULL, *source = NULL, *separator = " ";
421
422         if (argc == 1)
423         {
424                 print_usage(argv[0]);
425                 goto out;
426         }
427
428         while ((opt = getopt(argc, argv, "hi:s:e:t:F:l:q")) != -1)
429         {
430                 switch (opt)
431                 {
432                 case 'h':
433                         print_usage(argv[0]);
434                         goto out;
435
436                 case 'i':
437                         input = fopen(optarg, "r");
438
439                         if (!input)
440                         {
441                                 fprintf(stderr, "Failed to open %s: %s\n",
442                                                 optarg, strerror(errno));
443
444                                 rv = 125;
445                                 goto out;
446                         }
447
448                         break;
449
450                 case 's':
451                         source = optarg;
452                         break;
453
454                 case 'F':
455                         if (optarg && *optarg)
456                                 separator = optarg;
457                         break;
458
459                 case 'l':
460                         limit = atoi(optarg);
461                         break;
462
463                 case 't':
464                 case 'e':
465                         if (!jsobj)
466                         {
467                                 jsobj = parse_json(input, source, &jserr);
468
469                                 if (!jsobj)
470                                 {
471                                         fprintf(stderr, "Failed to parse json data: %s\n",
472                                                 jserr);
473
474                                         rv = 126;
475                                         goto out;
476                                 }
477                         }
478
479                         if (!filter_json(opt, jsobj, optarg, separator, limit))
480                                 rv = 1;
481
482                         break;
483
484                 case 'q':
485                         fclose(stderr);
486                         break;
487                 }
488         }
489
490 out:
491         if (jsobj)
492                 json_object_put(jsobj);
493
494         if (input && input != stdin)
495                 fclose(input);
496
497         return rv;
498 }