Update cli
parent
f192f1f1f3
commit
18224b1ea7
|
@ -26,10 +26,9 @@
|
|||
pre-commit.settings.hooks = {
|
||||
autoflake.enable = true;
|
||||
black.enable = true;
|
||||
mypy.enable = true;
|
||||
# mypy.enable = true;
|
||||
ruff.enable = true;
|
||||
};
|
||||
pre-commit.settings.settings.mypy.binPath = "${pkgs.python311Packages.mypy}/bin/mypy";
|
||||
packages.default = package;
|
||||
devShells.default = pkgs.mkShell {
|
||||
shellHook = ''
|
||||
|
|
|
@ -58,7 +58,7 @@ python = ["3.11"]
|
|||
[tool.hatch.envs.typing]
|
||||
extra-dependencies = ["mypy"]
|
||||
[tool.hatch.envs.typing.scripts]
|
||||
check = "mypy --install-types {args:roll tests}"
|
||||
check = "mypy --install-types --non-interactive {args:roll tests}"
|
||||
|
||||
[tool.hatch.envs.lint]
|
||||
detached = true
|
||||
|
|
|
@ -1,50 +1,92 @@
|
|||
from typing import Literal
|
||||
|
||||
import click
|
||||
|
||||
from roll.__about__ import __version__
|
||||
from roll.cli.roll_param import ROLL
|
||||
from roll.roll import Roll
|
||||
from roll.throw import Throw
|
||||
|
||||
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
||||
|
||||
|
||||
@click.group(
|
||||
context_settings={"help_option_names": ["-h", "--help"]},
|
||||
invoke_without_command=True,
|
||||
)
|
||||
@click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True)
|
||||
@click.version_option(version=__version__, prog_name="roll")
|
||||
@click.argument("rolls", nargs=-1, type=ROLL)
|
||||
def roll(rolls: list[Roll]):
|
||||
"""Throw each roll specified in ROLLS and print the results.
|
||||
@click.argument("roll", type=ROLL, default="1d20")
|
||||
def cli(roll: Roll | Literal["advantage"] | Literal["disadvantage"]) -> None:
|
||||
"""throw a ROLL and print the results
|
||||
|
||||
Rolls are specified as
|
||||
ROLL is specified as
|
||||
|
||||
DdS[(+|-)M]
|
||||
|
||||
where D = # of dice, S = sides per die, and M = optional modifier.
|
||||
where D = # of dice, S = sides per die, and M = optional modifier
|
||||
|
||||
Example usage:
|
||||
if no ROLL is provided, a 1d20 is thrown
|
||||
|
||||
instead of a ROLL specifier, you can use a substring of "advantage"
|
||||
or "disadvantage" to throw 2d20 and take the appropriate result
|
||||
|
||||
example usage:
|
||||
|
||||
\b
|
||||
$ roll 2d20+3 3d6-1
|
||||
$ roll 2d20+3
|
||||
rolling 2d20+3:
|
||||
1: | 2
|
||||
2: | 13
|
||||
mod: +3
|
||||
total: 18
|
||||
\b
|
||||
rolling 3d6-1:
|
||||
1: | 3
|
||||
2: | 3
|
||||
3: | 1
|
||||
mod: -1
|
||||
total: 6
|
||||
|
||||
$ roll
|
||||
rolling 1d20:
|
||||
1: | 11
|
||||
total: 11
|
||||
\b
|
||||
$ roll advantage
|
||||
rolling 2d20 with advantage:
|
||||
1: | 13
|
||||
1: | 2
|
||||
total: 13
|
||||
\b
|
||||
$ roll dis
|
||||
rolling 2d20 with disadvantage:
|
||||
1: | 1
|
||||
2: | 19
|
||||
total: 1
|
||||
critical miss!
|
||||
"""
|
||||
for roll in rolls:
|
||||
click.echo()
|
||||
click.echo(f"rolling {roll.to_str()}:")
|
||||
if isinstance(roll, str):
|
||||
throw = _advantage(roll)
|
||||
else:
|
||||
throw = _throw(roll)
|
||||
click.echo(f"total:\t{throw.total: >5}")
|
||||
if throw.is_critical_hit: # pragma: no cover (random)
|
||||
click.echo("critical hit!")
|
||||
elif throw.is_critical_miss: # pragma: no cover (random)
|
||||
click.echo("critical miss!")
|
||||
|
||||
|
||||
def _advantage(advantage: str) -> Throw:
|
||||
"""print the results of an advantage or disadvantage roll"""
|
||||
click.echo(f"throwing 2d20 with {advantage}:")
|
||||
roll = Roll()
|
||||
fst = roll.throw()
|
||||
snd = roll.throw()
|
||||
click.echo(f"1:\t|{fst.total: >4}")
|
||||
click.echo(f"2:\t|{snd.total: >4}")
|
||||
if advantage == "advantage":
|
||||
throw = fst if fst.total > snd.total else snd
|
||||
else:
|
||||
throw = fst if fst.total < snd.total else snd
|
||||
return throw
|
||||
|
||||
|
||||
def _throw(roll: Roll) -> Throw:
|
||||
"""print the results of a roll"""
|
||||
click.echo(f"throwing {roll.to_str()}:")
|
||||
throw = roll.throw()
|
||||
for i, result in enumerate(throw.results):
|
||||
click.echo(f"{i + 1}:\t| {result: >3}")
|
||||
if roll.modifier:
|
||||
mod = roll.modifier_str()
|
||||
click.echo(f"mod:\t {mod: >4}")
|
||||
click.echo(f"total:\t {throw.total: >3}")
|
||||
for i, result in enumerate(throw.results, start=1):
|
||||
click.echo(f"{i}:\t|{result: >4}")
|
||||
if roll.modifier is not None:
|
||||
click.echo(f"mod:\t{roll.modifier_str(): >5}")
|
||||
return throw
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Literal
|
||||
|
||||
import click
|
||||
|
||||
from roll.roll import Roll
|
||||
|
@ -6,14 +8,20 @@ from roll.roll import Roll
|
|||
class RollParam(click.ParamType):
|
||||
name = "roll"
|
||||
|
||||
def convert(self, value: str | Roll, param: click.Parameter | None, ctx: click.Context | None) -> Roll:
|
||||
"""Parse a Roll from a command line string."""
|
||||
def convert(
|
||||
self, value: str | Roll, param: click.Parameter | None, ctx: click.Context | None
|
||||
) -> Roll | Literal["advantage"] | Literal["disadvantage"]:
|
||||
"""parse a Roll from a command line string"""
|
||||
if isinstance(value, Roll):
|
||||
return value
|
||||
elif "advantage".startswith(value):
|
||||
return "advantage"
|
||||
elif "disadvantage".startswith(value):
|
||||
return "disadvantage"
|
||||
try:
|
||||
return Roll.from_str(value)
|
||||
except Exception as e:
|
||||
self.fail(f"invalid roll: {value!r}, caused by {e}", param, ctx)
|
||||
self.fail(f"{e}", param, ctx)
|
||||
|
||||
|
||||
ROLL = RollParam()
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from roll.cli import cli
|
||||
from roll.roll import Roll
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner() -> CliRunner:
|
||||
return CliRunner()
|
||||
|
||||
|
||||
def test_cli_smoke(runner: CliRunner):
|
||||
result = runner.invoke(cli, [])
|
||||
assert result.exit_code == 0
|
||||
assert result.output.startswith("throwing 1d20:\n1:\t|")
|
||||
assert "total" in result.output
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["2d20", "4d6-2", "9d100+3"])
|
||||
def test_cli_roll(runner: CliRunner, value: str):
|
||||
result = runner.invoke(cli, [value])
|
||||
roll = Roll.from_str(value)
|
||||
assert result.exit_code == 0
|
||||
assert result.output.startswith(f"throwing {value}:\n1:\t|")
|
||||
assert len(result.output.splitlines()) >= roll.dice_count + 2
|
||||
assert "total" in result.output
|
||||
|
||||
|
||||
@pytest.mark.parametrize("advantage", ["advantage", "disadvantage"])
|
||||
def test_cli_advantage(runner: CliRunner, advantage: str):
|
||||
result = runner.invoke(cli, [advantage])
|
||||
assert result.exit_code == 0
|
||||
assert result.output.startswith(f"throwing 2d20 with {advantage}:\n1:\t|")
|
||||
assert "total" in result.output
|
|
@ -0,0 +1,38 @@
|
|||
import click
|
||||
import pytest
|
||||
|
||||
from roll.cli.roll_param import RollParam
|
||||
from roll.roll import Roll
|
||||
|
||||
|
||||
def test_roll_param_passthrough():
|
||||
roll = Roll()
|
||||
result = RollParam().convert(roll, None, None)
|
||||
assert result == roll
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["1d20", "2d20+3", "4d6-3"])
|
||||
def test_roll_param_valid(value):
|
||||
result = RollParam().convert(value, None, None)
|
||||
assert isinstance(result, Roll)
|
||||
assert result.to_str() == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["advantage", "disadvantage"])
|
||||
def test_roll_param_advantage(value):
|
||||
result = RollParam().convert(value, None, None)
|
||||
assert result == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["d90", "0d", "d-1", "aba", "000", "1d1d1d"])
|
||||
def test_roll_param_invalid(value):
|
||||
with pytest.raises(click.exceptions.BadParameter) as e:
|
||||
RollParam().convert(value, None, None)
|
||||
assert value in e.value.message
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["0d0", "0d20", "1d0", "1d1"])
|
||||
def test_roll_param_invalid_properties(value):
|
||||
with pytest.raises(click.exceptions.BadParameter) as e:
|
||||
RollParam().convert(value, None, None)
|
||||
assert "sides" in e.value.message or "dice" in e.value.message
|
Loading…
Reference in New Issue