Merge branch 'master' into master

This commit is contained in:
Jack Timmins
2021-06-09 01:13:10 -04:00
committed by GitHub
50 changed files with 12446 additions and 592 deletions
+163 -29
View File
@@ -9,24 +9,23 @@ import re
from pathlib import Path
from multiprocessing import Pool, cpu_count
from itertools import product
from typing import Union, Mapping, Sequence, Optional
from jinja2 import Environment, PackageLoader
from dungeonsheets import character as _char
from dungeonsheets import exceptions, readers, latex
from dungeonsheets.stats import mod_str
from dungeonsheets import character as _char, exceptions, readers, latex, monsters
from dungeonsheets.stats import mod_str, findattr
from dungeonsheets.fill_pdf_template import (
create_character_pdf_template,
create_personality_pdf_template,
create_spells_pdf_template,
)
from dungeonsheets.character import Character
from dungeonsheets.entity import Entity
"""Program to take character definitions and build a PDF of the
character sheet."""
PDFTK_CMD = "pdftk"
log = logging.getLogger(__name__)
ORDINALS = {
@@ -55,6 +54,10 @@ jinja_env.filters["mod_str"] = mod_str
PDFTK_CMD = "pdftk"
# Custom types
File = Union[Path, str]
def create_subclasses_tex(
character: Character,
use_dnd_decorations: bool = False,
@@ -79,6 +82,23 @@ def create_magic_items_tex(
return template.render(character=character, use_dnd_decorations=use_dnd_decorations)
def create_monsters_tex(
monsters: Sequence[Union[monsters.Monster, str]],
use_dnd_decorations: bool = False,
) -> str:
# Convert strings to Monster objects
template = jinja_env.get_template("monsters_template.tex")
return template.render(monsters=monsters, use_dnd_decorations=use_dnd_decorations)
def create_party_summary_tex(
party: Sequence[Entity],
use_dnd_decorations: bool = False,
) -> str:
template = jinja_env.get_template("party_summary_template.tex")
return template.render(party=party, use_dnd_decorations=use_dnd_decorations)
def create_spellbook_tex(
character: Character,
use_dnd_decorations: bool = False,
@@ -106,22 +126,16 @@ def create_druid_shapes_tex(
def make_sheet(
character_file,
character=None,
flatten=False,
latex_template=True,
fancy_decorations=False,
debug=False,
sheet_file: File,
flatten: bool = False,
fancy_decorations: bool = False,
debug: bool = False,
):
"""Prepare a PDF character sheet from the given character file.
"""Make a character or GM sheet into a PDF.
Parameters
----------
character_file : str
sheet_file
File (.py) to load character from. Will save PDF using same name
character : Character, optional
If provided, will not load from the character file, just use
file for PDF name
flatten : bool, optional
If true, the resulting PDF will look better and won't be
fillable form.
@@ -132,9 +146,126 @@ def make_sheet(
Provide extra info and preserve temporary files.
"""
if character is None:
character = _char.Character.load(character_file)
# Parse the file
sheet_file = Path(sheet_file)
sheet_props = readers.read_sheet_file(sheet_file)
# Create the sheet
if sheet_props.get("sheet_type", "") == "gm":
ret = make_gm_sheet(
gm_file=sheet_file,
fancy_decorations=fancy_decorations,
debug=debug,
)
else:
ret = make_character_sheet(
char_file=sheet_file,
flatten=flatten,
fancy_decorations=fancy_decorations,
debug=debug,
)
return ret
def make_gm_sheet(
gm_file: Union[str, Path],
fancy_decorations: bool = False,
debug: bool = False,
):
"""Prepare a PDF character sheet from the given character file.
Parameters
----------
gm_file
The file with the gm_sheet definitions.
fancy_decorations
Use fancy page layout and decorations for extra sheets, namely
the dnd style file: https://github.com/rpgtex/DND-5e-LaTeX-Template.
debug
Provide extra info and preserve temporary files.
"""
# Parse the GM file and filename
gm_file = Path(gm_file)
basename = gm_file.stem
gm_props = readers.read_sheet_file(gm_file)
# Create the intro tex
tex = [
jinja_env.get_template("preamble.tex").render(
use_dnd_decorations=fancy_decorations,
title=gm_props["session_title"],
)
]
# Add the party stats table
party = []
for char_file in gm_props.get("party", []):
# Resolve the file path
char_file = Path(char_file)
if not char_file.is_absolute():
char_file = gm_file.parent / char_file
char_file = char_file.resolve()
# Load the character file
character_props = readers.read_sheet_file(char_file)
member = _char.Character.load(character_props)
party.append(member)
if len(party) > 0:
tex.append(
create_party_summary_tex(party, use_dnd_decorations=fancy_decorations)
)
# Add the monsters
monsters_ = [findattr(monsters, m)() for m in gm_props.get("monsters", [])]
if len(monsters_) > 0:
tex.append(
create_monsters_tex(monsters_, use_dnd_decorations=fancy_decorations)
)
# Add the closing TeX
tex.append(
jinja_env.get_template("postamble.tex").render(
use_dnd_decorations=fancy_decorations
)
)
# Typeset combined LaTeX file
try:
if len(tex) > 2:
latex.create_latex_pdf(
tex="".join(tex),
basename=basename,
keep_temp_files=debug,
use_dnd_decorations=fancy_decorations,
)
except exceptions.LatexNotFoundError:
log.warning(f"``pdflatex`` not available. Skipping {basename}")
def make_character_sheet(
char_file: Union[str, Path],
character: Optional[Character] = None,
flatten: bool = False,
fancy_decorations: bool = False,
debug: bool = False,
):
"""Prepare a PDF character sheet from the given character file.
Parameters
----------
basename
The basename for saving files (PDFs, etc).
character
If provided, will not load from the character file, just use
file for PDF name
flatten
If true, the resulting PDF will look better and won't be
fillable form.
fancy_decorations
Use fancy page layout and decorations for extra sheets, namely
the dnd style file: https://github.com/rpgtex/DND-5e-LaTeX-Template.
debug
Provide extra info and preserve temporary files.
"""
# Load properties from file
if character is None:
character_props = readers.read_sheet_file(char_file)
character = _char.Character.load(character_props)
# Set the fields in the FDF
char_base = os.path.splitext(character_file)[0] + "_char"
person_base = os.path.splitext(character_file)[0] + "_person"
@@ -142,7 +273,8 @@ def make_sheet(
pages = []
tex = [
jinja_env.get_template("preamble.tex").render(
use_dnd_decorations=fancy_decorations
use_dnd_decorations=fancy_decorations,
title="Features, Magical Items and Spells",
)
]
@@ -157,14 +289,13 @@ def make_sheet(
pages.append(person_pdf)
if character.is_spellcaster:
# Create spell sheet
spell_base = "{:s}_spells".format(os.path.splitext(character_file)[0])
spell_base = "{:s}_spells".format(basename)
create_spells_pdf_template(
character=character, basename=spell_base, flatten=flatten
)
sheets.append(spell_base + ".pdf")
# end of PDF gen
features_base = "{:s}_features".format(os.path.splitext(character_file)[0])
features_base = "{:s}_features".format(basename)
# Create a list of subcasses
if character.subclasses:
tex.append(
@@ -210,13 +341,13 @@ def make_sheet(
try:
if len(tex) > 2:
latex.create_latex_pdf(
"".join(tex),
features_base,
tex="".join(tex),
basename=features_base,
keep_temp_files=debug,
use_dnd_decorations=fancy_decorations,
)
sheets.append(features_base + ".pdf")
final_pdf = os.path.splitext(character_file)[0] + ".pdf"
final_pdf = f"{basename}.pdf"
merge_pdfs(sheets, final_pdf, clean_up=True)
except exceptions.LatexNotFoundError:
log.warning(
@@ -256,7 +387,7 @@ def _build(filename, args) -> int:
print(f"Processing {basename}...")
try:
make_sheet(
character_file=filename,
sheet_file=filename,
flatten=(not args.editable),
debug=args.debug,
fancy_decorations=args.fancy_decorations,
@@ -274,7 +405,7 @@ def _build(filename, args) -> int:
return 1
def main():
def main(args=None):
# Prepare an argument parser
parser = argparse.ArgumentParser(
description="Prepare Dungeons and Dragons character sheets as PDFs"
@@ -313,7 +444,7 @@ def main():
action="store_true",
help="Provide verbose logging for debugging purposes.",
)
args = parser.parse_args()
args = parser.parse_args(args)
# Prepare logging if necessary
if args.debug:
logging.basicConfig(level=logging.DEBUG)
@@ -332,6 +463,8 @@ def main():
valid_files.extend(get_char_files(f, parse_dirs=args.recursive))
elif fpath.suffix in known_extensions:
valid_files.append(fpath)
else:
log.info(f"Unhandled file: {str(fpath)}")
return valid_files
temp_filenames = []
@@ -350,6 +483,7 @@ def main():
# Process the requested files
if args.debug:
for filename in filenames:
print("building")
_build(filename, args)
else:
with Pool(cpu_count()) as p: