Skip to content

Commit c7672b9

Browse files
committed
fix: get-object and get-array now actually work
Previously, it would set the associative array as an indexed array (and visa versa) - it did not show up in the test suite because only one element was in the associative array
1 parent 2fb470a commit c7672b9

File tree

7 files changed

+232
-97
lines changed

7 files changed

+232
-97
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,13 @@ STATUS: IN DEVELOPMENT!
5353
# With bpm (highly recommended)
5454
bpm add hyperupcall/bash-object
5555
```
56+
57+
## TODO
58+
- do not auto create tree with set (or add -p flag that does so)
59+
- test errors on parser fail
60+
- workshop set-* functions
61+
- add support for set-object and set-array
62+
- error on invalid references (`type=object` in virtual object metadata, when it is referencing an array)
63+
- add tests for array in array (like object in object)
64+
- ensure error (for set primarily) if the virtual object references a variable that does not exist
65+
- "queried for X, but found object": print object in error (same with indexed arrays)

pkg/lib/traverse-get.sh

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,20 @@ bash_object.traverse-get() {
4141
if [ -z "${current_object["$key"]+x}" ]; then
4242
echo "Error: Key '$key' is not in object '$current_object_name'"
4343
return 1
44+
# If 'key' is a member of an object or index of array
4445
else
45-
# If 'key' is a member of an object, or index of array
46-
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
47-
stdtrace.log 2 "BLOCK: OBJECT/ARRAY"
48-
fi
49-
5046
local key_value="${current_object["$key"]}"
5147

52-
# If the 'key_value' is a virtual object, it starts with the byte sequence
53-
# This means we will be setting either an object or an array
48+
# If 'key_value' is a virtual object, dereference it
5449
if [ "${key_value::2}" = $'\x1C\x1D' ]; then
55-
virtual_item="${key_value#??}"
50+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
51+
stdtrace.log 2 "BLOCK: OBJECT/ARRAY"
52+
fi
5653

54+
virtual_item="${key_value#??}"
5755
bash_object.parse_virtual_object "$virtual_item"
5856
local current_object_name="$REPLY1"
5957
local vmd_dtype="$REPLY2"
60-
6158
local -n current_object="$current_object_name"
6259

6360
if ((i+1 < ${#REPLIES[@]})); then
@@ -88,9 +85,14 @@ bash_object.traverse-get() {
8885
if [ "$final_value_type" = object ]; then
8986
case "$vmd_dtype" in
9087
object)
91-
REPLY=("${current_object[@]}")
88+
declare -gA REPLY=()
89+
local key=
90+
for key in "${!current_object[@]}"; do
91+
REPLY["$key"]="${current_object["$key"]}"
92+
done
9293
;;
9394
array)
95+
# TODO: prepend 'existing' for all (existing array)
9496
bash_object.util.traverse_fail 'ERROR_VALUE_INCORRECT_TYPE' 'Queried for object, but found array'
9597
return
9698
;;
@@ -106,11 +108,7 @@ bash_object.traverse-get() {
106108
return
107109
;;
108110
array)
109-
declare -ga REPLY=()
110-
local key=
111-
for key in "${!current_object[@]}"; do
112-
REPLY["$key"]="${current_object["$key"]}"
113-
done
111+
REPLY=("${current_object[@]}")
114112
;;
115113
*)
116114
bash_object.util.traverse_fail 'ERROR_INTERNAL_INVALID_VOBJ' "vmd_dtype: $vmd_dtype"
@@ -143,28 +141,29 @@ bash_object.traverse-get() {
143141
fi
144142

145143
if ((i+1 < ${#REPLIES[@]})); then
144+
# Means the query is one level deeper than expected. Expected
145+
# object/array, but got string
146146
# TODO
147-
echo "mu" >&3
148-
# return 2
149-
:
147+
echo "mu '$key_value'" >&3
148+
return 2
150149
elif ((i+1 == ${#REPLIES[@]})); then
151150
local value="${current_object["$key"]}"
152151
if [ "$final_value_type" = object ]; then
153-
bash_object.util.traverse_fail 'ERROR_VALUE_INCORRECT_TYPE' 'Queried for object, but found string'
152+
bash_object.util.traverse_fail 'ERROR_VALUE_INCORRECT_TYPE' "Queried for object, but found string '$value'"
154153
return
155154
elif [ "$final_value_type" = array ]; then
156-
bash_object.util.traverse_fail 'ERROR_VALUE_INCORRECT_TYPE' 'Queried for array, but found string'
155+
bash_object.util.traverse_fail 'ERROR_VALUE_INCORRECT_TYPE' "Queried for array, but found string '$value'"
157156
return
158157
elif [ "$final_value_type" = string ]; then
159158
REPLY="$value"
160159
fi
161160
fi
162161
fi
162+
fi
163163

164-
bash_object.trace_current_object
165-
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
166-
stdtrace.log 0 "END BLOCK"
167-
fi
164+
bash_object.trace_current_object
165+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
166+
stdtrace.log 0 "END BLOCK"
168167
fi
169168
done
170169
}

pkg/lib/traverse-set.sh

Lines changed: 91 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -31,70 +31,110 @@ bash_object.traverse-set() {
3131

3232
bash_object.trace_loop
3333

34-
# If 'key' is not a member of object, create the object, and set it
34+
# If 'key' is not a member of object or index of array, error
3535
if [ -z "${current_object["$key"]+x}" ]; then
36-
# If we are before the last element in the query, then set
36+
# If we are before the last element in the query, then error
3737
if ((i+1 < ${#REPLIES[@]})); then
38-
# The variable is 'new_current_object_name', but it also could
39-
# be the name of a new _array_
40-
local new_current_object_name="__bash_object_${root_object_name}_tree_${key}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}"
41-
42-
# TODO: double-check if new_current_object_name only has underscores, dots, etc. (printf %q?)
43-
if ! eval "declare -gA $new_current_object_name=()"; then
44-
printf '%s\n' 'Error: bash-object: eval declare failed'
45-
return 1
46-
fi
47-
48-
current_object["$key"]=$'\x1C\x1D'"type=object;&$new_current_object_name"
49-
50-
current_object_name="$new_current_object_name"
51-
# shellcheck disable=SC2178
52-
local -n current_object="$new_current_object_name"
38+
echo "could not traverse property does not exist"
39+
return 2
40+
# # The variable is 'new_current_object_name', but it also could
41+
# # be the name of a new _array_
42+
# local new_current_object_name="__bash_object_${root_object_name}_tree_${key}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}"
43+
44+
# # TODO: double-check if new_current_object_name only has underscores, dots, etc. (printf %q?)
45+
# if ! eval "declare -gA $new_current_object_name=()"; then
46+
# printf '%s\n' 'Error: bash-object: eval declare failed'
47+
# return 1
48+
# fi
49+
50+
# current_object["$key"]=$'\x1C\x1D'"type=object;&$new_current_object_name"
51+
52+
# current_object_name="$new_current_object_name"
53+
# # shellcheck disable=SC2178
54+
# local -n current_object="$new_current_object_name"
5355
# If we are at the last element in the query
5456
elif ((i+1 == ${#REPLIES[@]})); then
55-
# TODO: object, arrays
56-
current_object["$key"]="$final_value"
57+
if [ "$final_value_type" = object ]; then
58+
# TODO: late bash srandom
59+
local new_current_object_name="__bash_object_${root_object_name}_tree_${key}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}"
60+
61+
# TODO: test if name is already used
62+
63+
# TODO: double-check if new_current_object_name only has underscores, dots, etc. (printf %q?)
64+
65+
# 1. Create object
66+
if ! eval "declare -gA $new_current_object_name=()"; then
67+
printf '%s\n' 'Error: bash-object: eval declare failed'
68+
return 1
69+
fi
70+
71+
# 2. Create virtual object
72+
current_object["$key"]=$'\x1C\x1D'"type=object;&$new_current_object_name"
73+
74+
local -n new_current_object="$new_current_object_name"
75+
local -n object_to_copy="$final_value"
76+
for key in "${!new_current_object[@]}"; do
77+
new_current_object["$key"]="${object_to_copy["$key"]}"
78+
done
79+
elif [ "$final_value_type" = array ]; then
80+
# local new_current_object_name="__bash_object_${root_object_name}_tree_${key}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}_${RANDOM}"
81+
# current_object["$key"]=$'\x1C\x1D'"type=array;&$new_current_object_name"
82+
83+
# local -n new_array = "$final_value"
84+
:
85+
# current_object["$key"]=("${new_array[@]}")
86+
elif [ "$final_value_type" = string ]; then
87+
current_object["$key"]="$final_value"
88+
else
89+
bash_object.util.traverse_fail 'ERROR_INTERNAL_INVALID_PARAM' "final_value_type: $final_value_type"
90+
return
91+
fi
5792
fi
58-
# If 'key' is already a member of object, use it if it's a virtual object. If
59-
# it's not a virtual object, then a throw an error
93+
# If 'key' is already a member of object or index of array
6094
else
61-
if ((i+1 < ${#REPLIES[@]})); then
62-
local key_value="${current_object["$key"]}"
63-
64-
if [ "${key_value::2}" = $'\x1C\x1D' ]; then
65-
virtual_item="${key_value#??}"
95+
local key_value="${current_object["$key"]}"
6696

67-
bash_object.parse_virtual_object "$virtual_item"
68-
local current_object_name="$REPLY1"
69-
local vmd_dtype="$REPLY2"
97+
# If 'key_value' is a virtual object, dereference it
98+
if [ "${key_value::2}" = $'\x1C\x1D' ]; then
99+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
100+
stdtrace.log 2 "BLOCK: OBJECT/ARRAY"
101+
fi
70102

71-
local -n current_object="$current_object_name"
103+
virtual_item="${key_value#??}"
104+
bash_object.parse_virtual_object "$virtual_item"
105+
local current_object_name="$REPLY1"
106+
local vmd_dtype="$REPLY2"
107+
local -n current_object="$current_object_name"
72108

73-
# Get the next value (number, string), and construct the next
74-
# element accordingly
109+
if ((i+1 < ${#REPLIES[@]})); then
110+
# TODO: test these internal invalid errors (error when type=array references object, etc.)?
111+
:
112+
elif ((i+1 == ${#REPLIES[@]})); then
75113
case "$vmd_dtype" in
76114
object)
115+
bash_object.util.traverse_fail 'ERROR_VALUE_INCORRECT_TYPE' 'Was going to set-string, but found existing object'
116+
return
117+
;;
118+
array)
119+
bash_object.util.traverse_fail 'ERROR_VALUE_INCORRECT_TYPE' 'Was going to set-string, but found existing array'
120+
return
121+
;;
122+
*)
123+
bash_object.util.traverse_fail 'ERROR_INTERNAL_INVALID_VOBJ' "vmd_dtype: $vmd_dtype"
124+
return
77125
;;
78-
array) ;;
79126
esac
80-
else
81-
# TODO: throw error
82-
echo "phi" >&3
83-
# return 1
84127
fi
85-
:
86-
elif ((i+1 == ${#REPLIES[@]})); then
87-
local key_value="${current_object["$key"]}"
88-
89-
if [ "${key_value::2}" = $'\x1C\x1D' ]; then
90-
virtual_item="${key_value#??}"
91-
92-
bash_object.parse_virtual_object "$virtual_item"
93-
local current_object_name="$REPLY1"
94-
local vmd_dtype="$REPLY2"
95-
96-
local -n current_object="$current_object_name"
128+
# Otherwise, 'key_value' is a string
129+
else
130+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
131+
stdtrace.log 2 "BLOCK: STRING"
132+
fi
97133

134+
if ((i+1 < ${#REPLIES[@]})); then
135+
echo "omicron" >&3
136+
:
137+
elif ((i+1 == ${#REPLIES[@]})); then
98138
if [ "$final_value_type" = object ]; then
99139
case "$vmd_dtype" in
100140
object)
@@ -124,22 +164,13 @@ bash_object.traverse-set() {
124164
esac
125165
fi
126166
current_object["$key"]="$final_value"
127-
else
128-
# TODO: throw error
129-
echo "omicron" >&3
130-
return 1
131167
fi
132168
fi
133169
fi
134170

171+
bash_object.trace_current_object
135172
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
136-
stdtrace.log 1 'AFTER OPERATION'
137-
stdtrace.log 1 "current_object_name: '$current_object_name'"
138-
stdtrace.log 1 "current_object=("
139-
for debug_key in "${!current_object[@]}"; do
140-
stdtrace.log 1 " [$debug_key]='${current_object["$debug_key"]}'"
141-
done
142-
stdtrace.log 1 ")"
173+
stdtrace.log 0 "END BLOCK"
143174
fi
144175
done
145176
}

tests/get-object.bats

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ load './util/init.sh'
7070
assert [ "$REPLY" = pi ]
7171
}
7272

73+
@test "correctly gets multi-key object in subobject" {
74+
declare -A SUB_SUB_OBJECT=([omicron]=pi [rho]=sigma [tau]=upsilon)
75+
declare -A SUB_OBJECT=([delta]=$'\x1C\x1Dtype=object;&SUB_SUB_OBJECT')
76+
declare -A OBJ=([gamma]=$'\x1C\x1Dtype=object;&SUB_OBJECT')
77+
78+
bash_object.traverse-get object 'OBJ' '.gamma.delta'
79+
assert [ "${REPLY[omicron]}" = pi ]
80+
assert [ "${REPLY[rho]}" = sigma ]
81+
assert [ "${REPLY[tau]}" = upsilon ]
82+
83+
bash_object.traverse-get string 'OBJ' '.gamma.delta.omicron'
84+
assert [ "$REPLY" = pi ]
85+
86+
bash_object.traverse-get string 'OBJ' '.gamma.delta.rho'
87+
assert [ "$REPLY" = sigma ]
88+
89+
bash_object.traverse-get string 'OBJ' '.gamma.delta.tau'
90+
assert [ "$REPLY" = upsilon ]
91+
}
92+
7393
@test "correctly gets object in subarray" {
7494
declare -A SUB_SUB_OBJECT=([pi]='rho')
7595
declare -a SUB_ARRAY=('foo' 'bar' $'\x1C\x1Dtype=object;&SUB_SUB_OBJECT')

tests/set-array.bats

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bats
2+
3+
load './util/init.sh'
4+
5+
# @test "correctly sets array" {
6+
# declare -a arr=(omicron pi rho)
7+
# declare -A OBJECT=()
8+
9+
# bash_object.traverse-set array
10+
# }

tests/set-object.bats

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bats
2+
3+
load './util/init.sh'
4+
5+
@test "correctly sets object" {
6+
declare -a obj=([omicron]=pi rho=sigma tau=upsilon)
7+
declare -A OBJECT=()
8+
9+
bash_object.traverse-set object OBJECT '.obj' obj
10+
11+
bash_object.traverse-get object OBJECT '.obj'
12+
assert [ ${#REPLY} -eq 3 ]
13+
assert [ "${REPLY[omicron]}" = pi ]
14+
assert [ "${REPLY[rho]}" = sigma ]
15+
assert [ "${REPLY[tau]}" = upsilon ]
16+
17+
bash_object.traverse-get string OBJECT '.obj.rho'
18+
assert [ "$REPLY" = sigma ]
19+
}

0 commit comments

Comments
 (0)