Skip to content

Commit 6f7ad9e

Browse files
committed
refactor: Split traverse into two functions
1 parent c61f532 commit 6f7ad9e

File tree

11 files changed

+359
-322
lines changed

11 files changed

+359
-322
lines changed

docs/errors.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Errors
2+
3+
Depending on your operation, your object transaction may fail. This details the reasons
4+
5+
In this context, value means either "member of object" or "index of array"
6+
7+
## Get
8+
9+
- `ERROR_VALUE_NOT_FOUND`
10+
- Attempted to access a value that does not exist
11+
- `ERROR_VALUE_INCORRECT_TYPE`
12+
- Attempted to access a value that has the wrong type. This can happen if the query uses `.[30]` to get an array index, but an object exists instead. It can also happen if the user writes `get-string`, and it found a non-string value after evaluating the query. It can also happen if the user writes `set-string`, and the place to write the new value already has a value of a different type
13+
- `ERROR_INTERNAL_INVALID_VOBJ`
14+
- The virtual object (a string that contains a reference to the real global object) had keys with unexpected values. You shouldn't get this error unless something has seriously gone wrong

pkg/lib/cmd/bobject.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,47 @@ bobject() {
1515
exit 1
1616
fi
1717

18-
bash_object.traverse get string "$@"
18+
bash_object.traverse-get string "$@"
1919
;;
2020
get-array)
2121
if (($# != 2)); then
2222
printf '%s\n' "bash-object: Error: Incorrect arguments for subcommand '$subcmd'"
2323
exit 1
2424
fi
2525

26-
bash_object.traverse get array "$@"
26+
bash_object.traverse-get array "$@"
2727
;;
2828
get-object)
2929
if (($# != 2)); then
3030
printf '%s\n' "bash-object: Error: Incorrect arguments for subcommand '$subcmd'"
3131
exit 1
3232
fi
3333

34-
bash_object.traverse get object "$@"
34+
bash_object.traverse-get object "$@"
3535
;;
3636
set-string)
3737
if (($# != 3)); then
3838
printf '%s\n' "bash-object: Error: Incorrect arguments for subcommand '$subcmd'"
3939
exit 1
4040
fi
4141

42-
bash_object.traverse set string "$@"
42+
bash_object.traverse-set string "$@"
4343
;;
4444
set-array)
4545
if (($# != 3)); then
4646
printf '%s\n' "bash-object: Error: Incorrect arguments for subcommand '$subcmd'"
4747
exit 1
4848
fi
4949

50-
bash_object.traverse set array "$@"
50+
bash_object.traverse-set array "$@"
5151
;;
5252
set-object)
5353
if (($# != 3)); then
5454
printf '%s\n' "bash-object: Error: Incorrect arguments for subcommand '$subcmd'"
5555
exit 1
5656
fi
5757

58-
bash_object.traverse set object "$@"
58+
bash_object.traverse-set object "$@"
5959
;;
6060
*)
6161
printf '%s\n' "bash-object: Error: Subcommand '$subcmd' not recognized"

pkg/lib/traverse-get.sh

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# shellcheck shell=bash
2+
3+
bash_object.traverse-get() {
4+
REPLY=
5+
6+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
7+
stdtrace.log 0 ''
8+
stdtrace.log 0 "CALL: bash_object.traverse: $*"
9+
fi
10+
11+
local final_value_type="$1"
12+
local root_object_name="$2"
13+
local filter="$3"
14+
15+
# Start traversing at the root object
16+
local current_object_name="$root_object_name"
17+
local -n current_object="$root_object_name"
18+
19+
# A stack of all the evaluated filter elements
20+
local -a filter_stack=()
21+
22+
# Parse the filter, and recurse over their elements
23+
case "$filter" in
24+
*']'*) bash_object.parse_filter --advanced "$filter" ;;
25+
*) bash_object.parse_filter --simple "$filter" ;;
26+
esac
27+
for ((i=0; i<${#REPLIES[@]}; i++)); do
28+
local key="${REPLIES[$i]}"
29+
filter_stack+=("$key")
30+
31+
bash_object.trace_loop
32+
33+
# If 'key' is not a member of object, error
34+
if [ -z "${current_object["$key"]+x}" ]; then
35+
echo "Error: Key '$key' is not in object '$current_object_name'"
36+
exit 1
37+
# If 'key' is a member of object, then we check to see if it's an object, array, or string
38+
else
39+
local key_value="${current_object["$key"]}"
40+
41+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
42+
stdtrace.log 1 "key: '$key'"
43+
stdtrace.log 1 "key_value: '$key_value'"
44+
stdtrace.log 1 "current_object_name: '$current_object_name'"
45+
stdtrace.log 1 "current_object=("
46+
for debug_key in "${!current_object[@]}"; do
47+
stdtrace.log 1 " [$debug_key]='${current_object["$debug_key"]}'"
48+
done
49+
stdtrace.log 1 ")"
50+
fi
51+
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
54+
if [ "${key_value::2}" = $'\x1C\x1D' ]; then
55+
virtual_item="${key_value#??}"
56+
57+
bash_object.parse_virtual_object "$virtual_item"
58+
local current_object_name="$REPLY1"
59+
local vmd_dtype="$REPLY2"
60+
61+
local -n current_object="$current_object_name"
62+
63+
# If we are not on the last element of the query, then do nothing. We have
64+
# already set 'current_object_name' and 'current_object', so at the next loop
65+
# iteration, the just-"dereferenced" virtual object will be evaluated
66+
if ((i+1 < ${#REPLIES[@]})); then
67+
:
68+
# If we are the last element, then we actually perform the get operation. Set
69+
# REPLY (and go to next loop)
70+
elif ((i+1 == ${#REPLIES[@]})); then
71+
if [ "$final_value_type" = object ]; then
72+
case "$vmd_dtype" in
73+
object)
74+
REPLY=("${current_object[@]}")
75+
;;
76+
array)
77+
printf '%s\n' "Error: 'A query for type 'object' was given, but an array was found"
78+
return 1
79+
;;
80+
esac
81+
elif [ "$final_value_type" = array ]; then
82+
case "$vmd_dtype" in
83+
object)
84+
printf '%s\n' "Error: 'A query for type 'array' was given, but an object was found"
85+
return 1
86+
;;
87+
array)
88+
# TODO: Perf: Use 'REPLY=("${current_object[@]}")'?
89+
local key=
90+
for key in "${!current_object[@]}"; do
91+
REPLY["$key"]="${current_object["$key"]}"
92+
done
93+
;;
94+
esac
95+
elif [ "$final_value_type" = string ]; then
96+
case "$vmd_dtype" in
97+
object)
98+
printf '%s\n' "Error: 'A query for type 'string' was given, but an object was found"
99+
return 1
100+
;;
101+
array)
102+
printf '%s\n' "Error: 'A query for type 'string' was given, but an array was found"
103+
return 1
104+
;;
105+
esac
106+
fi
107+
fi
108+
else
109+
# If we are getting a string
110+
111+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
112+
stdtrace.log 2 "BLOCK: STRING"
113+
fi
114+
115+
# If we are less than the last element in the query, and the object member has a type
116+
# of 'string', throw an error. This means the user expected an object to have a key
117+
# with type 'object', but the type really is 'string'
118+
if ((i+1 < ${#REPLIES[@]})); then
119+
:
120+
elif ((i+1 == ${#REPLIES[@]})); then
121+
local value="${current_object["$key"]}"
122+
if [ "$final_value_type" = object ]; then
123+
printf '%s\n' "Error: bash-object: A query for type 'object' was given, but a string was found"
124+
exit 1
125+
elif [ "$final_value_type" = array ]; then
126+
printf '%s\n' "Error: bash-object: A query for type 'array' was given, but a string was found"
127+
exit 1
128+
elif [ "$final_value_type" = string ]; then
129+
REPLY="$value"
130+
fi
131+
fi
132+
fi
133+
134+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
135+
stdtrace.log 1 "current_object_name: '$current_object_name'"
136+
stdtrace.log 1 "current_object=("
137+
for debug_key in "${!current_object[@]}"; do
138+
stdtrace.log 1 " [$debug_key]='${current_object["$debug_key"]}'"
139+
done
140+
stdtrace.log 1 ")"
141+
stdtrace.log 1 "final_value: '$final_value'"
142+
stdtrace.log 1 "END BLOCK 2"
143+
fi
144+
fi
145+
done
146+
}

pkg/lib/traverse-set.sh

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# shellcheck shell=bash
2+
3+
bash_object.traverse-set() {
4+
if [ -n "${TRACE_BASH_OBJECT_TRAVERSE+x}" ]; then
5+
stdtrace.log 0 ''
6+
stdtrace.log 0 "CALL: bash_object.traverse: $*"
7+
fi
8+
9+
# TODO: errors if vmd_dtype, final_value_type is not one of the known ones
10+
11+
local final_value_type="$1"
12+
local root_object_name="$2"
13+
local filter="$3"
14+
local final_value="$4"
15+
16+
# Start traversing at the root object
17+
local current_object_name="$root_object_name"
18+
local -n current_object="$root_object_name"
19+
20+
# A stack of all the evaluated filter elements
21+
local -a filter_stack=()
22+
23+
# Parse the filter, and recurse over their elements
24+
case "$filter" in
25+
*']'*) bash_object.parse_filter --advanced "$filter" ;;
26+
*) bash_object.parse_filter --simple "$filter" ;;
27+
esac
28+
for ((i=0; i<${#REPLIES[@]}; i++)); do
29+
local key="${REPLIES[$i]}"
30+
filter_stack+=("$key")
31+
32+
bash_object.trace_loop
33+
34+
# If 'key' is not a member of object, create the object, and set it
35+
if [ -z "${current_object["$key"]+x}" ]; then
36+
# If we are before the last element in the query, then set
37+
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+
exit 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"
53+
# If we are at the last element in the query
54+
elif ((i+1 == ${#REPLIES[@]})); then
55+
# TODO: object, arrays
56+
current_object["$key"]="$final_value"
57+
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
60+
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#??}"
66+
67+
bash_object.parse_virtual_object "$virtual_item"
68+
local current_object_name="$REPLY1"
69+
local vmd_dtype="$REPLY2"
70+
71+
local -n current_object="$current_object_name"
72+
73+
# Get the next value (number, string), and construct the next
74+
# element accordingly
75+
case "$vmd_dtype" in
76+
object)
77+
;;
78+
array) ;;
79+
esac
80+
else
81+
# TODO: throw error
82+
echo "phi" >&3
83+
exit 1
84+
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"
97+
98+
if [ "$final_value_type" = object ]; then
99+
case "$vmd_dtype" in
100+
object)
101+
102+
;;
103+
array)
104+
;;
105+
esac
106+
elif [ "$final_value_type" = array ]; then
107+
case "$vmd_dtype" in
108+
object)
109+
;;
110+
array)
111+
;;
112+
esac
113+
elif [ "$final_value_type" = string ]; then
114+
case "$vmd_dtype" in
115+
object)
116+
# TODO: test this
117+
echo "Error: Cannot set string on object"
118+
exit 1
119+
;;
120+
array)
121+
echo "Error: Cannot set string on array"
122+
exit 1
123+
;;
124+
esac
125+
fi
126+
current_object["$key"]="$final_value"
127+
else
128+
# TODO: throw error
129+
echo "omicron" >&3
130+
exit 1
131+
fi
132+
fi
133+
fi
134+
135+
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 ")"
143+
fi
144+
done
145+
}

0 commit comments

Comments
 (0)