|
|
@@ -0,0 +1,110 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+"""Launch CALC.COM under the project's qemu boot harness.
|
|
|
+
|
|
|
+Examples:
|
|
|
+ python3 tests/calc_cli.py
|
|
|
+ python3 tests/calc_cli.py --keys '\\r\\r/Q'
|
|
|
+ python3 tests/calc_cli.py --keys-file keys.txt --raw > screen.bin
|
|
|
+ python3 tests/calc_cli.py --com /path/to/CALC.COM --keys '\\r/Q'
|
|
|
+
|
|
|
+The default keys are the welcome-return, a second return for the main loop and
|
|
|
+`/Q' in CALC's command menu, so the command terminates instead of waiting for a
|
|
|
+keyboard. `--keys' accepts Python-style escapes (\\r, \\n, \\x1b, ...).
|
|
|
+"""
|
|
|
+import argparse
|
|
|
+import codecs
|
|
|
+import os
|
|
|
+import shutil
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+import tempfile
|
|
|
+
|
|
|
+HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
+SHELL = os.path.dirname(HERE)
|
|
|
+RES = os.path.join(os.path.dirname(SHELL), "Resources", "TP3")
|
|
|
+sys.path.insert(0, HERE)
|
|
|
+import run_com_exec as r # noqa: E402
|
|
|
+
|
|
|
+
|
|
|
+def build_calc_com(verbose=False):
|
|
|
+ """Compile Resources/TP3/CALC.PAS in a temporary include directory."""
|
|
|
+ td = tempfile.mkdtemp(prefix="tpcalc-build-")
|
|
|
+ for name in ("CALC.PAS", "CALC.INC"):
|
|
|
+ shutil.copy(os.path.join(RES, name), os.path.join(td, name))
|
|
|
+ exe = r.ensure_comtest()
|
|
|
+ src = os.path.join(td, "CALC.PAS")
|
|
|
+ p = subprocess.run([exe], cwd=td, input=(src + "\n").encode(),
|
|
|
+ capture_output=True)
|
|
|
+ coms = [os.path.join(td, n) for n in os.listdir(td)
|
|
|
+ if n.upper().endswith(".COM")]
|
|
|
+ if p.returncode != 0 or not coms:
|
|
|
+ raise RuntimeError("comtest did not produce CALC.COM:\n%s"
|
|
|
+ % p.stdout.decode(errors="replace"))
|
|
|
+ com = open(coms[0], "rb").read()
|
|
|
+ if verbose:
|
|
|
+ sys.stderr.write(p.stdout.decode(errors="replace"))
|
|
|
+ sys.stderr.write("built %d-byte CALC.COM\n" % len(com))
|
|
|
+ return com, td
|
|
|
+
|
|
|
+
|
|
|
+def decode_keys(text):
|
|
|
+ """Decode the small escape language accepted by --keys."""
|
|
|
+ return codecs.decode(text, "unicode_escape").encode("latin-1")
|
|
|
+
|
|
|
+
|
|
|
+def main(argv=None):
|
|
|
+ ap = argparse.ArgumentParser(description=__doc__,
|
|
|
+ formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
|
+ ap.add_argument("--com", help="use an existing CALC.COM instead of building")
|
|
|
+ ap.add_argument("--keys", default="\\r\\r/Q",
|
|
|
+ help="keys to feed CALC (Python escapes; default: welcome+quit)")
|
|
|
+ ap.add_argument("--keys-file", help="read raw key bytes from this file")
|
|
|
+ ap.add_argument("--timeout", type=int, default=15,
|
|
|
+ help="qemu timeout in seconds (default 15)")
|
|
|
+ ap.add_argument("--raw", action="store_true",
|
|
|
+ help="write CALC's serial output as raw bytes")
|
|
|
+ ap.add_argument("--quiet", action="store_true",
|
|
|
+ help="only print the exit status")
|
|
|
+ ap.add_argument("--verbose", action="store_true",
|
|
|
+ help="show the compiler output and image size")
|
|
|
+ args = ap.parse_args(argv)
|
|
|
+
|
|
|
+ td = None
|
|
|
+ try:
|
|
|
+ if args.com:
|
|
|
+ com = open(args.com, "rb").read()
|
|
|
+ else:
|
|
|
+ com, td = build_calc_com(args.verbose)
|
|
|
+ if len(com) > r.COM_MAX:
|
|
|
+ sys.exit("CALC.COM is %d bytes; loader maximum is %d"
|
|
|
+ % (len(com), r.COM_MAX))
|
|
|
+ keys = (open(args.keys_file, "rb").read()
|
|
|
+ if args.keys_file else decode_keys(args.keys))
|
|
|
+ boot = r.build_boot_sector()
|
|
|
+ with tempfile.TemporaryDirectory(prefix="tpcalc-run-") as runtd:
|
|
|
+ img = os.path.join(runtd, "calc.img")
|
|
|
+ open(img, "wb").write(r.build_floppy(boot, com, keys))
|
|
|
+ old_timeout = r.TIMEOUT
|
|
|
+ r.TIMEOUT = args.timeout
|
|
|
+ try:
|
|
|
+ out, rc, note = r.run_qemu(img)
|
|
|
+ finally:
|
|
|
+ r.TIMEOUT = old_timeout
|
|
|
+ code = r.exit_code_of(rc)
|
|
|
+ if args.raw:
|
|
|
+ sys.stdout.buffer.write(out)
|
|
|
+ elif not args.quiet:
|
|
|
+ if note:
|
|
|
+ sys.stderr.write(note + "\n")
|
|
|
+ sys.stderr.write("CALC.COM: %d bytes, exit=%s\n" % (len(com), code))
|
|
|
+ sys.stdout.write(r.visible_output(out))
|
|
|
+ if out and not out.endswith(b"\n"):
|
|
|
+ sys.stdout.write("\n")
|
|
|
+ return 0 if code == 0 else 1
|
|
|
+ finally:
|
|
|
+ if td:
|
|
|
+ shutil.rmtree(td, ignore_errors=True)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ sys.exit(main())
|