• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pronovic / apologies / 17222784979

25 Aug 2025 10:53PM UTC coverage: 82.922% (+0.1%) from 82.79%
17222784979

push

github

web-flow
Ruff linter fixes (#72)

235 of 286 new or added lines in 13 files covered. (82.17%)

2 existing lines in 1 file now uncovered.

908 of 1095 relevant lines covered (82.92%)

3.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.0
/src/apologies/cli.py
1
# vim: set ft=python ts=4 sw=4 expandtab:
2
# ruff: noqa: T201
3

4
"""
5
Implementations for command-line (CLI) tools.
6
"""
7

8
import argparse
4✔
9
import sys
4✔
10
from typing import IO, Any
4✔
11

12
from apologies.demo import run_demo
4✔
13
from apologies.game import MAX_PLAYERS, Game, GameMode
4✔
14
from apologies.render import render_board
4✔
15
from apologies.simulation import run_simulation
4✔
16
from apologies.source import source
4✔
17

18
# Constants used by the demo CLI
19
_DEMO_DEFAULT_PLAYERS = MAX_PLAYERS
4✔
20
_DEMO_DEFAULT_MODE = GameMode.STANDARD.name
4✔
21
_DEMO_MODE_CHOICES = [GameMode.STANDARD.name, GameMode.ADULT.name]
4✔
22
_DEMO_DEFAULT_SOURCE = "apologies.source.RandomInputSource"
4✔
23
_DEMO_DEFAULT_DELAY_SEC = 1
4✔
24

25
# Constants used by the simulation CLI
26
_SIM_DEFAULT_ITERATIONS = 10
4✔
27
_SIM_DEFAULT_OUT = "simulation.csv"
4✔
28

29

30
def demo(argv: list[str], _stdout: IO[str], _stderr: IO[str]) -> None:
4✔
31
    """Run a game with simulated players, displaying output on the terminal."""
32
    parser = argparse.ArgumentParser(
×
33
        description="Run a game with simulated players, displaying output on the terminal.",
34
        epilog=f"By default, the game runs in {_DEMO_DEFAULT_MODE} mode with "
35
        f"{_DEMO_DEFAULT_PLAYERS} players. A source is a class that "
36
        f"chooses a player's move.",
37
    )
38

39
    parser.add_argument(
×
40
        "--players",
41
        type=int,
42
        default=_DEMO_DEFAULT_PLAYERS,
43
        help="Number of simulated players in the game",
44
    )
NEW
45
    parser.add_argument(
×
46
        "--mode",
47
        type=str,
48
        default=_DEMO_DEFAULT_MODE,
49
        choices=_DEMO_MODE_CHOICES,
50
        help="Choose the game mode",
51
    )
NEW
52
    parser.add_argument(
×
53
        "--source",
54
        type=str,
55
        default=_DEMO_DEFAULT_SOURCE,
56
        help="Fully-qualified name of the character source",
57
    )
NEW
58
    parser.add_argument(
×
59
        "--exit",
60
        action="store_true",
61
        help="Exit immediately when the game completes",
62
    )
NEW
63
    parser.add_argument(
×
64
        "--delay",
65
        type=float,
66
        default=_DEMO_DEFAULT_DELAY_SEC,
67
        help="Delay between computer-generated moves (fractional seconds)",
68
    )
69

70
    args = parser.parse_args(args=argv[1:])
×
71
    run_demo(
×
72
        players=args.players,
73
        mode=GameMode[args.mode],
74
        source=source(args.source),
75
        delay_sec=args.delay,
76
        exit_immediately=args.exit,
77
    )
78

79

80
def simulation(argv: list[str], _stdout: IO[str], _stderr: IO[str]) -> None:
4✔
81
    """Run a simulation and display results."""
82
    parser = argparse.ArgumentParser(
×
83
        description="Run a simulation to see how well different character input sources behave.",
84
        epilog=f"Every combination of game mode, number of players, and input source is tested, "
85
        f"using {_SIM_DEFAULT_ITERATIONS} iterations by default.  A spreadsheet is "
86
        f"generated containing statistics on mean and median turns and duration to "
87
        f"win, as well as number of wins for each source.",
88
    )
89

NEW
90
    parser.add_argument(
×
91
        "--iter",
92
        type=int,
93
        default=_SIM_DEFAULT_ITERATIONS,
94
        help="Number of iterations per scenario",
95
    )
96

NEW
97
    parser.add_argument(
×
98
        "--out",
99
        type=str,
100
        default=_SIM_DEFAULT_OUT,
101
        help="Path to the output CSV file",
102
    )
103

NEW
104
    parser.add_argument(
×
105
        "source",
106
        type=str,
107
        nargs="+",
108
        help="Fully-qualified name of the character sources to test",
109
    )
110

111
    args = parser.parse_args(args=argv[1:])
×
112

113
    errors = []
×
114
    if args.iter <= 0:
×
115
        errors.append("simulation: error: there must be at least 1 iteration")
×
116

117
    if errors:
×
118
        parser.print_usage()
×
119
        print("\n".join(errors))
×
120
        sys.exit(1)
×
121

122
    run_simulation(iterations=args.iter, output=args.out, sources=[source(s) for s in args.source])
×
123

124

125
def render(_argv: list[str], stdout: IO[str], _stderr: IO[str]) -> None:
4✔
126
    """Render an empty board."""
127
    game = Game(4)
4✔
128
    board = render_board(game)
4✔
129
    stdout.write(f"{board}")
4✔
130

131

132
def example(argv: list[str], stdout: IO[str], stderr: IO[str]) -> None:
4✔
133
    """Execute the example script, which just writes some input to its outputs."""
134
    stdout.write(f"Hello, stdout: {argv[0]}\n")
4✔
135
    stderr.write(f"Hello, stderr: {argv[0]}\n")
4✔
136

137

138
def _lookup_method(method: str) -> Any:
4✔
139
    """Look up the method in this module with the passed-in name."""
140
    module = sys.modules[__name__]
4✔
141
    return getattr(module, f"{method}")
4✔
142

143

144
def cli(script: str) -> None:
4✔
145
    """
146
    Run the main routine for the named script.
147

148
    Args:
149
        script(str): Name of the script to execute
150
    """
151
    _lookup_method(script)(sys.argv, sys.stdout, sys.stderr)
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc