#!/usr/bin/env python3 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 count: int shout: bool def positive_int(value: str) -> int: """Parse a positive integer for argparse.""" try: number = int(value) except ValueError as exc: raise argparse.ArgumentTypeError(f"{value!r} is not an integer") from exc if number < 1: raise argparse.ArgumentTypeError("value must be at least 1") return number def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Print a configurable greeting.") parser.add_argument("name", nargs="?", default="world", help="name to greet") parser.add_argument("-n", "--count", type=positive_int, default=1, help="number of greetings") parser.add_argument("--shout", action="store_true", help="print the greeting in uppercase") parser.add_argument("--self-test", action="store_true", help=argparse.SUPPRESS) return parser def options_from_args(args: argparse.Namespace) -> Options: return Options(name=args.name, count=args.count, shout=args.shout) def render_greeting(options: Options) -> str: greeting = f"Hello, {options.name}!" if options.shout: greeting = greeting.upper() # Repeat by joining lines so output has no extra trailing newline. return "\n".join(greeting for _ in range(options.count)) def main(argv: Sequence[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) if args.self_test: suite = unittest.defaultTestLoader.loadTestsFromTestCase(ParserTests) result = unittest.TextTestRunner(verbosity=2).run(suite) return 0 if result.wasSuccessful() else 1 print(render_greeting(options_from_args(args))) return 0 class ParserTests(unittest.TestCase): def test_defaults(self) -> None: args = build_parser().parse_args([]) self.assertEqual(options_from_args(args), Options(name="world", count=1, shout=False)) def test_render_repeated_shout(self) -> None: output = render_greeting(Options(name="Ada", count=2, shout=True)) self.assertEqual(output, "HELLO, ADA!\nHELLO, ADA!") if __name__ == "__main__": raise SystemExit(main(sys.argv[1:]))