/*
- * 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
#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, 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 == json_tokener_success && !array)
+ break;
- if (!err || err != json_tokener_continue)
+ if (err != json_tokener_continue)
break;
}
}
return NULL;
}
- return obj;
+ return array ? array : obj;
}
static void
}
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",
"string"
};
+ if (list_empty(matches))
+ return;
+
+ if (prefix)
+ 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("export %s=%s; ", prefix, types[json_object_get_type(jsobj)]);
+ 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);
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");
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)
{
}
}
- if (!filter_json(opt, jsobj, optarg))
+ if (!filter_json(opt, jsobj, optarg, separator, limit))
rv = 1;
break;
if (jsobj)
json_object_put(jsobj);
- if (input != stdin)
+ if (input && input != stdin)
fclose(input);
return rv;