shutils.jq turns JSON options and arguments into Bash declarations. jira-nice used it so command bodies could read ordinary shell variables. The consuming line was:
. /dev/stdin <<<"$(
jqm shutils:main-rc '["JIRA","rx",true]' <<EOF
{"OPT": $JIRA_OPT}
{"ARG": $JIRA_ARG}
EOF
)"jqm interpreted shutils:main-rc as module, callable, and jq flags. The arguments selected the JIRA prefix, readonly/exported declarations, and metadata companions. The generated text went straight into the current shell.
For this input:
{"OPT":{"count":3,"ratio":1.5,"tags":["a b","c"]}}the ordinary declarations include:
declare -rxi JIRA_OPT_COUNT='3'
declare -rx JIRA_OPT_RATIO='1.5'
declare -rxa JIRA_OPT_TAGS=('a b' 'c')The integer becomes a Bash integer. The decimal becomes a string. The array keeps its two elements, including the space inside the first. jq’s @sh quotes the values; it does not quote the generated variable names.
Each variable also gets __json and __type companions. Containers get __keys as well. The companions describe the value at that node:
| Variable | __type |
Contents of __json |
|---|---|---|
JIRA_OPT_COUNT |
number |
3 |
JIRA_OPT_RATIO |
string |
"1.5" |
JIRA_OPT_TAGS |
array |
["a b","c"] |
JIRA_OPT |
object |
{"count":3,"ratio":1.5,"tags":["a b","c"]} |
The parent snapshot is taken before descending into its children. It still contains the numeric ratio even though that child’s own metadata records a string. After sourcing the declarations, this returns the original parsed OPT object:
printf '%s\n' "$JIRA_OPT__json" | jq .That is a saved semantic copy, not a reverse compiler for subsequently edited shell variables. Parsing has already discarded the input’s whitespace and numeric spelling. Bash arrays also stay in the current shell; marking an array for export does not turn the process environment into an array carrier. The scalar JSON companion is what can cross that boundary intact.
The traversal itself is small:
def loop:
conv, (more | loop)
;
loopconv emits the current node; more prefixes each child name and descends. Object members become an array of child-variable names. Nested names are uppercased and punctuation becomes _, but the parent builds its member list before that normalization. A member c-d therefore leaves the parent naming JIRA_OPT_C-D while the child is declared as JIRA_OPT_C_D. Add a sibling named c_d and both declarations target the same variable. The readonly second assignment errors; set -e stops that consumer.
Container values have another limit. @sh can render an array of scalars, but rejects an array containing an object or another array. With {"OPT":{"nested":[[1],[2]]}}, jq emits the parent declarations and then fails. The consuming line above still sources that partial program and can return success, even under set -e: the failed command substitution supplies a redirection, and the successful source command determines the status. Checking generation in a separate assignment before sourcing would make that failure stop the handoff.
Top-level scalars are rejected by to_entries. Top-level keys are appended raw to the prefix, so hostile names can become shell syntax; generated names can collide with each other or the metadata suffixes. Sourcing through /dev/stdin remains execution. This was useful for jira-nice’s known option and argument shapes. The trust in those shapes was part of the interface.