mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 13:15:53 +02:00
Missing weapons now raise a warning, and recursive directory parsing.
In order to avoid parsing arbitrary python files encountered during directory recursion, ``makesheets`` now checks each python file for a string listing the *dungeonsheets_version* before trying to import the character file.
This commit is contained in:
+15
-5
@@ -14,7 +14,14 @@ A tool to create character sheets for Dungeons and Dragons.
|
||||
|
||||
.. image:: https://readthedocs.org/projects/dungeon-sheets/badge/?version=latest
|
||||
:target: https://dungeon-sheets.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Documentation Status
|
||||
:alt: Documentation Status
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
Documentation can be found on readthedocs_.
|
||||
|
||||
.. _readthedocs: https://dungeon-sheets.readthedocs.io/en/latest/?badge=latest
|
||||
|
||||
Installation
|
||||
============
|
||||
@@ -59,13 +66,16 @@ not, then this feature will be skipped.
|
||||
Usage
|
||||
=====
|
||||
|
||||
Each character is described by a python (or JSON) file, which gives
|
||||
many attributes associated with the character. See examples_ for more
|
||||
information about the character descriptions.
|
||||
Each character is described by a python (or a VTTES JSON) file, which
|
||||
gives many attributes associated with the character. See examples_ for
|
||||
more information about the character descriptions.
|
||||
|
||||
.. _examples: https://github.com/canismarko/dungeon-sheets/tree/master/examples
|
||||
|
||||
The PDF's can then be generated using the ``makesheets`` command.
|
||||
The PDF's can then be generated using the ``makesheets`` command. If
|
||||
no filename is given, the current directory will be parsed and any
|
||||
character files found will be processed. If the ``--recursive`` option
|
||||
is used, sub-folders will also be parsed.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
|
||||
@@ -257,8 +257,16 @@ spells and known cantrips** should be listed in the
|
||||
# List of all the known wild shapes
|
||||
wild_shapes = ["wolf", "crocodile", 'ape', 'ankylosaurus']
|
||||
|
||||
|
||||
VTTES JSON Files
|
||||
================
|
||||
|
||||
Dungeonsheets has partial support for reading JSON files exporting
|
||||
using the `VTTES browser extension`_. This allows character sheets to
|
||||
be exported from systems like Roll20.net, and then rendered into full
|
||||
character sheets.
|
||||
|
||||
.. _player's handbook: http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook
|
||||
|
||||
.. _issue: https://github.com/canismarko/dungeon-sheets/issues
|
||||
|
||||
.. _VTTES browser extension: https://wiki.5e.tools/index.php/R20es_Install_Guide
|
||||
|
||||
+5
-5
@@ -15,7 +15,8 @@
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
from pathlib import Path
|
||||
proj_root = Path(__file__).parent.parent
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
@@ -23,11 +24,10 @@ project = 'dungeonsheets'
|
||||
copyright = '2018, Mark Wolfman'
|
||||
author = 'Mark Wolfman'
|
||||
|
||||
# The short X.Y version
|
||||
version = ''
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.4.2'
|
||||
|
||||
release = open(proj_root/'VERSION').read()
|
||||
# The short X.Y version
|
||||
version = ".".join(release.split('.')[0:2])
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
|
||||
+94
-5
@@ -2,19 +2,108 @@
|
||||
Examples
|
||||
==========
|
||||
|
||||
This page lists example character files. These files are found in the
|
||||
``examples/`` directory, and can be compiled with ``$ makesheets
|
||||
examples/``.
|
||||
|
||||
.. contents:: :local:
|
||||
|
||||
Wizard
|
||||
Artificer
|
||||
=========
|
||||
|
||||
.. literalinclude:: ../examples/artificer1.py
|
||||
|
||||
Barbarian
|
||||
=========
|
||||
|
||||
.. literalinclude:: ../examples/barbarian1.py
|
||||
|
||||
.. literalinclude:: ../examples/barbarian2.py
|
||||
|
||||
Bard
|
||||
====
|
||||
|
||||
.. literalinclude:: ../examples/bard1.py
|
||||
|
||||
.. literalinclude:: ../examples/bard2.py
|
||||
|
||||
Cleric
|
||||
======
|
||||
|
||||
.. literalinclude:: ../examples/wizard.py
|
||||
.. literalinclude:: ../examples/cleric1.py
|
||||
|
||||
Warlock
|
||||
.. literalinclude:: ../examples/cleric2.py
|
||||
|
||||
Druid
|
||||
=====
|
||||
|
||||
.. literalinclude:: ../examples/druid1.py
|
||||
|
||||
.. literalinclude:: ../examples/druid2.py
|
||||
|
||||
.. literalinclude:: ../examples/druid3.py
|
||||
|
||||
Fighter
|
||||
=======
|
||||
|
||||
.. literalinclude:: ../examples/warlock.py
|
||||
.. literalinclude:: ../examples/fighter1.py
|
||||
|
||||
.. literalinclude:: ../examples/fighter2.py
|
||||
|
||||
Monk
|
||||
====
|
||||
|
||||
.. literalinclude:: ../examples/monk1.py
|
||||
|
||||
.. literalinclude:: ../examples/monk2.py
|
||||
|
||||
Multi-Classing
|
||||
==============
|
||||
|
||||
.. literalinclude:: ../examples/multiclass1.py
|
||||
|
||||
.. literalinclude:: ../examples/multiclass2.py
|
||||
|
||||
Paladin
|
||||
=======
|
||||
|
||||
.. literalinclude:: ../examples/paladin1.py
|
||||
|
||||
.. literalinclude:: ../examples/paladin2.py
|
||||
|
||||
Ranger
|
||||
======
|
||||
|
||||
.. literalinclude:: ../examples/ranger1.py
|
||||
|
||||
.. literalinclude:: ../examples/ranger2.py
|
||||
|
||||
.. literalinclude:: ../examples/ranger3.py
|
||||
|
||||
Rogue
|
||||
=====
|
||||
|
||||
.. literalinclude:: ../examples/rogue.py
|
||||
.. literalinclude:: ../examples/rogue1.py
|
||||
|
||||
.. literalinclude:: ../examples/rogue2.py
|
||||
|
||||
Sorceror
|
||||
=========
|
||||
|
||||
.. literalinclude:: ../examples/sorceror1.py
|
||||
|
||||
.. literalinclude:: ../examples/sorceror2.py
|
||||
|
||||
Warlock
|
||||
=======
|
||||
|
||||
.. literalinclude:: ../examples/warlock1.py
|
||||
|
||||
.. literalinclude:: ../examples/warlock2.py
|
||||
|
||||
Wizard
|
||||
======
|
||||
|
||||
.. literalinclude:: ../examples/wizard1.py
|
||||
|
||||
.. literalinclude:: ../examples/wizard2.py
|
||||
|
||||
+2
-5
@@ -13,14 +13,11 @@ Welcome to Dungeonsheets's documentation!
|
||||
character_files
|
||||
examples
|
||||
|
||||
To-Do Tasks
|
||||
===========
|
||||
.. todolist::
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. include:: ../README.rst
|
||||
|
||||
@@ -697,13 +697,10 @@ class Character():
|
||||
try:
|
||||
NewWeapon = findattr(weapons, weapon)
|
||||
except AttributeError:
|
||||
try:
|
||||
findattr(spells, weapon)
|
||||
except AttributeError:
|
||||
raise AttributeError(f'Weapon "{weapon}" is not defined')
|
||||
else:
|
||||
warnings.warn(f"Ignoring spell {weapon} listed as weapon.")
|
||||
return
|
||||
warnings.warn(f"Unknown weapon '{weapon}'. Please add it to ``weapons.py`` "
|
||||
"or submit an issue: https://github.com/canismarko/dungeon-sheets/issues",
|
||||
RuntimeWarning)
|
||||
return
|
||||
weapon_ = NewWeapon(wielder=self)
|
||||
elif issubclass(weapon, weapons.Weapon):
|
||||
weapon_ = weapon(wielder=self)
|
||||
|
||||
@@ -681,19 +681,8 @@ load_character_file = readers.read_character_file
|
||||
|
||||
|
||||
def _build(filename, args) -> int:
|
||||
known_extensions = readers.readers_by_extension.keys()
|
||||
# Check if it's a directory we can recurse through
|
||||
if filename.is_dir():
|
||||
num_imported = 0
|
||||
for child_file in filename.iterdir():
|
||||
num_imported += _build(child_file, args)
|
||||
return num_imported
|
||||
# Check if we know how to import this file
|
||||
if filename.suffix not in known_extensions:
|
||||
return 0
|
||||
# Single known file, so import it
|
||||
basename = filename.stem
|
||||
print(f"Processing {basename}...")
|
||||
print(f"Processing {basename}...")
|
||||
try:
|
||||
make_sheet(character_file=filename, flatten=(not args.editable),
|
||||
debug=args.debug, fancy_decorations=args.fancy_decorations)
|
||||
@@ -714,10 +703,12 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Prepare Dungeons and Dragons character sheets as PDFs')
|
||||
parser.add_argument('filename', type=str, nargs="*",
|
||||
help="Python file with character definition")
|
||||
help="File with character definition, or directory containing such files")
|
||||
parser.add_argument('--editable', '-e', action="store_true",
|
||||
help="Keep the PDF fields in place once processed.")
|
||||
parser.add_argument('--fancy-decorations', '-F', action="store_true",
|
||||
help="Keep the PDF fields in place once processed")
|
||||
parser.add_argument('--recursive', '-r', action="store_true",
|
||||
help="Descend into subfolders looking for character files")
|
||||
parser.add_argument('--fancy-decorations', '--fancy', '-F', action="store_true",
|
||||
help=("Render extra pages using fancy decorations "
|
||||
"(experimental, requires https://github.com/rpgtex/DND-5e-LaTeX-Template)"))
|
||||
parser.add_argument('--debug', '-d', action="store_true",
|
||||
@@ -733,18 +724,25 @@ def main():
|
||||
input_filenames = [Path()]
|
||||
else:
|
||||
input_filenames = [Path(f) for f in input_filenames]
|
||||
def get_char_files(fpath):
|
||||
def get_char_files(fpath, parse_dirs=False):
|
||||
valid_files = []
|
||||
if fpath.is_dir():
|
||||
if fpath.is_dir() and parse_dirs:
|
||||
for f in fpath.iterdir():
|
||||
valid_files.extend(get_char_files(f))
|
||||
valid_files.extend(get_char_files(f, parse_dirs=args.recursive))
|
||||
elif fpath.suffix in known_extensions:
|
||||
valid_files.append(fpath)
|
||||
return valid_files
|
||||
filenames = []
|
||||
temp_filenames = []
|
||||
for fpath in input_filenames:
|
||||
filenames.extend(get_char_files(fpath))
|
||||
# Process the requested files
|
||||
temp_filenames.extend(get_char_files(fpath, parse_dirs=True))
|
||||
# IMPORTANT: Check that the files are valid dungeonsheets files without importing them
|
||||
filenames = []
|
||||
version_re = re.compile(r"^dungeonsheets_version = [\'\"](?P<version>[0-9.]+)[\'\"]\s*$", re.MULTILINE)
|
||||
for fpath in temp_filenames:
|
||||
with open(fpath, mode='r') as fp:
|
||||
if version_re.search(fp.read()) or fpath.suffix != '.py':
|
||||
filenames.append(fpath)
|
||||
# Process the requested files
|
||||
if args.debug:
|
||||
for filename in filenames:
|
||||
_build(filename, args)
|
||||
|
||||
@@ -155,13 +155,9 @@ class JSONCharacterReader(BaseCharacterReader):
|
||||
match = item_re.match(obj['name'])
|
||||
if match:
|
||||
weapon_name = self.get_attrib(match.group()).lower()
|
||||
if weapon_name[:3] == "i. ":
|
||||
# Ignore artificer infusions
|
||||
warnings.warn("Ignoring weapon infusion")
|
||||
else:
|
||||
weapon_name = weapon_name.split('(')[0].strip()
|
||||
weapon_name = weapon_name.split(',')[0].strip()
|
||||
yield weapon_name
|
||||
weapon_name = weapon_name.split('(')[0].strip()
|
||||
weapon_name = weapon_name.split(',')[0].strip()
|
||||
yield weapon_name
|
||||
|
||||
def __call__(self, filename: str):
|
||||
"""Create a character property dictionary from the JSON file."""
|
||||
|
||||
@@ -484,6 +484,7 @@ class Unarmed(MeleeWeapon):
|
||||
ability = "strength"
|
||||
|
||||
|
||||
MonkUnarmedStrike = Unarmed
|
||||
UnarmedStrike = Unarmed
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user