"""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 print n, or exit 1 ints.py knob print n; empty gives , invalid gives + 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, 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 | knob ", file=sys.stderr) return 2 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))