pax_global_header00006660000000000000000000000064122255025750014517gustar00rootroot0000000000000052 comment=a970a8840c698b880ab23172d6a4bbbd4f257753 jshon-20131010/000077500000000000000000000000001222550257500130715ustar00rootroot00000000000000jshon-20131010/LICENSE000066400000000000000000000020661222550257500141020ustar00rootroot00000000000000Copyright (c) 2010-2013 Kyle Keen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jshon-20131010/Makefile000066400000000000000000000017171222550257500145370ustar00rootroot00000000000000# jshon - command line JSON parsing CFLAGS := -std=c99 -Wall -pedantic -Wextra -Werror ${CFLAGS} LDLIBS = -ljansson INSTALL=install DESTDIR?=/ MANDIR=$(DESTDIR)/usr/share/man/man1/ TARGET_PATH=$(DESTDIR)/usr/bin DISTFILES=jshon MANFILE=jshon.1 #VERSION=$(shell date +%Y%m%d) VERSION=$(shell git show -s --format="%ci" HEAD | cut -d ' ' -f 1 | tr -d '-') #VERSION=$(grep "^#define JSHONVER" | cut -d ' ' -f 3) all: $(DISTFILES) $(DISTFILES): jshon.o strip: $(DISFILES) strip --strip-all $(DISTFILES) clean: rm -f *.o $(DISTFILES) install: $(INSTALL) -D $(DISTFILES) $(TARGET_PATH)/$(DISTFILES) $(INSTALL) -D $(MANFILE) $(MANDIR)/$(MANFILE) dist: clean sed -i "s/#define JSHONVER .*/#define JSHONVER ${VERSION}/" jshon.c sed -i "s/Version:.*"/Version:\t${VERSION}" jshon.spec mkdir jshon-${VERSION} cp jshon.c jshon.1 Makefile LICENSE jshon-${VERSION} tar czf jshon-${VERSION}.tar.gz jshon-${VERSION} ${RM} -r jshon-${VERSION} .PHONY: all clean dist strip jshon-20131010/PKGBUILD000066400000000000000000000012301222550257500142110ustar00rootroot00000000000000# $Id: PKGBUILD 80520 2012-11-23 18:21:28Z kkeen $ # Maintainer: Kyle Keen pkgname=jshon pkgver=20130815 pkgrel=1 pkgdesc="A json parser for the shell." arch=('i686' 'x86_64') url="http://kmkeen.com/jshon/" license=('MIT') depends=('jansson') source=(http://kmkeen.com/$pkgname/$pkgname-$pkgver.tar.gz) md5sums=('3ef31b1954ef0838f67bcaf20993dcdd') build() { cd "$srcdir/$pkgname-$pkgver" make } package() { cd "$srcdir/$pkgname-$pkgver" install -Dm755 $pkgname "$pkgdir/usr/bin/$pkgname" install -Dm644 $pkgname.1 "$pkgdir/usr/share/man/man1/$pkgname.1" install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" } jshon-20131010/jshon.1000066400000000000000000000163671222550257500143110ustar00rootroot00000000000000.\" man 7 groff_mdoc Best resource ever .Dd September 1, 2013 .Dt JSHON \&1 "Jshon Manual" .Os " " .Sh NAME .Nm jshon .Nd JSON parser for the shell .Sh SYNOPSIS .Nm jshon -[P|S|Q|V|C|I|0] [-F path] -[t|l|k|u|p|a] -[s|n] value -[e|i|d] index .Sh DESCRIPTION .Nm parses, reads and creates JSON. It is designed to be as usable as possible from within the shell and replaces fragile adhoc parsers made from grep/sed/awk as well as heavyweight one-line parsers made from perl/python. .Pp .Nm loads json text from stdin, performs actions, then displays the last action on stdout. Some of the options output json, others output plain text summaries. Because Bash has very poor nested datastructures, .Nm does not return the JSON as a native object as a typical library would. Instead .Nm retains a history of edits in a stack, and you manipulate the topmost JSON element. . .Sh ACTIONS Each action takes the form of a short option. Some require arguments. While many instances of .Nm can be piped through each other, actions should be chained sequentially to reduce calls. All examples use this json sample: .Pp \& {"a":1,"b":[true,false,null,"str"],"c":{"d":4,"e":5}} .br \& jshon [actions] < sample.json .Pp Most common read-only uses will only need several .Nm \-e actions and one .Nm \-a in the middle of them. .Pp .Bl -tag -width ".." -compact .It Cm -t (type) returns string, object, array, number, bool, null .Pp \& jshon -t -> object .Pp .It Cm -l (length) returns an integer. Only works on string, object, array. .Pp \& jshon -l -> 3 .Pp .It Cm -k (keys) returns a newline separated list of keys. Only works on object. .Pp \& jshon -k -> a b c .Pp .It Cm -e index (extract) returns json value at "index". Only works on object, array. The index of an array is an integer. .Pp \& jshon -e c -> {"d":4,"e":5} .Pp .It Cm -a (across) maps the remaining actions across the selected element. Only works on objects and arrays. Multiple .Nm \-a calls can be nested, though the need is rare in practice. .Pp \& jshon -e b -a -t -> bool bool null string .Pp .It Cm -s value (string) returns a json encoded string. Can later be (-i)nserted to an existing structure. .Pp \& jshon -s "back\[rs]slash" -> "back\[rs]\[rs]slash" .Pp .It Cm -n value (nonstring/number) returns a json element. Can later be (-i)nserted to an existing structure. Valid values are 'true', 'false', 'null', 'array', 'object', integers and floats. Abbreviations t, f, n, [] and {} respectively also work. .Pp \& jshon -n object -> {} .Pp .It Cm -u (unstring) returns a decoded string. Only works on simple types: string, int, real, boolean, null. .Pp \& jshon -e b -e 3 -u -> str .Pp .It Cm -p (pop) pops the last manipulation from the stack, rewinding the history. Useful for extracting multiple values from one object. .Pp \& jshon -e c -e d -u -p -e e -u -> 4 5 .Pp .It Cm -d index (delete) removes an item in an array or object. Negative array indexes will wrap around. .Pp \& jshon -d b -> {"a":1,"c":{"d":4,"e":5}} .Pp .It Cm -i index (insert) is complicated. It is the reverse of extract. Extract puts a json sub-element on the stack. Insert removes a sub-element from the stack, and inserts that bit of json into the larger array/object underneath. Use extract to dive into the json tree, delete/string/nonstring to change things, and insert to push the changes back into the tree. .Pp \& jshon -e a -i a -> the orginal json .br \& jshon -s one -i a -> {"a":"one", ...} .Pp Arrays are handled in a special manner. Passing integers will insert a value without overwriting. Negative integers are acceptable, as is the string 'append'. To overwrite a value in an array: delete the index, .Nm \-n/s the new value, and then insert at the index. .Pp \& jshon -e b -d 0 -s q -i 0 -> {"b":"q",false,null,"str"} . .Pp .Sh NON-MANIPULATION There are several meta-options that do not directly edit json. Call these at most once per invocation. .Pp .Bl -tag -width ".." -compact .It Cm -F (file) reads from a file instead of stdin. The only non-manipulation option to take an argument. .Pp .It Cm -P (jsonp) strips a jsonp callback before continuing normally. .Pp .It Cm -S (sort) returns json sorted by key, instead of the original ordering. .Pp .It Cm -Q (quiet) disables error reporting on stderr, so you don't have to sprinkle "2> /dev/null" throughout your script. .Pp .It Cm -V (by-value) enables pass-by-value on the edit history stack. In extreme cases with thousands of deeply nested values this may result in .Nm running several times slower while using several times more memory. However by-value is safer than by-reference and generally causes less surprise. By-reference is enabled by default because there is no risk during read-only operations and generally makes editing json more convenient. .Pp \& jshon -e c -n 7 -i d -p -> c["d"] == 7 .br \& jshon -V -e c -n 7 -i d -p -> c["d"] == 5 .br \& jshon -V -e c -n 7 -i d -i c -> c["d"] == 7 .Pp With .Nm \-V , changes must be manually inserted back through the stack instead of simply popping off the intermediate values. .Pp .It Cm -C (continue) on potentially recoverable errors. For example, extracting values that don't exist will add 'null' to the edit stack instead of aborting. Behavior may change in the future. .Pp .It Cm -I (in-place) file editing. Requires a file to modify and so only works with -F. This is meant for making slight changes to a json file. When used, normal output is suppressed and the bottom of the edit stack is written out. .Pp .It Cm -0 (null delimiters) Changes the delimiter of -u from a newline to a null. This option only affects -u because that is the only time a newline may legitimately appear in the output. .Pp .It Cm --version Returns a YYYYMMDD timestamp and exits. . .Pp .Sh OTHER TOOLS .Nm always outputs one field per line. Many unix tools expect multiple tab separated fields per line. Pipe the output through 'paste' to fix this. However, paste can not handle empty lines so pad those with a placeholder. Here is an example: .Pp \& jshon ... | sed 's/^$/-/' | paste -s -d '\\t\\t\\n' .Pp This replaces blanks with '-' and merges every three lines into one. .Pp There are more and more tools that produce json output. Often these use a line-oriented json/plaintext hybrid where each line is an independent json structure. Sadly this means the output as a whole is not legitimate json. Either loop though the data line by line (calling .Nm once for each line) or convert it to a legitimate json array. For example: .Pp \& while read line; do jshon <<< "$line"; done < <(journalctl -o json) .Pp \& journalctl -o json | sed -e '1i[' -e '$!s/$/,/' -e '$a]' | jshon .Pp . .Pp .Sh GOLF If you care about extremely short one liners, arguments can be condensed when it does not cause ambiguity. The example from .Nm \-p(op) can be golfed as follows: .Pp \& jshon -e c -e d -u -p -e e -u == jshon -ec -ed -upee -u .Pp I do not recommend doing this (it makes things much harder to understand) but some people golf despite the consequences. . .Pp .Sh AUTHORS .An -nosplit .Pp .Nm was written by .An Kyle Keen Aq keenerd@gmail.com with patches from .An Dave Reisner Aq d@falconindy.com , .An AndrewF (BSD, OSX, jsonp, sorting), and .An Jean-Marc A (solaris). . .Pp .Sh BUGS Numerous! Floats may lose precision. Could be more convenient to use. Documentation is brief. jshon-20131010/jshon.c000066400000000000000000000673161222550257500143730ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include // MIT licensed, (c) 2011 Kyle Keen /* build with gcc -o jshon jshon.c -ljansson stdin is always json stdout is always json (except for -u, -t, -l, -k) -P -> detect and ignore JSONP wrapper, if present -S -> sort keys when writing objects -Q -> quiet, suppress stderr -V -> enable slower/safer pass-by-value -C -> continue through errors -F path -> read from file instead of stdin -I -> change file in place, requires -F -0 -> null delimiters -t(ype) -> str, object, list, number, bool, null -l(ength) -> only works on str, dict, list -k(eys) -> only works on dict -e(xtract) index -> only works on dict, list -s(tring) value -> adds json escapes -n(onstring) value -> creates true/false/null/array/object/int/float -u(nstring) -> removes json escapes, display value -p(op) -> pop/undo the last manipulation -d(elete) index -> remove an element from an object or array -i(nsert) index -> opposite of extract, merges json up the stack objects will overwrite, arrays will insert arrays can take negative numbers or 'append' -a(cross) -> iterate across the current dict or list --version -> returns an arbitrary number, exits Multiple commands can be chained. Entire json is loaded into memory. -e/-a copies and stores on a stack with -V. Could use up a lot of memory, usually does not. (For now we don't have to worry about circular refs, but adding 'swap' breaks that proof.) Consider a golf mode with shortcuts for -e -a -u -p -l -g 'results.*.Name.!.^.Version.!.^.Description.!' -g 'data.children.*.data.url.!' -g 'c.d.!.^.e.!' (! on object/array does -l) If you have keys with .!^* in them, use the normal options. Implementing this is going to be a pain. Maybe overwrite the original argv data? Maybe two nested parse loops? -L(abel) add jsonpipe/style/prefix/labels\t to pretty-printed json color? loadf for stdin? */ #define JSHONVER 20130901 // deal with API incompatibility between jansson 1.x and 2.x #ifndef JANSSON_MAJOR_VERSION # define JANSSON_MAJOR_VERSION (1) #endif #if JANSSON_MAJOR_VERSION < 2 # define compat_json_loads json_loads #else static json_t *compat_json_loads(const char *input, json_error_t *error) { return json_loads(input, 0, error); } #endif #if JANSSON_VERSION_HEX < 0x020400 # define JSON_ESCAPE_SLASH 0 #endif #if (defined (__SVR4) && defined (__sun)) #include int asprintf(char **ret, const char *format, ...) { va_list ap; fprintf(stderr, "%s\n", "in the asprintf"); *ret = NULL; /* Ensure value can be passed to free() */ va_start(ap, format); int count = vsnprintf(NULL, 0, format, ap); va_end(ap); if (count >= 0) { char* buffer = malloc(count + 1); if (buffer == NULL) {return -1;} va_start(ap, format); count = vsnprintf(buffer, count + 1, format, ap); va_end(ap); if (count < 0) { free(buffer); return count; } *ret = buffer; } return count; } #endif int dumps_flags = JSON_INDENT(1) | JSON_PRESERVE_ORDER | JSON_ESCAPE_SLASH; int by_value = 0; int in_place = 0; char delim = '\n'; char* file_path = ""; // for error reporting int quiet = 0; int crash = 1; char** g_argv; // stack depth is limited by maxargs // if you need more depth, use a SAX parser #define STACKDEPTH 128 json_t* stack[STACKDEPTH]; json_t** stackpointer = stack; void err(char* message) // also see arg_err() and json_err() below { if (!quiet) {fprintf(stderr, "%s\n", message);} if (crash) {exit(1);} } void hard_err(char* message) { err(message); exit(1); } void arg_err(char* message) { char* temp; int i; i = asprintf(&temp, message, optind-1, g_argv[optind-1]); if (i == -1) {hard_err("internal error: out of memory");} err(temp); } void PUSH(json_t* json) { if (stackpointer >= &stack[STACKDEPTH]) {hard_err("internal error: stack overflow");} if (json == NULL) { arg_err("parse error: bad json on arg %i, \"%s\""); json = json_null(); } *stackpointer++ = json; } json_t** stack_safe_peek() { if (stackpointer < &stack[1]) { err("internal error: stack underflow"); PUSH(json_null()); } return stackpointer - 1; } // can not use two macros on the same line #define POP *((stackpointer = stack_safe_peek())) #define PEEK *(stack_safe_peek()) json_t* maybe_deep(json_t* json) { if (by_value) {return json_deep_copy(json);} return json; } typedef struct { void* itr; // object iterator json_t** stk; // stack reentry uint lin; // array iterator int opt; // optind reentry int fin; // finished iteration } mapping; mapping mapstack[STACKDEPTH]; mapping* mapstackpointer = mapstack; mapping* map_safe_peek() { if (mapstackpointer < &mapstack[1]) {hard_err("internal error: mapstack underflow");} return mapstackpointer - 1; } void MAPPUSH() { if (mapstackpointer >= &mapstack[STACKDEPTH]) {hard_err("internal error: mapstack overflow");} mapstackpointer++; map_safe_peek()->stk = stack_safe_peek(); map_safe_peek()->opt = optind; switch (json_typeof(PEEK)) { case JSON_OBJECT: map_safe_peek()->itr = json_object_iter(PEEK); map_safe_peek()->fin = !map_safe_peek()->itr; break; case JSON_ARRAY: map_safe_peek()->lin = 0; map_safe_peek()->fin = json_array_size(*(map_safe_peek()->stk)) == 0; break; default: err("parse error: type not mappable"); } } void MAPNEXT() { stackpointer = map_safe_peek()->stk + 1; optind = map_safe_peek()->opt; switch (json_typeof(*(map_safe_peek()->stk))) { case JSON_OBJECT: json_object_iter_key(map_safe_peek()->itr); PUSH(maybe_deep(json_object_iter_value(map_safe_peek()->itr))); map_safe_peek()->itr = json_object_iter_next(*(map_safe_peek()->stk), map_safe_peek()->itr); if (!map_safe_peek()->itr) {map_safe_peek()->fin = 1;} break; case JSON_ARRAY: PUSH(maybe_deep(json_array_get(*(map_safe_peek()->stk), map_safe_peek()->lin))); map_safe_peek()->lin++; if (map_safe_peek()->lin >= json_array_size(*(map_safe_peek()->stk))) {map_safe_peek()->fin = 1;} break; default: err("parse error: type not mappable"); map_safe_peek()->fin = 1; } } void MAPPOP() { stackpointer = map_safe_peek()->stk; optind = map_safe_peek()->opt; mapstackpointer = map_safe_peek(); } // can not use two macros on the same line #define MAPPEEK *(map_safe_peek()) #define MAPEMPTY (mapstackpointer == mapstack) char* loop_read_fd(int fd) { char buffer[BUFSIZ]; char *content = NULL; size_t content_size = 0; size_t content_capacity = BUFSIZ * 2.5; content = malloc(content_capacity); if (content == NULL) { fprintf(stderr, "error: failed to allocate %zd bytes\n", content_capacity); return NULL; } for (;;) { ssize_t bytes_r = read(fd, buffer, sizeof(buffer)); if (bytes_r < 0) { fprintf(stderr, "error: failed to read from fd: %s\n", strerror(errno)); goto fail; } if (bytes_r == 0) { return content; } if (content_size + bytes_r >= content_capacity) { content_capacity *= 2.5; void *newalloc = realloc(content, content_capacity); if (newalloc == NULL) { fprintf(stderr, "error: failed to reallocate buffer to %zd bytes\n", content_capacity); goto fail; } content = newalloc; } memcpy(&content[content_size], buffer, bytes_r); content_size += bytes_r; content[content_size] = '\0'; } return content; fail: free(content); return NULL; } char* read_stream(FILE* fp) { struct stat st; char *buffer; if (fstat(fileno(fp), &st) < 0) { fprintf(stderr, "failed to stat file: %s\n", strerror(errno)); return NULL; } if (st.st_size == 0 && lseek(fileno(fp), 0, SEEK_CUR) < 0) { return loop_read_fd(fileno(fp)); } buffer = malloc(st.st_size + 1); if (buffer == NULL) { fprintf(stderr, "error: failed to allocate %zd bytes\n", st.st_size + 1); return NULL; } size_t bytes_r = fread(buffer, 1, st.st_size, fp); if ((ssize_t)bytes_r != st.st_size) { fprintf(stderr, "short read: expected to read %zd bytes, only got %zd\n", st.st_size, bytes_r); } return buffer; } char* read_stdin(void) { if (isatty(fileno(stdin))) {return "";} return read_stream(stdin); } char* read_file(char* path) { FILE* fp; char* content; fp = fopen(path, "r"); content = read_stream(fp); fclose(fp); return content; } char* remove_jsonp_callback(char* in, int* rows_skipped, int* cols_skipped) // this 'removes' jsonp callback code which can surround json, by returning // a pointer to first byte of real JSON, and overwriting the jsonp stuff at // the end of the input with a null byte. it also writes out the number of // lines, and then columns, which were skipped over. // // if a legitimate jsonp callback surround is not detected, the original // input is returned and no other action is taken. this means that JSONP // syntax errors will be effectively ignored, and will then fail json parsing // // this doesn't detect all conceivable JSONP wrappings. a simple function call // with a reasonable ASCII identifier will work, and that covers 99% of the // real world { #define JSON_WHITE(x) ((x) == 0x20 || (x) == 0x9 || (x) == 0xA || (x) == 0xD) #define JSON_IDENTIFIER(x) (isalnum(x) || (x) == '$' || (x) == '_' || (x) == '.') char* first = in; char* last = in + strlen(in) - 1; // skip over whitespace and semicolons at the end while (first < last && (JSON_WHITE(*last) || *last == ';')) {--last;} // count closing brackets at the end, still skipping whitespace int brackets = 0; while (first < last && (JSON_WHITE(*last) || *last == ')')) { if (*last == ')') {++brackets;} --last; } // no closing brackets? it's not jsonp if (brackets == 0) {return in;} // skip leading whitespace while (first < last && JSON_WHITE(*first)) {++first;} // skip leading identifier if present while (first < last && JSON_IDENTIFIER(*first)) {++first;} // skip over forward brackets and whitespace, counting down the opening brackets // against the closing brackets we've already done while (first < last && (JSON_WHITE(*first) || *first == '(')) { if (*first == '(') {--brackets;} ++first; } // at this point we have a valid jsonp wrapper, provided that the number of opening // and closing brackets matched, and provided the two pointers didn't meet in // the middle (leaving no room for any actual JSON) if (brackets != 0 || !(first < last)) {return in;} // count lines and columns skipped over *rows_skipped = *cols_skipped = 0; while (in < first) { ++*cols_skipped; if (*in++ == '\n') { *cols_skipped = 0; ++*rows_skipped; } } // strip off beginning and end *(last+1) = '\0'; return first; } #if JANSSON_VERSION_HEX < 0x020100 char* smart_dumps(json_t* json) // json_dumps is broken on simple types { char* temp; char* temp2; json_t* j2; int i; switch (json_typeof(json)) { case JSON_OBJECT: return json_dumps(json, dumps_flags); case JSON_ARRAY: return json_dumps(json, dumps_flags); case JSON_STRING: // hack to print escaped string j2 = json_array(); json_array_append(j2, json); temp = json_dumps(j2, JSON_ESCAPE_SLASH); i = asprintf(&temp2, "%.*s", (signed)strlen(temp)-2, &temp[1]); if (i == -1) {hard_err("internal error: out of memory");} return temp2; case JSON_INTEGER: i = asprintf(&temp, "%" JSON_INTEGER_FORMAT, json_integer_value(json)); if (i == -1) {hard_err("internal error: out of memory");} return temp; case JSON_REAL: i = asprintf(&temp, "%f", json_real_value(json)); if (i == -1) {hard_err("internal error: out of memory");} return temp; case JSON_TRUE: return "true"; case JSON_FALSE: return "false"; case JSON_NULL: return "null"; default: err("internal error: unknown type"); return "null"; } } #else char* smart_dumps(json_t* json) { switch (json_typeof(json)) { case JSON_OBJECT: case JSON_ARRAY: case JSON_STRING: case JSON_INTEGER: case JSON_REAL: case JSON_TRUE: case JSON_FALSE: case JSON_NULL: return json_dumps(json, dumps_flags | JSON_ENCODE_ANY); default: err("internal error: unknown type"); return "null"; } } #endif /*char* pretty_dumps(json_t* json) // underscore-style colorizing // needs a more or less rewrite of dumps() { int depth = 0; // loop over everything // needs a stack // number, orange // string, green // null, bold white // string, purple? }*/ #if JANSSON_VERSION_HEX < 0x020300 json_t* smart_loads(char* j_string) // json_loads is broken on simple types { json_t* json; json_error_t error; char *temp; int i; i = asprintf(&temp, "[%s]", j_string); if (i == -1) {hard_err("internal error: out of memory");} json = compat_json_loads(temp, &error); if (!json) {return json_string(j_string);} return json_array_get(json, 0); } #else json_t* smart_loads(char* j_string) { json_error_t error; return json_loads(j_string, JSON_DECODE_ANY, &error); } #endif char* pretty_type(json_t* json) { if (json == NULL) {err("internal error: null pointer"); return "NULL";} switch (json_typeof(json)) { case JSON_OBJECT: return "object"; case JSON_ARRAY: return "array"; case JSON_STRING: return "string"; case JSON_INTEGER: case JSON_REAL: return "number"; case JSON_TRUE: case JSON_FALSE: return "bool"; case JSON_NULL: return "null"; default: err("internal error: unknown type"); return "NULL"; } } void json_err(char* message, json_t* json) { char* temp; int i; i = asprintf(&temp, "parse error: type '%s' %s (arg %i)", pretty_type(json), message, optind-1); if (i == -1) {hard_err("internal error: out of memory");} err(temp); } int length(json_t* json) { switch (json_typeof(json)) { case JSON_OBJECT: return json_object_size(json); case JSON_ARRAY: return json_array_size(json); case JSON_STRING: return strlen(json_string_value(json)); case JSON_INTEGER: case JSON_REAL: case JSON_TRUE: case JSON_FALSE: case JSON_NULL: default: json_err("has no length", json); return 0; } } int compare_strcmp(const void *a, const void *b) { const char *sa = ((const char**)a)[0]; const char *sb = ((const char**)b)[0]; return strcmp(sa, sb); } void keys(json_t* json) // shoddy, prints directly { void* iter; const char** keys; size_t i, n; if (!json_is_object(json)) {json_err("has no keys", json); return;} if (!((keys = malloc(sizeof(char*) * json_object_size(json))))) {hard_err("internal error: out of memory");} iter = json_object_iter(json); n = 0; while (iter) { keys[n++] = json_object_iter_key(iter); iter = json_object_iter_next(json, iter); } if (dumps_flags & JSON_SORT_KEYS) {qsort(keys, n, sizeof(char*), compare_strcmp);} for (i = 0; i < n; ++i) {printf("%s\n", keys[i]);} free(keys); } json_t* nonstring(char* arg) { json_t* temp; char* endptr; if (!strcmp(arg, "null") || !strcmp(arg, "n")) {return json_null();} if (!strcmp(arg, "true") || !strcmp(arg, "t")) {return json_true();} if (!strcmp(arg, "false") || !strcmp(arg, "f")) {return json_false();} if (!strcmp(arg, "array") || !strcmp(arg, "[]")) {return json_array();} if (!strcmp(arg, "object") || !strcmp(arg, "{}")) {return json_object();} errno = 0; temp = json_integer(strtol(arg, &endptr, 10)); if (!errno && *endptr=='\0') {return temp;} errno = 0; temp = json_real(strtod(arg, &endptr)); if (!errno && *endptr=='\0') {return temp;} arg_err("parse error: illegal nonstring on arg %i, \"%s\""); return json_null(); } const char* unstring(json_t* json) { switch (json_typeof(json)) { case JSON_STRING: return json_string_value(json); case JSON_INTEGER: case JSON_REAL: case JSON_TRUE: case JSON_FALSE: case JSON_NULL: return smart_dumps(json); case JSON_OBJECT: case JSON_ARRAY: default: json_err("is not simple/printable", json); return ""; } } int estrtol(char* key) // strtol with more error handling { int i; char* endptr; errno = 0; i = strtol(key, &endptr, 10); if (errno || *endptr!='\0') { arg_err("parse error: illegal index on arg %i, \"%s\""); //return json_null(); i = 0; } return i; } json_t* extract(json_t* json, char* key) { int i, s; json_t* temp; switch (json_typeof(json)) { case JSON_OBJECT: temp = json_object_get(json, key); if (temp == NULL) {break;} return temp; case JSON_ARRAY: s = json_array_size(json); if (s == 0) {json_err("index out of bounds", json); break;} i = estrtol(key); if ((i < -s) || (i >= s)) {json_err("index out of bounds", json);} // stupid fix for a stupid modulus operation while (i<0) {i+=s;} return json_array_get(json, i % s); case JSON_STRING: case JSON_INTEGER: case JSON_REAL: case JSON_TRUE: case JSON_FALSE: case JSON_NULL: default: break; } json_err("has no elements to extract", json); return json_null(); } json_t* delete(json_t* json, char* key) // no error checking { int i, s; switch (json_typeof(json)) { case JSON_OBJECT: json_object_del(json, key); return json; case JSON_ARRAY: s = json_array_size(json); if (s == 0) {return json;} i = estrtol(key); json_array_remove(json, i % s); return json; case JSON_STRING: case JSON_INTEGER: case JSON_REAL: case JSON_TRUE: case JSON_FALSE: case JSON_NULL: default: json_err("cannot lose elements", json); return json; } } json_t* update_native(json_t* json, char* key, json_t* j_value) // no error checking { int i, s; switch (json_typeof(json)) { case JSON_OBJECT: json_object_set(json, key, j_value); return json; case JSON_ARRAY: if (!strcmp(key, "append")) { json_array_append(json, j_value); return json; } // otherwise, insert i = estrtol(key); s = json_array_size(json); if (s == 0) {i = 0;} else {i = i % s;} json_array_insert(json, i, j_value); return json; case JSON_STRING: case JSON_INTEGER: case JSON_REAL: case JSON_TRUE: case JSON_FALSE: case JSON_NULL: default: json_err("cannot gain elements", json); return json; } } json_t* update(json_t* json, char* key, char* j_string) { return update_native(json, key, smart_loads(j_string)); } void debug_stack(int optchar) { json_t** j; printf("BEGIN STACK DUMP %c\n", optchar); for (j=stack; jstk)));} } int main (int argc, char *argv[]) #define ALL_OPTIONS "PSQVCI0tlkupaF:e:s:n:d:i:" { char* content = ""; char* arg1 = ""; FILE* fp; json_t* json = NULL; json_t* jval = NULL; json_error_t error; int output = 1; // flag if json should be printed int optchar; int jsonp = 0; // flag if we should tolerate JSONP wrapping int jsonp_rows = 0, jsonp_cols = 0; // rows+cols skipped over by JSONP prologue int empty; g_argv = argv; // todo: get more jsonp stuff out of main // avoiding getopt_long for now because the BSD version is a pain if (argc == 2 && strncmp(argv[1], "--version", 9) == 0) {printf("%i\n", JSHONVER); exit(0);} // non-manipulation options while ((optchar = getopt(argc, argv, ALL_OPTIONS)) != -1) { switch (optchar) { case 'P': jsonp = 1; break; case 'S': dumps_flags &= ~JSON_PRESERVE_ORDER; dumps_flags |= JSON_SORT_KEYS; break; case 'Q': quiet = 1; break; case 'V': by_value = 1; break; case 'C': crash = 0; break; case 'I': in_place = 1; break; case 'F': file_path = (char*) strdup(optarg); break; case '0': delim = '\0'; break; case 't': case 'l': case 'k': case 'u': case 'p': case 'e': case 's': case 'n': case 'd': case 'i': case 'a': break; default: if (!quiet) {fprintf(stderr, "Valid: -[P|S|Q|V|C|I|0] [-F path] -[t|l|k|u|p|a] -[s|n] value -[e|i|d] index\n");} if (crash) {exit(2);} break; } } optind = 1; #ifdef BSD optreset = 1; #endif if (in_place && strlen(file_path)==0) {err("warning: in-place editing (-I) requires -F");} if (!strcmp(file_path, "-")) {content = read_stdin();} else if (strlen(file_path) > 0) {content = read_file(file_path);} else {content = read_stdin();} if (!content[0] && !quiet) {fprintf(stderr, "warning: nothing to read\n");} if (jsonp) {content = remove_jsonp_callback(content, &jsonp_rows, &jsonp_cols);} if (content[0]) {json = compat_json_loads(content, &error);} if (!json && content[0]) { const char *jsonp_status = ""; if (jsonp) {jsonp_status = (jsonp_rows||jsonp_cols) ? "(jsonp detected) " : "(jsonp not detected) ";} #if JANSSON_MAJOR_VERSION < 2 if (!quiet) {fprintf(stderr, "json %sread error: line %0d: %s\n", jsonp_status, error.line + jsonp_rows, error.text);} #else if (!quiet) {fprintf(stderr, "json %sread error: line %0d column %0d: %s\n", jsonp_status, error.line + jsonp_rows, error.column + jsonp_cols, error.text);} #endif exit(1); } if (json) {PUSH(json);} do { if (! MAPEMPTY) { while (map_safe_peek()->fin) { MAPPOP(); if (MAPEMPTY) {exit(0);} } MAPNEXT(); } while ((optchar = getopt(argc, argv, ALL_OPTIONS)) != -1) { empty = 0; switch (optchar) { case 't': // id type printf("%s\n", pretty_type(PEEK)); output = 0; break; case 'l': // length printf("%i\n", length(PEEK)); output = 0; break; case 'k': // keys keys(PEEK); output = 0; break; case 'u': // unescape string printf("%s%c", unstring(PEEK), delim); output = 0; break; case 'p': // pop stack json = POP; if (by_value) {json_decref(json);} output = 1; break; case 's': // load string arg1 = (char*) strdup(optarg); PUSH(json_string(arg1)); output = 1; break; case 'n': // load nonstring arg1 = (char*) strdup(optarg); PUSH(nonstring(arg1)); output = 1; break; case 'e': // extract arg1 = (char*) strdup(optarg); json = PEEK; PUSH(extract(maybe_deep(json), arg1)); output = 1; break; case 'd': // delete arg1 = (char*) strdup(optarg); json = POP; PUSH(delete(json, arg1)); output = 1; break; case 'i': // insert arg1 = (char*) strdup(optarg); jval = POP; json = POP; PUSH(update_native(json, arg1, jval)); output = 1; break; case 'a': // across // something about -a is not mappable? MAPPUSH(); empty = map_safe_peek()->fin; if (!empty) {MAPNEXT();} output = 0; break; case 'P': // not manipulations case 'S': case 'Q': case 'V': case 'C': case 'I': case 'F': case '0': break; default: if (crash) {exit(2);} break; } if (empty) {break;} } if (!in_place && output && stackpointer != stack) {printf("%s\n", smart_dumps(PEEK));} } while (! MAPEMPTY); if (in_place && strlen(file_path) > 0) { fp = fopen(file_path, "w"); fprintf(fp, "%s\n", smart_dumps(stack[0])); fclose(fp); } return 0; } jshon-20131010/jshon.spec000066400000000000000000000013471222550257500150730ustar00rootroot00000000000000Name: jshon Version: 20130922 Release: 0%{?dist} Summary: Jshon is a JSON parser designed for maximum convenience within the shell Group: Applications/System License: MIT URL: http://kmkeen.com/jshon Source0: jshon-%{version}.tar.gz Patch0: jshon-makefile-enable-install.patch BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) BuildRequires: gcc >= 3.4.6, jansson Requires: jansson %description jshon %prep %setup -q %patch0 -p0 %build make %{?_smp_mflags} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} %clean rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_bindir} %doc %_mandir/man1/jshon.1.gz %changelog * Tue Oct 1 2013 Jiri Horky - Initial spec file