Added feature enhancements for Druid's

- Circle now properly reflects the druid's available wild_shapes
- Unavaiable wild_shapes are not listed on the sheet but ghosted.
- ``spells`` is now longer relevant, only use ``spells_prepared`` in
  the character file.
This commit is contained in:
Mark Wolfman
2018-10-31 18:16:35 -05:00
parent d2c24cfb2a
commit f10867719d
13 changed files with 370 additions and 128 deletions
+1 -1
View File
@@ -1 +1 @@
0.6.0 0.6.1
+49
View File
@@ -151,6 +151,14 @@ correspond to spells described in the `player's handbook`_.
spells_prepared = ('blindness deafness', 'false life', 'mage armor', spells_prepared = ('blindness deafness', 'false life', 'mage armor',
'ray of sickness', 'shield', 'sleep',) 'ray of sickness', 'shield', 'sleep',)
.. note::
Some character classes have modified spellcasting mechanics that
affects how these entries are intepreted.
- `Druid`_
Personality and Backstory Personality and Backstory
========================= =========================
@@ -210,6 +218,47 @@ source file more readable, but are not required.
priests there for assistance that wont endanger them.""") priests there for assistance that wont endanger them.""")
Class-Specific Features
=======================
Druid
-----
At level 2, druids choose a **circle**. This choice can affect
available wild_forms, and spellcasting abilities. The ``circle`` entry
should be set appropriately.
Druid's can transform into **wild shapes**, allowing them to adopt
some of the abilities of their new form. To aid in keeping track on
the possible shapes, Druids can have a listing for
``wild_shapes``. This list should contain names of beasts listed in
:py:mod:`dungeonsheets.monsters`, or instances of a subclass of
:py:class:`dungeonsheets.monsters.Monster`. If given, an extra *monster
sheet* will be produced as part of the PDF. Beasts familiar to the
druid but not yet accessible should still be listed to aid in record
keeping; they will be greyed-out on the sheet.
Additionally, druids don't learn spells, instead **druids can prepare
any spell available** provided it meets their level requirements. As
such, the listing for ``spells`` is not needed and **all prepared
spells and known cantrips** should be listed in the
``spells_prepared`` entry.
.. code:: python
# We're a moon druid, why not
circle = 'Moon'
# Spells are empty because we don't learn any spells
spells = []
# This one has all prepared spells and cantrips
spells_prepared = ['druidcraft', 'cure wounds']
# List of all the known wild shapes
wild_shapes = ["wolf", "crocodile", 'ape', 'ankylosaurus']
.. _player's handbook: http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook .. _player's handbook: http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook
.. _issue: https://github.com/canismarko/dungeon-sheets/issues .. _issue: https://github.com/canismarko/dungeon-sheets/issues
+96 -78
View File
@@ -2,6 +2,7 @@
import re import re
import warnings import warnings
import math
from .stats import Ability, Skill, findattr from .stats import Ability, Skill, findattr
from .dice import read_dice_str from .dice import read_dice_str
@@ -86,8 +87,6 @@ class Character():
spellcasting_ability = None spellcasting_ability = None
spells = tuple() spells = tuple()
spells_prepared = tuple() spells_prepared = tuple()
# Druid wilf shape transofmration options
_wild_shapes = ()
def __init__(self, **attrs): def __init__(self, **attrs):
"""Takes a bunch of attrs and passes them to ``set_attrs``""" """Takes a bunch of attrs and passes them to ``set_attrs``"""
@@ -100,82 +99,6 @@ class Character():
def __repr__(self): def __repr__(self):
return f"<{self.class_name}: {self.name}>" return f"<{self.class_name}: {self.name}>"
@property
def all_wild_shapes(self):
"""Return all wild shapes, regardless of validity."""
return self._wild_shapes
@property
def wild_shapes(self):
"""Return a list of valid wild shapes for this Druid."""
valid_shapes = []
for shape in self._wild_shapes:
# Check if shape can be transformed into
if self.can_assume_shape(shape):
valid_shapes.append(shape)
return valid_shapes
@wild_shapes.setter
def wild_shapes(self, new_shapes):
actual_shapes = []
# Retrieve the actual monster classes if possible
for shape in new_shapes:
if isinstance(shape, monsters.Monster):
# Already a monster shape so just add it as is
new_shape = shape
else:
# Not already a monster so see if we can find one
try:
NewMonster = findattr(monsters, shape)
new_shape = NewMonster()
except AttributeError:
msg = f'Wild shape "{shape}" not found. Please add it to ``monsters.py``'
raise exceptions.MonsterError(msg)
actual_shapes.append(new_shape)
# Save the updated list for later
self._wild_shapes = actual_shapes
def can_assume_shape(self, shape: monsters.Monster)-> bool:
"""Determine if a given shape meets the requirements for transforming.
See Pg 66 of player's handbook.
Parameters
==========
shape
A monster that the Druid wishes to transform into.
Returns
=======
can_assume
True if the monster meets the C/R, swim and flying speed
restrictions.
"""
# Determine acceptable states based on druid level
if self.level < 2:
max_cr = -1
max_swim = 0
max_fly = 0
elif self.level < 4:
max_cr = 1/4
max_swim = 0
max_fly = 0
elif self.level < 8:
max_cr = 1/2
max_swim = None
max_fly = 0
else:
max_cr = None
max_swim = None
max_fly = None
# Check if the beast shape can be assumed
valid_cr = (max_cr is None or shape.challenge_rating <= max_cr)
valid_swim = (max_swim is None or shape.swim_speed <= max_swim)
valid_fly = (max_fly is None or shape.fly_speed <= max_fly)
can_assume = shape.is_beast and valid_cr and valid_swim and valid_fly
return can_assume
@property @property
def speed(self): def speed(self):
return getattr(self.race, 'speed', 30) return getattr(self.race, 'speed', 30)
@@ -424,6 +347,8 @@ class Cleric(Character):
class Druid(Character): class Druid(Character):
class_name = 'Druid' class_name = 'Druid'
circle = "" # Moon, land
_wild_shapes = ()
hit_dice_faces = 8 hit_dice_faces = 8
saving_throw_proficiencies = ('intelligence', 'wisdom') saving_throw_proficiencies = ('intelligence', 'wisdom')
spellcasting_ability = 'wisdom' spellcasting_ability = 'wisdom'
@@ -461,6 +386,99 @@ class Druid(Character):
20: (4, 4, 3, 3, 3, 3, 2, 2, 1, 1), 20: (4, 4, 3, 3, 3, 3, 2, 2, 1, 1),
} }
@property
def all_wild_shapes(self):
"""Return all wild shapes, regardless of validity."""
return self._wild_shapes
@property
def wild_shapes(self):
"""Return a list of valid wild shapes for this Druid."""
valid_shapes = []
for shape in self._wild_shapes:
# Check if shape can be transformed into
if self.can_assume_shape(shape):
valid_shapes.append(shape)
return valid_shapes
@wild_shapes.setter
def wild_shapes(self, new_shapes):
actual_shapes = []
# Retrieve the actual monster classes if possible
for shape in new_shapes:
if isinstance(shape, monsters.Monster):
# Already a monster shape so just add it as is
new_shape = shape
else:
# Not already a monster so see if we can find one
try:
NewMonster = findattr(monsters, shape)
new_shape = NewMonster()
except AttributeError:
msg = f'Wild shape "{shape}" not found. Please add it to ``monsters.py``'
raise exceptions.MonsterError(msg)
actual_shapes.append(new_shape)
# Save the updated list for later
self._wild_shapes = actual_shapes
def can_assume_shape(self, shape: monsters.Monster)-> bool:
"""Determine if a given shape meets the requirements for transforming.
See Pg 66 of player's handbook.
Parameters
==========
shape
A monster that the Druid wishes to transform into.
Returns
=======
can_assume
True if the monster meets the C/R, swim and flying speed
restrictions.
"""
# Determine acceptable states based on druid level
if self.level < 2:
max_cr = -1
max_swim = 0
max_fly = 0
elif self.level < 4:
max_cr = 1/4
max_swim = 0
max_fly = 0
elif self.level < 8:
max_cr = 1/2
max_swim = None
max_fly = 0
else:
max_cr = 1
max_swim = None
max_fly = None
# Make adjustments for moon cirlce druids
if self.circle.lower() == "moon":
if 2 <= self.level < 6:
max_cr = 1
elif self.level >= 6:
max_cr = math.floor(self.level / 3)
# Check if the beast shape can be assumed
valid_cr = (max_cr is None or shape.challenge_rating <= max_cr)
valid_swim = (max_swim is None or shape.swim_speed <= max_swim)
valid_fly = (max_fly is None or shape.fly_speed <= max_fly)
can_assume = shape.is_beast and valid_cr and valid_swim and valid_fly
return can_assume
@property
def spells(self):
return tuple(S() for S in self.spells_prepared)
@spells.setter
def spells(self, val):
if len(val) > 0:
warnings.warn("Druids cannot learn spells, "
"use ``spells_prepared`` instead.",
RuntimeWarning)
class Fighter(Character): class Fighter(Character):
class_name = 'Fighter' class_name = 'Fighter'
+12 -2
View File
@@ -26,9 +26,17 @@
] ]
[% for shape in character.wild_shapes|sort(attribute='challenge_rating') %] [% for shape in character.all_wild_shapes|sort(attribute='challenge_rating') %]
[% if not character.can_assume_shape(shape) %]
{\color{mygrey}
[% else %]
{
[% endif %]
\section*{[[ shape.name ]]} \section*{[[ shape.name ]]}
[% if shape.description %]
\subsection*{[[ shape.description ]]} \subsection*{[[ shape.description ]]}
[% endif %]
\begin{tabular}{c | c | c} \begin{tabular}{c | c | c}
Armor Class & Hit Points & Speed \\ Armor Class & Hit Points & Speed \\
@@ -57,15 +65,17 @@
\vspace{0.2cm} \vspace{0.2cm}
\begin{tabular}{l l} \begin{tabular}{p{0.1\textwidth} p{0.32\textwidth}}
\textbf{Skills:} & [[ shape.skills ]] \\ \textbf{Skills:} & [[ shape.skills ]] \\
\textbf{Senses:} & [[ shape.senses ]] \\ \textbf{Senses:} & [[ shape.senses ]] \\
\textbf{Languages:} & [[ shape.languages ]] \\
\end{tabular} \end{tabular}
\vspace{0.2cm} \vspace{0.2cm}
[[ shape.__doc__ | rst_to_latex ]] [[ shape.__doc__ | rst_to_latex ]]
} %\color
[% endfor %] [% endfor %]
\end{document} \end{document}
+8 -2
View File
@@ -24,12 +24,18 @@ character sheet."""
bold_re = re.compile(r'\*\*([^*]+)\*\*') bold_re = re.compile(r'\*\*([^*]+)\*\*')
it_re = re.compile(r'\*([^*]+)\*') it_re = re.compile(r'\*([^*]+)\*')
tt_re = re.compile(r'``([^`]+)``') tt_re = re.compile(r'``([^`]+)``')
# A dice string, with optinal backticks: ``1d6 + 3``
dice_re = re.compile(r'`*(\d+d\d+(?:\s*\+\s*\d+)?)`*')
def rst_to_latex(rst): def rst_to_latex(rst):
"""Basic markup of RST to LaTeX code.""" """Basic markup of RST to LaTeX code."""
if rst is None:
tex = ""
else:
tex = rst tex = rst
tex = bold_re.sub(r'\\textbf{\1}', tex) tex = bold_re.sub(r'\\textbf{\1}', tex)
tex = it_re.sub(r'\\textit{\1}', tex) tex = it_re.sub(r'\\textit{\1}', tex)
tex = dice_re.sub(r'\\texttt{\1}', tex)
tex = tt_re.sub(r'\\texttt{\1}', tex) tex = tt_re.sub(r'\\texttt{\1}', tex)
return tex return tex
@@ -437,7 +443,6 @@ def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
def make_sheet(character_file, flatten=False): def make_sheet(character_file, flatten=False):
"""Prepare a PDF character sheet from the given character file. """Prepare a PDF character sheet from the given character file.
Parameters Parameters
@@ -472,7 +477,8 @@ def make_sheet(character_file, flatten=False):
else: else:
sheets.append(spellbook_base + '.pdf') sheets.append(spellbook_base + '.pdf')
# Create a list of Druid wild_shapes # Create a list of Druid wild_shapes
if len(char.wild_shapes) > 0: wild_shapes = getattr(char, 'wild_shapes', [])
if len(wild_shapes) > 0:
shapes_base = os.path.splitext(character_file)[0] + '_wild_shapes' shapes_base = os.path.splitext(character_file)[0] + '_wild_shapes'
try: try:
create_druid_shapes_pdf(character=char, basename=shapes_base) create_druid_shapes_pdf(character=char, basename=shapes_base)
+122 -3
View File
@@ -12,6 +12,8 @@ class Monster():
challenge_rating = 0 challenge_rating = 0
armor_class = 0 armor_class = 0
skills = "Perception +3, Stealth +4" skills = "Perception +3, Stealth +4"
senses = ""
languages = ""
strength = Ability() strength = Ability()
dexterity = Ability() dexterity = Ability()
constitution = Ability() constitution = Ability()
@@ -30,12 +32,129 @@ class Monster():
return is_beast return is_beast
class Crocodile(Monster): class Ankylosaurus(Monster):
name = "Crocodile" """Thick armor plating covers the body of the plant-eating dinosaur
ankylosaurus, which defends itself against predators with a
knobbed tail that delivers a devastating strike.
**Tail:** *Melee Weapon Attack:* +7 to hit, reach 10 ft., one
target. *Hit:* 18 (4d6+4) bludgeoning damage. If the target is a
creature, it must succeed on a DC 14 Strength saving throw or be
knocked prone.
"""
name = "Ankylosaurus"
description = "Huge beast, unaligned"
challenge_rating = 3
armor_class = 15
skills = ""
senses = "Passive perception 11"
strength = Ability(19)
dexterity = Ability(11)
constitution = Ability(15)
intelligence = Ability(2)
wisdom = Ability(12)
charisma = Ability(5)
speed = 30
swim_speed = 0
fly_speed = 0
hp_max = 68
hit_dice = '8d12+16'
class Ape(Monster):
"""**Multiattack:** The ape makes two fist attacks.
**Fist:** *Melee Weapon Attack:* +5 to hit, reach 5 ft., one
target. *Hit:* 6 (1d6+3) bludgeoning damage.
**Rock:** *Ranged Weapon Attack:* +5 to hit, range 25/50 ft., one
target. *Hit:* 6 (1d6+3) bludgeoning damage.
"""
name = "Ape"
description = "Medium beast, unaligned"
challenge_rating = 1 / 2
armor_class = 12
skills = "Athletics +5, Perception +3"
senses = "Passive perception 13"
strength = Ability(16)
dexterity = Ability(14)
constitution = Ability(14)
intelligence = Ability(6)
wisdom = Ability(12)
charisma = Ability(7)
speed = 30
swim_speed = 0
fly_speed = 0
hp_max = 19
hit_dice = '3d8+6'
class Crocodile(Monster):
"""**Hold Breath:** The crocodile can hold its breath for 15 minutes.
**Bite:** *Melee Weapon Attack:* +4 to hit, reach 5 ft., one
creature. *Hit:* 7 (1d10+2) piercing damage, and the target is
Grappled (escape DC 12). Until this grapple ends, the target is
Restrained, and the crocodile can't bite another target.
"""
name = "Crocodile"
description = "Large beast, unaligned"
challenge_rating = 1/2
armor_class = 12
skills = "Stealth +2"
senses = "Passive perception 10"
strength = Ability(15)
dexterity = Ability(10)
constitution = Ability(13)
intelligence = Ability(2)
wisdom = Ability(10)
charisma = Ability(5)
speed = 30
swim_speed = 30
fly_speed = 0
hp_max = 19
hit_dice = '3d10+3'
class GiantEagle(Monster): class GiantEagle(Monster):
"""A giant eagle is a noble creature that speaks its own language and
understands Speech in the Common tongue. A mated pair of giant
eagles typically has up to four eggs or young in their nest (treat
the young as normal eagles).
**Keen Sight:** The eagle has advantage on Wisdom (Perception)
checks that rely on sight.
**Multiattack:** The eagle makes two attacks: one with its beak
and one with its talons.
**Beak:** *Melee Weapon Attack:* +5 to hit, reach 5 ft., one
target. *Hit:* 6 (1d6 + 3) piercing damage.
**Talons:** *Melee Weapon Attack:* +5 to hit, reach 5 ft., one
target. *Hit:* 10 (2d6 + 3) slashing damage.
"""
name = "Giant eagle" name = "Giant eagle"
description = "Large beast, neutral good"
challenge_rating = 1
armor_class = 13
skills = "Perception +4"
senses = "Passive perception 14"
languages = "Giant Eagle, understands common and Auran but can't speak."
strength = Ability(16)
dexterity = Ability(17)
constitution = Ability(13)
intelligence = Ability(8)
wisdom = Ability(14)
charisma = Ability(10)
speed = 10
swim_speed = 0
fly_speed = 80
hp_max = 26
hit_dice = '4d10+4'
class Spider(Monster): class Spider(Monster):
@@ -105,7 +224,7 @@ class Wolf(Monster):
the creature and the ally isn't incapacitated. Actions the creature and the ally isn't incapacitated. Actions
**Bite.** *Melee Weapon Attack:* +4 to hit, reach 5 ft., one **Bite.** *Melee Weapon Attack:* +4 to hit, reach 5 ft., one
target. *Hit:* (2d4 + 2) piercing damage. If the target is a target. *Hit:* (2d4+2) piercing damage. If the target is a
creature, it must succeed on a DC 11 Strength saving throw or be creature, it must succeed on a DC 11 Strength saving throw or be
knocked prone knocked prone
Binary file not shown.
+4 -5
View File
@@ -9,6 +9,7 @@ dungeonsheets_version = "0.5.0"
name = 'Dain Torunn' name = 'Dain Torunn'
character_class = 'Druid' character_class = 'Druid'
circle = 'moon'
player_name = 'Emily' player_name = 'Emily'
background = "Sailor" background = "Sailor"
race = "Hill Dwarf" race = "Hill Dwarf"
@@ -45,13 +46,11 @@ shield = "" # Eg "shield"
equipment = "TODO: Describe your equipment from your Druid class and Sailor background." equipment = "TODO: Describe your equipment from your Druid class and Sailor background."
attacks_and_spellcasting = "TODO: Describe specifics for how your Druid attacks." attacks_and_spellcasting = "TODO: Describe specifics for how your Druid attacks."
wild_shapes = ["wolf", "crocodile", "giant eagle"] wild_shapes = ["wolf", "crocodile", "giant eagle", 'ape', 'ankylosaurus']
# List of known spells # List of known spells
# Example: spells = ('magic missile', 'mage armor') # Which spells have been prepared (including cantrips)
spells = () # Todo: Learn some spells spells_prepared = ('shillelagh', 'poison spray', 'druidcraft','speak with animals', 'entangle', 'cure wounds', 'create or destroy water')
# Which spells have been prepared (not including cantrips)
spells_prepared = ()
# Backstory # Backstory
# TODO: Describe your backstory here # TODO: Describe your backstory here
Binary file not shown.
Binary file not shown.
Binary file not shown.
+57 -33
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
from unittest import TestCase from unittest import TestCase
import warnings
from dungeonsheets import race, monsters, exceptions from dungeonsheets import race, monsters, exceptions, spells
from dungeonsheets.character import Character, Wizard, Druid from dungeonsheets.character import Character, Wizard, Druid
from dungeonsheets.weapons import Weapon, Shortsword from dungeonsheets.weapons import Weapon, Shortsword
from dungeonsheets.armor import Armor, LeatherArmor, Shield from dungeonsheets.armor import Armor, LeatherArmor, Shield
@@ -131,6 +132,51 @@ class TestCharacter(TestCase):
self.assertEqual(char.spell_slots(spell_level=1), 3) self.assertEqual(char.spell_slots(spell_level=1), 3)
self.assertEqual(char.spell_slots(spell_level=2), 0) self.assertEqual(char.spell_slots(spell_level=2), 0)
def test_equip_armor(self):
char = Character(dexterity=16)
char.wear_armor('leather armor')
self.assertTrue(isinstance(char.armor, Armor))
# Now make sure the armor class is correct
self.assertEqual(char.armor_class, 14)
# Try passing an Armor object directly
char.wear_armor(LeatherArmor())
self.assertEqual(char.armor_class, 14)
# Test equipped armor with max dexterity mod_str
char.armor.dexterity_mod_max = 1
self.assertEqual(char.armor_class, 12)
def test_wield_shield(self):
char = Character(dexterity=16)
char.wield_shield('shield')
self.assertTrue(isinstance(char.shield, Shield), msg=char.shield)
# Now make sure the armor class is correct
self.assertEqual(char.armor_class, 15)
# Try passing an Armor object directly
char.wield_shield(Shield)
self.assertEqual(char.armor_class, 15)
def test_speed(self):
# Check that the speed pulls from the character's race
char = Character(race='halfling')
self.assertEqual(char.speed, 25)
# Check that a character with no race defaults to 30 feet
char = Character()
char.race = None
self.assertEqual(char.speed, 30)
class DruidTestCase(TestCase):
def test_learned_spells(self):
"""For a druid, learning spells is not necessary and this field should
be ignored."""
char = Druid()
with warnings.catch_warnings():
warnings.filterwarnings('ignore', message="Druids cannot learn spells")
char.set_attrs(spells=['invisibility'],
spells_prepared=['druidcraft'])
self.assertEqual(len(char.spells), 1)
self.assertIsInstance(char.spells[0], spells.Druidcraft)
def test_wild_shapes(self): def test_wild_shapes(self):
char = Druid() char = Druid()
# Druid level 2 # Druid level 2
@@ -149,6 +195,16 @@ class TestCharacter(TestCase):
self.assertEqual(len(char.wild_shapes), 1) self.assertEqual(len(char.wild_shapes), 1)
self.assertIsInstance(char.wild_shapes[0], monsters.Wolf) self.assertIsInstance(char.wild_shapes[0], monsters.Wolf)
def test_moon_druid_wild_shapes(self):
# Moon druid level 2 gets beasts up to CR 1
char = Druid(level=2, wild_shapes=['Ape'], circle='moon')
self.assertEqual(len(char.wild_shapes), 1)
self.assertIsInstance(char.wild_shapes[0], monsters.Ape)
# Moon druid above level 6 gets beasts up to CR level / 3
char = Druid(level=9, wild_shapes=['ankylosaurus'], circle='moon')
self.assertEqual(len(char.wild_shapes), 1)
self.assertIsInstance(char.wild_shapes[0], monsters.Ankylosaurus)
def test_can_assume_shape(self): def test_can_assume_shape(self):
class Beast(monsters.Monster): class Beast(monsters.Monster):
description = 'beast' description = 'beast'
@@ -188,35 +244,3 @@ class TestCharacter(TestCase):
not_beast = monsters.Monster() not_beast = monsters.Monster()
not_beast.description = "monster" not_beast.description = "monster"
self.assertFalse(low_druid.can_assume_shape(not_beast)) self.assertFalse(low_druid.can_assume_shape(not_beast))
def test_equip_armor(self):
char = Character(dexterity=16)
char.wear_armor('leather armor')
self.assertTrue(isinstance(char.armor, Armor))
# Now make sure the armor class is correct
self.assertEqual(char.armor_class, 14)
# Try passing an Armor object directly
char.wear_armor(LeatherArmor())
self.assertEqual(char.armor_class, 14)
# Test equipped armor with max dexterity mod_str
char.armor.dexterity_mod_max = 1
self.assertEqual(char.armor_class, 12)
def test_wield_shield(self):
char = Character(dexterity=16)
char.wield_shield('shield')
self.assertTrue(isinstance(char.shield, Shield), msg=char.shield)
# Now make sure the armor class is correct
self.assertEqual(char.armor_class, 15)
# Try passing an Armor object directly
char.wield_shield(Shield)
self.assertEqual(char.armor_class, 15)
def test_speed(self):
# Check that the speed pulls from the character's race
char = Character(race='halfling')
self.assertEqual(char.speed, 25)
# Check that a character with no race defaults to 30 feet
char = Character()
char.race = None
self.assertEqual(char.speed, 30)
+17
View File
@@ -30,3 +30,20 @@ class PdfOutputTeestCase(unittest.TestCase):
char.saving_throw_proficiencies = ['strength'] char.saving_throw_proficiencies = ['strength']
make_sheets.create_character_pdf(character=char, basename=self.basename) make_sheets.create_character_pdf(character=char, basename=self.basename)
self.assertTrue(os.path.exists(pdf_name), f'{pdf_name} not created.') self.assertTrue(os.path.exists(pdf_name), f'{pdf_name} not created.')
class MarkdownTestCase(unittest.TestCase):
"""Check that conversion of markdown formats to LaTeX code works
correctly."""
def test_rst_bold(self):
text = make_sheets.rst_to_latex('**hello**')
self.assertEqual(text, '\\textbf{hello}')
def test_hit_dice(self):
text = make_sheets.rst_to_latex('1d6+3')
self.assertEqual(text, '\\texttt{1d6+3}')
def test_no_text(self):
text = make_sheets.rst_to_latex(None)
self.assertEqual(text, '')