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.
This commit is contained in:
cproudlock
2026-08-27 13:55:00 -04:00
parent 5e249e8ad0
commit 47f5d4603a

View File

@@ -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