#!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")" # LinuxCNC source basis: generated switchkins/remap tables are extracted from # LinuxCNC configs/sim INI/HAL files and remap_subs/{428,429,430}remap.ngc # sources listed in linuxcnc-kinematics-source-files.txt. linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} mode=table all_output_dir= usage() { echo "usage: $0 [--config-cases|--json-cases|--all-output-dir DIR] [manifest]" >&2 } case "${1:-}" in --config-cases) mode=config-cases shift ;; --json-cases) mode=json-cases shift ;; --all-output-dir) mode=all if [[ -z "${2:-}" || "${2:-}" == --* ]]; then usage echo "missing output directory for --all-output-dir" >&2 exit 1 fi all_output_dir=$2 shift 2 ;; --help|-h) usage exit 0 ;; --*) usage echo "unknown switchkins remap table generator option: $1" >&2 exit 1 ;; esac manifest=${1:-linuxcnc-kinematics-source-files.txt} if [[ -n "${2:-}" ]]; then usage echo "too many switchkins remap table generator arguments" >&2 exit 1 fi if [[ ! -d "$linuxcnc_root" ]]; then echo "missing LinuxCNC root: $linuxcnc_root" >&2 exit 1 fi if [[ ! -f "$manifest" ]]; then echo "missing kinematics manifest: $manifest" >&2 exit 1 fi LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest" if [[ "$mode" == all && -z "$all_output_dir" ]]; then echo "missing output directory for --all-output-dir" >&2 exit 1 fi if [[ "$mode" == all && "$all_output_dir" =~ [[:space:]] ]]; then echo "switchkins remap output directory must not contain whitespace: $all_output_dir" >&2 exit 1 fi linuxcnc_root=$(cd "$linuxcnc_root" && pwd) # LinuxCNC source basis: src/hal/utils/halcmd_commands.cc do_loadusr_cmd() # parses loadusr options with getopt("+wWin:"), and # src/hal/utils/halcmd_completion.c lists the common -W/-Wn/-w/-iw forms. read_ini_switchkins_config() { local file=$1 awk ' function trim(value) { sub(/^[[:space:]]*/, "", value) sub(/[[:space:]]*$/, "", value) return value } function loadusr_program(value, fields, field_count, i, token) { sub(/^.*(^|[[:space:]])loadusr[[:space:]]+/, "", value) field_count = split(value, fields, /[[:space:]]+/) for (i = 1; i <= field_count; i++) { token = fields[i] if (token == "-W" || token == "-w" || token == "-i") { continue } if (token == "-Wn" || token == "-n") { i++ continue } if (token ~ /^-Wn.+/ || token ~ /^-n.+/) { continue } if (token ~ /^-[wWi]+$/) { continue } if (token ~ /^-/) { continue } return token } return "" } { line = $0 sub(/[[:space:]]*[#;].*$/, "", line) line = trim(line) if (line ~ /^\[[^][]+\]$/) { section = tolower(substr(line, 2, length(line) - 2)) next } if (line !~ /=/) { next } key = line sub(/[[:space:]]*=.*/, "", key) key = tolower(trim(key)) value = line sub(/^[^=]*=[[:space:]]*/, "", value) value = trim(value) if (section == "emc" && key == "machine") { print "machine\t" value } else if (section == "kins" && key == "kinematics") { print "kinematics\t" value } else if (section == "hal" && key == "halfile") { print "halfile\t" value } else if (section == "hal" && key == "postgui_halfile") { print "postgui_halfile\t" value } else if (section == "hal" && key == "halcmd") { lower = tolower(value) if (lower ~ /(^|[[:space:]])loadusr([[:space:]]|$)/) { command = loadusr_program(value) if (command != "") { print "halcmd_loadusr\t" command } } } else if (section == "emcio" && key == "tool_table") { print "tool_table\t" value } else if (section == "rs274ngc" && key == "subroutine_path") { print "subroutine_path\t" value } else if (section == "rs274ngc" && key == "on_abort_command") { lower = tolower(value) if (match(lower, /o[[:space:]]*<[^>]+>[[:space:]]*call/)) { ngc = substr(value, RSTART, RLENGTH) sub(/^[Oo][[:space:]]*[[:space:]]*[Cc][Aa][Ll][Ll].*$/, "", ngc) print "on_abort_ngc\t" ngc } } else if (section == "rs274ngc" && key == "remap") { lower = tolower(value) for (mcode = 428; mcode <= 430; mcode++) { if (lower ~ "m" mcode "([^0-9]|$)" && match(lower, /ngc[[:space:]]*=[[:space:]]*[^[:space:]]+/)) { ngc = substr(value, RSTART, RLENGTH) sub(/^[Nn][Gg][Cc][[:space:]]*=[[:space:]]*/, "", ngc) print "remap_" mcode "\t" ngc } } if (match(lower, /ngc[[:space:]]*=[[:space:]]*[^[:space:]]+/)) { ngc = substr(value, RSTART, RLENGTH) sub(/^[Nn][Gg][Cc][[:space:]]*=[[:space:]]*/, "", ngc) print "remap_ngc\t" ngc } if (match(lower, /python[[:space:]]*=[[:space:]]*[^[:space:]]+/)) { python = substr(value, RSTART, RLENGTH) sub(/^[Pp][Yy][Tt][Hh][Oo][Nn][[:space:]]*=[[:space:]]*/, "", python) print "python_remap\t" python } } else if (section == "python" && key == "path_append") { print "python_path_append\t" value } else if (section == "python" && key == "toplevel") { print "python_toplevel\t" value } } ' "$file" } read_loadusr_commands() { local file=$1 awk ' function trim(value) { sub(/^[[:space:]]*/, "", value) sub(/[[:space:]]*$/, "", value) return value } function loadusr_program(value, fields, field_count, i, token) { sub(/^.*(^|[[:space:]])loadusr[[:space:]]+/, "", value) field_count = split(value, fields, /[[:space:]]+/) for (i = 1; i <= field_count; i++) { token = fields[i] if (token == "-W" || token == "-w" || token == "-i") { continue } if (token == "-Wn" || token == "-n") { i++ continue } if (token ~ /^-Wn.+/ || token ~ /^-n.+/) { continue } if (token ~ /^-[wWi]+$/) { continue } if (token ~ /^-/) { continue } return token } return "" } { line = $0 sub(/[[:space:]]*#.*/, "", line) line = trim(line) lower = tolower(line) if (lower !~ /(^|[[:space:]])loadusr([[:space:]]|$)/) { next } command = loadusr_program(line) if (command != "") { print command } } ' "$file" } resolved_config_path= resolve_config_path() { local config_dir=$1 local path=$2 if [[ "$path" = /* ]]; then resolved_config_path=$path else resolved_config_path=$config_dir/$path fi } resolved_linuxcnc_path= declare -A relative_linuxcnc_path_cache=() relative_linuxcnc_path() { local path=$1 if [[ -n "${relative_linuxcnc_path_cache[$path]+x}" ]]; then resolved_linuxcnc_path=${relative_linuxcnc_path_cache[$path]} return 0 fi local absolute_path absolute_path=$(cd "${path%/*}" && pwd)/${path##*/} if [[ "$absolute_path" != "$linuxcnc_root/"* ]]; then echo "resolved LinuxCNC source path escapes LinuxCNC root: $absolute_path" >&2 exit 1 fi resolved_linuxcnc_path=${absolute_path#"$linuxcnc_root/"} relative_linuxcnc_path_cache[$path]=$resolved_linuxcnc_path } declare -A config_dir_cache=() declare -A manifest_config_sources=() declare -A used_config_sources=() declare -A manifest_remap_sources=() declare -A used_remap_sources=() declare -A manifest_tooldata_sources=() declare -A used_tooldata_sources=() declare -A manifest_asset_sources=() declare -A used_asset_sources=() manifest_config_count=0 manifest_remap_count=0 manifest_tooldata_count=0 manifest_asset_count=0 validate_manifest_source_path() { local group=$1 local path=$2 if [[ -z "$path" ]]; then echo "empty LinuxCNC $group source path in $manifest" >&2 exit 1 fi if [[ "$path" = /* || "$path" == *..* ]]; then echo "LinuxCNC $group source path must stay under LinuxCNC root: $path" >&2 exit 1 fi if [[ ! -f "$linuxcnc_root/$path" ]]; then echo "missing LinuxCNC $group source listed in $manifest: $path" >&2 exit 1 fi } while IFS=: read -r manifest_group manifest_path _manifest_note; do case "$manifest_group" in config) validate_manifest_source_path "$manifest_group" "$manifest_path" if [[ "$manifest_path" != *.ini && "$manifest_path" != *.hal ]]; then echo "LinuxCNC config source must be an INI or HAL file in $manifest: $manifest_path" >&2 exit 1 fi if [[ -n "${manifest_config_sources[$manifest_path]:-}" ]]; then echo "duplicate LinuxCNC config source in $manifest: $manifest_path" >&2 exit 1 fi manifest_config_sources[$manifest_path]=1 manifest_config_count=$((manifest_config_count + 1)) ;; remap) validate_manifest_source_path "$manifest_group" "$manifest_path" if [[ "$manifest_path" != *.ngc ]]; then echo "LinuxCNC remap source must be an ngc file in $manifest: $manifest_path" >&2 exit 1 fi case "${manifest_path##*/}" in 428remap.ngc|429remap.ngc|430remap.ngc) ;; *) echo "LinuxCNC switchkins remap source must be 428/429/430remap.ngc in $manifest: $manifest_path" >&2 exit 1 ;; esac if [[ -n "${manifest_remap_sources[$manifest_path]:-}" ]]; then echo "duplicate LinuxCNC remap source in $manifest: $manifest_path" >&2 exit 1 fi manifest_remap_sources[$manifest_path]=1 manifest_remap_count=$((manifest_remap_count + 1)) ;; tooldata) validate_manifest_source_path "$manifest_group" "$manifest_path" if [[ "$manifest_path" != *.tbl ]]; then echo "LinuxCNC switchkins tooldata source must be a tbl file in $manifest: $manifest_path" >&2 exit 1 fi if [[ -n "${manifest_tooldata_sources[$manifest_path]:-}" ]]; then echo "duplicate LinuxCNC switchkins tooldata source in $manifest: $manifest_path" >&2 exit 1 fi manifest_tooldata_sources[$manifest_path]=1 manifest_tooldata_count=$((manifest_tooldata_count + 1)) ;; asset) validate_manifest_source_path "$manifest_group" "$manifest_path" if [[ -n "${manifest_asset_sources[$manifest_path]:-}" ]]; then echo "duplicate LinuxCNC asset source in $manifest: $manifest_path" >&2 exit 1 fi manifest_asset_sources[$manifest_path]=1 manifest_asset_count=$((manifest_asset_count + 1)) ;; esac done <"$manifest" resolved_config_dir= config_dir_for() { local config=$1 if [[ -z "${config_dir_cache[$config]:-}" ]]; then config_dir_cache[$config]=$(cd "${config%/*}" && pwd) fi resolved_config_dir=${config_dir_cache[$config]} } resolved_remap_source= remap_source_for_ngc() { local config=$1 local ngc_name=$2 local subroutine_paths=${3:-} resolved_remap_source= if [[ "$ngc_name" != *.ngc ]]; then ngc_name="$ngc_name.ngc" fi local config_dir config_dir_for "$config" config_dir=$resolved_config_dir if [[ -z "$subroutine_paths" ]]; then subroutine_paths="." fi local subroutine_path while IFS= read -r subroutine_path; do [[ -n "$subroutine_path" ]] || continue local path_part IFS=: read -ra path_parts <<<"$subroutine_path" for path_part in "${path_parts[@]}"; do [[ -n "$path_part" ]] || continue local candidate_dir resolve_config_path "$config_dir" "$path_part" candidate_dir=$resolved_config_path if [[ -f "$candidate_dir/$ngc_name" ]]; then relative_linuxcnc_path "$candidate_dir/$ngc_name" resolved_remap_source=$resolved_linuxcnc_path return 0 fi done done <<<"$subroutine_paths" return 1 } resolved_config_source= config_source_for_halfile() { local config=$1 local halfile=$2 resolved_config_source= [[ -n "$halfile" ]] || return 1 [[ "$halfile" == LIB:* ]] && return 1 local hal_path=${halfile%%[[:space:]]*} [[ -n "$hal_path" ]] || return 1 local config_dir config_dir_for "$config" config_dir=$resolved_config_dir local resolved resolve_config_path "$config_dir" "$hal_path" resolved=$resolved_config_path if [[ ! -f "$resolved" ]]; then echo "missing LinuxCNC HAL source referenced by ${config#"$linuxcnc_root/"}: $halfile" >&2 exit 1 fi relative_linuxcnc_path "$resolved" resolved_config_source=$resolved_linuxcnc_path } resolved_tooldata_source= tooldata_source_for_tool_table() { local config=$1 local tool_table=$2 resolved_tooldata_source= [[ -n "$tool_table" ]] || return 1 local tool_path=${tool_table%%[[:space:]]*} [[ -n "$tool_path" ]] || return 1 local config_dir config_dir_for "$config" config_dir=$resolved_config_dir local resolved resolve_config_path "$config_dir" "$tool_path" resolved=$resolved_config_path [[ -f "$resolved" ]] || return 1 relative_linuxcnc_path "$resolved" resolved_tooldata_source=$resolved_linuxcnc_path } resolved_asset_source= asset_source_for_path() { local config=$1 local path=$2 resolved_asset_source= [[ -n "$path" ]] || return 1 local config_dir config_dir_for "$config" config_dir=$resolved_config_dir local resolved resolve_config_path "$config_dir" "$path" resolved=$resolved_config_path [[ -f "$resolved" ]] || return 1 relative_linuxcnc_path "$resolved" resolved_asset_source=$resolved_linuxcnc_path } resolved_loadusr_asset_source= loadusr_asset_source_for_command() { local config=$1 local command=$2 resolved_loadusr_asset_source= [[ -n "$command" ]] || return 1 if [[ "$command" == */* || "$command" == *.py ]]; then if asset_source_for_path "$config" "$command"; then resolved_loadusr_asset_source=$resolved_asset_source return 0 fi if [[ "$command" != *.py ]] && asset_source_for_path "$config" "$command.py"; then resolved_loadusr_asset_source=$resolved_asset_source return 0 fi return 1 fi local source="src/hal/user_comps/vismach/$command.py" if [[ -f "$linuxcnc_root/$source" ]]; then resolved_loadusr_asset_source=$source return 0 fi if asset_source_for_path "$config" "$command.py"; then resolved_loadusr_asset_source=$resolved_asset_source return 0 fi return 1 } record_loadusr_asset() { local config_path=$1 local config=$2 local command=$3 local label=$4 if loadusr_asset_source_for_command "$config" "$command"; then loadusr_asset_source=$resolved_loadusr_asset_source record_asset_source "$config_path" "$loadusr_asset_source" "$label" record_python_import_assets "$config_path" "$loadusr_asset_source" fi } resolved_python_module_source= python_module_source_for_name() { local config=$1 local module=$2 local python_paths=${3:-} resolved_python_module_source= [[ -n "$module" ]] || return 1 [[ "$module" == *.* ]] && return 1 local module_path=${module//.//} if [[ -z "$python_paths" ]]; then python_paths="." fi local config_dir config_dir_for "$config" config_dir=$resolved_config_dir local python_path while IFS= read -r python_path; do [[ -n "$python_path" ]] || continue local path_part IFS=: read -ra path_parts <<<"$python_path" for path_part in "${path_parts[@]}"; do [[ -n "$path_part" ]] || continue local candidate_dir resolve_config_path "$config_dir" "$path_part" candidate_dir=$resolved_config_path if [[ -f "$candidate_dir/$module_path.py" ]]; then relative_linuxcnc_path "$candidate_dir/$module_path.py" resolved_python_module_source=$resolved_linuxcnc_path return 0 fi done done <<<"$python_paths" return 1 } record_asset_source() { local config_path=$1 local source=$2 local label=$3 if [[ -z "${manifest_asset_sources[$source]:-}" ]]; then echo "LinuxCNC $label asset source is not listed as asset in $manifest: $source ($config_path)" >&2 exit 1 fi used_asset_sources[$source]=1 } declare -A walked_python_import_assets=() record_python_import_assets() { local config_path=$1 local source=$2 local walk_key="$config_path|$source" if [[ -n "${walked_python_import_assets[$walk_key]:-}" ]]; then return 0 fi walked_python_import_assets[$walk_key]=1 local source_dir=${source%/*} local imported while IFS= read -r imported; do [[ -n "$imported" ]] || continue local import_source="$source_dir/$imported.py" if [[ -f "$linuxcnc_root/$import_source" ]]; then record_asset_source "$config_path" "$import_source" "Python import" record_python_import_assets "$config_path" "$import_source" fi done < <(awk ' /^[[:space:]]*import[[:space:]]+[A-Za-z_][A-Za-z0-9_]*([[:space:]]|$)/ { value = $0 sub(/^[[:space:]]*import[[:space:]]+/, "", value) sub(/[[:space:],].*$/, "", value) print value } /^[[:space:]]*from[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]+import[[:space:]]+/ { value = $0 sub(/^[[:space:]]*from[[:space:]]+/, "", value) sub(/[[:space:]].*$/, "", value) print value } ' "$linuxcnc_root/$source") } resolved_kinstype= declare -A kinstype_cache=() kinstype_for_remap_source() { local source source=$1 resolved_kinstype= if [[ -z "$source" ]]; then resolved_kinstype=-1 return 0 fi if [[ -n "${kinstype_cache[$source]+x}" ]]; then resolved_kinstype=${kinstype_cache[$source]} return 0 fi resolved_kinstype=$(awk ' /^[[:space:]]*#[[:space:]]*=/ { value = $0 sub(/^[[:space:]]*#[[:space:]]*=[[:space:]]*/, "", value) sub(/[^0-9].*$/, "", value) print value exit } ' "$linuxcnc_root/$source") kinstype_cache[$source]=$resolved_kinstype } require_remap_source() { local config_path=$1 local mcode=$2 local ngc_name=$3 local source=$4 if [[ -z "$source" ]]; then echo "missing LinuxCNC M$mcode remap source for $config_path: ngc=$ngc_name" >&2 exit 1 fi } require_kinstype() { local config_path=$1 local mcode=$2 local source=$3 local kinstype=$4 if [[ -z "$kinstype" || "$kinstype" == -1 ]]; then echo "missing # assignment for LinuxCNC M$mcode remap source: $source ($config_path)" >&2 exit 1 fi } record_remap_source() { local config_path=$1 local mcode=$2 local source=$3 if [[ -z "${manifest_remap_sources[$source]:-}" ]]; then echo "LinuxCNC M$mcode remap source is not listed in $manifest: $source ($config_path)" >&2 exit 1 fi used_remap_sources[$source]=1 } record_config_source() { local config_path=$1 local source=$2 local label=$3 if [[ -z "${manifest_config_sources[$source]:-}" ]]; then echo "LinuxCNC $label source is not listed in $manifest: $source ($config_path)" >&2 exit 1 fi used_config_sources[$source]=1 } record_tooldata_source() { local config_path=$1 local source=$2 if [[ -z "${manifest_tooldata_sources[$source]:-}" ]]; then echo "LinuxCNC TOOL_TABLE source is not listed as tooldata in $manifest: $source ($config_path)" >&2 exit 1 fi used_tooldata_sources[$source]=1 } declare -A seen_aliases=() declare -A seen_cases=() declare -A seen_case_values=() entries=() case_entries=() json_case_entries=() processed_config_count=0 resolved_remap_count=0 unique_config_source_count=0 unique_resolved_remap_count=0 unique_tooldata_source_count=0 add_alias() { local alias=$1 local m428=$2 local m429=$3 local m430=$4 alias=${alias,,} alias=${alias//[[:space:]]/} alias=${alias//\\/\\\\} alias=${alias//\"/\\\"} [[ -n "$alias" ]] || return 0 local types="$m428,$m429,$m430" if [[ -n "${seen_aliases[$alias]:-}" ]]; then if [[ "${seen_aliases[$alias]}" != "$types" ]]; then echo "ambiguous switchkins alias '$alias': ${seen_aliases[$alias]} and $types" >&2 exit 1 fi return 0 fi seen_aliases[$alias]=$types entries+=(" {\"$alias\", $m428, $m429, $m430},") } add_case() { local field=$1 local value=$2 local m428=$3 local m429=$4 local m430=$5 [[ -n "$value" ]] || return 0 local types="$m428,$m429,$m430" local value_key="$field|$value" if [[ -n "${seen_case_values[$value_key]:-}" && "${seen_case_values[$value_key]}" != "$types" ]]; then echo "ambiguous switchkins config case '$field=$value': ${seen_case_values[$value_key]} and $types" >&2 exit 1 fi seen_case_values[$value_key]=$types local key="$field|$value|$types" [[ -z "${seen_cases[$key]:-}" ]] || return 0 seen_cases[$key]=1 local cpp_field=$field local cpp_value=$value cpp_field=${cpp_field//\\/\\\\} cpp_field=${cpp_field//\"/\\\"} cpp_value=${cpp_value//\\/\\\\} cpp_value=${cpp_value//\"/\\\"} case_entries+=(" {\"$cpp_field\", \"$cpp_value\", $m428, $m429, $m430},") json_case_entries+=("{\"field\":\"$cpp_field\",\"value\":\"$cpp_value\",\"m428\":$m428,\"m429\":$m429,\"m430\":$m430}") } trimmed_machine_label= trim_machine_label() { local machine=$1 machine=${machine%%(*} machine="${machine#"${machine%%[![:space:]]*}"}" machine="${machine%"${machine##*[![:space:]]}"}" trimmed_machine_label=$machine } add_kinematics_variants() { local callback=$1 local field=$2 local kinematics=$3 local m428=$4 local m429=$5 local m430=$6 [[ -n "$kinematics" ]] || return 0 "$callback" "$field" "$kinematics" "$m428" "$m429" "$m430" local first_word=${kinematics%%[[:space:]]*} "$callback" "$field" "$first_word" "$m428" "$m429" "$m430" "$callback" "$field" "${first_word%_kins}" "$m428" "$m429" "$m430" "$callback" "$field" "${first_word%-kins}" "$m428" "$m429" "$m430" local first_word_hyphen=${first_word//_/-} local first_word_without_underscore_kins=${first_word%_kins} local first_word_without_dash_kins=${first_word%-kins} "$callback" "$field" "$first_word_hyphen" "$m428" "$m429" "$m430" "$callback" "$field" "${first_word_without_underscore_kins//_/-}" "$m428" "$m429" "$m430" "$callback" "$field" "${first_word_without_dash_kins//_/-}" "$m428" "$m429" "$m430" } add_alias_variant() { local _field=$1 shift add_alias "$@" } add_case_variant() { add_case "$@" } add_halfile_case_fields() { local field=$1 local halfile=$2 local m428=$3 local m429=$4 local m430=$5 local hal_base=${halfile##*/} local hal_stem=${hal_base%.hal} add_case "$field" "$halfile" "$m428" "$m429" "$m430" add_case "$field" "$hal_base" "$m428" "$m429" "$m430" add_case "$field" "$hal_stem" "$m428" "$m429" "$m430" } add_machine_aliases() { local machine=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$machine" ]] || return 0 add_alias "$machine" "$m428" "$m429" "$m430" trim_machine_label "$machine" add_alias "$trimmed_machine_label" "$m428" "$m429" "$m430" } add_kinematics_aliases() { local kinematics=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$kinematics" ]] || return 0 add_kinematics_variants add_alias_variant "" "$kinematics" "$m428" "$m429" "$m430" } add_switchkins_cases() { local config_dir=$1 local config_stem=$2 local machine=$3 local kinematics=$4 local m428=$5 local m429=$6 local m430=$7 add_case "switchkins" "${config_dir##*/}" "$m428" "$m429" "$m430" add_case "switchkins" "$config_stem" "$m428" "$m429" "$m430" if [[ -n "$machine" ]]; then add_case "switchkins" "$machine" "$m428" "$m429" "$m430" trim_machine_label "$machine" add_case "switchkins" "$trimmed_machine_label" "$m428" "$m429" "$m430" fi if [[ -n "$kinematics" ]]; then add_kinematics_variants add_case_variant "switchkins" "$kinematics" "$m428" "$m429" "$m430" fi } add_halfile_aliases() { local halfile=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$halfile" ]] || return 0 [[ "$halfile" == LIB:* ]] && return 0 add_alias "$halfile" "$m428" "$m429" "$m430" local hal_base=${halfile##*/} add_alias "$hal_base" "$m428" "$m429" "$m430" add_alias "${hal_base%.hal}" "$m428" "$m429" "$m430" } add_halfile_cases() { local halfile=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$halfile" ]] || return 0 [[ "$halfile" == LIB:* ]] && return 0 add_halfile_case_fields "halFile" "$halfile" "$m428" "$m429" "$m430" add_halfile_case_fields "halfile" "$halfile" "$m428" "$m429" "$m430" add_halfile_case_fields "hal_file" "$halfile" "$m428" "$m429" "$m430" add_halfile_case_fields "HALFILE" "$halfile" "$m428" "$m429" "$m430" } add_halfile_source_cases() { local source=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$source" ]] || return 0 add_case "halFile" "$source" "$m428" "$m429" "$m430" add_case "halfile" "$source" "$m428" "$m429" "$m430" add_case "hal_file" "$source" "$m428" "$m429" "$m430" add_case "HALFILE" "$source" "$m428" "$m429" "$m430" } add_postgui_halfile_cases() { local halfile=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$halfile" ]] || return 0 [[ "$halfile" == LIB:* ]] && return 0 add_halfile_case_fields "postguiHalFile" "$halfile" "$m428" "$m429" "$m430" add_halfile_case_fields "postguihalfile" "$halfile" "$m428" "$m429" "$m430" add_halfile_case_fields "postgui_halfile" "$halfile" "$m428" "$m429" "$m430" add_halfile_case_fields "postgui_hal_file" "$halfile" "$m428" "$m429" "$m430" add_halfile_case_fields "POSTGUI_HALFILE" "$halfile" "$m428" "$m429" "$m430" } add_postgui_halfile_source_cases() { local source=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$source" ]] || return 0 add_case "postguiHalFile" "$source" "$m428" "$m429" "$m430" add_case "postguihalfile" "$source" "$m428" "$m429" "$m430" add_case "postgui_halfile" "$source" "$m428" "$m429" "$m430" add_case "postgui_hal_file" "$source" "$m428" "$m429" "$m430" add_case "POSTGUI_HALFILE" "$source" "$m428" "$m429" "$m430" } add_subroutine_path_cases() { local subroutine_path=$1 local m428=$2 local m429=$3 local m430=$4 [[ -n "$subroutine_path" ]] || return 0 add_case "subroutinePath" "$subroutine_path" "$m428" "$m429" "$m430" add_case "subroutinepath" "$subroutine_path" "$m428" "$m429" "$m430" add_case "subroutine_path" "$subroutine_path" "$m428" "$m429" "$m430" add_case "SUBROUTINE_PATH" "$subroutine_path" "$m428" "$m429" "$m430" } while IFS=: read -r group path _note; do [[ "$group" == "config" && "$path" == *.ini ]] || continue config="$linuxcnc_root/$path" [[ -f "$config" ]] || { echo "missing LinuxCNC config source: $path" >&2 exit 1 } machine= kinematics= halfiles= postgui_halfiles= tool_tables= subroutine_paths= remap_ngcs= on_abort_ngcs= python_paths= python_toplevels= python_remaps= halcmd_loadusrs= remap_ngc_428= remap_ngc_429= remap_ngc_430= while IFS=$'\t' read -r ini_key ini_value; do case "$ini_key" in machine) [[ -n "$machine" ]] || machine=$ini_value ;; kinematics) [[ -n "$kinematics" ]] || kinematics=$ini_value ;; halfile) halfiles+="${halfiles:+$'\n'}$ini_value" ;; postgui_halfile) postgui_halfiles+="${postgui_halfiles:+$'\n'}$ini_value" ;; tool_table) tool_tables+="${tool_tables:+$'\n'}$ini_value" ;; subroutine_path) subroutine_paths+="${subroutine_paths:+$'\n'}$ini_value" ;; remap_ngc) remap_ngcs+="${remap_ngcs:+$'\n'}$ini_value" ;; on_abort_ngc) on_abort_ngcs+="${on_abort_ngcs:+$'\n'}$ini_value" ;; python_path_append) python_paths+="${python_paths:+$'\n'}$ini_value" ;; python_toplevel) python_toplevels+="${python_toplevels:+$'\n'}$ini_value" ;; python_remap) python_remaps+="${python_remaps:+$'\n'}$ini_value" ;; halcmd_loadusr) halcmd_loadusrs+="${halcmd_loadusrs:+$'\n'}$ini_value" ;; remap_428) [[ -n "$remap_ngc_428" ]] || remap_ngc_428=$ini_value ;; remap_429) [[ -n "$remap_ngc_429" ]] || remap_ngc_429=$ini_value ;; remap_430) [[ -n "$remap_ngc_430" ]] || remap_ngc_430=$ini_value ;; esac done < <(read_ini_switchkins_config "$config") if [[ -z "$remap_ngc_428" && -z "$remap_ngc_429" && -z "$remap_ngc_430" ]]; then continue fi if [[ -z "$subroutine_paths" ]]; then subroutine_paths="." fi remap_source_428= remap_source_429= remap_source_430= if [[ -n "$remap_ngc_428" ]]; then if remap_source_for_ngc "$config" "$remap_ngc_428" "$subroutine_paths"; then remap_source_428=$resolved_remap_source fi require_remap_source "$path" 428 "$remap_ngc_428" "$remap_source_428" record_remap_source "$path" 428 "$remap_source_428" resolved_remap_count=$((resolved_remap_count + 1)) fi if [[ -n "$remap_ngc_429" ]]; then if remap_source_for_ngc "$config" "$remap_ngc_429" "$subroutine_paths"; then remap_source_429=$resolved_remap_source fi require_remap_source "$path" 429 "$remap_ngc_429" "$remap_source_429" record_remap_source "$path" 429 "$remap_source_429" resolved_remap_count=$((resolved_remap_count + 1)) fi if [[ -n "$remap_ngc_430" ]]; then if remap_source_for_ngc "$config" "$remap_ngc_430" "$subroutine_paths"; then remap_source_430=$resolved_remap_source fi require_remap_source "$path" 430 "$remap_ngc_430" "$remap_source_430" record_remap_source "$path" 430 "$remap_source_430" resolved_remap_count=$((resolved_remap_count + 1)) fi kinstype_for_remap_source "$remap_source_428" m428=$resolved_kinstype kinstype_for_remap_source "$remap_source_429" m429=$resolved_kinstype kinstype_for_remap_source "$remap_source_430" m430=$resolved_kinstype [[ -z "$remap_ngc_428" ]] || require_kinstype "$path" 428 "$remap_source_428" "$m428" [[ -z "$remap_ngc_429" ]] || require_kinstype "$path" 429 "$remap_source_429" "$m429" [[ -z "$remap_ngc_430" ]] || require_kinstype "$path" 430 "$remap_source_430" "$m430" [[ -n "$m428" ]] || m428=-1 [[ -n "$m429" ]] || m429=-1 [[ -n "$m430" ]] || m430=-1 processed_config_count=$((processed_config_count + 1)) record_config_source "$path" "$path" "INI config" config_dir=${path%/*} config_base=${path##*/} config_stem=${config_base%.ini} add_case "config" "$path" "$m428" "$m429" "$m430" add_case "configPath" "$path" "$m428" "$m429" "$m430" add_case "configpath" "$path" "$m428" "$m429" "$m430" add_case "config_path" "$path" "$m428" "$m429" "$m430" add_case "ini" "$path" "$m428" "$m429" "$m430" add_case "iniFile" "$path" "$m428" "$m429" "$m430" add_case "inifile" "$path" "$m428" "$m429" "$m430" add_case "iniFileName" "$path" "$m428" "$m429" "$m430" add_case "inifilename" "$path" "$m428" "$m429" "$m430" add_case "ini_file" "$path" "$m428" "$m429" "$m430" add_case "ini_file_name" "$path" "$m428" "$m429" "$m430" add_case "INI_FILE_NAME" "$path" "$m428" "$m429" "$m430" add_case "machine" "$machine" "$m428" "$m429" "$m430" add_case "kinematics" "$kinematics" "$m428" "$m429" "$m430" add_switchkins_cases "$config_dir" "$config_stem" "$machine" "$kinematics" "$m428" "$m429" "$m430" add_alias "$path" "$m428" "$m429" "$m430" add_alias "$config_dir" "$m428" "$m429" "$m430" add_alias "${config_dir##*/}" "$m428" "$m429" "$m430" add_alias "$config_stem" "$m428" "$m429" "$m430" add_machine_aliases "$machine" "$m428" "$m429" "$m430" add_kinematics_aliases "$kinematics" "$m428" "$m429" "$m430" while IFS= read -r halfile; do if config_source_for_halfile "$config" "$halfile"; then hal_source=$resolved_config_source record_config_source "$path" "$hal_source" "HALFILE" add_alias "$hal_source" "$m428" "$m429" "$m430" add_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430" while IFS= read -r loadusr_command; do [[ -n "$loadusr_command" ]] || continue record_loadusr_asset "$path" "$linuxcnc_root/$hal_source" "$loadusr_command" "HALFILE loadusr" done < <(read_loadusr_commands "$linuxcnc_root/$hal_source") fi add_halfile_cases "$halfile" "$m428" "$m429" "$m430" add_halfile_aliases "$halfile" "$m428" "$m429" "$m430" done <<<"$halfiles" while IFS= read -r halfile; do if config_source_for_halfile "$config" "$halfile"; then hal_source=$resolved_config_source record_config_source "$path" "$hal_source" "POSTGUI_HALFILE" add_alias "$hal_source" "$m428" "$m429" "$m430" add_postgui_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430" while IFS= read -r loadusr_command; do [[ -n "$loadusr_command" ]] || continue record_loadusr_asset "$path" "$linuxcnc_root/$hal_source" "$loadusr_command" "POSTGUI_HALFILE loadusr" done < <(read_loadusr_commands "$linuxcnc_root/$hal_source") fi add_postgui_halfile_cases "$halfile" "$m428" "$m429" "$m430" add_halfile_aliases "$halfile" "$m428" "$m429" "$m430" done <<<"$postgui_halfiles" while IFS= read -r tool_table; do if tooldata_source_for_tool_table "$config" "$tool_table"; then record_tooldata_source "$path" "$resolved_tooldata_source" fi done <<<"$tool_tables" while IFS= read -r ngc_name; do [[ -n "$ngc_name" ]] || continue if remap_source_for_ngc "$config" "$ngc_name" "$subroutine_paths"; then if [[ -z "${manifest_remap_sources[$resolved_remap_source]:-}" ]]; then record_asset_source "$path" "$resolved_remap_source" "RS274NGC REMAP ngc" fi fi done <<<"$remap_ngcs" while IFS= read -r ngc_name; do [[ -n "$ngc_name" ]] || continue if remap_source_for_ngc "$config" "$ngc_name" "$subroutine_paths"; then record_asset_source "$path" "$resolved_remap_source" "ON_ABORT_COMMAND" fi done <<<"$on_abort_ngcs" while IFS= read -r toplevel; do [[ -n "$toplevel" ]] || continue if asset_source_for_path "$config" "$toplevel"; then python_toplevel_source=$resolved_asset_source record_asset_source "$path" "$python_toplevel_source" "PYTHON TOPLEVEL" record_python_import_assets "$path" "$python_toplevel_source" fi done <<<"$python_toplevels" while IFS= read -r python_remap; do [[ -n "$python_remap" ]] || continue if python_module_source_for_name "$config" "$python_remap" "$python_paths"; then python_remap_source=$resolved_python_module_source record_asset_source "$path" "$python_remap_source" "Python REMAP module" record_python_import_assets "$path" "$python_remap_source" fi done <<<"$python_remaps" while IFS= read -r loadusr_command; do [[ -n "$loadusr_command" ]] || continue record_loadusr_asset "$path" "$config" "$loadusr_command" "HALCMD loadusr" done <<<"$halcmd_loadusrs" for remap_source in "$remap_source_428" "$remap_source_429" "$remap_source_430"; do if [[ -n "$remap_source" ]]; then remap_dir=${remap_source%/*} add_alias "$remap_source" "$m428" "$m429" "$m430" add_alias "$remap_dir" "$m428" "$m429" "$m430" add_alias "${remap_dir%/*}" "$m428" "$m429" "$m430" add_alias "${remap_dir%/remap_subs}" "$m428" "$m429" "$m430" remap_parent=${remap_dir%/remap_subs} add_alias "${remap_parent##*/}" "$m428" "$m429" "$m430" add_case "remap" "$remap_source" "$m428" "$m429" "$m430" add_case "remap" "$remap_dir" "$m428" "$m429" "$m430" add_case "remap" "$remap_parent" "$m428" "$m429" "$m430" add_case "remap" "${remap_parent##*/}" "$m428" "$m429" "$m430" add_subroutine_path_cases "$remap_dir" "$m428" "$m429" "$m430" fi done done <"$manifest" unique_config_source_count=${#used_config_sources[@]} unique_resolved_remap_count=${#used_remap_sources[@]} unique_tooldata_source_count=${#used_tooldata_sources[@]} unique_asset_source_count=${#used_asset_sources[@]} validate_generated_content() { if ((manifest_config_count == 0)); then echo "no LinuxCNC config sources listed in manifest: $manifest" >&2 exit 1 fi if ((manifest_remap_count == 0)); then echo "no LinuxCNC remap sources listed in manifest: $manifest" >&2 exit 1 fi if ((processed_config_count == 0)); then echo "no LinuxCNC switchkins remap configs found in manifest: $manifest" >&2 exit 1 fi if ((resolved_remap_count == 0)); then echo "no LinuxCNC M428/M429/M430 remap sources resolved from manifest: $manifest" >&2 exit 1 fi if ((unique_resolved_remap_count == 0)); then echo "no unique LinuxCNC M428/M429/M430 remap sources resolved from manifest: $manifest" >&2 exit 1 fi if ((unique_config_source_count == 0)); then echo "no unique LinuxCNC config sources used from manifest: $manifest" >&2 exit 1 fi if ((manifest_tooldata_count > 0 && unique_tooldata_source_count == 0)); then echo "no unique LinuxCNC TOOL_TABLE sources used from manifest: $manifest" >&2 exit 1 fi if ((unique_config_source_count != manifest_config_count)); then echo "LinuxCNC config manifest coverage mismatch: used $unique_config_source_count of $manifest_config_count config sources" >&2 local config_source for config_source in "${!manifest_config_sources[@]}"; do if [[ -z "${used_config_sources[$config_source]:-}" ]]; then echo "unused LinuxCNC config manifest source: $config_source" >&2 fi done exit 1 fi if ((unique_tooldata_source_count != manifest_tooldata_count)); then echo "LinuxCNC tooldata manifest coverage mismatch: used $unique_tooldata_source_count of $manifest_tooldata_count tooldata sources" >&2 local tooldata_source for tooldata_source in "${!manifest_tooldata_sources[@]}"; do if [[ -z "${used_tooldata_sources[$tooldata_source]:-}" ]]; then echo "unused LinuxCNC tooldata manifest source: $tooldata_source" >&2 fi done exit 1 fi if ((unique_asset_source_count > manifest_asset_count)); then echo "LinuxCNC asset manifest coverage mismatch: used $unique_asset_source_count of $manifest_asset_count asset sources" >&2 exit 1 fi if ((unique_resolved_remap_count != manifest_remap_count)); then echo "LinuxCNC remap manifest coverage mismatch: resolved $unique_resolved_remap_count of $manifest_remap_count remap sources" >&2 local remap_source for remap_source in "${!manifest_remap_sources[@]}"; do if [[ -z "${used_remap_sources[$remap_source]:-}" ]]; then echo "unused LinuxCNC remap manifest source: $remap_source" >&2 fi done exit 1 fi if ((${#entries[@]} == 0)); then echo "empty generated switchkins remap alias table" >&2 exit 1 fi if ((${#case_entries[@]} == 0)); then echo "empty generated switchkins remap config cases" >&2 exit 1 fi if ((${#case_entries[@]} != ${#json_case_entries[@]})); then echo "generated C++/JSON switchkins config case count mismatch: ${#case_entries[@]} != ${#json_case_entries[@]}" >&2 exit 1 fi } validate_generated_content write_table() { cat <<'EOF' // Generated by ./generate-linuxcnc-switchkins-remap-table.sh. // Source: LinuxCNC INI MACHINE/KINEMATICS/HALFILE/POSTGUI_HALFILE/SUBROUTINE_PATH/REMAP entries and // adjacent remap_subs/{428,429,430}remap.ngc # assignments. EOF printf '%s\n' "${entries[@]}" } write_config_cases() { cat <<'EOF' // Generated by ./generate-linuxcnc-switchkins-remap-table.sh --config-cases. // Source: LinuxCNC INI config path, MACHINE, KINEMATICS, non-LIB HALFILE/POSTGUI_HALFILE, // SUBROUTINE_PATH-resolved remap_subs entries, and adjacent M428/M429/M430 # assignments. EOF printf '%s\n' "${case_entries[@]}" } write_json_cases() { printf '[\n' for i in "${!json_case_entries[@]}"; do suffix=, if [[ "$i" == "$((${#json_case_entries[@]} - 1))" ]]; then suffix= fi printf ' %s%s\n' "${json_case_entries[$i]}" "$suffix" done printf ']\n' } write_generated_file() { local path=$1 local label=$2 local writer=$3 if [[ -e "$path" && ! -f "$path" ]]; then echo "cannot write generated $label: $path is not a file" >&2 exit 1 fi "$writer" >"$path" } if [[ "$mode" == table ]]; then write_table elif [[ "$mode" == config-cases ]]; then write_config_cases elif [[ "$mode" == json-cases ]]; then write_json_cases elif [[ "$mode" == all ]]; then if [[ -e "$all_output_dir" && ! -d "$all_output_dir" ]]; then echo "switchkins remap output path is not a directory: $all_output_dir" >&2 exit 1 fi mkdir -p "$all_output_dir" all_output_dir=$(cd "$all_output_dir" && pwd) if [[ "$all_output_dir" == "/" ]]; then echo "switchkins remap output directory must not be the filesystem root" >&2 exit 1 fi write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_table.inc" "table" write_table write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.inc" "config cases" write_config_cases write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.json" "JSON config cases" write_json_cases else echo "unknown switchkins remap table generator mode: $mode" >&2 exit 1 fi