mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-19 04:33:26 +02:00
Ability to output GM sheets as EPUB files (without CSS for now).
This commit is contained in:
@@ -24,6 +24,7 @@ script:
|
||||
- cd examples/
|
||||
- makesheets --debug
|
||||
- makesheets --debug --fancy
|
||||
- makesheets --debug --output-format=epub
|
||||
- cd ../
|
||||
after_success:
|
||||
- coveralls
|
||||
@@ -0,0 +1,139 @@
|
||||
from typing import Mapping
|
||||
|
||||
from ebooklib import epub
|
||||
from docutils import core
|
||||
from sphinx.util.docstrings import prepare_docstring
|
||||
from docutils.writers.html5_polyglot import Writer as HTMLWriter
|
||||
|
||||
from dungeonsheets.latex import dice_re
|
||||
|
||||
|
||||
def create_epub(
|
||||
chapters: Mapping,
|
||||
title: str,
|
||||
basename: str,
|
||||
use_dnd_decorations: bool = False
|
||||
):
|
||||
"""Prepare an EPUB file from the list of chapters.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
chapters
|
||||
A mapping where the keys are chapter names (spines) and the
|
||||
values are strings of HTML to be rendered as the chapter
|
||||
contents.
|
||||
basename
|
||||
The basename for saving files (PDFs, etc). The resulting epub
|
||||
file will be "{basename}.epub".
|
||||
use_dnd_decorations
|
||||
If true, style sheets will be included to produce D&D stylized
|
||||
stat blocks, etc.
|
||||
|
||||
"""
|
||||
# Create a new epub book
|
||||
book = epub.EpubBook()
|
||||
book.set_identifier('id123456')
|
||||
book.set_title(title)
|
||||
book.set_language('en')
|
||||
# Create the separate chapters
|
||||
html_chapters = []
|
||||
for chap_title, content in chapters.items():
|
||||
chap_fname = "{}.html".format(chap_title.replace(" ", "_").lower())
|
||||
chapter = epub.EpubHtml(title=chap_title, file_name=chap_fname, lang="en")
|
||||
chapter.set_content(content)
|
||||
book.add_item(chapter)
|
||||
html_chapters.append(chapter)
|
||||
# Add the table of contents
|
||||
book.toc = html_chapters
|
||||
book.spine = ("nav", *html_chapters)
|
||||
# add default NCX and Nav file
|
||||
book.add_item(epub.EpubNcx())
|
||||
book.add_item(epub.EpubNav())
|
||||
# Save the file
|
||||
epub_fname = f"{basename}.epub"
|
||||
epub.write_epub(epub_fname, book)
|
||||
|
||||
|
||||
def html_parts(
|
||||
input_string,
|
||||
source_path=None,
|
||||
destination_path=None,
|
||||
input_encoding="unicode",
|
||||
doctitle=True,
|
||||
initial_header_level=1,
|
||||
):
|
||||
"""
|
||||
Given an input string, returns a dictionary of HTML document parts.
|
||||
|
||||
Dictionary keys are the names of parts, and values are Unicode strings;
|
||||
encoding is up to the client.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `input_string`: A multi-line text string; required.
|
||||
- `source_path`: Path to the source file or object. Optional, but useful
|
||||
for diagnostic output (system messages).
|
||||
- `destination_path`: Path to the file or object which will receive the
|
||||
output; optional. Used for determining relative paths (stylesheets,
|
||||
source links, etc.).
|
||||
- `input_encoding`: The encoding of `input_string`. If it is an encoded
|
||||
8-bit string, provide the correct encoding. If it is a Unicode string,
|
||||
use "unicode", the default.
|
||||
- `doctitle`: Disable the promotion of a lone top-level section title to
|
||||
document title (and subsequent section title to document subtitle
|
||||
promotion); enabled by default.
|
||||
- `initial_header_level`: The initial level for header elements (e.g. 1
|
||||
for "<h1>").
|
||||
"""
|
||||
# Remove indentation, etc
|
||||
input_string = "\n".join(prepare_docstring(input_string))
|
||||
# Parse from rst to TeX
|
||||
overrides = {
|
||||
"input_encoding": input_encoding,
|
||||
"doctitle_xform": doctitle,
|
||||
"initial_header_level": initial_header_level,
|
||||
}
|
||||
writer = HTMLWriter()
|
||||
parts = core.publish_parts(
|
||||
source=input_string,
|
||||
source_path=source_path,
|
||||
destination_path=destination_path,
|
||||
writer=writer,
|
||||
settings_overrides=overrides,
|
||||
)
|
||||
return parts
|
||||
|
||||
|
||||
def rst_to_html(rst, top_heading_level=0):
|
||||
"""Basic markup of reST to HTML code.
|
||||
|
||||
The translation between reST headings and LaTeX headings is
|
||||
modified by the *top_heading_level* parameter. A value of 0
|
||||
(default) translates "# Heading" -> "<h1>{Heading}</h1>". A value
|
||||
of 1 translates "# Heading" -> "<h2>{Heading}</h2>", etc.
|
||||
|
||||
Note: heading translation is currently broken.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
rst
|
||||
reStructured text input to be parsed.
|
||||
top_heading_level : optional
|
||||
The highest level heading that will be added to the HTML as
|
||||
described above.
|
||||
|
||||
Returns
|
||||
=======
|
||||
html : str
|
||||
The reST text parsed into HTML markup.
|
||||
|
||||
"""
|
||||
if rst is None:
|
||||
# No reST, so return an empty string
|
||||
html = ""
|
||||
else:
|
||||
# Mark hit dice in monospace font
|
||||
rst = dice_re.sub(r"``\1``", rst)
|
||||
_html_parts = html_parts(rst)
|
||||
html = _html_parts["body"]
|
||||
return html
|
||||
@@ -24,3 +24,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."""
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<h1 id="gm-monsters">Monsters</h1>
|
||||
|
||||
[% for monster in monsters|sort(attribute='name') %]
|
||||
<h1 id="gm-monsters-[[ monster.name ]]">[[ monster.name ]]</h1>
|
||||
|
||||
[% if monster.description %]
|
||||
<h2>[[ monster.description ]]</h2>
|
||||
[% endif %]
|
||||
|
||||
<!-- Basic properties -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>Armor Class</th>
|
||||
<th>Hit Points</th>
|
||||
<th>Speed</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[[ monster.armor_class ]]</td>
|
||||
<td>[[ monster.hp_max ]] ([[ monster.hit_dice ]])</td>
|
||||
<td>[[ monster.speed ]][% if monster.swim_speed %],
|
||||
[[ monster.swim_speed ]] swim[% endif %][% if monster.fly_speed %],
|
||||
[[ monster.fly_speed ]] fly[% endif %][% if monster.burrow_speed %],
|
||||
[[ monster.burrow_speed ]] burrow[% endif %]</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Attributes -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>STR</th>
|
||||
<th>DEX</th>
|
||||
<th>CON</th>
|
||||
<th>INT</th>
|
||||
<th>WIS</th>
|
||||
<th>CHA</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[[ monster.strength.value ]]</td>
|
||||
<td>[[ monster.dexterity.value ]]</td>
|
||||
<td>[[ monster.constitution.value ]]</td>
|
||||
<td>[[ monster.intelligence.value ]]</td>
|
||||
<td>[[ monster.wisdom.value ]]</td>
|
||||
<td>[[ monster.charisma.value ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>([[ monster.strength.modifier|mod_str ]])</td>
|
||||
<td>([[ monster.dexterity.modifier|mod_str ]])</td>
|
||||
<td>([[ monster.constitution.modifier|mod_str ]])</td>
|
||||
<td>([[ monster.intelligence.modifier|mod_str ]])</td>
|
||||
<td>([[ monster.wisdom.modifier|mod_str ]])</td>
|
||||
<td>([[ monster.charisma.modifier|mod_str ]])</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<dl>
|
||||
[% if monster.skills != "" %]<dt>Skills:</dt><dd>[[ monster.skills ]]</dd>[% endif %]
|
||||
<dt>Senses:</dt><dd>[% if monster.senses != "" %][[ monster.senses ]][% else %]--[% endif %]</dd>
|
||||
<dt>Languages:</dt><dd>[% if monster.languages != "" %][[ monster.languages ]][% else %]--[% endif %]</dd>
|
||||
[% if monster.damage_resistances != "" %]<dt>Damage Resistances:</dt><dd>[[ monster.damage_resistances ]]</dd>[% endif %]
|
||||
[% if monster.damage_immunities != "" %]<dt>Damage Immunities:</dt><dd>[[ monster.damage_immunities ]]</dd>[% endif %]
|
||||
[% if monster.damage_vulnerabilities != "" %]<dt>Damage Vulnerabilities:</dt><dd>[[ monster.damage_vulnerabilities ]]</dd>[% endif %]
|
||||
[% if monster.condition_immunities != "" %]<dt>Condition Immunuties:</dt><dd>[[ monster.condition_immunities ]]</dd>[% endif %]
|
||||
[% if monster.saving_throws != "" %]<dt>Saving Throws:</dt><dd>[[ monster.saving_throws ]]</dd>[% endif %]
|
||||
<dt>Challenge:<dd>[[ monster.challenge_rating ]]</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
[[ monster.__doc__ | rst_to_html(top_heading_level=2) ]]
|
||||
|
||||
[% endfor %]
|
||||
@@ -0,0 +1,56 @@
|
||||
[% if summary %]
|
||||
|
||||
<h1 id="gm-summary">Summary</h1>
|
||||
|
||||
[[ summary | rst_to_html ]]
|
||||
|
||||
[% endif %]
|
||||
|
||||
[% if party %]
|
||||
|
||||
<h1 id="gm-party">Party</h1>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Str</th>
|
||||
<th>Dex</th>
|
||||
<th>Con</th>
|
||||
<th>Int</th>
|
||||
<th>Wis</th>
|
||||
<th>Cha</th>
|
||||
</tr>
|
||||
[% for member in party %]
|
||||
<tr>
|
||||
<td>[[ member.name ]]</td>
|
||||
<td>[[ member.strength.modifier | mod_str ]]</td>
|
||||
<td>[[ member.dexterity.modifier | mod_str ]]</td>
|
||||
<td>[[ member.constitution.modifier | mod_str ]]</td>
|
||||
<td>[[ member.intelligence.modifier | mod_str ]]</td>
|
||||
<td>[[ member.wisdom.modifier | mod_str ]]</td>
|
||||
<td>[[ member.charisma.modifier | mod_str ]]</td>
|
||||
</tr>
|
||||
[% endfor %]
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>AC</th>
|
||||
<th>Pass. Per.</th>
|
||||
<th>Spell DC</th>
|
||||
</tr>
|
||||
[% for member in party %]
|
||||
<tr>
|
||||
<td>[[ member.name[:28] ]]</td>
|
||||
<td>[[ member.armor_class ]]</td>
|
||||
<td>[[ member.perception + 10 ]]</td>
|
||||
<td>[% for class in member.class_list %]
|
||||
[% if class.spellcasting_ability %] [[ member.spell_save_dc(class) ]], [% endif %]
|
||||
[% endfor %]
|
||||
</td>
|
||||
</tr>
|
||||
[% endfor %]
|
||||
</table>
|
||||
|
||||
[% endif %]
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My title</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -0,0 +1,539 @@
|
||||
<h1 id="gm-random-tables">Random Tables</h1>
|
||||
|
||||
[% if conjure_animals %]
|
||||
|
||||
<!-- https://the-azure-triskele.obsidianportal.com/wikis/conjure-animals-table -->
|
||||
<h2 id="gm-random-tables-conjure-animals">Conjure Animals</h2>
|
||||
|
||||
<!-- Which category of beasts to summon -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d4</th>
|
||||
<th>Number of Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>One beast of challenge rating 2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Two beasts of challenge rating 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Four beasts of challenge rating 1/2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Eight beasts of challenge rating 1/4 or lower</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- CR2 Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>CR2 Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-2</td>
|
||||
<td>Allosaurus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-4</td>
|
||||
<td>Giant Boar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5-6</td>
|
||||
<td>Giant Constrictor Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7-8</td>
|
||||
<td>Giant Elk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9-10</td>
|
||||
<td>Hunter Shark</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Plesiosaurus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12-13</td>
|
||||
<td>Polar Bear</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14-15</td>
|
||||
<td>Rhinoceros</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16-17</td>
|
||||
<td>Saber-toothed Tiger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18-19</td>
|
||||
<td>Swarm of Poisonous Snakes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Roll on CR 1 Beast Table</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- CR1 Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d12</th>
|
||||
<th>Challenge Rating 1 Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Brown Bear</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Dire Wolf</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Fire Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Giant Eagle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Giant Hyena</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Giant Octopus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Giant Spider</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Giant Toad</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Giant Vulture</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Lion</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Tiger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Roll on CR ½ Beast Table</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Challenge Rating ½ Beasts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-2</td>
|
||||
<td>Ape</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-4</td>
|
||||
<td>Black Bear</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5-6</td>
|
||||
<td>Crocodile</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7-8</td>
|
||||
<td>Giant Goat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9-10</td>
|
||||
<td>Giant Sea Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11-12</td>
|
||||
<td>Giant Wasp</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13-14</td>
|
||||
<td>Reef Shark</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15-16</td>
|
||||
<td>Swarm of Insects (below)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17-18</td>
|
||||
<td>Warhorse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Worg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Roll on Lesser Beast Menu Table</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- Swarm of insects (mostly for flavor) -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d6</th>
|
||||
<th>Swarm of Insects</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Ant</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Beatles</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Centipedes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Locusts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Spiders</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Wasps</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Challenge Rating 1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d6</th>
|
||||
<th>CR ¼ and Lesser Beast Menu</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1-2</td>
|
||||
<td>Menu A</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3-4</td>
|
||||
<td>Menu B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5-6</td>
|
||||
<td>Menu C</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- CR1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Lesser Beast Menu A</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Axe Beak</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Baboon</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Badger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Bat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Blood Hawk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Boar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Camel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Cat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Chicken*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Constrictor Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Crab</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Deer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td>Draft Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>Eagle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>Elk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>Flying Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17</td>
|
||||
<td>Frog</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18</td>
|
||||
<td>Giant Badger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Giant Bat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Giant Centipede</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<dl>
|
||||
<dt>*Chicken:</dt>
|
||||
<dd>Raven stats with Advantage on checks to wake
|
||||
up targets instead of mimicry</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<!-- CR1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Lesser Beast Menu B</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Giant Crab </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Giant Fire Beetle </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Giant Frog</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Giant Lizard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Giant Owl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Giant Poisonous Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Giant Rat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Giant Weasel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Giant Wolf Spider</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Goat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Hawk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Hyena</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td>Jackal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>Lemur*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>Lizard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>Mastiff</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17</td>
|
||||
<td>Mule</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18</td>
|
||||
<td>Newt**</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Octopus</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Octopus, Cascadian Tree***</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<dl>
|
||||
<dt>*Lemur</dt>
|
||||
<dd>Weasel stats with a common Climb speed instead of a
|
||||
bite attack</dd>
|
||||
<dt>**Newt:</dt>
|
||||
<dd>Lizard stats with Amphibious instead of a bite
|
||||
attack</dd>
|
||||
<dt>***Octopus, Cascadian Tree:</dt>
|
||||
<dd>Octopus stats with Amphibious
|
||||
and a 10 ft land speed instead of camouflage</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<!-- CR1/4 and Lesser Beasts -->
|
||||
<table>
|
||||
<tr>
|
||||
<th>1d20</th>
|
||||
<th>Lesser Beast Menu C</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Owl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Panther</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Poisonous Snake</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Pony</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Pteranodon</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Quipper</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Rat</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Raven</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Riding Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Scorpion</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Sea Horse</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Shocker Lizard*</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td>Spider</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>Swarm of Bats</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>Swarm of Rats</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>Swarm of Ravens</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>17</td>
|
||||
<td>Turtle**</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>18</td>
|
||||
<td>Vulture</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19</td>
|
||||
<td>Weasel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>Wolf</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<dl>
|
||||
<dt>*Shocker Lizard:</dt>
|
||||
<dd>Lizard stats with Static Electricity ranged attack of 1d6
|
||||
Electricity damage Close/Medium.</dd>
|
||||
<dt>**Turtle:</dt>
|
||||
<dd>Lizard stats with 14 natural armor and no climb speed.</dd>
|
||||
</dl>
|
||||
|
||||
[% endif %]
|
||||
@@ -46,7 +46,7 @@ def _remove_temp_files(basename_):
|
||||
filename.unlink()
|
||||
|
||||
|
||||
def create_latex_pdf(tex, basename, keep_temp_files=False, use_dnd_decorations=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:
|
||||
|
||||
@@ -13,7 +13,7 @@ from typing import Union, Sequence, Optional
|
||||
|
||||
from jinja2 import Environment, PackageLoader
|
||||
|
||||
from dungeonsheets import character as _char, exceptions, readers, latex, monsters
|
||||
from dungeonsheets import character as _char, exceptions, readers, latex, epub, monsters
|
||||
from dungeonsheets.stats import mod_str
|
||||
from dungeonsheets.content_registry import find_content
|
||||
from dungeonsheets.fill_pdf_template import (
|
||||
@@ -49,6 +49,7 @@ jinja_env = Environment(
|
||||
variable_end_string="]]",
|
||||
)
|
||||
jinja_env.filters["rst_to_latex"] = latex.rst_to_latex
|
||||
jinja_env.filters["rst_to_html"] = epub.rst_to_html
|
||||
jinja_env.filters["mod_str"] = mod_str
|
||||
|
||||
|
||||
@@ -83,22 +84,24 @@ def create_magic_items_tex(
|
||||
return template.render(character=character, use_dnd_decorations=use_dnd_decorations)
|
||||
|
||||
|
||||
def create_monsters_tex(
|
||||
def create_monsters_content(
|
||||
monsters: Sequence[Union[monsters.Monster, str]],
|
||||
suffix: str,
|
||||
use_dnd_decorations: bool = False,
|
||||
) -> str:
|
||||
# Convert strings to Monster objects
|
||||
template = jinja_env.get_template("monsters_template.tex")
|
||||
template = jinja_env.get_template(f"monsters_template.{suffix}")
|
||||
return template.render(monsters=monsters, use_dnd_decorations=use_dnd_decorations)
|
||||
|
||||
|
||||
def create_party_summary_tex(
|
||||
def create_party_summary_content(
|
||||
party: Sequence[Entity],
|
||||
summary_rst: str,
|
||||
suffix: str,
|
||||
use_dnd_decorations: bool = False,
|
||||
) -> str:
|
||||
log.debug("Preparing summary table for party: %s", party)
|
||||
template = jinja_env.get_template("party_summary_template.tex")
|
||||
template = jinja_env.get_template(f"party_summary_template.{suffix}")
|
||||
return template.render(
|
||||
party=party, summary=summary_rst, use_dnd_decorations=use_dnd_decorations
|
||||
)
|
||||
@@ -130,11 +133,12 @@ def create_druid_shapes_tex(
|
||||
return template.render(character=character, use_dnd_decorations=use_dnd_decorations)
|
||||
|
||||
|
||||
def create_random_tables_tex(
|
||||
def create_random_tables_content(
|
||||
conjure_animals: bool,
|
||||
suffix: str,
|
||||
use_dnd_decorations: bool = False,
|
||||
) -> str:
|
||||
template = jinja_env.get_template("random_tables_template.tex")
|
||||
template = jinja_env.get_template(f"random_tables_template.{suffix}")
|
||||
return template.render(conjure_animals=conjure_animals,
|
||||
use_dnd_decorations=use_dnd_decorations)
|
||||
|
||||
@@ -142,6 +146,7 @@ def create_random_tables_tex(
|
||||
def make_sheet(
|
||||
sheet_file: File,
|
||||
flatten: bool = False,
|
||||
output_format: str = "pdf",
|
||||
fancy_decorations: bool = False,
|
||||
debug: bool = False,
|
||||
):
|
||||
@@ -153,6 +158,8 @@ def make_sheet(
|
||||
flatten : bool, optional
|
||||
If true, the resulting PDF will look better and won't be
|
||||
fillable form.
|
||||
output_format
|
||||
Either "pdf" or "epub" to generate a PDF file or an EPUB file.
|
||||
fancy_decorations : bool, optional
|
||||
Use fancy page layout and decorations for extra sheets, namely
|
||||
the dnd style file: https://github.com/rpgtex/DND-5e-LaTeX-Template.
|
||||
@@ -167,6 +174,7 @@ def make_sheet(
|
||||
if sheet_props.get("sheet_type", "") == "gm":
|
||||
ret = make_gm_sheet(
|
||||
gm_file=sheet_file,
|
||||
output_format=output_format,
|
||||
fancy_decorations=fancy_decorations,
|
||||
debug=debug,
|
||||
)
|
||||
@@ -180,8 +188,15 @@ def make_sheet(
|
||||
return ret
|
||||
|
||||
|
||||
format_suffixes = {
|
||||
"pdf": "tex",
|
||||
"epub": "html",
|
||||
}
|
||||
|
||||
|
||||
def make_gm_sheet(
|
||||
gm_file: Union[str, Path],
|
||||
output_format: str = "pdf",
|
||||
fancy_decorations: bool = False,
|
||||
debug: bool = False,
|
||||
):
|
||||
@@ -191,6 +206,8 @@ def make_gm_sheet(
|
||||
----------
|
||||
gm_file
|
||||
The file with the gm_sheet definitions.
|
||||
output_format
|
||||
Either "pdf" or "epub" to generate a PDF file or an EPUB file.
|
||||
fancy_decorations
|
||||
Use fancy page layout and decorations for extra sheets, namely
|
||||
the dnd style file: https://github.com/rpgtex/DND-5e-LaTeX-Template.
|
||||
@@ -203,8 +220,9 @@ def make_gm_sheet(
|
||||
basename = gm_file.stem
|
||||
gm_props = readers.read_sheet_file(gm_file)
|
||||
# Create the intro tex
|
||||
tex = [
|
||||
jinja_env.get_template("preamble.tex").render(
|
||||
content_suffix = format_suffixes[output_format]
|
||||
content = [
|
||||
jinja_env.get_template(f"preamble.{content_suffix}").render(
|
||||
use_dnd_decorations=fancy_decorations,
|
||||
title=gm_props.pop("session_title", "GM Session Notes"),
|
||||
)
|
||||
@@ -222,9 +240,9 @@ def make_gm_sheet(
|
||||
member = _char.Character.load(character_props)
|
||||
party.append(member)
|
||||
summary = gm_props.pop("summary", "")
|
||||
tex.append(
|
||||
create_party_summary_tex(
|
||||
party, summary_rst=summary, use_dnd_decorations=fancy_decorations
|
||||
content.append(
|
||||
create_party_summary_content(
|
||||
party, summary_rst=summary, suffix=content_suffix, use_dnd_decorations=fancy_decorations
|
||||
)
|
||||
)
|
||||
# Add the monsters
|
||||
@@ -244,20 +262,21 @@ def make_gm_sheet(
|
||||
new_monster = MyMonster()
|
||||
monsters_.append(new_monster)
|
||||
if len(monsters_) > 0:
|
||||
tex.append(
|
||||
create_monsters_tex(monsters_, use_dnd_decorations=fancy_decorations)
|
||||
content.append(
|
||||
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", [])]
|
||||
tex.append(
|
||||
create_random_tables_tex(
|
||||
content.append(
|
||||
create_random_tables_content(
|
||||
conjure_animals=("conjure_animals" in random_tables),
|
||||
suffix=content_suffix,
|
||||
use_dnd_decorations=fancy_decorations,
|
||||
)
|
||||
)
|
||||
# Add the closing TeX
|
||||
tex.append(
|
||||
jinja_env.get_template("postamble.tex").render(
|
||||
content.append(
|
||||
jinja_env.get_template(f"postamble.{format_suffixes[output_format]}").render(
|
||||
use_dnd_decorations=fancy_decorations
|
||||
)
|
||||
)
|
||||
@@ -268,17 +287,30 @@ def make_gm_sheet(
|
||||
msg = f"Unhandled attributes in '{str(gm_file)}': {','.join(gm_props.keys())}"
|
||||
log.warn(msg)
|
||||
warnings.warn(msg)
|
||||
# Produce the combined output depending on the format requested
|
||||
if output_format == "pdf":
|
||||
# Typeset combined LaTeX file
|
||||
try:
|
||||
if len(tex) > 2:
|
||||
if len(content) > 2:
|
||||
latex.create_latex_pdf(
|
||||
tex="".join(tex),
|
||||
tex="".join(content),
|
||||
basename=basename,
|
||||
keep_temp_files=debug,
|
||||
use_dnd_decorations=fancy_decorations,
|
||||
)
|
||||
except exceptions.LatexNotFoundError:
|
||||
log.warning(f"``pdflatex`` not available. Skipping {basename}")
|
||||
elif output_format == "epub":
|
||||
epub.create_epub(
|
||||
chapters={"GM Sheet": "".join(content)},
|
||||
basename=basename,
|
||||
title=gm_props.get("session_title", f"GM Notes: {basename}"),
|
||||
use_dnd_decorations=fancy_decorations,
|
||||
)
|
||||
else:
|
||||
raise exceptions.UnknownOutputFormat(
|
||||
f"Unknown output format requested: {output_format}. Valid options are: 'pdf', 'epub'"
|
||||
)
|
||||
|
||||
|
||||
def make_character_sheet(
|
||||
@@ -435,6 +467,7 @@ def _build(filename, args) -> int:
|
||||
make_sheet(
|
||||
sheet_file=filename,
|
||||
flatten=(not args.editable),
|
||||
output_format=args.output_format,
|
||||
debug=args.debug,
|
||||
fancy_decorations=args.fancy_decorations,
|
||||
)
|
||||
@@ -484,6 +517,12 @@ def main(args=None):
|
||||
"(experimental, requires https://github.com/rpgtex/DND-5e-LaTeX-Template)"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-format", "-o",
|
||||
help="Specify the output format for the sheets.",
|
||||
choices=["pdf", "epub"],
|
||||
default="pdf",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
"-d",
|
||||
@@ -529,7 +568,7 @@ def main(args=None):
|
||||
# Process the requested files
|
||||
if args.debug:
|
||||
for filename in filenames:
|
||||
print("building")
|
||||
log.debug("building")
|
||||
_build(filename, args)
|
||||
else:
|
||||
with Pool(cpu_count()) as p:
|
||||
|
||||
@@ -4,3 +4,4 @@ npyscreen
|
||||
jinja2
|
||||
sphinx
|
||||
pdfrw
|
||||
EbookLib
|
||||
|
||||
@@ -25,7 +25,7 @@ setup(name='dungeonsheets',
|
||||
'../VERSION']
|
||||
},
|
||||
install_requires=[
|
||||
'fdfgen', 'npyscreen', 'jinja2', 'pdfrw', 'sphinx',
|
||||
'fdfgen', 'npyscreen', 'jinja2', 'pdfrw', 'sphinx', 'EbookLib',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import unittest
|
||||
|
||||
from dungeonsheets import spells, features, epub
|
||||
|
||||
|
||||
class MarkdownTestCase(unittest.TestCase):
|
||||
"""Check that conversion of markdown formats to LaTeX code works
|
||||
correctly."""
|
||||
|
||||
def test_rst_bold(self):
|
||||
text = epub.rst_to_html("**hello**")
|
||||
self.assertEqual(text, "<p><strong>hello</strong></p>\n")
|
||||
|
||||
def test_hit_dice(self):
|
||||
text = epub.rst_to_html("1d6+3")
|
||||
self.assertEqual(text.strip("\n"), '<p><span class="docutils literal">1d6+3</span></p>')
|
||||
|
||||
def test_no_text(self):
|
||||
text = epub.rst_to_html(None)
|
||||
self.assertEqual(text, "")
|
||||
|
||||
def test_verbatim(self):
|
||||
text = epub.rst_to_html("``hello, world``")
|
||||
self.assertIn('<p><span class="docutils literal">hello, world</span></p>', text)
|
||||
|
||||
def test_literal_backslash(self):
|
||||
text = epub.rst_to_html(r"\\")
|
||||
self.assertEqual(r"<p>\</p>", text.strip("\n"))
|
||||
|
||||
@unittest.skip(
|
||||
"Headings are all screwed up because it treats them as the document title"
|
||||
)
|
||||
def test_headings(self):
|
||||
# Simple heading by itself
|
||||
text = epub.rst_to_html("Hello, world\n------------\n\nGoodbye, world")
|
||||
self.assertEqual("\\section*{Hello, world}\n", text)
|
||||
# Simple heading with leading whitespace
|
||||
text = epub.rst_to_html(" Hello, world\n ============\n")
|
||||
self.assertEqual("\\section*{Hello, world}\n", text)
|
||||
# Heading with text after it
|
||||
text = epub.rst_to_html("Hello, world\n============\n\nThis is some text")
|
||||
self.assertEqual("\\section*{Hello, world}\n\nThis is some text", text)
|
||||
# Heading with text before it
|
||||
text = epub.rst_to_html("This is a paragraph\n\nHello, world\n============\n")
|
||||
self.assertEqual("This is a paragraph\n\n\\section*{Hello, world}\n", text)
|
||||
# Check that levels of headings are parsed appropriately
|
||||
text = epub.rst_to_html("Hello, world\n^^^^^^^^^^^^\n")
|
||||
self.assertEqual("\\subsubsection*{Hello, world}\n", text)
|
||||
text = epub.rst_to_html("Hello, world\n^^^^^^^^^^^^\n", top_heading_level=3)
|
||||
self.assertEqual("\\subparagraph*{Hello, world}\n", text)
|
||||
# This is a bad heading missing with all the underline on one line
|
||||
text = epub.rst_to_html("Hello, world^^^^^^^^^^^^\n")
|
||||
self.assertEqual("Hello, world\\^\\^\\^\\^\\^\\^\\^\\^\\^\\^\\^\\^\n", text)
|
||||
|
||||
def test_bullet_list(self):
|
||||
tex = epub.rst_to_html("\n- Hello\n- World\n\n")
|
||||
expected_tex = '<ul class="simple">\n<li><p>Hello</p></li>\n<li><p>World</p></li>\n</ul>'
|
||||
self.assertEqual(expected_tex, tex.strip("\n"))
|
||||
# Other bullet characters
|
||||
tex = epub.rst_to_html("\n* Hello\n* World\n\n")
|
||||
self.assertEqual(expected_tex, tex.strip("\n"))
|
||||
tex = epub.rst_to_html("\n+ Hello\n+ World\n\n")
|
||||
self.assertEqual(expected_tex, tex.strip("\n"))
|
||||
# A real list taken from a docstring
|
||||
real_list = """
|
||||
- Secondhand (you have heard of the target) - +5
|
||||
- Firsthand (you have met the target) - +0
|
||||
- Familiar (you know the target well) - -5
|
||||
|
||||
"""
|
||||
tex = epub.rst_to_html(real_list)
|
||||
self.assertIn('<ul class="simple">', tex)
|
||||
|
||||
def test_multiline_bullet_list(self):
|
||||
md_list = """
|
||||
- Secondhand (you have heard
|
||||
of the target) - +5
|
||||
- Firsthand (you have met
|
||||
the target) - +0
|
||||
- Familiar (you know the target
|
||||
well) - -5
|
||||
|
||||
"""
|
||||
tex = epub.rst_to_html(md_list)
|
||||
self.assertIn('<ul class="simple">', tex)
|
||||
|
||||
def test_simple_table(self):
|
||||
table_rst = """
|
||||
===== ===== =======
|
||||
A B A and B
|
||||
===== ===== =======
|
||||
False False False
|
||||
True False False
|
||||
False True False
|
||||
True True True
|
||||
===== ===== =======
|
||||
"""
|
||||
tex = epub.rst_to_html(table_rst)
|
||||
# Check begin/end environment is fixed
|
||||
self.assertIn("<table>", tex)
|
||||
|
||||
def test_rst_all_spells(self):
|
||||
for spell in spells.all_spells():
|
||||
tex = epub.rst_to_html(spell.__doc__)
|
||||
self.assertNotIn(
|
||||
"DUadmonition", tex, f"spell {spell} is not valid reStructured text"
|
||||
)
|
||||
|
||||
def test_rst_all_features(self):
|
||||
for feature in features.all_features():
|
||||
tex = epub.rst_to_html(feature.__doc__)
|
||||
self.assertNotIn(
|
||||
"DUadmonition", tex, f"feature {feature} is not valid reStructured text"
|
||||
)
|
||||
@@ -52,6 +52,21 @@ class MakeSheetsTestCase(unittest.TestCase):
|
||||
f"GM PDF ({self.gm_pdf.resolve()}) not created.")
|
||||
|
||||
|
||||
class EpubOutputTestCase(unittest.TestCase):
|
||||
gm_epub = Path(f"{GMFILE.stem}.epub").resolve()
|
||||
|
||||
def tearDown(self):
|
||||
for f in [self.gm_epub]:
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
def test_file_created(self):
|
||||
# Check that a file is created once the function is run
|
||||
# self.assertFalse(os.path.exists(pdf_name), f'{pdf_name} already exists.')
|
||||
make_sheets.make_gm_sheet(gm_file=GMFILE, output_format="epub")
|
||||
self.assertTrue(self.gm_epub.exists(), f"{self.gm_epub} not created.")
|
||||
|
||||
|
||||
class PdfOutputTestCase(unittest.TestCase):
|
||||
basename = "clara"
|
||||
|
||||
@@ -142,11 +157,11 @@ class TexCreatorTestCase(unittest.TestCase):
|
||||
|
||||
def test_create_monsters_tex(self):
|
||||
monsters_ = [monsters.GiantEagle()]
|
||||
tex = make_sheets.create_monsters_tex(monsters=monsters_)
|
||||
tex = make_sheets.create_monsters_content(monsters=monsters_, suffix="tex")
|
||||
self.assertIn(r"Giant Eagle", tex)
|
||||
# Check extended properties
|
||||
monsters_ = [VashtaNerada()]
|
||||
tex = make_sheets.create_monsters_tex(monsters=monsters_)
|
||||
tex = make_sheets.create_monsters_content(monsters=monsters_, suffix="tex")
|
||||
self.assertIn(r"Vashta Nerada", tex)
|
||||
self.assertIn(r"35", tex)
|
||||
self.assertIn(r"45 fly", tex)
|
||||
@@ -162,7 +177,8 @@ class TexCreatorTestCase(unittest.TestCase):
|
||||
self.assertIn(r"Languages:", tex)
|
||||
self.assertIn(r"Skills:", tex)
|
||||
# Check fancy extended properties
|
||||
tex = make_sheets.create_monsters_tex(monsters=monsters_,
|
||||
tex = make_sheets.create_monsters_content(monsters=monsters_,
|
||||
suffix="tex",
|
||||
use_dnd_decorations=True)
|
||||
self.assertIn(r"Vashta Nerada", tex)
|
||||
self.assertIn(r"35 ft.", tex)
|
||||
@@ -174,19 +190,20 @@ class TexCreatorTestCase(unittest.TestCase):
|
||||
|
||||
def test_create_party_summary_tex(self):
|
||||
char = self.new_character()
|
||||
tex = make_sheets.create_party_summary_tex(party=[char], summary_rst="")
|
||||
tex = make_sheets.create_party_summary_content(party=[char], suffix="tex", summary_rst="")
|
||||
self.assertIn(r"\section*{Party}", tex)
|
||||
self.assertIn(char.name, tex)
|
||||
|
||||
def test_create_summary_tex(self):
|
||||
rst = "The party's create *adventure*."
|
||||
tex = make_sheets.create_party_summary_tex(party=[], summary_rst=rst)
|
||||
tex = make_sheets.create_party_summary_content(party=[], suffix="tex", summary_rst=rst)
|
||||
self.assertIn(r"\section*{Summary}", tex)
|
||||
# Check that the RST is parsed
|
||||
self.assertIn(r"\emph{adventure}", tex)
|
||||
|
||||
def test_random_tables_tex(self):
|
||||
tex = make_sheets.create_random_tables_tex(
|
||||
tex = make_sheets.create_random_tables_content(
|
||||
suffix="tex",
|
||||
conjure_animals=True,
|
||||
)
|
||||
self.assertIn(r"\subsection*{Conjure Animals}", tex)
|
||||
|
||||
Reference in New Issue
Block a user