X-Git-Url: http://git.openwrt.org/?p=project%2Fjsonpath.git;a=blobdiff_plain;f=main.c;h=a0c7b0390c41997d2b600295a202a22a1411cd5c;hp=78fa0e4087b65fcce272020445829a4d65f74ebd;hb=HEAD;hpb=d7e77a322e98474071e850bbaaac46528abb81e8 diff --git a/main.c b/main.c index 78fa0e4..a0c7b03 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Jo-Philipp Wich + * Copyright (C) 2013-2014 Jo-Philipp Wich * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -25,35 +26,128 @@ #include #endif +#include + #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 | -s \"json...\"] {-t | -e }\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 Print the type of values matched by pattern\n" + " -e Print the values matched by pattern\n" + " -e VAR= 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, const char *source, const char **error) +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; + } + + if (array_mode) + { + array = json_object_new_array(); + + if (!array) + { + json_tokener_free(tok); + *error = "Out of memory"; + return NULL; + } + } if (source) { - obj = json_tokener_parse_ex(tok, source, strlen(source)); - err = json_tokener_get_error(tok); + obj = parse_json_chunk(tok, array, source, strlen(source), &err); } else { while ((len = fread(buf, 1, sizeof(buf), fd)) > 0) { - obj = json_tokener_parse_ex(tok, buf, len); - err = json_tokener_get_error(tok); + obj = parse_json_chunk(tok, array, buf, len, &err); - if (!err || err != json_tokener_continue) + if ((err == json_tokener_success && array_mode == false) || + (err != json_tokener_continue && err != json_tokener_success)) break; } } @@ -69,7 +163,7 @@ parse_json(FILE *fd, const char *source, const char **error) return NULL; } - return obj; + return array ? array : obj; } static void @@ -91,75 +185,135 @@ print_string(const char *s) } static void -export_value(struct json_object *jsobj, const char *prefix) +print_separator(const char *sep, int *sc, int sl) { - int n, len; - bool first = true; - - if (prefix) + if (*sc > 0) { - switch (json_object_get_type(jsobj)) + switch (sep[(*sc - 1) % sl]) { - case json_type_object: - printf("export %s=", prefix); - json_object_object_foreach(jsobj, key, val) - { - if (!val) - continue; + case '"': + printf("'\"'"); + break; - if (!first) - printf("\\ "); + case '\'': + printf("\"'\""); + break; - print_string(key); - first = false; - } - printf("; "); + case ' ': + printf("\\ "); break; - case json_type_array: - printf("export %s=", prefix); - for (n = 0, len = json_object_array_length(jsobj); n < len; n++) + default: + printf("%c", sep[(*sc - 1) % sl]); + } + } + + (*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 (prefix) + { + printf("export %s=", prefix); + + list_for_each_entry(item, matches, list) + { + if (limit-- <= 0) + break; + + switch (json_object_get_type(item->jsobj)) { - if (!first) - printf("\\ "); + case json_type_object: + ; /* a label can only be part of a statement */ + json_object_object_foreach(item->jsobj, key, val) + { + if (!val) + continue; - printf("%d", n); - first = false; - } - printf("; "); - break; + print_separator(sep, &sc, sl); + print_string(key); + } + break; - case json_type_boolean: - printf("export %s=%d; ", prefix, json_object_get_boolean(jsobj)); - break; + 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_int: - printf("export %s=%d; ", prefix, json_object_get_int(jsobj)); - break; + case json_type_boolean: + print_separator(sep, &sc, sl); + printf("%d", json_object_get_boolean(item->jsobj)); + break; - case json_type_double: - printf("export %s=%f; ", prefix, json_object_get_double(jsobj)); - break; + case json_type_int: + print_separator(sep, &sc, sl); + printf("%" PRId64, json_object_get_int64(item->jsobj)); + break; - case json_type_string: - printf("export %s=", prefix); - print_string(json_object_get_string(jsobj)); - printf("; "); - break; + case json_type_double: + print_separator(sep, &sc, sl); + printf("%f", json_object_get_double(item->jsobj)); + break; - case json_type_null: - 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("; "); } else { - printf("%s\n", json_object_to_json_string(jsobj)); + list_for_each_entry(item, matches, list) + { + if (limit-- <= 0) + 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; + } + } } } static void -export_type(struct json_object *jsobj, const char *prefix) +export_type(struct list_head *matches, const char *prefix, int limit) { + bool first = true; + struct match_item *item; const char *types[] = { "null", "boolean", @@ -170,47 +324,136 @@ export_type(struct json_object *jsobj, const char *prefix) "string" }; + if (list_empty(matches)) + return; + if (prefix) - printf("export %s=%s; ", prefix, types[json_object_get_type(jsobj)]); + printf("export %s=", prefix); + + list_for_each_entry(item, matches, list) + { + if (!first) + printf("\\ "); + + if (limit-- <= 0) + break; + + printf("%s", types[json_object_get_type(item->jsobj)]); + first = false; + } + + if (prefix) + printf("; "); else - printf("%s\n", types[json_object_get_type(jsobj)]); + printf("\n"); +} + +static void +match_cb(struct json_object *res, void *priv) +{ + struct list_head *h = priv; + struct match_item *i = calloc(1, sizeof(*i)); + + if (i) + { + i->jsobj = res; + list_add_tail(&i->list, h); + } +} + +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; + } + + 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) +filter_json(int opt, struct json_object *jsobj, char *expr, const char *sep, + int limit) { struct jp_state *state; - struct json_object *res = NULL; const char *prefix = NULL; + struct list_head matches; + struct match_item *item, *tmp; + struct json_object *res = NULL; state = jp_parse(expr); - if (!state || state->error) + if (!state) { - fprintf(stderr, "In expression '%s': %s\n", - expr, state ? state->error : "Out of memory"); - + fprintf(stderr, "Out of memory\n"); + goto out; + } + else if (state->error_code) + { + print_error(state, expr); goto out; } - res = jp_match(state->path, jsobj); + INIT_LIST_HEAD(&matches); - if (res) - { - prefix = (state->path->type == T_LABEL) ? state->path->str : NULL; + 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(res, prefix); - break; + switch (opt) + { + case 't': + export_type(&matches, prefix, limit); + break; - default: - export_value(res, prefix); - 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); @@ -220,15 +463,30 @@ out: int main(int argc, char **argv) { - int opt, rv = 0; + bool array_mode = false; + int opt, rv = 0, limit = 0x7FFFFFFF; FILE *input = stdin; struct json_object *jsobj = NULL; - const char *jserr = NULL, *source = NULL; + const char *jserr = NULL, *source = NULL, *separator = " "; + + if (argc == 1) + { + print_usage(argv[0]); + goto out; + } - while ((opt = getopt(argc, argv, "i:s:e:t:q")) != -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"); @@ -247,11 +505,20 @@ int main(int argc, char **argv) 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, source, &jserr); + jsobj = parse_json(input, source, &jserr, array_mode); if (!jsobj) { @@ -263,7 +530,7 @@ int main(int argc, char **argv) } } - if (!filter_json(opt, jsobj, optarg)) + if (!filter_json(opt, jsobj, optarg, separator, limit)) rv = 1; break; @@ -278,7 +545,7 @@ out: if (jsobj) json_object_put(jsobj); - if (input != stdin) + if (input && input != stdin) fclose(input); return rv;