From e597bb208efe942f30139df88b5ef74e78c15925 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 10 Jun 2024 11:33:12 -0700 Subject: [PATCH] [Tests] use a better JSON parsing implementation --- test/common.sh | 234 +++++++++++++++---------- test/fast/Unit tests/nvm_process_nvmrc | 4 +- 2 files changed, 140 insertions(+), 98 deletions(-) diff --git a/test/common.sh b/test/common.sh index 3fccea135e..f5bf6ffbf6 100644 --- a/test/common.sh +++ b/test/common.sh @@ -102,104 +102,146 @@ watch() { return $EXIT_CODE } -parse_json() { - local json - json="$1" - local key - key="" - local value - value="" - local output - output="" - local in_key - in_key=0 - local in_value - in_value=0 - local in_string - in_string=0 - local escaped - escaped=0 - local buffer - buffer="" - local char - local len - len=${#json} - local arr_index - arr_index=0 - local in_array - in_array=0 - - for ((i = 0; i < len; i++)); do - char="${json:i:1}" - - if [ "$in_string" -eq 1 ]; then - if [ "$escaped" -eq 1 ]; then - buffer="$buffer$char" - escaped=0 - elif [ "$char" = "\\" ]; then - escaped=1 - elif [ "$char" = "\"" ]; then - in_string=0 - if [ "$in_key" -eq 1 ]; then - key="$buffer" - buffer="" - in_key=0 - elif [ "$in_value" -eq 1 ]; then - value="$buffer" - buffer="" - output="$output$key=\"$value\"\n" - in_value=0 - elif [ "$in_array" -eq 1 ]; then - value="$buffer" - buffer="" - output="$output$arr_index=\"$value\"\n" - arr_index=$((arr_index + 1)) - fi - else - buffer="$buffer$char" - fi - continue - fi - - case "$char" in - "\"") - in_string=1 - buffer="" - if [ "$in_value" -eq 0 ] && [ "$in_array" -eq 0 ]; then - in_key=1 - fi - ;; - ":") - in_value=1 - ;; - ",") - if [ "$in_value" -eq 1 ]; then - in_value=0 - fi - ;; - "[") - in_array=1 - ;; - "]") - in_array=0 - ;; - "{" | "}") - ;; - *) - if [ "$in_value" -eq 1 ] && [ "$char" != " " ] && [ "$char" != "\n" ] && [ "$char" != "\t" ]; then - buffer="$buffer$char" - fi - ;; - esac - done - printf "%b" "$output" +# JSON parsing from https://gist.github.com/assaf/ee377a186371e2e269a7 +nvm_json_throw() { + nvm_err "$*" + exit 1 +} + +nvm_json_awk_egrep() { + local pattern_string + pattern_string="${1}" + + awk '{ + while ($0) { + start=match($0, pattern); + token=substr($0, start, RLENGTH); + print token; + $0=substr($0, start+RLENGTH); + } + }' "pattern=${pattern_string}" +} + +nvm_json_tokenize() { + local GREP + GREP='grep -Eao' + + local ESCAPE + local CHAR + + # if echo "test string" | grep -Eo "test" > /dev/null 2>&1; then + # ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' + # CHAR='[^[:cntrl:]"\\]' + # else + GREP=nvm_json_awk_egrep + ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})' + CHAR='[^[:cntrl:]"\\\\]' + # fi + + local STRING + STRING="\"${CHAR}*(${ESCAPE}${CHAR}*)*\"" + local NUMBER + NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?' + local KEYWORD + KEYWORD='null|false|true' + local SPACE + SPACE='[[:space:]]+' + + $GREP "${STRING}|${NUMBER}|${KEYWORD}|${SPACE}|." | TERM=dumb grep -Ev "^${SPACE}$" +} + +_json_parse_array() { + local index=0 + local ary='' + read -r token + case "$token" in + ']') ;; + *) + while :; do + _json_parse_value "${1}" "${index}" + index=$((index+1)) + ary="${ary}${value}" + read -r token + case "${token}" in + ']') break ;; + ',') ary="${ary}," ;; + *) nvm_json_throw "EXPECTED , or ] GOT ${token:-EOF}" ;; + esac + read -r token + done + ;; + esac + : } -extract_value() { +_json_parse_object() { local key - key="$1" - local parsed - parsed="$2" - echo "$parsed" | grep "^$key=" | cut -d'=' -f2 | tr -d '"' + local obj='' + read -r token + case "$token" in + '}') ;; + *) + while :; do + case "${token}" in + '"'*'"') key="${token}" ;; + *) nvm_json_throw "EXPECTED string GOT ${token:-EOF}" ;; + esac + read -r token + case "${token}" in + ':') ;; + *) nvm_json_throw "EXPECTED : GOT ${token:-EOF}" ;; + esac + read -r token + _json_parse_value "${1}" "${key}" + obj="${obj}${key}:${value}" + read -r token + case "${token}" in + '}') break ;; + ',') obj="${obj}," ;; + *) nvm_json_throw "EXPECTED , or } GOT ${token:-EOF}" ;; + esac + read -r token + done + ;; + esac + : +} + +_json_parse_value() { + local jpath="${1:+$1,}$2" + local isleaf=0 + local isempty=0 + local print=0 + + case "$token" in + '{') _json_parse_object "${jpath}" ;; + '[') _json_parse_array "${jpath}" ;; + # At this point, the only valid single-character tokens are digits. + ''|[!0-9]) nvm_json_throw "EXPECTED value GOT >${token:-EOF}<" ;; + *) + value=$token + isleaf=1 + [ "${value}" = '""' ] && isempty=1 + ;; + esac + + [ "${value}" = '' ] && return + [ "${isleaf}" -eq 1 ] && [ $isempty -eq 0 ] && print=1 + [ "${print}" -eq 1 ] && printf "[%s]\t%s\n" "${jpath}" "${value}" + : +} + +_json_parse() { + read -r token + _json_parse_value + read -r token + case "${token}" in + '') ;; + *) nvm_json_throw "EXPECTED EOF GOT >${token}<" ;; + esac +} + +nvm_json_extract() { + nvm_json_tokenize | _json_parse | grep -e "${1}" | awk '{print $2 $3}' } diff --git a/test/fast/Unit tests/nvm_process_nvmrc b/test/fast/Unit tests/nvm_process_nvmrc index 65a8751e7b..5102c5764d 100755 --- a/test/fast/Unit tests/nvm_process_nvmrc +++ b/test/fast/Unit tests/nvm_process_nvmrc @@ -14,7 +14,7 @@ for f in ../../../test/fixtures/nvmrc/test/fixtures/valid/*; do STDOUT="$(nvm_process_nvmrc $f/.nvmrc 2>/dev/null)" EXIT_CODE="$(nvm_process_nvmrc $f/.nvmrc >/dev/null 2>/dev/null; echo $?)" - EXPECTED="$(extract_value node "$(parse_json "$(cat "$f/expected.json")")")" + EXPECTED="$(nvm_json_extract node < "${f}/expected.json" | tr -d '"')" [ "${EXIT_CODE}" = "0" ] || die "$(basename "${f}"): expected exit code of 0 but got ${EXIT_CODE}" @@ -26,7 +26,7 @@ for f in ../../../test/fixtures/nvmrc/test/fixtures/invalid/*; do STDERR="$(nvm_process_nvmrc $f/.nvmrc 2>&1 >/dev/null | awk '{if(NR > 8) print $0}' | strip_colors)" EXIT_CODE="$(nvm_process_nvmrc $f/.nvmrc >/dev/null 2>/dev/null; echo $?)" - EXPECTED="$(parse_json "$(cat "$f/expected.json")" | sed 's/^[0-9]*="//;s/"$//')" + EXPECTED="$(nvm_json_extract < "${f}/expected.json" | tr -d '"')" [ "${EXIT_CODE}" != "0" ] || die "$(basename "${f}"): expected exit code of 'not 0' but got ${EXIT_CODE}"