Menu

src-local/parse_params.sh

parse_params.sh

#!/usr/bin/env bash

# Shared shell-side parser for trusted key=value parameter and sweep files.
# Keys are allowlisted before becoming environment-variable names; values are
# treated as data and are never evaluated or sourced as shell code.

trim_string() {
  local value="\${1:-}"
  value="\${value#"\${value%%[![:space:]]*}"}"
  value="\${value%"\${value##*[![:space:]]}"}"
  printf '%s' "\$value"
}

valid_param_key() {
  [[ "\${1:-}" =~ ^[A-Za-z_][A-Za-z0-9_]*\$ ]]
}

clear_loaded_params() {
  local variable
  while IFS= read -r variable; do
    [[ "\$variable" == PARAM_* ]] && unset "\$variable"
  done < <(compgen -v PARAM_ || true)
}

parse_param_file() {
  local file="\${1:-}"
  local line key value
  [[ -f "\$file" ]] || { printf 'ERROR: parameter file not found: %s\n' "\$file" >&2; return 1; }
  clear_loaded_params

  while IFS= read -r line || [[ -n "\$line" ]]; do
    line="\${line%%#*}"
    line="\$(trim_string "\$line")"
    [[ -z "\$line" || "\$line" != *=* ]] && continue
    key="\$(trim_string "\${line%%=*}")"
    value="\$(trim_string "\${line#*=}")"
    valid_param_key "\$key" || {
      printf 'ERROR: invalid parameter key: %s\n' "\$key" >&2
      return 1
    }
    [[ -n "\$value" ]] || continue
    export "PARAM_\${key}=\${value}"
  done < "\$file"
}

get_param() {
  local key="\${1:-}"
  local default_value="\${2:-}"
  local variable="PARAM_\${key}"
  valid_param_key "\$key" || return 1
  printf '%s\n' "\${!variable:-\$default_value}"
}

# Print the last value for a key, matching the C parser's duplicate-key rule.
get_param_value() {
  local key="\${1:-}"
  local file="\${2:-}"
  local default_value="\${3:-}"
  valid_param_key "\$key" || { printf 'ERROR: invalid parameter key: %s\n' "\$key" >&2; return 1; }
  [[ -f "\$file" ]] || { printf 'ERROR: parameter file not found: %s\n' "\$file" >&2; return 1; }

  awk -F '=' -v wanted="\$key" -v fallback="\$default_value" '
    {
      line = \$0
      sub(/[[:space:]]*#.*/, "", line)
      separator = index(line, "=")
      if (!separator) next
      key = substr(line, 1, separator - 1)
      value = substr(line, separator + 1)
      gsub(/^[[:space:]]+|[[:space:]]+\$/, "", key)
      gsub(/^[[:space:]]+|[[:space:]]+\$/, "", value)
      if (key == wanted && value != "") {
        found = 1
        result = value
      }
    }
    END { print found ? result : fallback }
  ' "\$file"
}

set_param_in_file() {
  local key="\${1:-}"
  local value="\${2:-}"
  local file="\${3:-}"
  local temporary mode
  valid_param_key "\$key" || { printf 'ERROR: invalid parameter key: %s\n' "\$key" >&2; return 1; }
  [[ -f "\$file" ]] || { printf 'ERROR: parameter file not found: %s\n' "\$file" >&2; return 1; }
  [[ "\$value" != *\$'\n'* && "\$value" != *\$'\r'* ]] || {
    printf 'ERROR: parameter values may not contain newlines\n' >&2
    return 1
  }

  temporary="\$(mktemp "\${file}.XXXXXX")" || {
    printf 'ERROR: cannot create temporary file for %s\n' "\$file" >&2
    return 1
  }
  if ! awk -v wanted="\$key" -v replacement="\$value" '
    BEGIN { replaced = 0 }
    {
      line = \$0
      plain = line
      sub(/[[:space:]]*#.*/, "", plain)
      separator = index(plain, "=")
      candidate = separator ? substr(plain, 1, separator - 1) : ""
      gsub(/^[[:space:]]+|[[:space:]]+\$/, "", candidate)
      if (candidate == wanted) {
        if (!replaced) print wanted "=" replacement
        replaced = 1
      }
      else print line
    }
    END { if (!replaced) print wanted "=" replacement }
  ' "\$file" > "\$temporary"; then
    command rm -f "\$temporary"
    printf 'ERROR: failed to rewrite parameter file: %s\n' "\$file" >&2
    return 1
  fi
  if ! mode="\$(stat -f '%Lp' "\$file" 2>/dev/null)"; then
    mode="\$(stat -c '%a' "\$file")"
  fi
  chmod "\$mode" "\$temporary" || {
    command rm -f "\$temporary"
    printf 'ERROR: failed to preserve mode for: %s\n' "\$file" >&2
    return 1
  }
  mv "\$temporary" "\$file"
}

print_params() {
  local variable
  while IFS= read -r variable; do
    [[ "\$variable" == PARAM_* ]] || continue
    printf '%s=%s\n' "\${variable#PARAM_}" "\${!variable}"
  done < <(compgen -v PARAM_ | sort)
}