2 changed files with 216 additions and 0 deletions
@ -0,0 +1,208 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
throw() { |
||||
|
echo "$*" >&2 |
||||
|
exit 1 |
||||
|
} |
||||
|
|
||||
|
BRIEF=0 |
||||
|
LEAFONLY=0 |
||||
|
PRUNE=0 |
||||
|
NO_HEAD=0 |
||||
|
NORMALIZE_SOLIDUS=0 |
||||
|
|
||||
|
usage() { |
||||
|
echo |
||||
|
echo "Usage: JSON.sh [-b] [-l] [-p] [-s] [-h]" |
||||
|
echo |
||||
|
echo "-p - Prune empty. Exclude fields with empty values." |
||||
|
echo "-l - Leaf only. Only show leaf nodes, which stops data duplication." |
||||
|
echo "-b - Brief. Combines 'Leaf only' and 'Prune empty' options." |
||||
|
echo "-n - No-head. Do not show nodes that have no path (lines that start with [])." |
||||
|
echo "-s - Remove escaping of the solidus symbol (straight slash)." |
||||
|
echo "-h - This help text." |
||||
|
echo |
||||
|
} |
||||
|
|
||||
|
parse_options() { |
||||
|
set -- "$@" |
||||
|
local ARGN=$# |
||||
|
while [ "$ARGN" -ne 0 ] |
||||
|
do |
||||
|
case $1 in |
||||
|
-h) usage |
||||
|
exit 0 |
||||
|
;; |
||||
|
-b) BRIEF=1 |
||||
|
LEAFONLY=1 |
||||
|
PRUNE=1 |
||||
|
;; |
||||
|
-l) LEAFONLY=1 |
||||
|
;; |
||||
|
-p) PRUNE=1 |
||||
|
;; |
||||
|
-n) NO_HEAD=1 |
||||
|
;; |
||||
|
-s) NORMALIZE_SOLIDUS=1 |
||||
|
;; |
||||
|
?*) echo "ERROR: Unknown option." |
||||
|
usage |
||||
|
exit 0 |
||||
|
;; |
||||
|
esac |
||||
|
shift 1 |
||||
|
ARGN=$((ARGN-1)) |
||||
|
done |
||||
|
} |
||||
|
|
||||
|
awk_egrep () { |
||||
|
local pattern_string=$1 |
||||
|
|
||||
|
gawk '{ |
||||
|
while ($0) { |
||||
|
start=match($0, pattern); |
||||
|
token=substr($0, start, RLENGTH); |
||||
|
print token; |
||||
|
$0=substr($0, start+RLENGTH); |
||||
|
} |
||||
|
}' pattern="$pattern_string" |
||||
|
} |
||||
|
|
||||
|
tokenize () { |
||||
|
local GREP |
||||
|
local ESCAPE |
||||
|
local CHAR |
||||
|
|
||||
|
if echo "test string" | egrep -ao --color=never "test" >/dev/null 2>&1 |
||||
|
then |
||||
|
GREP='egrep -ao --color=never' |
||||
|
else |
||||
|
GREP='egrep -ao' |
||||
|
fi |
||||
|
|
||||
|
if echo "test string" | egrep -o "test" >/dev/null 2>&1 |
||||
|
then |
||||
|
ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' |
||||
|
CHAR='[^[:cntrl:]"\\]' |
||||
|
else |
||||
|
GREP=awk_egrep |
||||
|
ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' |
||||
|
CHAR='[^[:cntrl:]"\\\\]' |
||||
|
fi |
||||
|
|
||||
|
local STRING="\"$CHAR*($ESCAPE$CHAR*)*\"" |
||||
|
local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' |
||||
|
local KEYWORD='null|false|true' |
||||
|
local SPACE='[[:space:]]+' |
||||
|
|
||||
|
# Force zsh to expand $A into multiple words |
||||
|
local is_wordsplit_disabled=$(unsetopt 2>/dev/null | grep -c '^shwordsplit$') |
||||
|
if [ $is_wordsplit_disabled != 0 ]; then setopt shwordsplit; fi |
||||
|
$GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$" |
||||
|
if [ $is_wordsplit_disabled != 0 ]; then unsetopt shwordsplit; fi |
||||
|
} |
||||
|
|
||||
|
parse_array () { |
||||
|
local index=0 |
||||
|
local ary='' |
||||
|
read -r token |
||||
|
case "$token" in |
||||
|
']') ;; |
||||
|
*) |
||||
|
while : |
||||
|
do |
||||
|
parse_value "$1" "$index" |
||||
|
index=$((index+1)) |
||||
|
ary="$ary""$value" |
||||
|
read -r token |
||||
|
case "$token" in |
||||
|
']') break ;; |
||||
|
',') ary="$ary," ;; |
||||
|
*) throw "EXPECTED , or ] GOT ${token:-EOF}" ;; |
||||
|
esac |
||||
|
read -r token |
||||
|
done |
||||
|
;; |
||||
|
esac |
||||
|
[ "$BRIEF" -eq 0 ] && value=$(printf '[%s]' "$ary") || value= |
||||
|
: |
||||
|
} |
||||
|
|
||||
|
parse_object () { |
||||
|
local key |
||||
|
local obj='' |
||||
|
read -r token |
||||
|
case "$token" in |
||||
|
'}') ;; |
||||
|
*) |
||||
|
while : |
||||
|
do |
||||
|
case "$token" in |
||||
|
'"'*'"') key=$token ;; |
||||
|
*) throw "EXPECTED string GOT ${token:-EOF}" ;; |
||||
|
esac |
||||
|
read -r token |
||||
|
case "$token" in |
||||
|
':') ;; |
||||
|
*) throw "EXPECTED : GOT ${token:-EOF}" ;; |
||||
|
esac |
||||
|
read -r token |
||||
|
parse_value "$1" "$key" |
||||
|
obj="$obj$key:$value" |
||||
|
read -r token |
||||
|
case "$token" in |
||||
|
'}') break ;; |
||||
|
',') obj="$obj," ;; |
||||
|
*) throw "EXPECTED , or } GOT ${token:-EOF}" ;; |
||||
|
esac |
||||
|
read -r token |
||||
|
done |
||||
|
;; |
||||
|
esac |
||||
|
[ "$BRIEF" -eq 0 ] && value=$(printf '{%s}' "$obj") || value= |
||||
|
: |
||||
|
} |
||||
|
|
||||
|
parse_value () { |
||||
|
local jpath="${1:+$1,}$2" isleaf=0 isempty=0 print=0 |
||||
|
case "$token" in |
||||
|
'{') parse_object "$jpath" ;; |
||||
|
'[') parse_array "$jpath" ;; |
||||
|
# At this point, the only valid single-character tokens are digits. |
||||
|
''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;; |
||||
|
*) value=$token |
||||
|
# if asked, replace solidus ("\/") in json strings with normalized value: "/" |
||||
|
[ "$NORMALIZE_SOLIDUS" -eq 1 ] && value=$(echo "$value" | sed 's#\\/#/#g') |
||||
|
isleaf=1 |
||||
|
[ "$value" = '""' ] && isempty=1 |
||||
|
;; |
||||
|
esac |
||||
|
[ "$value" = '' ] && return |
||||
|
[ "$NO_HEAD" -eq 1 ] && [ -z "$jpath" ] && return |
||||
|
|
||||
|
[ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 0 ] && print=1 |
||||
|
[ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && [ $PRUNE -eq 0 ] && print=1 |
||||
|
[ "$LEAFONLY" -eq 0 ] && [ "$PRUNE" -eq 1 ] && [ "$isempty" -eq 0 ] && print=1 |
||||
|
[ "$LEAFONLY" -eq 1 ] && [ "$isleaf" -eq 1 ] && \ |
||||
|
[ $PRUNE -eq 1 ] && [ $isempty -eq 0 ] && print=1 |
||||
|
[ "$print" -eq 1 ] && printf "[%s]\t%s\n" "$jpath" "$value" |
||||
|
: |
||||
|
} |
||||
|
|
||||
|
parse () { |
||||
|
read -r token |
||||
|
parse_value |
||||
|
read -r token |
||||
|
case "$token" in |
||||
|
'') ;; |
||||
|
*) throw "EXPECTED EOF GOT $token" ;; |
||||
|
esac |
||||
|
} |
||||
|
|
||||
|
if ([ "$0" = "$BASH_SOURCE" ] || ! [ -n "$BASH_SOURCE" ]); |
||||
|
then |
||||
|
parse_options "$@" |
||||
|
tokenize | parse |
||||
|
fi |
||||
|
|
||||
|
# vi: expandtab sw=2 ts=2 |
Loading…
Reference in new issue