diff --git a/dungeonsheets/character.py b/dungeonsheets/character.py
index 0925ff2..a076112 100644
--- a/dungeonsheets/character.py
+++ b/dungeonsheets/character.py
@@ -467,11 +467,11 @@ class Character(Creature):
@property
def spellcasting_classes_excluding_warlock(self):
return [c for c in self.spellcasting_classes if not type(c) == classes.Warlock]
-
+
@property
def is_spellcaster(self):
return len(self.spellcasting_classes) > 0
-
+
def spell_slots(self, spell_level):
warlock_slots = 0
for c in self.spellcasting_classes:
diff --git a/dungeonsheets/content.py b/dungeonsheets/content.py
index 012054e..6f79943 100644
--- a/dungeonsheets/content.py
+++ b/dungeonsheets/content.py
@@ -105,7 +105,6 @@ class Content(ABC):
attrs = {"name": mechanic_name, "__doc__": msg, "source": "Unknown"}
Mechanic = type(class_name, (SuperClass,), attrs)
return Mechanic
-
class Creature(Content):
@@ -213,3 +212,7 @@ class Creature(Content):
self.medicine, self.nature, self.perception,
self.performance, self.persuasion, self.religion,
self.sleight_of_hand, self.stealth, self.survival]
+
+ @property
+ def is_spellcaster(self):
+ raise NotImplementedError
diff --git a/dungeonsheets/forms.py b/dungeonsheets/forms.py
index 8f89b43..35caa22 100644
--- a/dungeonsheets/forms.py
+++ b/dungeonsheets/forms.py
@@ -3,7 +3,7 @@ import re
from jinja2 import Environment, PackageLoader
-from dungeonsheets.stats import mod_str, ability_mod_str, stat_abbreviation
+from dungeonsheets.stats import mod_str, ability_mod_str, stat_abbreviation, str_to_list
from dungeonsheets.encounter import xp_thresholds
from dungeonsheets.monsters import challenge_rating_to_xp
@@ -37,4 +37,5 @@ def jinja_environment():
jinja_env.filters["stat_abbreviation"] = stat_abbreviation
jinja_env.filters["challenge_rating_to_xp"] = challenge_rating_to_xp
jinja_env.filters["xp_thresholds"] = xp_thresholds
+ jinja_env.filters["str_to_list"] = str_to_list
return jinja_env
diff --git a/dungeonsheets/forms/character_sheet_template.html b/dungeonsheets/forms/character_sheet_template.html
index d327390..0ba51a3 100644
--- a/dungeonsheets/forms/character_sheet_template.html
+++ b/dungeonsheets/forms/character_sheet_template.html
@@ -49,7 +49,7 @@
Current Hit Points
[[ character.hp_current ]]
Temporary Hit Points
- [% if character.hp_temp > 0 %][[ character.hp_temp ]][% endif %]
+ [[ character | selectattr('hp_temp', 0) ]]
Hit Dice Total
[[ character.hit_dice ]]
@@ -126,12 +126,10 @@
[[ character.ep ]] EP
[[ character.gp ]] GP
[[ character.pp ]] PP
- [% set inventory_items = character.magic_items_text.split(',') %]
- [% for item in inventory_items %]
+ [% for item in character | str_to_list('magic_items_text') %]
[[ item ]]
[% endfor %]
- [% set inventory_items = character.equipment.split(',') %]
- [% for item in inventory_items %]
+ [% for item in character | str_to_list('equipment') %]
[[ item ]]
[% endfor %]
diff --git a/dungeonsheets/make_sheets.py b/dungeonsheets/make_sheets.py
index acd10b4..bc789dc 100644
--- a/dungeonsheets/make_sheets.py
+++ b/dungeonsheets/make_sheets.py
@@ -387,12 +387,12 @@ def make_character_content(
content_suffix=content_format,
use_dnd_decorations=fancy_decorations))
# Create a list of subcasses, features, spells, etc
- if character.subclasses:
+ if len(getattr(character, 'subclasses', [])) > 0:
content.append(create_subclasses_content(character,
content_suffix=content_format,
use_dnd_decorations=fancy_decorations)
)
- if character.features:
+ if len(getattr(character, 'features', [])) > 0:
content.append(
create_features_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations)
)
diff --git a/dungeonsheets/monsters/monsters.py b/dungeonsheets/monsters/monsters.py
index 084d6d2..339808c 100644
--- a/dungeonsheets/monsters/monsters.py
+++ b/dungeonsheets/monsters/monsters.py
@@ -106,3 +106,7 @@ class Monster(Creature, metaclass=SpellFactory):
def has_feature(self, *args, **kwargs):
return False
+
+ @property
+ def is_spellcaster(self):
+ return len(self.spells) > 0
diff --git a/dungeonsheets/stats.py b/dungeonsheets/stats.py
index 67fc999..d1e6087 100644
--- a/dungeonsheets/stats.py
+++ b/dungeonsheets/stats.py
@@ -32,7 +32,43 @@ log = logging.getLogger(__name__)
def mod_str(modifier):
"""Converts a modifier to a string, eg 2 -> '+2'."""
- return "{:+d}".format(modifier)
+ try:
+ s = "{:+d}".format(modifier)
+ except TypeError:
+ s = "N/A"
+ return s
+
+
+def str_to_list(obj, attr: str, sep: str = ","):
+ """Find the string *obj.attr* if it exists, and returns it as a
+ list.
+
+ Parameters
+ ==========
+ obj
+ Any python object, presumably with an attribute *attr*.
+ attr
+ The name of the attribute to look up.
+ sep
+ The separator to use for splitting the string.
+
+ Returns
+ =======
+ items
+ A sequence of the items retrieved from *obj.attr* string. If
+ *obj.attr* does not exist, an empty list will be returned. If
+ *obj.attr* is not a string, then it will be presumed to be a
+ sequence already and returned as is.
+
+ """
+ string = getattr(obj, attr, [])
+ if hasattr(string, "split"):
+ # Convert the string to a list
+ lst = [s.strip() for s in string.split(sep)]
+ else:
+ # A non-string attribute was given, so just return it
+ lst = string
+ return lst
def ability_mod_str(character, ability):
diff --git a/examples/gm-campaign-notes.py b/examples/gm-campaign-notes.py
index 9ef6470..0d5de0e 100644
--- a/examples/gm-campaign-notes.py
+++ b/examples/gm-campaign-notes.py
@@ -5,7 +5,7 @@ monsters, etc.
"""
-from dungeonsheets import mechanics, monsters
+from dungeonsheets import mechanics, monsters as _monsters
dungeonsheets_version = "0.14.0"
@@ -18,6 +18,6 @@ haryk_omanie = mechanics.Character(
name="Haryk Omanie",
)
-party = ["rogue1.py", "paladin2.py", haryk_omanie, monsters.Veteran]
+party = ["rogue1.py", "paladin2.py", haryk_omanie, _monsters.Veteran]
random_tables = ["conjure animals"]
diff --git a/tests/test_stats.py b/tests/test_stats.py
index 84a0e17..e9541e6 100644
--- a/tests/test_stats.py
+++ b/tests/test_stats.py
@@ -8,6 +8,19 @@ class TestStats(TestCase):
self.assertEqual(stats.mod_str(-3), "-3")
self.assertEqual(stats.mod_str(0), "+0")
self.assertEqual(stats.mod_str(2), "+2")
+ self.assertEqual(stats.mod_str(None), "N/A")
+
+ def test_str_to_list(self):
+ char = character.Character(equipment="a, b, c")
+ # Regular string
+ self.assertEqual(stats.str_to_list(char, "equipment"), ["a", "b", "c"])
+ # Alternate separator
+ char.equipment = "a; b; c"
+ self.assertEqual(stats.str_to_list(char, "equipment", sep=";"), ["a", "b", "c"])
+ # No attribute
+ self.assertEqual(stats.str_to_list(char, "inventory"), [])
+ # Not a string
+ self.assertEqual(stats.str_to_list(char, "hp_max"), char.hp_max)
def test_saving_throw(self):
# Try it with an ST proficiency