From 47f5d4603a686ef16ec84d6da8363dc1ef33dd74 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Thu, 27 Aug 2026 13:55:00 -0400 Subject: [PATCH] fix(e10): --e10 passed bare silently skipped the controller Three bugs, all found by actually running the thing rather than reading it. 1. `--e10` with no value did nothing. Making the flag optional-valued gave it an empty-string constant, and the code deciding whether to drive the controller used a truth test - so the documented `--e10` form printed the full translation and quietly skipped the hardware. No error, because nothing failed. Now gated on `is not None`, with a comment saying why. 2. A missing part program dumped a traceback. The top-level handler tidied e10 errors and re-raised everything else. Now a clean message that lists the part programs it can actually see, which also covers Windows path confusion. 3. Errors could vanish when piped. stdout is block-buffered and stderr is not, so the message printed before the output it referred to and a `| tail` dropped it entirely - looking like a silent failure. stdout is flushed before writing an error. --- scripts/simulate-ednc.py | 58 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/scripts/simulate-ednc.py b/scripts/simulate-ednc.py index ea2fa60..30c55ed 100644 --- a/scripts/simulate-ednc.py +++ b/scripts/simulate-ednc.py @@ -74,6 +74,7 @@ TRANSLATION TO THE e10 """ import argparse +import os import re import sys @@ -322,8 +323,37 @@ def main(): for index, name in enumerate(args.var_names.split(","), start=1): VAR_NAMES[index] = name.strip() - with open(args.partprogram) as handle: - fileinfo, general, items = parse_part_program(handle.read()) + try: + with open(args.partprogram) as handle: + fileinfo, general, items = parse_part_program(handle.read()) + except FileNotFoundError: + sys.stdout.flush() + sys.stderr.write("no such part program: %s\n" % args.partprogram) + here = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "..", "partprograms") + for folder in (os.path.dirname(args.partprogram) or ".", here): + try: + found = sorted(f for f in os.listdir(folder) + if f.lower().endswith(".txt")) + except OSError: + continue + if found: + sys.stderr.write("part programs in %s:\n" + % os.path.normpath(folder)) + for name in found: + sys.stderr.write(" %s\n" % name) + break + else: + sys.stderr.write("Give the path to a .txt part program. On Windows" + " use backslashes:\n" + " python scripts\\simulate-ednc.py" + " partprograms\\TEL3L0001.txt ...\n") + sys.exit(1) + except IsADirectoryError: + sys.stdout.flush() + sys.stderr.write("%s is a folder, not a part program file\n" + % args.partprogram) + sys.exit(1) print("=" * 72) print("PART PROGRAM: %s" % args.partprogram) @@ -372,7 +402,11 @@ def main(): else: print(" %-28s [%s]" % (label, payload)) - if args.e10: + # `is not None`, NOT a truth test. --e10 passed bare sets this to an + # empty string, which is falsy, so a truth test silently turns a + # hardware run into a dry run - with no error to explain why nothing + # reached the controller. + if args.e10 is not None: drive_e10(seq, args) @@ -412,4 +446,20 @@ def drive_e10(seq, args): if __name__ == "__main__": - main() + try: + main() + except KeyboardInterrupt: + sys.exit(130) + except Exception as err: # noqa: BLE001 + # E10Error is defined in the client module, which is loaded + # dynamically, so catch by name rather than importing it up here. + if type(err).__name__ in ("E10Error", "E10Fault"): + # Flush stdout first. It is block-buffered when piped while stderr + # is not, so without this the error appears BEFORE the output it + # refers to - and a `| tail` drops it entirely, which looks like + # the command failed silently. + sys.stdout.flush() + sys.stderr.write("e10: %s\n" % err) + sys.stderr.flush() + sys.exit(1) + raise