diff --git a/dungeonsheets/epub.py b/dungeonsheets/epub.py index 7191ee4..27442c4 100644 --- a/dungeonsheets/epub.py +++ b/dungeonsheets/epub.py @@ -11,10 +11,7 @@ from dungeonsheets.forms import dice_re, jinja_environment def create_epub( - chapters: Mapping, - title: str, - basename: str, - use_dnd_decorations: bool = False + chapters: Mapping, title: str, basename: str, use_dnd_decorations: bool = False ): """Prepare an EPUB file from the list of chapters. @@ -34,14 +31,18 @@ def create_epub( """ # Create a new epub book book = epub.EpubBook() - book.set_identifier('id123456') + book.set_identifier("id123456") book.set_title(title) - book.set_language('en') + book.set_language("en") # Add the css files css_template = jinja_env.get_template("dungeonsheets_epub.css") style = css_template.render(use_dnd_decorations=use_dnd_decorations) - css = epub.EpubItem(uid="style_default", file_name="style/gm_sheet.css", - media_type="text/css", content=style) + css = epub.EpubItem( + uid="style_default", + file_name="style/gm_sheet.css", + media_type="text/css", + content=style, + ) book.add_item(css) toc = ["nav"] # Create the separate chapters @@ -49,15 +50,22 @@ def create_epub( for chap_title, content in chapters.items(): chap_fname = chap_title.replace(" - ", "-").replace(" ", "_").lower() chap_fname = "{}.html".format(chap_fname) - chapter = epub.EpubHtml(title=chap_title, - file_name=chap_fname, lang="en", - media_type="application/xhtml+xml") + chapter = epub.EpubHtml( + title=chap_title, + file_name=chap_fname, + lang="en", + media_type="application/xhtml+xml", + ) chapter.set_content(content) chapter.add_item(css) book.add_item(chapter) html_chapters.append(chapter) # Add entries for the table of contents - toc.append(toc_from_headings(html=content, filename=chap_fname, chapter_title=chap_title)) + toc.append( + toc_from_headings( + html=content, filename=chap_fname, chapter_title=chap_title + ) + ) # Add the table of contents book.toc = toc book.spine = ("nav", *html_chapters) @@ -74,7 +82,7 @@ class HeadingParser(HTMLParser): _curr_level = None _curr_id = None _curr_title = None - + def __init__(self, *args, **kwargs): self.headings = [] super().__init__(*args, **kwargs) @@ -85,22 +93,22 @@ class HeadingParser(HTMLParser): return int(match.group(1)) else: return None - + def handle_starttag(self, tag, attrs): this_level = self.heading_level(tag) if this_level is not None: # Found a heading, so process the properties self._curr_level = this_level attrs = {k: v for k, v in attrs} - self._curr_id = attrs.get('id') - + self._curr_id = attrs.get("id") + def handle_endtag(self, tag): this_level = self.heading_level(tag) if this_level is not None and this_level == self._curr_level: heading = { "level": this_level, "id": self._curr_id, - "title": self._curr_title + "title": self._curr_title, } self.headings.append(heading) @@ -110,7 +118,9 @@ class HeadingParser(HTMLParser): self._curr_title = data -def toc_from_headings(html: str, filename: str = "", chapter_title: str = "Sheet") -> list: +def toc_from_headings( + html: str, filename: str = "", chapter_title: str = "Sheet" +) -> list: """Accept a chapter of HTML, and extract a table of contents segment. Parameters @@ -120,12 +130,12 @@ def toc_from_headings(html: str, filename: str = "", chapter_title: str = "Sheet filename The name of this file to be used for hrefs. E.g. "index.html#heading_1". - + Returns ------- toc A sequence of table-of-contents links. - + """ # [(, # [(, @@ -149,18 +159,19 @@ def toc_from_headings(html: str, filename: str = "", chapter_title: str = "Sheet href = f"{filename}#{heading['id']}" parent_section = sections_stack[-1] is_last = idx == (len(headings) - 1) - is_leaf = is_last or heading['level'] >= headings[idx+1]['level'] + is_leaf = is_last or heading["level"] >= headings[idx + 1]["level"] # Add a leaf or branch depending on the heading structure if is_leaf: - parent_section[1].append(epub.Link(href=href, title=heading['title'], uid=href)) + parent_section[1].append( + epub.Link(href=href, title=heading["title"], uid=href) + ) else: - new_section = (epub.Section(href=href, title=heading['title']), - []) + new_section = (epub.Section(href=href, title=heading["title"]), []) parent_section[1].append(new_section) sections_stack.append(new_section) # Walk back up the stack if not is_last: - for idx in range(max(0, heading['level'] - headings[idx + 1]['level'])): + for idx in range(max(0, heading["level"] - headings[idx + 1]["level"])): sections_stack.pop() return toc @@ -258,5 +269,5 @@ def to_heading_id(inpt: str) -> str: # Prepare the jinja environment jinja_env = jinja_environment() -jinja_env.filters['rst_to_html'] = rst_to_html -jinja_env.filters['to_heading_id'] = to_heading_id +jinja_env.filters["rst_to_html"] = rst_to_html +jinja_env.filters["to_heading_id"] = to_heading_id diff --git a/dungeonsheets/exceptions.py b/dungeonsheets/exceptions.py index 9ba138f..a8263ac 100644 --- a/dungeonsheets/exceptions.py +++ b/dungeonsheets/exceptions.py @@ -25,5 +25,6 @@ class JSONFormatError(RuntimeError): class UnknownFileType(RuntimeError): """The input file does not match one of the known formats.""" + class UnknownOutputFormat(RuntimeError): """The output format requested is not one of the known outputs.""" diff --git a/dungeonsheets/fill_pdf_template.py b/dungeonsheets/fill_pdf_template.py index 5a9997f..838955c 100644 --- a/dungeonsheets/fill_pdf_template.py +++ b/dungeonsheets/fill_pdf_template.py @@ -160,7 +160,7 @@ def create_character_pdf_template(character, basename, flatten=False): # Additional attacks beyond 3 attack = [ f"{w.name}: Atk {w.attack_modifier:+d}, Dam {w.damage}/{w.damage_type}" - for w in character.weapons[len(weapon_fields):] + for w in character.weapons[len(weapon_fields) :] ] # Other attack information if character.armor: diff --git a/dungeonsheets/forms.py b/dungeonsheets/forms.py index 4711846..8cef656 100644 --- a/dungeonsheets/forms.py +++ b/dungeonsheets/forms.py @@ -3,15 +3,13 @@ import re from jinja2 import Environment, PackageLoader +from dungeonsheets.stats import mod_str + + # A dice string, with optional backticks: ``1d6 + 3`` dice_re = re.compile(r"`*(\d+d\d+(?:\s*\+\s*\d+)?)`*") -def mod_str(modifier): - """Converts a modifier to a string, eg 2 -> '+2'.""" - return "{:+d}".format(modifier) - - def jinja_environment(): """Prepare a new environment for Jinja templates. @@ -34,5 +32,3 @@ def jinja_environment(): ) jinja_env.filters["mod_str"] = mod_str return jinja_env - - diff --git a/dungeonsheets/latex.py b/dungeonsheets/latex.py index 6dda7b6..2907ee6 100644 --- a/dungeonsheets/latex.py +++ b/dungeonsheets/latex.py @@ -43,7 +43,12 @@ def _remove_temp_files(basename_): filename.unlink() -def create_latex_pdf(tex: str, basename: str, keep_temp_files: bool=False, use_dnd_decorations: bool=False): +def create_latex_pdf( + tex: str, + basename: str, + keep_temp_files: bool = False, + use_dnd_decorations: bool = False, +): # Create tex document tex_file = f"{basename}.tex" with open(tex_file, mode="w", encoding="utf-8") as f: diff --git a/dungeonsheets/make_sheets.py b/dungeonsheets/make_sheets.py index b3f8a4a..ffa8c02 100755 --- a/dungeonsheets/make_sheets.py +++ b/dungeonsheets/make_sheets.py @@ -11,7 +11,15 @@ from multiprocessing import Pool, cpu_count from itertools import product from typing import Union, Sequence, Optional -from dungeonsheets import character as _char, exceptions, readers, latex, epub, monsters, forms +from dungeonsheets import ( + character as _char, + exceptions, + readers, + latex, + epub, + monsters, + forms, +) from dungeonsheets.forms import mod_str from dungeonsheets.content_registry import find_content from dungeonsheets.fill_pdf_template import ( @@ -127,13 +135,14 @@ def create_druid_shapes_tex( def create_random_tables_content( - conjure_animals: bool, - suffix: str, - use_dnd_decorations: bool = False, + conjure_animals: bool, + suffix: str, + use_dnd_decorations: bool = False, ) -> str: template = jinja_env.get_template(f"random_tables_template.{suffix}") - return template.render(conjure_animals=conjure_animals, - use_dnd_decorations=use_dnd_decorations) + return template.render( + conjure_animals=conjure_animals, use_dnd_decorations=use_dnd_decorations + ) def make_sheet( @@ -236,7 +245,10 @@ def make_gm_sheet( summary = gm_props.pop("summary", "") content.append( create_party_summary_content( - party, summary_rst=summary, suffix=content_suffix, use_dnd_decorations=fancy_decorations + party, + summary_rst=summary, + suffix=content_suffix, + use_dnd_decorations=fancy_decorations, ) ) # Add the monsters @@ -257,10 +269,14 @@ def make_gm_sheet( monsters_.append(new_monster) if len(monsters_) > 0: content.append( - create_monsters_content(monsters_, suffix=content_suffix, use_dnd_decorations=fancy_decorations) + create_monsters_content( + monsters_, suffix=content_suffix, use_dnd_decorations=fancy_decorations + ) ) # Add the random tables - random_tables = [s.replace(" ", "_").lower() for s in gm_props.pop("random_tables", [])] + random_tables = [ + s.replace(" ", "_").lower() for s in gm_props.pop("random_tables", []) + ] content.append( create_random_tables_content( conjure_animals=("conjure_animals" in random_tables), @@ -303,7 +319,8 @@ def make_gm_sheet( ) else: raise exceptions.UnknownOutputFormat( - f"Unknown output format requested: {output_format}. Valid options are: 'pdf', 'epub'" + f"Unknown output format requested: {output_format}. Valid options are:" + " 'pdf', 'epub'" ) @@ -512,7 +529,8 @@ def main(args=None): ), ) parser.add_argument( - "--output-format", "-o", + "--output-format", + "-o", help="Specify the output format for the sheets.", choices=["pdf", "epub"], default="pdf", diff --git a/dungeonsheets/mechanics.py b/dungeonsheets/mechanics.py index 70f39bd..8f98091 100644 --- a/dungeonsheets/mechanics.py +++ b/dungeonsheets/mechanics.py @@ -4,7 +4,13 @@ game mechanics.""" from dungeonsheets.spells import Spell from dungeonsheets.features import Feature from dungeonsheets.infusions import Infusion -from dungeonsheets.weapons import Weapon, MeleeWeapon, RangedWeapon, SimpleWeapon, MartialWeapon +from dungeonsheets.weapons import ( + Weapon, + MeleeWeapon, + RangedWeapon, + SimpleWeapon, + MartialWeapon, +) from dungeonsheets.armor import Armor, Shield from dungeonsheets.magic_items import MagicItem from dungeonsheets.monsters import Monster diff --git a/dungeonsheets/monsters/monsters_b.py b/dungeonsheets/monsters/monsters_b.py index a874a1f..44eecf6 100644 --- a/dungeonsheets/monsters/monsters_b.py +++ b/dungeonsheets/monsters/monsters_b.py @@ -9,7 +9,7 @@ from dungeonsheets.stats import Ability class Baboon(Monster): - """ Pack Tactics. + """Pack Tactics. The baboon has advantage on an attack roll against a creature if at least one of the baboon's allies is within 5 ft. of the creature and the ally isn't incapacitated. @@ -109,11 +109,11 @@ class Balor(Monster): Variant: Summon Demon. The demon chooses what to summon and attempts a magical summoning. - + A balor has a 50 percent chance of summoning 1d8 vrocks, 1d6 hezrous, 1d4 glabrezus, 1d3 nalfeshnees, 1d2 mariliths, or one goristro. - + A summoned demon appears in an unoccupied space within 60 feet of its summoner, acts as an ally of its summoner, and can't summon other demons. It remains for 1 minute, until it or its @@ -148,7 +148,7 @@ class Bandit(Monster): Light Crossbow. Ranged Weapon Attack: +3 to hit, range 80 ft./320 ft., one target. Hit: 5 (1d8 + 1) piercing damage. - + """ name = "Bandit" @@ -262,20 +262,20 @@ class Basilisk(Monster): success, the effect ends. On a failure, the creature is petrified until freed by the greater restoration spell or other magic. - + A creature that isn't surprised can avert its eyes to avoid the saving throw at the start of its turn. If it does so, it can't see the basilisk until the start of its next turn, when it can avert its eyes again. If it looks at the basilisk in the meantime, it must immediately make the save. - + If the basilisk sees its reflection within 30 ft. of it in bright light, it mistakes itself for a rival and targets itself with its gaze. Bite. Melee Weapon Attack: +5 to hit, reach 5 ft., one target. Hit: 10 (2d6 + 3) piercing damage plus 7 (2d6) poison damage. - + """ name = "Basilisk" @@ -410,7 +410,7 @@ class Behir(Monster): other effects outside the behir, and it takes 21 (6d6) acid damage at the start of each of the behir's turns. A behir can have only one creature swallowed at a time. - + If the behir takes 30 damage or more on a single turn from the swallowed creature, the behir must succeed on a DC 14 Constitution saving throw at the end of that turn or regurgitate diff --git a/dungeonsheets/readers.py b/dungeonsheets/readers.py index 052bad9..a548d67 100644 --- a/dungeonsheets/readers.py +++ b/dungeonsheets/readers.py @@ -528,9 +528,7 @@ class FoundryCharacterReader(JSONCharacterReader): tool_profs.extend([s.strip() for s in custom_tool_profs.split(";")]) char_props["_proficiencies_text"] = tool_profs # Combat stats - char_props["hp_max"] = self.as_int( - json_data["data"]["attributes"]["hp"]["max"] - ) + char_props["hp_max"] = self.as_int(json_data["data"]["attributes"]["hp"]["max"]) # Equipment currency = json_data["data"]["currency"] char_props["cp"] = currency["cp"] diff --git a/dungeonsheets/stats.py b/dungeonsheets/stats.py index 31caa04..465e9b5 100644 --- a/dungeonsheets/stats.py +++ b/dungeonsheets/stats.py @@ -30,6 +30,11 @@ from dungeonsheets.features import ( log = logging.getLogger(__name__) +def mod_str(modifier): + """Converts a modifier to a string, eg 2 -> '+2'.""" + return "{:+d}".format(modifier) + + AbilityScore = namedtuple("AbilityScore", ("value", "modifier", "saving_throw"))