#!/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 excited: bool run_tests: bool def positive_int(value: str) -> int: number = int(value) if number < 1: raise argparse.ArgumentTypeError("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("-c", "--count", type=positive_int, default=1, help="number of greetings") parser.add_argument("--excited", action="store_true", help="add an exclamation mark") parser.add_argument("--run-tests", action="store_true", help="run parser tests and exit") return parser def parse_args(argv: Sequence[str]) -> Options: args = build_parser().parse_args(argv) return Options( name=args.name, count=args.count, excited=args.excited, run_tests=args.run_tests, ) def render_greeting(options: Options) -> str: punctuation = "!" if options.excited else "." return "\n".join(f"Hello, {options.name}{punctuation}" for _ in range(options.count)) 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(ParserTests) result = unittest.TextTestRunner(verbosity=2).run(suite) return 0 if result.wasSuccessful() else 1 print(render_greeting(options)) return 0 class ParserTests(unittest.TestCase): def test_defaults(self) -> None: self.assertEqual(parse_args([]), Options("world", 1, False, False)) def test_custom_values(self) -> None: self.assertEqual(parse_args(["Ada", "--count", "2", "--excited"]), Options("Ada", 2, True, False)) if __name__ == "__main__": raise SystemExit(main())