formatting fixes, fix a bug when resetting an existing table
This commit is contained in:
parent
6984bfc5e8
commit
c3a58696c2
|
@ -1,12 +1,13 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from rolltable.types import RollTable
|
|
||||||
from rolltable import tables
|
|
||||||
import typer
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from rich import print
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from rich import print
|
||||||
|
|
||||||
|
from rolltable import tables
|
||||||
|
from rolltable.types import RollTable
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
app_state = defaultdict(
|
app_state = defaultdict(
|
||||||
|
@ -16,89 +17,71 @@ app_state = defaultdict(
|
||||||
|
|
||||||
|
|
||||||
class OUTPUT_FORMATS(Enum):
|
class OUTPUT_FORMATS(Enum):
|
||||||
text = 'text'
|
text = "text"
|
||||||
yaml = 'yaml'
|
yaml = "yaml"
|
||||||
markdown = 'markdown'
|
markdown = "markdown"
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
def main(
|
def main(
|
||||||
frequency: str = typer.Option(
|
frequency: str = typer.Option("default", help="use the specified frequency from the source file"),
|
||||||
'default',
|
die: int = typer.Option(20, help="The size of the die for which to create a table"),
|
||||||
help='use the specified frequency from the source file'
|
|
||||||
),
|
|
||||||
die: int = typer.Option(
|
|
||||||
20,
|
|
||||||
help='The size of the die for which to create a table'
|
|
||||||
),
|
|
||||||
hide_rolls: bool = typer.Option(
|
hide_rolls: bool = typer.Option(
|
||||||
False,
|
False,
|
||||||
help='If True, do not show the Roll column.',
|
help="If True, do not show the Roll column.",
|
||||||
),
|
|
||||||
collapsed: bool = typer.Option(
|
|
||||||
True,
|
|
||||||
help='If True, collapse multiple die values with the same option.'
|
|
||||||
),
|
|
||||||
width: int = typer.Option(
|
|
||||||
120,
|
|
||||||
help='Width of the table.'
|
|
||||||
),
|
),
|
||||||
|
collapsed: bool = typer.Option(True, help="If True, collapse multiple die values with the same option."),
|
||||||
|
width: int = typer.Option(120, help="Width of the table."),
|
||||||
output: OUTPUT_FORMATS = typer.Option(
|
output: OUTPUT_FORMATS = typer.Option(
|
||||||
'text',
|
"text",
|
||||||
help='The output format to use.',
|
help="The output format to use.",
|
||||||
)
|
),
|
||||||
):
|
):
|
||||||
app_state['options'] = {
|
app_state["options"] = {
|
||||||
'frequency': frequency,
|
"frequency": frequency,
|
||||||
'die': die,
|
"die": die,
|
||||||
'hide_rolls': hide_rolls,
|
"hide_rolls": hide_rolls,
|
||||||
}
|
}
|
||||||
app_state['collapsed'] = collapsed
|
app_state["collapsed"] = collapsed
|
||||||
app_state['width'] = width
|
app_state["width"] = width
|
||||||
app_state['output'] = output
|
app_state["output"] = output
|
||||||
|
|
||||||
|
|
||||||
@app.command("custom")
|
@app.command("custom")
|
||||||
def custom(
|
def custom(
|
||||||
sources: List[Path] = typer.Argument(
|
sources: List[Path] = typer.Argument(..., help="Path to one or more yaml-formatted source file."),
|
||||||
...,
|
|
||||||
help="Path to one or more yaml-formatted source file."),
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create roll tables from custom sources.
|
Create roll tables from custom sources.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rt = RollTable([Path(s).read_text() for s in sources], **app_state['options'])
|
rt = RollTable([Path(s).read_text() for s in sources], **app_state["options"])
|
||||||
print_table(rt)
|
print_table(rt)
|
||||||
|
|
||||||
|
|
||||||
def print_table(table):
|
def print_table(table):
|
||||||
if app_state['output'] == OUTPUT_FORMATS.yaml:
|
if app_state["output"] == OUTPUT_FORMATS.yaml:
|
||||||
print(table.as_yaml())
|
print(table.as_yaml())
|
||||||
elif app_state['output'] == OUTPUT_FORMATS.markdown:
|
elif app_state["output"] == OUTPUT_FORMATS.markdown:
|
||||||
print(table.as_markdown())
|
print(table.as_markdown())
|
||||||
else:
|
else:
|
||||||
print(table.as_table(
|
print(table.as_table(width=app_state["width"], expanded=not app_state["collapsed"]))
|
||||||
width=app_state['width'],
|
|
||||||
expanded=not app_state['collapsed']
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def make_callback(roll_table_instance):
|
def make_callback(roll_table_instance):
|
||||||
def inner():
|
def inner():
|
||||||
roll_table_instance.frequency = app_state['options']['frequency']
|
roll_table_instance.frequency = app_state["options"]["frequency"]
|
||||||
roll_table_instance.die = app_state['options']['die']
|
roll_table_instance.die = app_state["options"]["die"]
|
||||||
print_table(roll_table_instance)
|
print_table(roll_table_instance)
|
||||||
|
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
||||||
# step through all the predfined tables and create a cli for each
|
# step through all the predfined tables and create a cli for each
|
||||||
for name, table in tables.index.items():
|
for name, table in tables.index.items():
|
||||||
help_text = name.replace('_', ' ').title()
|
help_text = name.replace("_", " ").title()
|
||||||
app.command(name=name, help=f"Create a roll table of {help_text}")(
|
app.command(name=name, help=f"Create a roll table of {help_text}")(make_callback(table))
|
||||||
make_callback(table)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
app()
|
app()
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from rolltable.types import RollTable
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from rolltable.types import RollTable
|
||||||
|
|
||||||
|
|
||||||
def from_sources(names: list[str] = []) -> list:
|
def from_sources(names: list[str] = []) -> list:
|
||||||
return RollTable([
|
return RollTable([(Path(__file__).parent / "sources" / name).read_text() for name in names])
|
||||||
(Path(__file__).parent / "sources" / name).read_text()
|
|
||||||
for name in names
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
index = dict(
|
index = dict(
|
||||||
psychadelic_effects=from_sources(['psychadelic_effects.yaml']),
|
psychadelic_effects=from_sources(["psychadelic_effects.yaml"]),
|
||||||
trinkets=from_sources(['trinkets.yaml']),
|
trinkets=from_sources(["trinkets.yaml"]),
|
||||||
wild_magic=from_sources(['wild_magic.yaml']),
|
wild_magic=from_sources(["wild_magic.yaml"]),
|
||||||
spells=from_sources(['spells.yaml']),
|
spells=from_sources(["spells.yaml"]),
|
||||||
encounters=from_sources(['encounters.yaml']),
|
encounters=from_sources(["encounters.yaml"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import yaml
|
|
||||||
from csv2md.table import Table
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from typing import Optional, List, Union
|
from typing import List, Optional, Union
|
||||||
from random_sets.datasources import DataSource
|
|
||||||
|
|
||||||
import rich.table
|
import rich.table
|
||||||
|
import yaml
|
||||||
|
from csv2md.table import Table
|
||||||
|
from random_sets.datasources import DataSource
|
||||||
|
|
||||||
|
|
||||||
class RollTable:
|
class RollTable:
|
||||||
|
@ -29,10 +29,15 @@ class RollTable:
|
||||||
d2-d4 Bar
|
d2-d4 Bar
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sources: Union[List[str], List[DataSource]], frequency: str = 'default',
|
def __init__(
|
||||||
die: Optional[int] = 20, hide_rolls: bool = False) -> None:
|
self,
|
||||||
|
sources: Union[List[str], List[DataSource]],
|
||||||
|
frequency: str = "default",
|
||||||
|
die: Optional[int] = 20,
|
||||||
|
hide_rolls: bool = False,
|
||||||
|
) -> None:
|
||||||
self._sources = sources
|
self._sources = sources
|
||||||
self._frequency = frequency
|
self.frequency = frequency
|
||||||
self.die = die
|
self.die = die
|
||||||
self.hide_rolls = hide_rolls
|
self.hide_rolls = hide_rolls
|
||||||
self.data = None
|
self.data = None
|
||||||
|
@ -70,21 +75,19 @@ class RollTable:
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
if not self._generated_values:
|
if not self._generated_values:
|
||||||
self._generated_values = list(zip(*[
|
self._generated_values = list(zip(*[t.random_values(self.die) for t in self._data]))
|
||||||
t.random_values(self.die) for t in self._data
|
|
||||||
]))
|
|
||||||
return self._generated_values
|
return self._generated_values
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rows(self) -> List:
|
def rows(self) -> List:
|
||||||
def formatted(lastrow, offset, row, i):
|
def formatted(lastrow, offset, row, i):
|
||||||
thisrow = [f'd{i}' if offset + 1 == i else f'd{offset+1}-d{i}']
|
thisrow = [f"d{i}" if offset + 1 == i else f"d{offset+1}-d{i}"]
|
||||||
thisrow += self._flatten(lastrow)
|
thisrow += self._flatten(lastrow)
|
||||||
return self._column_filter(thisrow)
|
return self._column_filter(thisrow)
|
||||||
|
|
||||||
lastrow = None
|
lastrow = None
|
||||||
offset = 0
|
offset = 0
|
||||||
self._rows = [self._column_filter(['Roll'] + self.headers)]
|
self._rows = [self._column_filter(["Roll"] + self.headers)]
|
||||||
|
|
||||||
for face in range(self.die):
|
for face in range(self.die):
|
||||||
row = self._values[face]
|
row = self._values[face]
|
||||||
|
@ -96,19 +99,20 @@ class RollTable:
|
||||||
self._rows.append(formatted(lastrow, offset, row, face))
|
self._rows.append(formatted(lastrow, offset, row, face))
|
||||||
lastrow = row
|
lastrow = row
|
||||||
offset = face
|
offset = face
|
||||||
self._rows.append(formatted(lastrow, offset, row, face+1))
|
self._rows.append(formatted(lastrow, offset, row, face + 1))
|
||||||
return self._rows
|
return self._rows
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def expanded_rows(self) -> List:
|
def expanded_rows(self) -> List:
|
||||||
self._rows = [self._column_filter(['Roll'] + self.headers)]
|
self._rows = [self._column_filter(["Roll"] + self.headers)]
|
||||||
for face in range(self.die):
|
for face in range(self.die):
|
||||||
row = self._values[face]
|
row = self._values[face]
|
||||||
self._rows.append(self._column_filter([f'd{face+1}'] + row))
|
self._rows.append(self._column_filter([f"d{face+1}"] + row))
|
||||||
return self._rows
|
return self._rows
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
self._generated_values = None
|
self._generated_values = None
|
||||||
|
self._config()
|
||||||
|
|
||||||
def as_markdown(self) -> str:
|
def as_markdown(self) -> str:
|
||||||
return Table(self.rows).markdown()
|
return Table(self.rows).markdown()
|
||||||
|
@ -118,9 +122,9 @@ class RollTable:
|
||||||
for row in self.rows[1:]:
|
for row in self.rows[1:]:
|
||||||
struct[row[0]] = {}
|
struct[row[0]] = {}
|
||||||
# pad rows with empty cols as necessary
|
# pad rows with empty cols as necessary
|
||||||
cols = row[1:] + [''] * (len(self.headers) - len(row[1:]))
|
cols = row[1:] + [""] * (len(self.headers) - len(row[1:]))
|
||||||
for idx, col in enumerate(cols):
|
for idx, col in enumerate(cols):
|
||||||
struct[row[0]][self.headers[idx] if idx < len(self.headers) else '_'] = col
|
struct[row[0]][self.headers[idx] if idx < len(self.headers) else "_"] = col
|
||||||
return yaml.dump(struct, sort_keys=False)
|
return yaml.dump(struct, sort_keys=False)
|
||||||
|
|
||||||
def as_table(self, width: int = 120, expanded: bool = False) -> str:
|
def as_table(self, width: int = 120, expanded: bool = False) -> str:
|
||||||
|
@ -138,7 +142,7 @@ class RollTable:
|
||||||
self._header_excludes = []
|
self._header_excludes = []
|
||||||
for i in range(len(self._headers)):
|
for i in range(len(self._headers)):
|
||||||
if self.headers[i] is None:
|
if self.headers[i] is None:
|
||||||
self._header_excludes.append(i+1)
|
self._header_excludes.append(i + 1)
|
||||||
|
|
||||||
def _config(self):
|
def _config(self):
|
||||||
"""
|
"""
|
||||||
|
@ -151,7 +155,7 @@ class RollTable:
|
||||||
if type(src) is str:
|
if type(src) is str:
|
||||||
src = [src]
|
src = [src]
|
||||||
for one_source in src:
|
for one_source in src:
|
||||||
ds = DataSource(one_source, frequency=self._frequency)
|
ds = DataSource(one_source, frequency=self.frequency)
|
||||||
ds.load_source()
|
ds.load_source()
|
||||||
self._data.append(ds)
|
self._data.append(ds)
|
||||||
|
|
||||||
|
@ -162,9 +166,9 @@ class RollTable:
|
||||||
self.set_headers(*headers)
|
self.set_headers(*headers)
|
||||||
|
|
||||||
def _column_filter(self, row):
|
def _column_filter(self, row):
|
||||||
cols = [col or '' for (pos, col) in enumerate(row) if pos not in self._header_excludes]
|
cols = [col or "" for (pos, col) in enumerate(row) if pos not in self._header_excludes]
|
||||||
# pad the row with empty columns if there are more headers than columns
|
# pad the row with empty columns if there are more headers than columns
|
||||||
cols = cols + [''] * (1 + len(self.headers) - len(row))
|
cols = cols + [""] * (1 + len(self.headers) - len(row))
|
||||||
# strip the leading column if we're hiding the dice rolls
|
# strip the leading column if we're hiding the dice rolls
|
||||||
return cols[1:] if self.hide_rolls else cols
|
return cols[1:] if self.hide_rolls else cols
|
||||||
|
|
||||||
|
@ -177,5 +181,5 @@ class RollTable:
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
rows = list(self.rows)
|
rows = list(self.rows)
|
||||||
str_format = '\t'.join(['{:10s}'] * len(rows[0]))
|
str_format = "\t".join(["{:10s}"] * len(rows[0]))
|
||||||
return "\n".join([str_format.format(*[r or '' for r in row]) for row in rows])
|
return "\n".join([str_format.format(*[r or "" for r in row]) for row in rows])
|
||||||
|
|
|
@ -3,26 +3,37 @@ import pytest
|
||||||
from rolltable import tables
|
from rolltable import tables
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('table, expected', [
|
@pytest.mark.parametrize(
|
||||||
(tables.wild_magic, ['A third eye', 'Advantage on perception checks']),
|
"table, expected",
|
||||||
(tables.trinkets, ['ivory mimic']),
|
[
|
||||||
(tables.psychadelic_effects, ['Cosmic', 'mind expands', 'it will become so']),
|
(tables.wild_magic, ["A third eye", "Advantage on perception checks"]),
|
||||||
(tables.encounters, ['None', 'Easy', 'Difficult', 'Dangerous', 'Deadly'])
|
(tables.trinkets, ["ivory mimic"]),
|
||||||
])
|
(tables.psychadelic_effects, ["Cosmic", "mind expands", "it will become so"]),
|
||||||
|
(tables.encounters, ["None", "Easy", "Difficult", "Dangerous", "Deadly"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_flat(table, expected):
|
def test_flat(table, expected):
|
||||||
table.die = 1000
|
table.die = 1000
|
||||||
assert 'd1000' in str(table)
|
assert "d1000" in str(table)
|
||||||
for txt in expected:
|
for txt in expected:
|
||||||
assert txt in str(table)
|
assert txt in str(table)
|
||||||
|
|
||||||
|
|
||||||
def test_encounter_frequencies():
|
def test_encounter_frequencies():
|
||||||
table = tables.encounters
|
table = tables.encounters
|
||||||
|
table.die = 1000
|
||||||
|
table.frequency = "none"
|
||||||
|
table.reset()
|
||||||
|
assert "Deadly" not in str(table)
|
||||||
|
|
||||||
|
table.frequency = "deadly"
|
||||||
|
table.reset()
|
||||||
|
assert "Deadly" in str(table)
|
||||||
|
|
||||||
|
|
||||||
def test_markdown():
|
def test_markdown():
|
||||||
tables.trinkets.die = 1
|
tables.trinkets.die = 1
|
||||||
md = tables.trinkets.as_markdown()
|
md = tables.trinkets.as_markdown()
|
||||||
assert '| Roll | Trinket ' in md
|
assert "| Roll | Trinket " in md
|
||||||
assert '| ---- |' in md
|
assert "| ---- |" in md
|
||||||
assert 'd1' in md
|
assert "d1" in md
|
||||||
|
|
Loading…
Reference in New Issue
Block a user