Skip to content

Instantly share code, notes, and snippets.

@donaldguy
Last active June 8, 2024 03:50
Show Gist options
  • Save donaldguy/a56b9dc19a48bd7e7b86de3e0052f6c8 to your computer and use it in GitHub Desktop.
Save donaldguy/a56b9dc19a48bd7e7b86de3e0052f6c8 to your computer and use it in GitHub Desktop.
Have you ever had a json object or array and wished it was a serious of zsh/bash variables? … yeah ¬_¬ , totally, me neither 😅 https://jqplay.org/s/biOH4MLXDfe
def declare: (
.t as $type | .var as $var |
{"array": "-a", "object": "-A"}[$type] as $arg |
"declare \($arg) \($var);"
);
def shellify: (
. as $container |
$container.value | keys | .[] | ( . as $k |
($container.value | .[$k]) as $item |
($item | type) as $item_type |
(if $container.type == "array" then
[
($container.var | rtrimstr("S") | rtrimstr("s")),
(($item["id"]? // $k) | tostring| gsub("[^A-Za-z0-9_]"; "_"))
]
else
[$container.var, ($k | tostring| gsub("[^A-Za-z0-9_]"; "_"))]
end) | .[0] as $var_prefix | .[1] as $var_suffix |
if ($item_type == "array" or $item_type == "object") then
(["p", $var_prefix, $var_suffix] | join("_")) as $item_var |
({t: $item_type, var: $item_var} | declare ) as $decl |
(if $container.type == "array" then
$k | . + 1
else
# in bash > 4, and zsh > 6.9 (unreleased) you could probs `declare -n` and use that?
"\($k)_P"
end) as $item_pointer_index |
([
$decl,
({var: $item_var, value: $item, type: $item_type} |
shellify ),
([$container.var, "[", $item_pointer_index, "]=\"", $item_var, "\";"] | join(""))
] | join("\n"))
else
$k | ((numbers | . + 1) // .) as $item_key |
$item | ((numbers | floor) // . | @sh ) as $item_value |
[$container.var, "[", $item_key, "]=", $item_value, ";"] | join("")
end)
);
type as $type |
[ ({t: $type, var: $k } | declare),
({
var: $k,
value: .,
type: $type,
} | shellify )] | join("\n")
function autoshell_json() {
local k=${1:-VALUE}
jq -r --arg k $k "$(cat shell_vars_from_json.jq)"
}
#e.g.
eval "$(curl "https://api.github.com/gists/a56b9dc19a48bd7e7b86de3e0052f6c8" | autoshell_json GIST)"
#whereafter
# $ typeset -p 1 GIST
# typeset -A GIST=(
# [comments]=2
# [comments_url]=https://api.github.com/gists/a56b9dc19a48bd7e7b86de3e0052f6c8/comments
# [commits_url]=https://api.github.com/gists/a56b9dc19a48bd7e7b86de3e0052f6c8/commits
# [created_at]=2024-06-07T18:36:54Z
# [description]='Have you ever had a json object or array and wished it was a serious of zsh/bash variables? … yeah ¬_¬ , totally, me neither 😅 https://jqplay.org/s/cGKAsD8mLdU'
# [files_P]=p_GIST_files
# [forks_P]=p_GIST_forks
# [forks_url]=https://api.github.com/gists/a56b9dc19a48bd7e7b86de3e0052f6c8/forks
# [git_pull_url]=https://gist.github.com/a56b9dc19a48bd7e7b86de3e0052f6c8.git
# [git_push_url]=https://gist.github.com/a56b9dc19a48bd7e7b86de3e0052f6c8.git
# [history_P]=p_GIST_history
# [html_url]=https://gist.github.com/donaldguy/a56b9dc19a48bd7e7b86de3e0052f6c8
# [id]=a56b9dc19a48bd7e7b86de3e0052f6c8
# [node_id]=G_kwDNqIDaACBhNTZiOWRjMTlhNDhiZDdlN2I4NmRlM2UwMDUyZjZjOA
# [owner_P]=p_GIST_owner
# [public]=true
# [truncated]=false
# [updated_at]=2024-06-07T20:41:05Z
# [url]=https://api.github.com/gists/a56b9dc19a48bd7e7b86de3e0052f6c8
# [user]=null
# )
# $ typeset p_GIST_files
# p_GIST_files=( [shell_vars_from_json.jq_P]=p_p_GIST_files_shell_vars_from_json_jq [usage.zsh_P]=p_p_GIST_files_usage_zsh )
echo "${(P)GIST[history_P]}"
# p_p_GIST_history_0 p_p_GIST_history_1 p_p_GIST_history_2 p_p_GIST_history_3 p_p_GIST_history_4 p_p_GIST_history_5 p_p_GIST_history_6 p_p_GIST_history_7
echo ${(P)GIST[history_P][1]}
# p_p_GIST_history_0
for p in ${(@P)GIST[history_P]}; do echo ${${(P)p}[committed_at]}; done
# 2024-06-07T20:23:56Z
# 2024-06-07T20:23:31Z
# 2024-06-07T19:19:39Z
# 2024-06-07T18:47:21Z
# 2024-06-07T18:44:44Z
# 2024-06-07T18:39:03Z
# 2024-06-07T18:37:34Z
# 2024-06-07T18:36:54Z
autoshell_json_stream() {
jq --join-output --arg prefix ${1:-INPUT} --stream '
1 as $true | 0 as $false | "$NULL" as $null |
[.[0], .[1:]] as [$path, $maybe_val]
| reduce ($path[0:-1][]) as $p (
{acc: [$prefix], collection: [[$prefix]]};
(.acc + [($p | tostring | gsub("[^A-Za-z0-9]"; "00"))]) as $new |
{acc: $new, collection: (.collection + [$new])})
| (.collection | map(join("_")) ) as $varcs
|
reduce range($varcs | length) as $i ([]; . + [
"declare -\(
$path[$i] | ((numbers | "a") // (strings | "A"))
) \($varcs[$i]);\n"] )
+(
if ($maybe_val | length) == 1 then ($maybe_val as [$val] |
$val | [
($varcs| last),
"[", ($path|last| ((numbers | . + 1) // .) ), "]=",
((strings | @sh) //
(numbers | "\"$(( \(tostring) ))\"") //
(booleans | if . then $true else $false end) //
(nulls | $null)
), ";\n"
]) else
$varcs | last |
["test -n \"${ZSH_VERSION}\" && whence _json_autoshell_end_of_var &> /dev/null && _json_autoshell_end_of_var \(.)\n"]
end) | join("")'
}
_json_autoshell_end_of_var() {
local parent_v=$1
local parent_v_type=${$(typeset +m $parent_v)#$parent_v}
local turn_glob_back_off=$(setopt | grep -q extendedglob && echo "no")
setopt extendedglob
local -a child_vars=($(typeset +m "${parent_v}_[^_]#"))
while [[ ${#child_vars} > 0 ]]; do
local var_name=$child_vars[2]
local var_type=$child_vars[1]
shift 2 child_vars
case $parent_v_type in
array*)
eval "${parent_v}+=($var_name)" ;;
association*)
local var_suffix=${var_name#${parent_v}_}
case $var_type in
array)
eval "${parent_v}[.${var_suffix}@]=${var_name}" ;;
association)
eval "${parent_v}[.${var_suffix}]=${var_name}" ;;
esac
esac
done
if [[ "${turn_glob_back_off:-yes}" != "no" ]]; then unsetopt extendedglob; fi
}
@donaldguy
Copy link
Author

Individual nested access is kinda terrible:

echo ${${(P)${${(P)GIST[history_P]}[1]}}[committed_at]
2024-06-07T19:19:39Z

As comment mentions I think namerefs might make this less bad in modern bash and forthcoming zsh, but I'm not sure

@donaldguy
Copy link
Author

That this is the case kinda makes me understand why zstyle is a thing ... but only kinda

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment