main: fix spurious premature parse aborts in array mode
[project/jsonpath.git] / main.c
diff --git a/main.c b/main.c
index 05e445c1569353f666aac89a2c872409e3c57b03..a0c7b0390c41997d2b600295a202a22a1411cd5c 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ * Copyright (C) 2013-2014 Jo-Philipp Wich <jo@mein.io>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -15,6 +15,8 @@
  */
 
 #include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
 #include <unistd.h>
 #include <errno.h>
 
        #include <json-c/json.h>
 #endif
 
+#include <libubox/list.h>
+
 #include "lexer.h"
 #include "parser.h"
 #include "matcher.h"
 
+
+struct match_item {
+       struct json_object *jsobj;
+       struct list_head list;
+};
+
+static void
+print_usage(char *app)
+{
+       printf(
+       "== Usage ==\n\n"
+       "  # %s [-a] [-i <file> | -s \"json...\"] {-t <pattern> | -e <pattern>}\n"
+       "  -q           Quiet, no errors are printed\n"
+       "  -h, --help   Print this help\n"
+       "  -a           Implicitely treat input as array, useful for JSON logs\n"
+       "  -i path      Specify a JSON file to parse\n"
+       "  -s \"json\"  Specify a JSON string to parse\n"
+       "  -l limit     Specify max number of results to show\n"
+       "  -F separator Specify a field separator when using export\n"
+       "  -t <pattern> Print the type of values matched by pattern\n"
+       "  -e <pattern> Print the values matched by pattern\n"
+       "  -e VAR=<pat> Serialize matched value for shell \"eval\"\n\n"
+       "== Patterns ==\n\n"
+       "  Patterns are JsonPath: http://goessner.net/articles/JsonPath/\n"
+       "  This tool implements $, @, [], * and the union operator ','\n"
+       "  plus the usual expressions and literals.\n"
+       "  It does not support the recursive child search operator '..' or\n"
+       "  the '?()' and '()' filter expressions as those would require a\n"
+       "  complete JavaScript engine to support them.\n\n"
+       "== Examples ==\n\n"
+       "  Display the first IPv4 address on lan:\n"
+       "  # ifstatus lan | %s -e '@[\"ipv4-address\"][0].address'\n\n"
+       "  Extract the release string from the board information:\n"
+       "  # ubus call system board | %s -e '@.release.description'\n\n"
+       "  Find all interfaces which are up:\n"
+       "  # ubus call network.interface dump | \\\n"
+       "       %s -e '@.interface[@.up=true].interface'\n\n"
+       "  Export br-lan traffic counters for shell eval:\n"
+       "  # devstatus br-lan | %s -e 'RX=@.statistics.rx_bytes' \\\n"
+       "       -e 'TX=@.statistics.tx_bytes'\n",
+               app, app, app, app, app);
+}
+
 static struct json_object *
-parse_json(FILE *fd)
+parse_json_chunk(struct json_tokener *tok, struct json_object *array,
+                 const char *buf, size_t len, enum json_tokener_error *err)
 {
-       int len;
-       char buf[256];
        struct json_object *obj = NULL;
+
+       while (len)
+       {
+               obj = json_tokener_parse_ex(tok, buf, len);
+               *err = json_tokener_get_error(tok);
+
+               if (*err == json_tokener_success)
+               {
+                       if (array)
+                       {
+                               json_object_array_add(array, obj);
+                       }
+                       else
+                       {
+                               break;
+                       }
+               }
+               else if (*err != json_tokener_continue)
+               {
+                       break;
+               }
+
+               buf += tok->char_offset;
+               len -= tok->char_offset;
+       }
+
+       return obj;
+}
+
+static struct json_object *
+parse_json(FILE *fd, const char *source, const char **error, bool array_mode)
+{
+       size_t len;
+       char buf[256];
+       struct json_object *obj = NULL, *array = NULL;
        struct json_tokener *tok = json_tokener_new();
        enum json_tokener_error err = json_tokener_continue;
 
        if (!tok)
+       {
+               *error = "Out of memory";
                return NULL;
+       }
 
-       while ((len = fread(buf, 1, sizeof(buf), fd)) > 0)
+       if (array_mode)
        {
-               obj = json_tokener_parse_ex(tok, buf, len);
-               err = json_tokener_get_error(tok);
+               array = json_object_new_array();
 
-               if (!err || err != json_tokener_continue)
-                       break;
+               if (!array)
+               {
+                       json_tokener_free(tok);
+                       *error = "Out of memory";
+                       return NULL;
+               }
+       }
+
+       if (source)
+       {
+               obj = parse_json_chunk(tok, array, source, strlen(source), &err);
+       }
+       else
+       {
+               while ((len = fread(buf, 1, sizeof(buf), fd)) > 0)
+               {
+                       obj = parse_json_chunk(tok, array, buf, len, &err);
+
+                       if ((err == json_tokener_success && array_mode == false) ||
+                           (err != json_tokener_continue && err != json_tokener_success))
+                               break;
+               }
        }
 
        json_tokener_free(tok);
 
-       return err ? NULL : obj;
+       if (err)
+       {
+               if (err == json_tokener_continue)
+                       err = json_tokener_error_parse_eof;
+
+               *error = json_tokener_error_desc(err);
+               return NULL;
+       }
+
+       return array ? array : obj;
 }
 
 static void
