Skip to content

feat: local -n nameref with associative arrays — harness patterns #801

@chaliy

Description

@chaliy

Context

wedow/harness uses local -n extensively to pass associative arrays and indexed arrays by reference into helper functions. The existing nameref spec tests (13 pass, 1 skip) cover basic patterns, but harness uses more complex patterns that need validation:

  1. Nameref to associative array — iterate keys with ${!ref[@]}
  2. Nameref to associative array — assign new keys via ref["key"]="value"
  3. Nameref to indexed array — append via ref+=("value")
  4. Multiple namerefs in the same function (map + order array)
  5. local arr=(...) syntax (currently skipped in spec tests — parser limitation)

The key harness function pattern:

# From bin/harness — _collect_hooks_from()
_collect_hooks_from() {
  local dir="$1"
  local -n map_ref="$2"       # nameref → associative array
  local -n order_ref="$3"     # nameref → indexed array
  for f in "${dir}"/*; do
    [[ -x "${f}" ]] || continue
    local base="$(basename "${f}")"
    map_ref["${base}"]="${f}"        # assign key in caller's assoc array
    order_ref+=("${base}")           # append to caller's indexed array
  done
}

declare -A hook_map
hook_order=()
_collect_hooks_from "/plugins/hooks.d/assemble" hook_map hook_order

Test cases

1. Nameref assign key to caller's associative array

add_entry() {
  local -n ref="$1"
  ref["hello"]="world"
  ref["foo"]="bar"
}

declare -A mymap
add_entry mymap
echo "${mymap[hello]}"
echo "${mymap[foo]}"
# Expected stdout:
#   world
#   bar

2. Nameref iterate keys of associative array

show_keys() {
  local -n ref="$1"
  for key in "${!ref[@]}"; do
    echo "${key}=${ref[$key]}"
  done | sort
}

declare -A colors=([red]=ff0000 [green]=00ff00 [blue]=0000ff)
show_keys colors
# Expected stdout:
#   blue=0000ff
#   green=00ff00
#   red=ff0000

3. Nameref append to caller's indexed array

collect() {
  local -n arr_ref="$1"
  arr_ref+=("alpha")
  arr_ref+=("beta")
  arr_ref+=("gamma")
}

items=()
collect items
echo "${#items[@]}"
echo "${items[0]} ${items[1]} ${items[2]}"
# Expected stdout:
#   3
#   alpha beta gamma

4. Two namerefs in same function (harness pattern)

collect_from() {
  local dir="$1"
  local -n map_ref="$2"
  local -n order_ref="$3"
  map_ref["key-a"]="${dir}/file-a"
  map_ref["key-b"]="${dir}/file-b"
  order_ref+=("key-a")
  order_ref+=("key-b")
}

declare -A my_map
my_order=()
collect_from "/src" my_map my_order

echo "${my_map[key-a]}"
echo "${my_map[key-b]}"
echo "${my_order[0]} ${my_order[1]}"
# Expected stdout:
#   /src/file-a
#   /src/file-b
#   key-a key-b

5. Nameref with associative array length

count_entries() {
  local -n ref="$1"
  echo "${#ref[@]}"
}

declare -A data=([x]=1 [y]=2 [z]=3)
count_entries data
# Expected stdout: 3

6. Nameref overwrite existing key in associative array

update() {
  local -n ref="$1"
  ref["name"]="updated"
}

declare -A record=([name]=original [age]=30)
update record
echo "${record[name]}"
echo "${record[age]}"
# Expected stdout:
#   updated
#   30

7. Nested function calls with nameref passthrough

inner() {
  local -n ref="$1"
  ref["from_inner"]="yes"
}

outer() {
  local -n ref="$1"
  ref["from_outer"]="yes"
  inner "$1"
}

declare -A result
outer result
echo "${result[from_outer]}"
echo "${result[from_inner]}"
# Expected stdout:
#   yes
#   yes

8. local arr=(...) syntax (currently skipped)

caller() {
  local items=(one two three)
  show items
}

show() {
  local -n ref="$1"
  echo "${ref[1]}"
}

caller
# Expected stdout: two

This is currently blocked by a parser limitation (local arr=(...) syntax). See spec test nameref_local_dynamic_scope which is skipped for this reason.

9. Nameref to associative array — check key existence

has_key() {
  local -n ref="$1"
  local key="$2"
  if [[ -v "ref[$key]" ]]; then
    echo "found"
  else
    echo "missing"
  fi
}

declare -A config=([debug]=true)
has_key config debug
has_key config verbose
# Expected stdout:
#   found
#   missing

10. Nameref loop — discover and collect (full harness pattern)

mkdir -p /plugins/tools
echo "tool-a" > /plugins/tools/grep
chmod +x /plugins/tools/grep
echo "tool-b" > /plugins/tools/sed
chmod +x /plugins/tools/sed

discover_tools() {
  local dir="$1"
  local -n tmap_ref="$2"
  for f in "${dir}"/*; do
    [[ -f "${f}" ]] || continue
    local name
    name="$(basename "${f}")"
    tmap_ref["${name}"]="${f}"
  done
}

declare -A tool_map
discover_tools "/plugins/tools" tool_map

for name in $(echo "${!tool_map[@]}" | tr ' ' '\n' | sort); do
  echo "${name}=${tool_map[$name]}"
done
# Expected stdout:
#   grep=/plugins/tools/grep
#   sed=/plugins/tools/sed

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions