#!/usr/bin/env python3 """Small typed CLI example with a few local tests.""" from __future__ import annotations import argparse import sys import unittest from dataclasses import dataclass from typing import Sequence @dataclass(frozen=True) class Options: name: str times: int shout: bool run_tests: bool def positive_int(value: str) -> int: """argparse type callback that accepts only positive integers.""" try: parsed = int(value) except ValueError as exc: raise argparse.ArgumentTypeError(f"{value!r} is not an integer") from exc if parsed < 1: raise argparse.ArgumentTypeError("value must be at least 1") return parsed def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Print a greeting.") parser.add_argument("name", nargs="?", default="world", help="name to greet") parser.add_argument("-n", "--times", type=positive_int, default=1, help="number of greetings") parser.add_argument("--shout", action="store_true", help="uppercase the greeting") parser.add_argument("--run-tests", action="store_true", help="run this file's tests") return parser def parse_args(argv: Sequence[str]) -> Options: namespace = build_parser().parse_args(argv) return Options( name=namespace.name, times=namespace.times, shout=namespace.shout, run_tests=namespace.run_tests, ) def make_greeting(name: str, shout: bool = False) -> str: message = f"Hello, {name}!" return message.upper() if shout else message def main(argv: Sequence[str] | None = None) -> int: options = parse_args(sys.argv[1:] if argv is None else argv) if options.run_tests: suite = unittest.defaultTestLoader.loadTestsFromTestCase(CliTests) result = unittest.TextTestRunner(verbosity=2).run(suite) return 0 if result.wasSuccessful() else 1 for _ in range(options.times): print(make_greeting(options.name, options.shout)) return 0 class CliTests(unittest.TestCase): def test_make_greeting_default_case(self) -> None: self.assertEqual(make_greeting("Ada"), "Hello, Ada!") def test_parse_args_with_options(self) -> None: options = parse_args(["Ada", "--times", "2", "--shout"]) self.assertEqual(options, Options(name="Ada", times=2, shout=True, run_tests=False)) if __name__ == "__main__": raise SystemExit(main())