@@ -73,113 +185,308 @@ print_string(const char *s)
 }
 
 static void
-export_json(struct json_object *jsobj, char *expr)
+print_separator(const char *sep, int *sc, int sl)
 {
-       bool first;
-       struct jp_state *state;
-       struct json_object *res;
-       const char *prefix;
+       if (*sc > 0)
+       {
+               switch (sep[(*sc - 1) % sl])
+               {
+               case '"':
+                       printf("'\"'");
+                       break;
 
-       state = jp_parse(expr);
+               case '\'':
+                       printf("\"'\"");
+                       break;
 
-       if (!state || state->error)
-       {
-               fprintf(stderr, "In expression '%s': %s\n",
-                       expr, state ? state->error : "Out of memory");
+               case ' ':
+                       printf("\\ ");
+                       break;
 
-               goto out;
+               default:
+                       printf("%c", sep[(*sc - 1) % sl]);
+               }
        }
 
-       res = jp_match(state->path, jsobj);
+       (*sc)++;
+}
+
+static void
+export_value(struct list_head *matches, const char *prefix, const char *sep,
+             int limit)
+{
+       int n, len;
+       int sc = 0, sl = strlen(sep);
+       struct match_item *item;
+
+       if (list_empty(matches))
+               return;
 
-       if (state->path->type == T_LABEL)
+       if (prefix)
        {
-               prefix = state->path->str;
+               printf("export %s=", prefix);
 
-               switch (json_object_get_type(res))
+               list_for_each_entry(item, matches, list)
                {
-               case json_type_object:
-                       printf("export %s_TYPE=object; ", prefix);
+                       if (limit-- <= 0)
+                               break;
 
-                       first = true;
-                       printf("export %s_KEYS=", prefix);
-                       json_object_object_foreach(res, key, val)
+                       switch (json_object_get_type(item->jsobj))
                        {
-                               if (!val)
-                                       continue;
+                       case json_type_object:
+                               ; /* a label can only be part of a statement */
+                               json_object_object_foreach(item->jsobj, key, val)
+                               {
+                                       if (!val)
+                                               continue;
 
-                               if (!first)
-                                       printf("\\ ");
+                                       print_separator(sep, &sc, sl);
+                                       print_string(key);
+                               }
+                               break;
 
-                               print_string(key);
-                               first = false;
+                       case json_type_array:
+                               for (n = 0, len = json_object_array_length(item->jsobj);
+                                    n < len; n++)
+                               {
+                                       print_separator(sep, &sc, sl);
+                                       printf("%d", n);
+                               }
+                               break;
+
+                       case json_type_boolean:
+                               print_separator(sep, &sc, sl);
+                               printf("%d", json_object_get_boolean(item->jsobj));
+                               break;
+
+                       case json_type_int:
+                               print_separator(sep, &sc, sl);
+                               printf("%" PRId64, json_object_get_int64(item->jsobj));
+                               break;
+
+                       case json_type_double:
+                               print_separator(sep, &sc, sl);
+                               printf("%f", json_object_get_double(item->jsobj));
+                               break;
+
+                       case json_type_string:
+                               print_separator(sep, &sc, sl);
+                               print_string(json_object_get_string(item->jsobj));
+                               break;
+
+                       case json_type_null:
+                               break;
                        }
-                       printf("; ");
+               }
 
-                       //printf("export %s=", prefix);
-                       //print_string(json_object_to_json_string(res));
-                       //printf("; ");
+               printf("; ");
+       }
+       else
+       {
+               list_for_each_entry(item, matches, list)
+               {
+                       if (limit-- <= 0)
+                               break;
 
-                       break;
+                       switch (json_object_get_type(item->jsobj))
+                       {
+                       case json_type_object:
+                       case json_type_array:
+                       case json_type_boolean:
+                       case json_type_int:
+                       case json_type_double:
+                               printf("%s\n", json_object_to_json_string(item->jsobj));
+                               break;
+
+                       case json_type_string:
+                               printf("%s\n", json_object_get_string(item->jsobj));
+                               break;
+
+                       case json_type_null:
+                               break;
+                       }
+               }
+       }
+}
 
-               case json_type_array:
-                       printf("export %s_TYPE=array; ", prefix);
-                       printf("export %s_LENGTH=%d; ",
-                              prefix, json_object_array_length(res));
+static void
+export_type(struct list_head *matches, const char *prefix, int limit)
+{
+       bool first = true;
+       struct match_item *item;
+       const char *types[] = {
+               "null",
+               "boolean",
+               "double",
+               "int",
+               "object",
+               "array",
+               "string"
+       };
+
+       if (list_empty(matches))
+               return;
+
+       if (prefix)
+               printf("export %s=", prefix);
+
+       list_for_each_entry(item, matches, list)
+       {
+               if (!first)
+                       printf("\\ ");
 
-                       //printf("export %s=", prefix);
-                       //print_string(json_object_to_json_string(res));
-                       //printf("; ");
+               if (limit-- <= 0)
                        break;
 
-               case json_type_boolean:
-                       printf("export %s_TYPE=bool; ", prefix);
-                       printf("export %s=%d; ", prefix, json_object_get_boolean(res));
-                       break;
+               printf("%s", types[json_object_get_type(item->jsobj)]);
+               first = false;
+       }
 
-               case json_type_int:
-                       printf("export %s_TYPE=int; ", prefix);
-                       printf("export %s=%d; ", prefix, json_object_get_int(res));
-                       break;
+       if (prefix)
+               printf("; ");
+       else
+               printf("\n");
+}
 
-               case json_type_double:
-                       printf("export %s_TYPE=double; ", prefix);
-                       printf("export %s=%f; ", prefix, json_object_get_double(res));
-                       break;
+static void
+match_cb(struct json_object *res, void *priv)
+{
+       struct list_head *h = priv;
+       struct match_item *i = calloc(1, sizeof(*i));
 
-               case json_type_string:
-                       printf("export %s_TYPE=string; ", prefix);
-                       printf("export %s=", prefix);
-                       print_string(json_object_get_string(res));
-                       printf("; ");
-                       break;
+       if (i)
+       {
+               i->jsobj = res;
+               list_add_tail(&i->list, h);
+       }
+}
 
-               case json_type_null:
-                       printf("unset %s %s_TYPE %s_LENGTH %s_KEYS; ",
-                                  prefix, prefix, prefix, prefix);
-                       break;
+static void
+print_error(struct jp_state *state, char *expr)
+{
+       int i;
+       bool first = true;
+
+       fprintf(stderr, "Syntax error: ");
+
+       switch (state->error_code)
+       {
+       case -4:
+               fprintf(stderr, "Unexpected character\n");
+               break;
+
+       case -3:
+               fprintf(stderr, "String or label literal too long\n");
+               break;
+
+       case -2:
+               fprintf(stderr, "Invalid escape sequence\n");
+               break;
+
+       case -1:
+               fprintf(stderr, "Unterminated string\n");
+               break;
+
+       default:
+               for (i = 0; i < sizeof(state->error_code) * 8; i++)
+               {
+                       if (state->error_code & (1 << i))
+                       {
+                               fprintf(stderr,
+                                       first ? "Expecting %s" : " or %s", tokennames[i]);
+
+                               first = false;
+                       }
                }
+
+               fprintf(stderr, "\n");
+               break;
        }
-       else
+
+       fprintf(stderr, "In expression %s\n", expr);
+       fprintf(stderr, "Near here ----");
+
+       for (i = 0; i < state->error_pos; i++)
+               fprintf(stderr, "-");
+
+       fprintf(stderr, "^\n");
+}
+
+static bool
+filter_json(int opt, struct json_object *jsobj, char *expr, const char *sep,
+            int limit)
+{
+       struct jp_state *state;
+       const char *prefix = NULL;
+       struct list_head matches;
+       struct match_item *item, *tmp;
+       struct json_object *res = NULL;
+
+       state = jp_parse(expr);
+
+       if (!state)
        {
-               printf("%s\n", json_object_to_json_string(res));
+               fprintf(stderr, "Out of memory\n");
+               goto out;
        }
+       else if (state->error_code)
+       {
+               print_error(state, expr);
+               goto out;
+       }
+
+       INIT_LIST_HEAD(&matches);
+
+       res = jp_match(state->path, jsobj, match_cb, &matches);
+       prefix = (state->path->type == T_LABEL) ? state->path->str : NULL;
+
+       switch (opt)
+       {
+       case 't':
+               export_type(&matches, prefix, limit);
+               break;
+
+       default:
+               export_value(&matches, prefix, sep, limit);
+               break;
+       }
+
+       list_for_each_entry_safe(item, tmp, &matches, list)
+               free(item);
 
 out:
        if (state)
                jp_free(state);
+
+       return !!res;
 }
 
 int main(int argc, char **argv)
 {
-       int opt;
+       bool array_mode = false;
+       int opt, rv = 0, limit = 0x7FFFFFFF;
        FILE *input = stdin;
        struct json_object *jsobj = NULL;
+       const char *jserr = NULL, *source = NULL, *separator = " ";
+
+       if (argc == 1)
+       {
+               print_usage(argv[0]);
+               goto out;
+       }
 
-       while ((opt = getopt(argc, argv, "i:e:")) != -1)
+       while ((opt = getopt(argc, argv, "ahi:s:e:t:F:l:q")) != -1)
        {
                switch (opt)
                {
+               case 'a':
+                       array_mode = true;
+                       break;
+
+               case 'h':
+                       print_usage(argv[0]);
+                       goto out;
+
                case 'i':
                        input = fopen(optarg, "r");
 
@@ -188,32 +495,58 @@ int main(int argc, char **argv)
                                fprintf(stderr, "Failed to open %s: %s\n",
                                                optarg, strerror(errno));
 
-                               exit(1);
+                               rv = 125;
+                               goto out;
                        }
 
                        break;
 
+               case 's':
+                       source = optarg;
+                       break;
+
+               case 'F':
+                       if (optarg && *optarg)
+                               separator = optarg;
+                       break;
+
+               case 'l':
+                       limit = atoi(optarg);
+                       break;
+
+               case 't':
                case 'e':
                        if (!jsobj)
                        {
-                               jsobj = parse_json(input);
+                               jsobj = parse_json(input, source, &jserr, array_mode);
 
                                if (!jsobj)
                                {
-                                       fprintf(stderr, "Failed to parse json data\n");
-                                       exit(2);
+                                       fprintf(stderr, "Failed to parse json data: %s\n",
+                                               jserr);
+
+                                       rv = 126;
+                                       goto out;
                                }
                        }
 
-                       export_json(jsobj, optarg);
+                       if (!filter_json(opt, jsobj, optarg, separator, limit))
+                               rv = 1;
+
+                       break;
+
+               case 'q':
+                       fclose(stderr);
                        break;
                }
        }
 
+out:
        if (jsobj)
                json_object_put(jsobj);
 
-       fclose(input);
+       if (input && input != stdin)
+               fclose(input);
 
-       return 0;
+       return rv;
 }