diff --git a/ReadMe.pod b/ReadMe.pod index b94f3a5..ccf1934 100644 --- a/ReadMe.pod +++ b/ReadMe.pod @@ -1,7 +1,7 @@ =pod =for comment -DO NOT EDIT. This Pod was generated by Swim v0.1.41. +DO NOT EDIT. This Pod was generated by Swim v0.1.43. See http://github.com/ingydotnet/swim-pm#readme =encoding utf8 @@ -10,8 +10,7 @@ See http://github.com/ingydotnet/swim-pm#readme JSON - JSON for Bash -=for html -json-bash + =head1 Synopsis @@ -113,7 +112,22 @@ This function takes a linear tree as input and generates JSON as output. With no arguments, input is read from stdin. With one argument, input is taken from the provided variable name. To use the internal cache, use C<-> as the -variable name. Output is always written to stdout. +variable name. Output is always written to stdout. Formatting of the output +can be set using JSON.style (see below). + +=item C<< JSON.style minimal|normal|pretty [] >> + +This function sets style of output formatting for all subsequent calls to +JSON.dump. + +There are three different styles available: + + * `minimal` - single line, no unnecessary whitespace + * `normal` - single line, keys and values separated by single space + * `pretty` - one value per line, indented + +Before first call to this function, the style is set to "normal". The second +argument is only honored for "pretty" formatting style. =item C<< JSON.get [-a|-s|-b|-n|-z] [] >> diff --git a/bin/json-format b/bin/json-format new file mode 100755 index 0000000..8ad79b0 --- /dev/null +++ b/bin/json-format @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +PATH="$(dirname $0)/../lib:$PATH" source json.bash + +[ $# -gt 0 ] && JSON.style "$@" + +JSON.load | JSON.dump diff --git a/bin/json-to-linear b/bin/json-to-linear index 179094b..b3939ac 100755 --- a/bin/json-to-linear +++ b/bin/json-to-linear @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash PATH="$(dirname $0)/../lib:$PATH" source json.bash diff --git a/bin/json-value b/bin/json-value index 6b6a30e..a7cba76 100755 --- a/bin/json-value +++ b/bin/json-value @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash PATH="$(dirname $0)/../lib:$PATH" source json.bash diff --git a/doc/json.swim b/doc/json.swim index a0bf156..70356d9 100644 --- a/doc/json.swim +++ b/doc/json.swim @@ -97,7 +97,21 @@ remove data from the linear form. With no arguments, input is read from stdin. With one argument, input is taken from the provided variable name. To use the internal cache, use `-` as - the variable name. Output is always written to stdout. + the variable name. Output is always written to stdout. Formatting of + the output can be set using JSON.style (see below). + +- `JSON.style minimal|normal|pretty []` + + This function sets style of output formatting for all subsequent calls to + JSON.dump. + + There are three different styles available: + * `minimal` - single line, no unnecessary whitespace + * `normal` - single line, keys and values separated by single space + * `pretty` - one value per line, indented + + Before first call to this function, the style is set to "normal". + The second argument is only honored for "pretty" formatting style. - `JSON.get [-a|-s|-b|-n|-z] []` diff --git a/lib/json.bash b/lib/json.bash index 7141ee6..2c25b12 100644 --- a/lib/json.bash +++ b/lib/json.bash @@ -22,24 +22,126 @@ JSON.load() { : } + +JSON.style() { + local style="${1:?Style type is required}" + local indent=' ' + [[ $# -eq 1 ]] || indent="$2" + + case "$style" in + minimal) + JSON_INDENT="" + JSON_FIELD_SEP="," + JSON_KEY_SEP=":" + JSON_ARR_BEGIN="[" + JSON_ARR_END="]" + JSON_OBJ_BEGIN="{" + JSON_OBJ_END="}" + ;; + normal) + JSON_INDENT="" + JSON_FIELD_SEP=", " + JSON_KEY_SEP=": " + JSON_ARR_BEGIN="[" + JSON_ARR_END="]" + JSON_OBJ_BEGIN="{" + JSON_OBJ_END="}" + ;; + pretty) + JSON_INDENT="$indent" + JSON_FIELD_SEP=$',\n' + JSON_KEY_SEP=": " + JSON_ARR_BEGIN=$'[\n' + JSON_ARR_END=$'\nINDENT]' + JSON_OBJ_BEGIN=$'{\n' + JSON_OBJ_END=$'\nINDENT}' + ;; + *) JSON.die 'Usage: JSON.style minimal|normal|pretty []' ;; + esac +} +JSON.style normal + JSON.dump() { - JSON.die 'JSON.dump not yet implemented.' set -o pipefail case $# in 0) - JSON.normalize | sort | JSON.emit-json + JSON._dump ;; 1) if [[ $1 == '-' ]]; then - echo "$JSON__cache" | JSON.dump-json + echo "$JSON__cache" | JSON.dump else - echo ${!1} | JSON.dump-json + echo "${!1}" | JSON.dump fi ;; *) JSON.die 'Usage: JSON.dump []' ;; esac } +JSON._indent() { + [ "$1" -le 0 ] || printf "$JSON_INDENT%.0s" $(seq 1 "$1") +} + +JSON._dump() { + local stack=() + local prev=("/") + local first="" + while IFS=$'/\t' read -r -a line; do + [ ${#line[@]} -gt 0 ] || continue + last=$((${#line[@]}-1)) + val="${line[$last]}" + unset line[$last] + ((last--)) + for i in ${!line[@]}; do + [ "${prev[$i]}" != "${line[$i]}" ] || continue + while [ $i -lt ${#stack} ]; do + local type="${stack:0:1}" + stack="${stack:1}" + if [ "$type" = "a" ]; then + echo -n "${JSON_ARR_END//INDENT/$(JSON._indent ${#stack})}" + else + echo -n "${JSON_OBJ_END//INDENT/$(JSON._indent ${#stack})}" + fi + done + if [ $i -gt 0 ]; then + if [ -z "$first" ]; then + echo -n "$JSON_FIELD_SEP" + else + first=""; + fi + echo -n "$(JSON._indent ${#stack})" + [ "${stack:0:1}" = "a" ] || echo -n "\"${line[$i]}\"$JSON_KEY_SEP" + fi + if [ $i -eq $last ]; then + echo -n "$val" + else + if [[ "${line[((i+1))]}" =~ [0-9]+ ]]; then + stack="a$stack"; + echo -n "$JSON_ARR_BEGIN" + else + stack="o$stack"; + echo -n "$JSON_OBJ_BEGIN" + fi + first="1" + fi + done + prev=("${line[@]}") + done < <(sed 's/\t/\n/;' | + sed '1~2{;s|[0-9]\{1,\}|00000000000&|g;s|0*\([0-9]\{12,\}\)|\1|g;}' | + paste - - | + sort -k '1,1' -u) + local indent=$(( ${#stack} - 1 )) + for (( i=0; i<${#stack}; i++ )); do + if [ "${stack:$i:1}" = "a" ]; then + echo -n "${JSON_ARR_END//INDENT/$(JSON._indent $indent)}" + else + echo -n "${JSON_OBJ_END//INDENT/$(JSON._indent $indent)}" + fi + (( indent-- )) + done + echo +} + JSON.get() { local flag="" if [[ $# -gt 0 && $1 =~ ^-([asnbz])$ ]]; then diff --git a/man/man1/json.1 b/man/man1/json.1 index 00a56c6..c480bd5 100644 --- a/man/man1/json.1 +++ b/man/man1/json.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) +.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.29) .\" .\" Standard preamble: .\" ======================================================================== @@ -71,7 +71,7 @@ .\" ======================================================================== .\" .IX Title "\&\s-1JSON 1" -.TH \&\s-1JSON 1 "January 2016" "Generated by Swim v0.1.41" "JSON\s0 for Bash" +.TH \&\s-1JSON 1 "March 2016" "Generated by Swim v0.1.43" "JSON\s0 for Bash" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -79,6 +79,8 @@ .SH "Name" .IX Header "Name" \&\s-1JSON \- JSON\s0 for Bash +.PP + .SH "Synopsis" .IX Header "Synopsis" .Vb 1 @@ -169,7 +171,21 @@ With no arguments, input is read from stdin and output is written to stdout. Wit .IX Item "JSON.dump []" This function takes a linear tree as input and generates \s-1JSON\s0 as output. .Sp -With no arguments, input is read from stdin. With one argument, input is taken from the provided variable name. To use the internal cache, use \f(CW\*(C`\-\*(C'\fR as the variable name. Output is always written to stdout. +With no arguments, input is read from stdin. With one argument, input is taken from the provided variable name. To use the internal cache, use \f(CW\*(C`\-\*(C'\fR as the variable name. Output is always written to stdout. Formatting of the output can be set using \s-1JSON\s0.style (see below). +.ie n .IP """JSON.style minimal|normal|pretty []""" 4 +.el .IP "\f(CWJSON.style minimal|normal|pretty []\fR" 4 +.IX Item "JSON.style minimal|normal|pretty []" +This function sets style of output formatting for all subsequent calls to \s-1JSON\s0.dump. +.Sp +There are three different styles available: +.Sp +.Vb 3 +\& * \`minimal\` \- single line, no unnecessary whitespace +\& * \`normal\` \- single line, keys and values separated by single space +\& * \`pretty\` \- one value per line, indented +.Ve +.Sp +Before first call to this function, the style is set to \*(L"normal\*(R". The second argument is only honored for \*(L"pretty\*(R" formatting style. .ie n .IP """JSON.get [\-a|\-s|\-b|\-n|\-z] []""" 4 .el .IP "\f(CWJSON.get [\-a|\-s|\-b|\-n|\-z] []\fR" 4 .IX Item "JSON.get [-a|-s|-b|-n|-z] []" diff --git a/test/array.data b/test/array.data new file mode 100644 index 0000000..c4d5bf1 --- /dev/null +++ b/test/array.data @@ -0,0 +1,14 @@ +/0 "A" +/1 "B" +/2 "C" +/3/0 "D" +/3/1/0 "E" +/3/1/1 "F" +/4 "G" +/5 "H" +/6 "I" +/7 "J" +/8 "K" +/9 "L" +/10 "M" +/11 "N" diff --git a/test/dump.data b/test/dump.data new file mode 100644 index 0000000..263f880 --- /dev/null +++ b/test/dump.data @@ -0,0 +1,17 @@ +/int 12345678 +/str "json" +/x y z "spaces" +/sub/a "struct" +/sub/b 12345 +/sub/b 54321 +/sub/c 1.23 +/sub/d/e/f/g 54321 +/bool false +/empty null +/empty "" +/array/0 "A" +/array/1 "B" +/array/2 "C" +/array/3/0 "D" +/array/3/1/0 "E" +/array/3/1/1 "F" diff --git a/test/dump.t b/test/dump.t new file mode 100644 index 0000000..362dfdb --- /dev/null +++ b/test/dump.t @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +source test/setup + +use Test::More tests 27 +use JSON + +test_dump() { + JSON.style "$1" + ok $? "JSON.style succeeded" + + json1="$(JSON.dump < test/dump.data)" + ok $? "JSON.dump succeeded" + + [ -n "$json1" ] + ok $? "dumped result has content" + + JSON.load "$json1" tree1 + ok $? "dumped json can be loaded" + + json2="$(echo "$tree1" | JSON.dump)" + is "$json2" "$json1" "dump | load | dump produces same result as dump" + + is $(grep -o : <<<"$json1" | wc -l) \ + "$(grep -oE '/[^/ ]*' test/dump.data | sort -u | grep -cvE '/[0-9]*$')" \ + "dumped result contains correct number of keys" + + like "$json1" '"x y z": *"spaces"' "keys with spaces work correctly" + + json3="$(shuf test/array.data | JSON.dump)" + ok $? "JSON.dump succeeded" + + grep -o '"[A-Z]"' <<<"$json3" | sort -c + ok $? "keys are correctly ordered" +} + +test_dump normal +test_dump minimal +test_dump pretty diff --git a/test/keys.t b/test/keys.t index 577f356..e6a9415 100644 --- a/test/keys.t +++ b/test/keys.t @@ -15,14 +15,23 @@ is "$(JSON.get '/files/file 2.txt/type' tree1)" \ file_object=$(JSON.object '/files' tree1) +# XXX Can't get osx and linux to sort these the same. Workaround: +{ + if [[ "$(uname)" == Darwin ]]; then + expect="file 2.txt"$'\n'"file1.txt" + else + expect="file1.txt"$'\n'"file 2.txt" + fi +} + keys="$(JSON.keys '/' file_object)" is "$keys" \ - "file1.txt"$'\n'"file 2.txt" \ + "$expect" \ "JSON.keys '/'" #' keys="$(JSON.keys '/files' tree1)" is "$keys" \ - "file1.txt"$'\n'"file 2.txt" \ + "$expect" \ "JSON.keys '/files'" #' keys="$(JSON.keys '/' tree1)"