1080 lines
38 KiB
Bash
Executable File
1080 lines
38 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
|
|
manifest=${1:-linuxcnc-kinematics-source-files.txt}
|
|
|
|
usage() {
|
|
echo "usage: $0 [manifest]" >&2
|
|
}
|
|
|
|
if [[ "${manifest:-}" == "--help" || "${manifest:-}" == "-h" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
if [[ "${manifest:-}" == --* ]]; then
|
|
usage
|
|
echo "unknown switchkins remap table checker option: $manifest" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "${2:-}" ]]; then
|
|
usage
|
|
echo "too many switchkins remap table checker 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
|
|
|
|
validate_manifest_remap_kinstypes() {
|
|
local manifest_file=$1
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$linuxcnc_root" "$manifest_file" <<'PY'
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
linuxcnc_root, manifest_file = sys.argv[1:]
|
|
remap_re = re.compile(r'^\s*#<kinstype>\s*=\s*(-?\d+)\b')
|
|
seen = {}
|
|
with open(manifest_file, encoding="utf-8") as manifest:
|
|
for line_number, line in enumerate(manifest, 1):
|
|
parts = line.rstrip("\n").split(":", 2)
|
|
if len(parts) < 2 or parts[0] != "remap":
|
|
continue
|
|
path = parts[1]
|
|
basename = os.path.basename(path)
|
|
if basename not in {"428remap.ngc", "429remap.ngc", "430remap.ngc"}:
|
|
raise SystemExit(f"unexpected switchkins remap source at {manifest_file}:{line_number}: {path}")
|
|
if path in seen:
|
|
raise SystemExit(f"duplicate switchkins remap source at {manifest_file}:{line_number}: {path}")
|
|
seen[path] = line_number
|
|
source = os.path.join(linuxcnc_root, path)
|
|
with open(source, encoding="utf-8") as handle:
|
|
kinstypes = [int(match.group(1)) for match in map(remap_re.match, handle) if match]
|
|
if len(kinstypes) != 1:
|
|
raise SystemExit(f"expected exactly one #<kinstype> assignment in {path}, found {len(kinstypes)}")
|
|
if kinstypes[0] < 0:
|
|
raise SystemExit(f"invalid negative #<kinstype> assignment in {path}: {kinstypes[0]}")
|
|
if not seen:
|
|
raise SystemExit(f"no switchkins remap sources listed in {manifest_file}")
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$linuxcnc_root" "$manifest_file" <<'JS'
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const [linuxcncRoot, manifestFile] = process.argv.slice(2);
|
|
const remapRe = /^\s*#<kinstype>\s*=\s*(-?\d+)\b/;
|
|
const seen = new Map();
|
|
for (const [index, line] of fs.readFileSync(manifestFile, "utf8").split(/\r?\n/).entries()) {
|
|
const parts = line.split(":", 3);
|
|
if (parts.length < 2 || parts[0] !== "remap") {
|
|
continue;
|
|
}
|
|
const sourcePath = parts[1];
|
|
const basename = path.basename(sourcePath);
|
|
if (!["428remap.ngc", "429remap.ngc", "430remap.ngc"].includes(basename)) {
|
|
throw new Error(`unexpected switchkins remap source at ${manifestFile}:${index + 1}: ${sourcePath}`);
|
|
}
|
|
if (seen.has(sourcePath)) {
|
|
throw new Error(`duplicate switchkins remap source at ${manifestFile}:${index + 1}: ${sourcePath}`);
|
|
}
|
|
seen.set(sourcePath, index + 1);
|
|
const kinstypes = fs.readFileSync(path.join(linuxcncRoot, sourcePath), "utf8")
|
|
.split(/\r?\n/)
|
|
.map((sourceLine) => remapRe.exec(sourceLine))
|
|
.filter(Boolean)
|
|
.map((match) => Number(match[1]));
|
|
if (kinstypes.length !== 1) {
|
|
throw new Error(`expected exactly one #<kinstype> assignment in ${sourcePath}, found ${kinstypes.length}`);
|
|
}
|
|
if (kinstypes[0] < 0) {
|
|
throw new Error(`invalid negative #<kinstype> assignment in ${sourcePath}: ${kinstypes[0]}`);
|
|
}
|
|
}
|
|
if (seen.size === 0) {
|
|
throw new Error(`no switchkins remap sources listed in ${manifestFile}`);
|
|
}
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap manifest validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
validate_manifest_remap_kinstypes "$manifest"
|
|
|
|
validate_manifest_config_remaps() {
|
|
local manifest_file=$1
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$linuxcnc_root" "$manifest_file" <<'PY'
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
linuxcnc_root, manifest_file = sys.argv[1:]
|
|
remap_re = re.compile(r'REMAP\s*=\s*M(428|429|430)(?:\D|$)', re.I)
|
|
seen = {}
|
|
with open(manifest_file, encoding="utf-8") as manifest:
|
|
for line_number, line in enumerate(manifest, 1):
|
|
parts = line.rstrip("\n").split(":", 2)
|
|
if len(parts) < 2 or parts[0] != "config" or not parts[1].endswith(".ini"):
|
|
continue
|
|
source_path = parts[1]
|
|
source = os.path.join(linuxcnc_root, source_path)
|
|
with open(source, encoding="utf-8") as handle:
|
|
mcodes = {int(match.group(1)) for match in remap_re.finditer(handle.read())}
|
|
if not mcodes:
|
|
continue
|
|
seen[source_path] = mcodes
|
|
missing = {428, 429} - mcodes
|
|
if missing:
|
|
raise SystemExit(f"switchkins config {source_path} is missing required M-code remap(s): {sorted(missing)}")
|
|
if not seen:
|
|
raise SystemExit(f"no switchkins REMAP config sources listed in {manifest_file}")
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$linuxcnc_root" "$manifest_file" <<'JS'
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const [linuxcncRoot, manifestFile] = process.argv.slice(2);
|
|
const remapRe = /REMAP\s*=\s*M(428|429|430)(?:\D|$)/ig;
|
|
const seen = new Map();
|
|
for (const line of fs.readFileSync(manifestFile, "utf8").split(/\r?\n/)) {
|
|
const parts = line.split(":", 3);
|
|
if (parts.length < 2 || parts[0] !== "config" || !parts[1].endsWith(".ini")) {
|
|
continue;
|
|
}
|
|
const sourcePath = parts[1];
|
|
const text = fs.readFileSync(path.join(linuxcncRoot, sourcePath), "utf8");
|
|
const mcodes = new Set();
|
|
for (const match of text.matchAll(remapRe)) {
|
|
mcodes.add(Number(match[1]));
|
|
}
|
|
if (mcodes.size === 0) {
|
|
continue;
|
|
}
|
|
seen.set(sourcePath, mcodes);
|
|
const missing = [428, 429].filter((mcode) => !mcodes.has(mcode));
|
|
if (missing.length > 0) {
|
|
throw new Error(`switchkins config ${sourcePath} is missing required M-code remap(s): ${missing.join(",")}`);
|
|
}
|
|
}
|
|
if (seen.size === 0) {
|
|
throw new Error(`no switchkins REMAP config sources listed in ${manifestFile}`);
|
|
}
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap config validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
validate_manifest_config_remaps "$manifest"
|
|
|
|
validate_generator_cli_contract() {
|
|
local manifest_file=$1
|
|
local cli_dir
|
|
cli_dir=$(mktemp -d "${TMPDIR:-/tmp}/linuxcnc_switchkins_remap_cli.XXXXXX")
|
|
|
|
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --help >/dev/null 2>&1
|
|
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --unknown "$manifest_file" >/dev/null 2>&1; then
|
|
echo "switchkins remap generator accepted unknown option" >&2
|
|
exit 1
|
|
fi
|
|
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --all-output-dir >/dev/null 2>&1; then
|
|
echo "switchkins remap generator accepted --all-output-dir without directory" >&2
|
|
exit 1
|
|
fi
|
|
if LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh "$manifest_file" extra >/dev/null 2>&1; then
|
|
echo "switchkins remap generator accepted too many arguments" >&2
|
|
exit 1
|
|
fi
|
|
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --all-output-dir "$cli_dir/out" "$manifest_file"
|
|
for generated_file in \
|
|
linuxcnc_switchkins_remap_table.inc \
|
|
linuxcnc_switchkins_remap_config_cases.inc \
|
|
linuxcnc_switchkins_remap_config_cases.json
|
|
do
|
|
if [[ ! -s "$cli_dir/out/$generated_file" ]]; then
|
|
echo "switchkins remap generator --all-output-dir did not write $generated_file" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
rm -rf "$cli_dir"
|
|
}
|
|
validate_generator_cli_contract "$manifest"
|
|
|
|
generated_dir=$(mktemp -d "${TMPDIR:-/tmp}/linuxcnc_switchkins_remap.XXXXXX")
|
|
trap 'rm -rf "$generated_dir"' EXIT
|
|
|
|
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --all-output-dir "$generated_dir" "$manifest"
|
|
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh "$manifest" \
|
|
>"$generated_dir/linuxcnc_switchkins_remap_table.stdout.inc"
|
|
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --config-cases "$manifest" \
|
|
>"$generated_dir/linuxcnc_switchkins_remap_config_cases.stdout.inc"
|
|
LINUXCNC_ROOT="$linuxcnc_root" ./generate-linuxcnc-switchkins-remap-table.sh --json-cases "$manifest" \
|
|
>"$generated_dir/linuxcnc_switchkins_remap_config_cases.stdout.json"
|
|
|
|
validate_json() {
|
|
local file=$1
|
|
local label=$2
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$file" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
data = json.load(handle)
|
|
if not isinstance(data, list) or not data:
|
|
raise SystemExit("expected non-empty JSON array")
|
|
seen = set()
|
|
seen_values = {}
|
|
for index, entry in enumerate(data):
|
|
if not isinstance(entry, dict):
|
|
raise SystemExit(f"entry {index} is not an object")
|
|
if set(entry) != {"field", "value", "m428", "m429", "m430"}:
|
|
raise SystemExit(f"entry {index} has unexpected keys")
|
|
if not isinstance(entry["field"], str) or not entry["field"]:
|
|
raise SystemExit(f"entry {index} has invalid field")
|
|
if not isinstance(entry["value"], str) or not entry["value"]:
|
|
raise SystemExit(f"entry {index} has invalid value")
|
|
for key in ("m428", "m429", "m430"):
|
|
if not isinstance(entry[key], int):
|
|
raise SystemExit(f"entry {index} has non-integer {key}")
|
|
case_key = (entry["field"], entry["value"], entry["m428"], entry["m429"], entry["m430"])
|
|
if case_key in seen:
|
|
raise SystemExit(f"entry {index} duplicates an earlier case")
|
|
seen.add(case_key)
|
|
value_key = (entry["field"], entry["value"])
|
|
types = (entry["m428"], entry["m429"], entry["m430"])
|
|
if value_key in seen_values and seen_values[value_key] != types:
|
|
raise SystemExit(f"entry {index} has ambiguous field/value mapping")
|
|
seen_values[value_key] = types
|
|
PY
|
|
then
|
|
echo "invalid switchkins remap JSON: $label" >&2
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$file" <<'JS'
|
|
const fs = require("fs");
|
|
const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
if (!Array.isArray(data) || data.length === 0) {
|
|
throw new Error("expected non-empty JSON array");
|
|
}
|
|
const seen = new Set();
|
|
const seenValues = new Map();
|
|
for (const [index, entry] of data.entries()) {
|
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
throw new Error(`entry ${index} is not an object`);
|
|
}
|
|
const keys = Object.keys(entry).sort();
|
|
const expected = ["field", "m428", "m429", "m430", "value"];
|
|
if (keys.length !== expected.length || keys.some((key, i) => key !== expected[i])) {
|
|
throw new Error(`entry ${index} has unexpected keys`);
|
|
}
|
|
if (typeof entry.field !== "string" || entry.field.length === 0) {
|
|
throw new Error(`entry ${index} has invalid field`);
|
|
}
|
|
if (typeof entry.value !== "string" || entry.value.length === 0) {
|
|
throw new Error(`entry ${index} has invalid value`);
|
|
}
|
|
for (const key of ["m428", "m429", "m430"]) {
|
|
if (!Number.isInteger(entry[key])) {
|
|
throw new Error(`entry ${index} has non-integer ${key}`);
|
|
}
|
|
}
|
|
const caseKey = JSON.stringify([entry.field, entry.value, entry.m428, entry.m429, entry.m430]);
|
|
if (seen.has(caseKey)) {
|
|
throw new Error(`entry ${index} duplicates an earlier case`);
|
|
}
|
|
seen.add(caseKey);
|
|
const valueKey = JSON.stringify([entry.field, entry.value]);
|
|
const types = JSON.stringify([entry.m428, entry.m429, entry.m430]);
|
|
if (seenValues.has(valueKey) && seenValues.get(valueKey) !== types) {
|
|
throw new Error(`entry ${index} has ambiguous field/value mapping`);
|
|
}
|
|
seenValues.set(valueKey, types);
|
|
}
|
|
JS
|
|
then
|
|
echo "invalid switchkins remap JSON: $label" >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap JSON validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
count_cpp_config_cases() {
|
|
local file=$1
|
|
grep -Ec '^[[:space:]]*\{"[^"]+", "[^"]+", -?[0-9]+, -?[0-9]+, -?[0-9]+\},?$' "$file"
|
|
}
|
|
|
|
count_table_rows() {
|
|
local file=$1
|
|
grep -Ec '^[[:space:]]*\{"[^"]+", -?[0-9]+, -?[0-9]+, -?[0-9]+\},?$' "$file"
|
|
}
|
|
|
|
count_json_cases() {
|
|
local file=$1
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 - "$file" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
print(len(json.load(handle)))
|
|
PY
|
|
elif command -v node >/dev/null 2>&1; then
|
|
node -e 'console.log(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).length)' "$file"
|
|
else
|
|
echo "missing python3 or node for switchkins remap JSON counting" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_generated_header() {
|
|
local file=$1
|
|
local label=$2
|
|
local expected=$3
|
|
if [[ "$(head -n 1 "$file")" != "$expected" ]]; then
|
|
echo "unexpected generated switchkins remap $label header in $file" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_cpp_table() {
|
|
local file=$1
|
|
local label=$2
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$file" "$label" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
file, label = sys.argv[1:]
|
|
uppercase_or_space_re = re.compile(r'[A-Z\s]')
|
|
row_re = re.compile(
|
|
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
|
|
)
|
|
aliases = {}
|
|
row_count = 0
|
|
with open(file, encoding="utf-8") as handle:
|
|
for line_number, line in enumerate(handle, 1):
|
|
match = row_re.match(line)
|
|
if not match:
|
|
continue
|
|
row_count += 1
|
|
alias = json.loads(match.group(1))
|
|
if not alias:
|
|
raise SystemExit(f"{label} table has empty alias at line {line_number}")
|
|
if uppercase_or_space_re.search(alias):
|
|
raise SystemExit(f"{label} table has unnormalized alias {alias!r} at line {line_number}")
|
|
if alias in aliases:
|
|
raise SystemExit(
|
|
f"{label} table has duplicate alias {alias!r} at lines "
|
|
f"{aliases[alias]} and {line_number}"
|
|
)
|
|
aliases[alias] = line_number
|
|
types = (int(match.group(2)), int(match.group(3)), int(match.group(4)))
|
|
if types[0] < 0 or types[1] < 0:
|
|
raise SystemExit(f"{label} table has missing M428/M429 kinstype at line {line_number}")
|
|
for type_value in types:
|
|
if type_value < -1:
|
|
raise SystemExit(f"{label} table has invalid kinstype {type_value} at line {line_number}")
|
|
if row_count == 0:
|
|
raise SystemExit(f"{label} table has no rows")
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$file" "$label" <<'JS'
|
|
const fs = require("fs");
|
|
const [file, label] = process.argv.slice(2);
|
|
const rowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
|
|
const uppercaseOrSpaceRe = /[A-Z\s]/;
|
|
const aliases = new Map();
|
|
let rowCount = 0;
|
|
for (const [index, line] of fs.readFileSync(file, "utf8").split(/\r?\n/).entries()) {
|
|
const lineNumber = index + 1;
|
|
const match = rowRe.exec(line);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
rowCount += 1;
|
|
const alias = JSON.parse(match[1]);
|
|
if (!alias) {
|
|
throw new Error(`${label} table has empty alias at line ${lineNumber}`);
|
|
}
|
|
if (uppercaseOrSpaceRe.test(alias)) {
|
|
throw new Error(`${label} table has unnormalized alias ${JSON.stringify(alias)} at line ${lineNumber}`);
|
|
}
|
|
if (aliases.has(alias)) {
|
|
throw new Error(`${label} table has duplicate alias ${JSON.stringify(alias)} at lines ${aliases.get(alias)} and ${lineNumber}`);
|
|
}
|
|
aliases.set(alias, lineNumber);
|
|
const types = [Number(match[2]), Number(match[3]), Number(match[4])];
|
|
if (types[0] < 0 || types[1] < 0) {
|
|
throw new Error(`${label} table has missing M428/M429 kinstype at line ${lineNumber}`);
|
|
}
|
|
for (const typeValue of types) {
|
|
if (typeValue < -1) {
|
|
throw new Error(`${label} table has invalid kinstype ${typeValue} at line ${lineNumber}`);
|
|
}
|
|
}
|
|
}
|
|
if (rowCount === 0) {
|
|
throw new Error(`${label} table has no rows`);
|
|
}
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap table validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_cpp_cases() {
|
|
local file=$1
|
|
local label=$2
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$file" "$label" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
file, label = sys.argv[1:]
|
|
row_re = re.compile(
|
|
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
|
|
)
|
|
seen = set()
|
|
seen_values = {}
|
|
row_count = 0
|
|
with open(file, encoding="utf-8") as handle:
|
|
for line_number, line in enumerate(handle, 1):
|
|
match = row_re.match(line)
|
|
if not match:
|
|
continue
|
|
row_count += 1
|
|
field = json.loads(match.group(1))
|
|
value = json.loads(match.group(2))
|
|
types = (int(match.group(3)), int(match.group(4)), int(match.group(5)))
|
|
if types[0] < 0 or types[1] < 0:
|
|
raise SystemExit(f"{label} cases have missing M428/M429 kinstype at line {line_number}")
|
|
if any(type_value < -1 for type_value in types):
|
|
raise SystemExit(f"{label} cases have invalid kinstype at line {line_number}")
|
|
if not field or not value:
|
|
raise SystemExit(f"{label} cases have empty field/value at line {line_number}")
|
|
case_key = (field, value, *types)
|
|
if case_key in seen:
|
|
raise SystemExit(f"{label} cases duplicate an earlier case at line {line_number}")
|
|
seen.add(case_key)
|
|
value_key = (field, value)
|
|
if value_key in seen_values and seen_values[value_key] != types:
|
|
raise SystemExit(f"{label} cases have ambiguous field/value mapping at line {line_number}")
|
|
seen_values[value_key] = types
|
|
if row_count == 0:
|
|
raise SystemExit(f"{label} cases have no rows")
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$file" "$label" <<'JS'
|
|
const fs = require("fs");
|
|
const [file, label] = process.argv.slice(2);
|
|
const rowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
|
|
const seen = new Set();
|
|
const seenValues = new Map();
|
|
let rowCount = 0;
|
|
for (const [index, line] of fs.readFileSync(file, "utf8").split(/\r?\n/).entries()) {
|
|
const lineNumber = index + 1;
|
|
const match = rowRe.exec(line);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
rowCount += 1;
|
|
const field = JSON.parse(match[1]);
|
|
const value = JSON.parse(match[2]);
|
|
const types = [Number(match[3]), Number(match[4]), Number(match[5])];
|
|
if (types[0] < 0 || types[1] < 0) {
|
|
throw new Error(`${label} cases have missing M428/M429 kinstype at line ${lineNumber}`);
|
|
}
|
|
if (types.some((typeValue) => typeValue < -1)) {
|
|
throw new Error(`${label} cases have invalid kinstype at line ${lineNumber}`);
|
|
}
|
|
if (!field || !value) {
|
|
throw new Error(`${label} cases have empty field/value at line ${lineNumber}`);
|
|
}
|
|
const caseKey = JSON.stringify([field, value, ...types]);
|
|
if (seen.has(caseKey)) {
|
|
throw new Error(`${label} cases duplicate an earlier case at line ${lineNumber}`);
|
|
}
|
|
seen.add(caseKey);
|
|
const valueKey = JSON.stringify([field, value]);
|
|
const typeKey = JSON.stringify(types);
|
|
if (seenValues.has(valueKey) && seenValues.get(valueKey) !== typeKey) {
|
|
throw new Error(`${label} cases have ambiguous field/value mapping at line ${lineNumber}`);
|
|
}
|
|
seenValues.set(valueKey, typeKey);
|
|
}
|
|
if (rowCount === 0) {
|
|
throw new Error(`${label} cases have no rows`);
|
|
}
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap C++ case validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
compare_cpp_json_cases() {
|
|
local cpp_file=$1
|
|
local json_file=$2
|
|
local label=$3
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$cpp_file" "$json_file" "$label" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
cpp_file, json_file, label = sys.argv[1:]
|
|
row_re = re.compile(
|
|
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
|
|
)
|
|
cpp_cases = []
|
|
with open(cpp_file, encoding="utf-8") as handle:
|
|
for line_number, line in enumerate(handle, 1):
|
|
match = row_re.match(line)
|
|
if not match:
|
|
continue
|
|
cpp_cases.append({
|
|
"field": json.loads(match.group(1)),
|
|
"value": json.loads(match.group(2)),
|
|
"m428": int(match.group(3)),
|
|
"m429": int(match.group(4)),
|
|
"m430": int(match.group(5)),
|
|
})
|
|
|
|
with open(json_file, encoding="utf-8") as handle:
|
|
json_cases = json.load(handle)
|
|
|
|
if cpp_cases != json_cases:
|
|
raise SystemExit(f"{label} C++/JSON switchkins remap cases differ")
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$cpp_file" "$json_file" "$label" <<'JS'
|
|
const fs = require("fs");
|
|
const [cppFile, jsonFile, label] = process.argv.slice(2);
|
|
const rowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
|
|
const cppCases = [];
|
|
for (const line of fs.readFileSync(cppFile, "utf8").split(/\r?\n/)) {
|
|
const match = rowRe.exec(line);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
cppCases.push({
|
|
field: JSON.parse(match[1]),
|
|
value: JSON.parse(match[2]),
|
|
m428: Number(match[3]),
|
|
m429: Number(match[4]),
|
|
m430: Number(match[5]),
|
|
});
|
|
}
|
|
const jsonCases = JSON.parse(fs.readFileSync(jsonFile, "utf8"));
|
|
if (JSON.stringify(cppCases) !== JSON.stringify(jsonCases)) {
|
|
throw new Error(`${label} C++/JSON switchkins remap cases differ`);
|
|
}
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap C++/JSON comparison" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
extract_api_switchkins_alias_keys() {
|
|
local file=$1
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 - "$file" <<'PY'
|
|
import re
|
|
import sys
|
|
|
|
file = sys.argv[1]
|
|
pattern = re.compile(r'contains_string_value\(compact,\s*"([^"]+)",\s*alias\)')
|
|
function_started = False
|
|
brace_depth = 0
|
|
keys = []
|
|
with open(file, encoding="utf-8") as handle:
|
|
for line in handle:
|
|
if not function_started:
|
|
if line.startswith("bool contains_switchkins_config_alias("):
|
|
function_started = True
|
|
brace_depth += line.count("{") - line.count("}")
|
|
continue
|
|
brace_depth += line.count("{") - line.count("}")
|
|
keys.extend(pattern.findall(line))
|
|
if brace_depth <= 0:
|
|
break
|
|
|
|
if not keys:
|
|
raise SystemExit("missing switchkins config alias keys in cnc_sim_api.cpp")
|
|
|
|
for key in keys:
|
|
print(key)
|
|
PY
|
|
elif command -v node >/dev/null 2>&1; then
|
|
node - "$file" <<'JS'
|
|
const fs = require("fs");
|
|
const file = process.argv[2];
|
|
const pattern = /contains_string_value\(compact,\s*"([^"]+)",\s*alias\)/g;
|
|
const lines = fs.readFileSync(file, "utf8").split(/\r?\n/);
|
|
let started = false;
|
|
let braceDepth = 0;
|
|
const keys = [];
|
|
for (const line of lines) {
|
|
if (!started) {
|
|
if (line.startsWith("bool contains_switchkins_config_alias(")) {
|
|
started = true;
|
|
braceDepth += (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
|
|
}
|
|
continue;
|
|
}
|
|
braceDepth += (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
|
|
for (const match of line.matchAll(pattern)) {
|
|
keys.push(match[1]);
|
|
}
|
|
if (braceDepth <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (keys.length === 0) {
|
|
throw new Error("missing switchkins config alias keys in cnc_sim_api.cpp");
|
|
}
|
|
for (const key of keys) {
|
|
console.log(key);
|
|
}
|
|
JS
|
|
else
|
|
echo "missing python3 or node for switchkins remap alias extraction" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_switchkins_case_fields_against_api() {
|
|
local api_keys_csv=$1
|
|
local cpp_file=$2
|
|
local json_file=$3
|
|
local label=$4
|
|
if [[ -z "$api_keys_csv" ]]; then
|
|
echo "missing switchkins config alias keys in core/src/cnc_sim_api.cpp" >&2
|
|
exit 1
|
|
fi
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$api_keys_csv" "$cpp_file" "$json_file" "$label" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
api_keys_csv, cpp_file, json_file, label = sys.argv[1:]
|
|
allowed = {key for key in api_keys_csv.split(",") if key}
|
|
|
|
if not allowed:
|
|
raise SystemExit("missing switchkins config alias keys in cnc_sim_api.cpp")
|
|
|
|
row_re = re.compile(
|
|
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
|
|
)
|
|
|
|
def load_cases(path):
|
|
if path.endswith(".json"):
|
|
with open(path, encoding="utf-8") as handle:
|
|
return json.load(handle)
|
|
with open(path, encoding="utf-8") as handle:
|
|
return [
|
|
{
|
|
"field": json.loads(match.group(1)),
|
|
"value": json.loads(match.group(2)),
|
|
"m428": int(match.group(3)),
|
|
"m429": int(match.group(4)),
|
|
"m430": int(match.group(5)),
|
|
}
|
|
for match in row_re.finditer(handle.read())
|
|
]
|
|
|
|
cases = load_cases(cpp_file) + load_cases(json_file)
|
|
for index, entry in enumerate(cases):
|
|
field = entry["field"]
|
|
if field.lower() not in allowed:
|
|
raise SystemExit(f"{label} case {index} uses unsupported switchkins field {field!r}")
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$api_keys_csv" "$cpp_file" "$json_file" "$label" <<'JS'
|
|
const fs = require("fs");
|
|
const [apiKeysCsv, cppFile, jsonFile, label] = process.argv.slice(2);
|
|
const allowed = new Set(apiKeysCsv.split(",").filter(Boolean));
|
|
if (allowed.size === 0) {
|
|
throw new Error("missing switchkins config alias keys in cnc_sim_api.cpp");
|
|
}
|
|
const rowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
|
|
const cases = [];
|
|
for (const [path, isJson] of [[cppFile, false], [jsonFile, true]]) {
|
|
if (isJson) {
|
|
for (const entry of JSON.parse(fs.readFileSync(path, "utf8"))) {
|
|
cases.push(entry);
|
|
}
|
|
continue;
|
|
}
|
|
for (const line of fs.readFileSync(path, "utf8").split(/\r?\n/)) {
|
|
const match = rowRe.exec(line);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
cases.push({
|
|
field: JSON.parse(match[1]),
|
|
value: JSON.parse(match[2]),
|
|
m428: Number(match[3]),
|
|
m429: Number(match[4]),
|
|
m430: Number(match[5]),
|
|
});
|
|
}
|
|
}
|
|
cases.forEach((entry, index) => {
|
|
if (!allowed.has(entry.field.toLowerCase())) {
|
|
throw new Error(`${label} case ${index} uses unsupported switchkins field ${JSON.stringify(entry.field)}`);
|
|
}
|
|
});
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap API alias validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_case_values_against_table() {
|
|
local table_file=$1
|
|
local cpp_file=$2
|
|
local json_file=$3
|
|
local label=$4
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$table_file" "$cpp_file" "$json_file" "$label" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
table_file, cpp_file, json_file, label = sys.argv[1:]
|
|
table_row_re = re.compile(
|
|
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
|
|
)
|
|
case_row_re = re.compile(
|
|
r'^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'("(?:(?:\\.)|[^"\\])*"),\s*'
|
|
r'(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$'
|
|
)
|
|
aliases = {}
|
|
with open(table_file, encoding="utf-8") as handle:
|
|
for line in handle:
|
|
match = table_row_re.match(line)
|
|
if not match:
|
|
continue
|
|
aliases[json.loads(match.group(1))] = (
|
|
int(match.group(2)),
|
|
int(match.group(3)),
|
|
int(match.group(4)),
|
|
)
|
|
|
|
if not aliases:
|
|
raise SystemExit(f"{label} table has no aliases")
|
|
|
|
case_entries = []
|
|
with open(cpp_file, encoding="utf-8") as handle:
|
|
for line_number, line in enumerate(handle, 1):
|
|
match = case_row_re.match(line)
|
|
if not match:
|
|
continue
|
|
case_entries.append((
|
|
f"{cpp_file}:{line_number}",
|
|
json.loads(match.group(1)),
|
|
json.loads(match.group(2)),
|
|
(int(match.group(3)), int(match.group(4)), int(match.group(5))),
|
|
))
|
|
with open(json_file, encoding="utf-8") as handle:
|
|
for index, entry in enumerate(json.load(handle)):
|
|
case_entries.append((
|
|
f"{json_file}:{index}",
|
|
entry["field"],
|
|
entry["value"],
|
|
(entry["m428"], entry["m429"], entry["m430"]),
|
|
))
|
|
|
|
for location, field, value, types in case_entries:
|
|
normalized = "".join(ch for ch in value.lower() if not ch.isspace())
|
|
if normalized not in aliases:
|
|
raise SystemExit(f"{label} case value {value!r} at {location} is not covered by the alias table")
|
|
if aliases[normalized] != types:
|
|
raise SystemExit(
|
|
f"{label} case {field}={value!r} at {location} has kinstypes "
|
|
f"{types}, but alias table has {aliases[normalized]}"
|
|
)
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$table_file" "$cpp_file" "$json_file" "$label" <<'JS'
|
|
const fs = require("fs");
|
|
const [tableFile, cppFile, jsonFile, label] = process.argv.slice(2);
|
|
const tableRowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
|
|
const caseRowRe = /^\s*\{("(?:(?:\\.)|[^"\\])*"),\s*("(?:(?:\\.)|[^"\\])*"),\s*(-?\d+),\s*(-?\d+),\s*(-?\d+)\},?\s*$/;
|
|
const aliases = new Map();
|
|
for (const line of fs.readFileSync(tableFile, "utf8").split(/\r?\n/)) {
|
|
const match = tableRowRe.exec(line);
|
|
if (match) {
|
|
aliases.set(JSON.parse(match[1]), [Number(match[2]), Number(match[3]), Number(match[4])]);
|
|
}
|
|
}
|
|
if (aliases.size === 0) {
|
|
throw new Error(`${label} table has no aliases`);
|
|
}
|
|
const caseEntries = [];
|
|
for (const [index, line] of fs.readFileSync(cppFile, "utf8").split(/\r?\n/).entries()) {
|
|
const match = caseRowRe.exec(line);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
caseEntries.push({
|
|
location: `${cppFile}:${index + 1}`,
|
|
field: JSON.parse(match[1]),
|
|
value: JSON.parse(match[2]),
|
|
types: [Number(match[3]), Number(match[4]), Number(match[5])],
|
|
});
|
|
}
|
|
JSON.parse(fs.readFileSync(jsonFile, "utf8")).forEach((entry, index) => {
|
|
caseEntries.push({
|
|
location: `${jsonFile}:${index}`,
|
|
field: entry.field,
|
|
value: entry.value,
|
|
types: [entry.m428, entry.m429, entry.m430],
|
|
});
|
|
});
|
|
for (const entry of caseEntries) {
|
|
const { location, field, value, types } = entry;
|
|
const normalized = value.toLowerCase().replace(/\s+/g, "");
|
|
if (!aliases.has(normalized)) {
|
|
throw new Error(`${label} case value ${JSON.stringify(value)} at ${location} is not covered by the alias table`);
|
|
}
|
|
const aliasTypes = aliases.get(normalized);
|
|
if (JSON.stringify(aliasTypes) !== JSON.stringify(types)) {
|
|
throw new Error(`${label} case ${field}=${JSON.stringify(value)} at ${location} has kinstypes ${JSON.stringify(types)}, but alias table has ${JSON.stringify(aliasTypes)}`);
|
|
}
|
|
}
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap case/table validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
api_keys_csv=$(printf '%s,' $(extract_api_switchkins_alias_keys core/src/cnc_sim_api.cpp))
|
|
api_keys_csv=${api_keys_csv%,}
|
|
validate_api_switchkins_alias_keys() {
|
|
local keys_csv=$1
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 - "$keys_csv" <<'PY'
|
|
import sys
|
|
|
|
keys = [key for key in sys.argv[1].split(",") if key]
|
|
if not keys:
|
|
raise SystemExit("missing switchkins config alias keys in cnc_sim_api.cpp")
|
|
seen = set()
|
|
for key in keys:
|
|
if key != key.lower():
|
|
raise SystemExit(f"switchkins config alias key is not lowercase: {key}")
|
|
if any(ch.isspace() for ch in key):
|
|
raise SystemExit(f"switchkins config alias key contains whitespace: {key}")
|
|
if key in seen:
|
|
raise SystemExit(f"duplicate switchkins config alias key in cnc_sim_api.cpp: {key}")
|
|
seen.add(key)
|
|
PY
|
|
then
|
|
exit 1
|
|
fi
|
|
elif command -v node >/dev/null 2>&1; then
|
|
if ! node - "$keys_csv" <<'JS'
|
|
const keys = process.argv[2].split(",").filter(Boolean);
|
|
if (keys.length === 0) {
|
|
throw new Error("missing switchkins config alias keys in cnc_sim_api.cpp");
|
|
}
|
|
const seen = new Set();
|
|
for (const key of keys) {
|
|
if (key !== key.toLowerCase()) {
|
|
throw new Error(`switchkins config alias key is not lowercase: ${key}`);
|
|
}
|
|
if (/\s/.test(key)) {
|
|
throw new Error(`switchkins config alias key contains whitespace: ${key}`);
|
|
}
|
|
if (seen.has(key)) {
|
|
throw new Error(`duplicate switchkins config alias key in cnc_sim_api.cpp: ${key}`);
|
|
}
|
|
seen.add(key);
|
|
}
|
|
JS
|
|
then
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "missing python3 or node for switchkins remap API key validation" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
validate_api_switchkins_alias_keys "$api_keys_csv"
|
|
|
|
for generated_file in \
|
|
linuxcnc_switchkins_remap_table.inc \
|
|
linuxcnc_switchkins_remap_table.stdout.inc \
|
|
linuxcnc_switchkins_remap_config_cases.inc \
|
|
linuxcnc_switchkins_remap_config_cases.stdout.inc \
|
|
linuxcnc_switchkins_remap_config_cases.json \
|
|
linuxcnc_switchkins_remap_config_cases.stdout.json
|
|
do
|
|
if [[ ! -f "$generated_dir/$generated_file" ]]; then
|
|
echo "missing generated switchkins remap output: $generated_file" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -s "$generated_dir/$generated_file" ]]; then
|
|
echo "empty generated switchkins remap output: $generated_file" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
diff -u "$generated_dir/linuxcnc_switchkins_remap_table.inc" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_table.stdout.inc"
|
|
diff -u "$generated_dir/linuxcnc_switchkins_remap_config_cases.inc" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.stdout.inc"
|
|
diff -u "$generated_dir/linuxcnc_switchkins_remap_config_cases.json" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.stdout.json"
|
|
|
|
validate_generated_header \
|
|
"$generated_dir/linuxcnc_switchkins_remap_table.inc" \
|
|
"table" \
|
|
"// Generated by ./generate-linuxcnc-switchkins-remap-table.sh."
|
|
validate_generated_header \
|
|
core/src/linuxcnc_switchkins_remap_table.inc \
|
|
"tracked table" \
|
|
"// Generated by ./generate-linuxcnc-switchkins-remap-table.sh."
|
|
validate_generated_header \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.inc" \
|
|
"config cases" \
|
|
"// Generated by ./generate-linuxcnc-switchkins-remap-table.sh --config-cases."
|
|
validate_generated_header \
|
|
core/tests/linuxcnc_switchkins_remap_config_cases.inc \
|
|
"tracked config cases" \
|
|
"// Generated by ./generate-linuxcnc-switchkins-remap-table.sh --config-cases."
|
|
|
|
validate_json "$generated_dir/linuxcnc_switchkins_remap_config_cases.json" "generated linuxcnc_switchkins_remap_config_cases.json"
|
|
validate_json "$generated_dir/linuxcnc_switchkins_remap_config_cases.stdout.json" "generated stdout linuxcnc_switchkins_remap_config_cases.json"
|
|
validate_json web/public/linuxcnc_switchkins_remap_config_cases.json "web/public/linuxcnc_switchkins_remap_config_cases.json"
|
|
validate_cpp_table "$generated_dir/linuxcnc_switchkins_remap_table.inc" "generated"
|
|
validate_cpp_table core/src/linuxcnc_switchkins_remap_table.inc "tracked"
|
|
validate_cpp_cases "$generated_dir/linuxcnc_switchkins_remap_config_cases.inc" "generated"
|
|
validate_cpp_cases core/tests/linuxcnc_switchkins_remap_config_cases.inc "tracked"
|
|
|
|
generated_table_rows=$(count_table_rows "$generated_dir/linuxcnc_switchkins_remap_table.inc")
|
|
if ((generated_table_rows == 0)); then
|
|
echo "empty generated switchkins remap table rows" >&2
|
|
exit 1
|
|
fi
|
|
|
|
generated_cpp_cases=$(count_cpp_config_cases "$generated_dir/linuxcnc_switchkins_remap_config_cases.inc")
|
|
generated_json_cases=$(count_json_cases "$generated_dir/linuxcnc_switchkins_remap_config_cases.json")
|
|
tracked_cpp_cases=$(count_cpp_config_cases core/tests/linuxcnc_switchkins_remap_config_cases.inc)
|
|
tracked_json_cases=$(count_json_cases web/public/linuxcnc_switchkins_remap_config_cases.json)
|
|
if [[ "$generated_cpp_cases" != "$generated_json_cases" ]]; then
|
|
echo "generated switchkins remap C++/JSON case count mismatch: $generated_cpp_cases != $generated_json_cases" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$tracked_cpp_cases" != "$tracked_json_cases" ]]; then
|
|
echo "tracked switchkins remap C++/JSON case count mismatch: $tracked_cpp_cases != $tracked_json_cases" >&2
|
|
exit 1
|
|
fi
|
|
compare_cpp_json_cases \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.inc" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.json" \
|
|
"generated"
|
|
compare_cpp_json_cases \
|
|
core/tests/linuxcnc_switchkins_remap_config_cases.inc \
|
|
web/public/linuxcnc_switchkins_remap_config_cases.json \
|
|
"tracked"
|
|
validate_switchkins_case_fields_against_api \
|
|
"$api_keys_csv" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.inc" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.json" \
|
|
"generated"
|
|
validate_case_values_against_table \
|
|
"$generated_dir/linuxcnc_switchkins_remap_table.inc" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.inc" \
|
|
"$generated_dir/linuxcnc_switchkins_remap_config_cases.json" \
|
|
"generated"
|
|
validate_switchkins_case_fields_against_api \
|
|
"$api_keys_csv" \
|
|
core/tests/linuxcnc_switchkins_remap_config_cases.inc \
|
|
web/public/linuxcnc_switchkins_remap_config_cases.json \
|
|
"tracked"
|
|
validate_case_values_against_table \
|
|
core/src/linuxcnc_switchkins_remap_table.inc \
|
|
core/tests/linuxcnc_switchkins_remap_config_cases.inc \
|
|
web/public/linuxcnc_switchkins_remap_config_cases.json \
|
|
"tracked"
|
|
|
|
diff -u core/src/linuxcnc_switchkins_remap_table.inc "$generated_dir/linuxcnc_switchkins_remap_table.inc"
|
|
diff -u core/tests/linuxcnc_switchkins_remap_config_cases.inc "$generated_dir/linuxcnc_switchkins_remap_config_cases.inc"
|
|
diff -u web/public/linuxcnc_switchkins_remap_config_cases.json "$generated_dir/linuxcnc_switchkins_remap_config_cases.json"
|