Skip to content

Json.dump #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions ReadMe.pod
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -10,8 +10,7 @@ See http://github.com/ingydotnet/swim-pm#readme

JSON - JSON for Bash

=for html
<a href="https://travis-ci.org/ingydotnet/json-bash"><img src="https://travis-ci.org/ingydotnet/json-bash.png" alt="json-bash"></a>
<badge travis ingydotnet/json-bash>

=head1 Synopsis

Expand Down Expand Up @@ -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 [<indent-string>] >>

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] <key-path> [<linear-var-name>] >>

Expand Down
7 changes: 7 additions & 0 deletions bin/json-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

PATH="$(dirname $0)/../lib:$PATH" source json.bash

[ $# -gt 0 ] && JSON.style "$@"

JSON.load | JSON.dump
2 changes: 1 addition & 1 deletion bin/json-to-linear
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash

PATH="$(dirname $0)/../lib:$PATH" source json.bash

Expand Down
2 changes: 1 addition & 1 deletion bin/json-value
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash

PATH="$(dirname $0)/../lib:$PATH" source json.bash

Expand Down
16 changes: 15 additions & 1 deletion doc/json.swim
Original file line number Diff line number Diff line change
Expand Up @@ -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 [<indent-string>]`

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] <key-path> [<linear-var-name>]`

Expand Down
110 changes: 106 additions & 4 deletions lib/json.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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 [<indent-string>]' ;;
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 [<tree-var>]' ;;
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
Expand Down
22 changes: 19 additions & 3 deletions man/man1/json.1
Original file line number Diff line number Diff line change
@@ -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:
.\" ========================================================================
Expand Down Expand Up @@ -71,14 +71,16 @@
.\" ========================================================================
.\"
.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
.nh
.SH "Name"
.IX Header "Name"
\&\s-1JSON \- JSON\s0 for Bash
.PP
<badge travis ingydotnet/json\-bash>
.SH "Synopsis"
.IX Header "Synopsis"
.Vb 1
Expand Down Expand Up @@ -169,7 +171,21 @@ With no arguments, input is read from stdin and output is written to stdout. Wit
.IX Item "JSON.dump [<linear-var-name>]"
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 [<indent\-string>]""" 4
.el .IP "\f(CWJSON.style minimal|normal|pretty [<indent\-string>]\fR" 4
.IX Item "JSON.style minimal|normal|pretty [<indent-string>]"
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] <key\-path> [<linear\-var\-name>]""" 4
.el .IP "\f(CWJSON.get [\-a|\-s|\-b|\-n|\-z] <key\-path> [<linear\-var\-name>]\fR" 4
.IX Item "JSON.get [-a|-s|-b|-n|-z] <key-path> [<linear-var-name>]"
Expand Down
14 changes: 14 additions & 0 deletions test/array.data
Original file line number Diff line number Diff line change
@@ -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"
17 changes: 17 additions & 0 deletions test/dump.data
Original file line number Diff line number Diff line change
@@ -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"
39 changes: 39 additions & 0 deletions test/dump.t
Original file line number Diff line number Diff line change
@@ -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
13 changes: 11 additions & 2 deletions test/keys.t
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down