The wrapper keeps spawn / wait / kill, key injection and the herdr
diagnostic probe; what it decided in bash is now lib_py/delegation/wrapper.py,
reached through one `_decide` helper:
- finalize_unfinished: the clock choice, the disk grace and broker
re-observation, the close request / label, the close and the event
(finalize-begin / finalize-end; bash runs only the busy probe between them)
- start_stall_watch: whether to watch (MAM_STALL_WATCH, remote, human)
- _map_sub_rc, _rc_for (+ MAM_DELEGATE_FAIL_CLOSED), the subscriber
early-exit check and cmd_wait's rc normalisation
- mam_int / mam_int_knob run lib_py/ints.py, which is also the runner's
_positive_int: one implementation of the Issue #26 rule
No behaviour change: tests/test_issue17_item4_decisions.py runs the old
bash functions (copied verbatim from 7209750) and the new wrapper on the
same inputs and compares output, FINAL_JOB_STATUS and the registry /
publish calls. Three static tests that pinned the old bash literals now
pin the Python entry points instead.
Refs #17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
"""Issue #26 / #17 item 4: the one integer rule for knobs and flags.
|
|
|
|
lib_int.sh's mam_int / mam_int_knob run this file, and the loop runner's
|
|
_positive_int is positive_int below, so bash and Python share one
|
|
implementation. Surrounding ASCII blanks are allowed, then ASCII digits only,
|
|
read as DECIMAL (bash would read 0700 as octal 448), and
|
|
minimum <= n <= 999999999. The per-knob minimum lives in
|
|
tests/test_issue26_ints.py.
|
|
|
|
Stdlib only and Python 3.9: lib_int.sh runs it as a script (python3 -I),
|
|
where lib_py is not importable.
|
|
|
|
ints.py int <value> <min> print n, or exit 1
|
|
ints.py knob <name> <value> <default> <min> print n; empty gives <default>,
|
|
invalid gives <default> + a warning
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from typing import List, Optional
|
|
|
|
_BLANKS = " \t\n\r\v\f"
|
|
|
|
|
|
def positive_int(text: Optional[str], minimum: int = 1) -> Optional[int]:
|
|
"""n when `text` is a decimal integer >= minimum (and <= 999999999), else None."""
|
|
s = (text or "").strip(_BLANKS)
|
|
if not s or not s.isascii() or not s.isdigit():
|
|
return None
|
|
s = s.lstrip("0") or "0"
|
|
if len(s) > 9: # bounded before int(), which refuses > 4300 digits
|
|
return None
|
|
n = int(s)
|
|
return n if n >= minimum else None
|
|
|
|
|
|
def knob(name: str, raw: str, default: str, minimum: int) -> str:
|
|
"""mam_int_knob: the value of $name by the rule, <default> when empty or invalid."""
|
|
if not raw:
|
|
return default
|
|
n = positive_int(raw, minimum)
|
|
if n is not None:
|
|
return str(n)
|
|
print(f"WARNING: {name}={raw} ignored (not a decimal integer >= {minimum}); using {default}", file=sys.stderr)
|
|
return default
|
|
|
|
|
|
def main(argv: List[str]) -> int:
|
|
if len(argv) == 3 and argv[0] == "int":
|
|
n = positive_int(argv[1], int(argv[2]))
|
|
if n is None:
|
|
return 1
|
|
print(n)
|
|
return 0
|
|
if len(argv) == 5 and argv[0] == "knob":
|
|
print(knob(argv[1], argv[2], argv[3], int(argv[4])))
|
|
return 0
|
|
print("usage: ints.py int <value> <min> | knob <name> <value> <default> <min>", file=sys.stderr)
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|