From 4719ad43051562ba96f375453d0139f5a2240d40 Mon Sep 17 00:00:00 2001 From: Mark Wolfman Date: Wed, 7 Apr 2021 22:01:32 -0500 Subject: [PATCH] Switched to docutils/sphinx for parsing docstrings into LaTeX. --- dungeonsheets/forms/druid_shapes_template.tex | 27 +++++++ dungeonsheets/forms/features_template.tex | 30 ++++++++ dungeonsheets/forms/infusions_template.tex | 27 +++++++ dungeonsheets/forms/preamble.tex | 4 + dungeonsheets/forms/spellbook_template.tex | 28 +++++++ dungeonsheets/make_sheets.py | 76 +++++++++++-------- setup.py | 2 +- tests/test_make_sheets.py | 5 +- 8 files changed, 164 insertions(+), 35 deletions(-) create mode 100644 dungeonsheets/forms/preamble.tex diff --git a/dungeonsheets/forms/druid_shapes_template.tex b/dungeonsheets/forms/druid_shapes_template.tex index b42efaf..e6f9389 100644 --- a/dungeonsheets/forms/druid_shapes_template.tex +++ b/dungeonsheets/forms/druid_shapes_template.tex @@ -11,11 +11,38 @@ [% endif %] \definecolor{mygrey}{gray}{0.7} +[% include 'preamble.tex' %] + \title{Wild Shapes} \date{} \author{[[ character.name ]]} +%%% Fallback definitions for Docutils-specific commands +[% raw %] +% admonition (specially marked topic) +\providecommand{\DUadmonition}[2][class-arg]{% + % try \DUadmonition#1{#2}: + \ifcsname DUadmonition#1\endcsname% + \csname DUadmonition#1\endcsname{#2}% + \else + \begin{center} + \fbox{\parbox{0.9\linewidth}{#2}} + \end{center} + \fi +} + +% title for topics, admonitions, unsupported section levels, and sidebar +\providecommand*{\DUtitle}[2][class-arg]{% + % call \DUtitle#1{#2} if it exists: + \ifcsname DUtitle#1\endcsname% + \csname DUtitle#1\endcsname{#2}% + \else + \smallskip\noindent\textbf{#2}\smallskip% + \fi +} +[% endraw %] + \begin{document} diff --git a/dungeonsheets/forms/features_template.tex b/dungeonsheets/forms/features_template.tex index c760760..08870b3 100644 --- a/dungeonsheets/forms/features_template.tex +++ b/dungeonsheets/forms/features_template.tex @@ -11,10 +11,40 @@ \setlength{\zerosep}{-1em} [% endif %] +[% include 'preamble.tex' %] + \title{Features and Magic Items} \author{[[ character.name ]]} \date{} +%%% Fallback definitions for Docutils-specific commands +[% raw %] +\usepackage{longtable,ltcaption,array} +\setlength{\extrarowheight}{2pt} +\newlength{\DUtablewidth} % internal use in tables +% admonition (specially marked topic) +\providecommand{\DUadmonition}[2][class-arg]{% + % try \DUadmonition#1{#2}: + \ifcsname DUadmonition#1\endcsname% + \csname DUadmonition#1\endcsname{#2}% + \else + \begin{center} + \fbox{\parbox{0.9\linewidth}{#2}} + \end{center} + \fi +} + +% title for topics, admonitions, unsupported section levels, and sidebar +\providecommand*{\DUtitle}[2][class-arg]{% + % call \DUtitle#1{#2} if it exists: + \ifcsname DUtitle#1\endcsname% + \csname DUtitle#1\endcsname{#2}% + \else + \smallskip\noindent\textbf{#2}\smallskip% + \fi +} +[% endraw %] + \begin{document} \maketitle diff --git a/dungeonsheets/forms/infusions_template.tex b/dungeonsheets/forms/infusions_template.tex index 13d8f23..fba3f27 100644 --- a/dungeonsheets/forms/infusions_template.tex +++ b/dungeonsheets/forms/infusions_template.tex @@ -9,10 +9,37 @@ \setlength{\zerosep}{-1em} [% endif %] +[% include 'preamble.tex' %] + \title{Infusion Descriptions} \author{[[ character.name ]] (Artificer [[ character.Artificer.level ]] level)} \date{} +%%% Fallback definitions for Docutils-specific commands +[% raw %] +% admonition (specially marked topic) +\providecommand{\DUadmonition}[2][class-arg]{% + % try \DUadmonition#1{#2}: + \ifcsname DUadmonition#1\endcsname% + \csname DUadmonition#1\endcsname{#2}% + \else + \begin{center} + \fbox{\parbox{0.9\linewidth}{#2}} + \end{center} + \fi +} + +% title for topics, admonitions, unsupported section levels, and sidebar +\providecommand*{\DUtitle}[2][class-arg]{% + % call \DUtitle#1{#2} if it exists: + \ifcsname DUtitle#1\endcsname% + \csname DUtitle#1\endcsname{#2}% + \else + \smallskip\noindent\textbf{#2}\smallskip% + \fi +} +[% endraw %] + \begin{document} \maketitle diff --git a/dungeonsheets/forms/preamble.tex b/dungeonsheets/forms/preamble.tex new file mode 100644 index 0000000..e08a203 --- /dev/null +++ b/dungeonsheets/forms/preamble.tex @@ -0,0 +1,4 @@ +% For parsed docstrings +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{alltt} diff --git a/dungeonsheets/forms/spellbook_template.tex b/dungeonsheets/forms/spellbook_template.tex index 98d7c93..eae74ad 100644 --- a/dungeonsheets/forms/spellbook_template.tex +++ b/dungeonsheets/forms/spellbook_template.tex @@ -11,10 +11,38 @@ \setlength{\zerosep}{-1em} [% endif %] +[% include 'preamble.tex' %] + \title{Spell Descriptions} \author{[[ character.name ]]} \date{} +%%% Fallback definitions for Docutils-specific commands +[% raw %] +% admonition (specially marked topic) +\providecommand{\DUadmonition}[2][class-arg]{% + % try \DUadmonition#1{#2}: + \ifcsname DUadmonition#1\endcsname% + \csname DUadmonition#1\endcsname{#2}% + \else + \begin{center} + \fbox{\parbox{0.9\linewidth}{#2}} + \end{center} + \fi +} + +% title for topics, admonitions, unsupported section levels, and sidebar +\providecommand*{\DUtitle}[2][class-arg]{% + % call \DUtitle#1{#2} if it exists: + \ifcsname DUtitle#1\endcsname% + \csname DUtitle#1\endcsname{#2}% + \else + \smallskip\noindent\textbf{#2}\smallskip% + \fi +} +[% endraw %] + + \begin{document} \maketitle diff --git a/dungeonsheets/make_sheets.py b/dungeonsheets/make_sheets.py index e092b50..8e0fd48 100755 --- a/dungeonsheets/make_sheets.py +++ b/dungeonsheets/make_sheets.py @@ -14,6 +14,8 @@ from itertools import product from fdfgen import forge_fdf import pdfrw from jinja2 import Environment, PackageLoader +from docutils import core, io +from sphinx.util.docstrings import prepare_docstring from dungeonsheets import character as _char from dungeonsheets import exceptions, classes, readers @@ -102,6 +104,45 @@ def _parse_rst_headings(rst): yield heading_rst, heading, level +def latex_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 "

"). + """ + # 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} + parts = core.publish_parts( + source=input_string, source_path=source_path, + destination_path=destination_path, + writer_name='latex', settings_overrides=overrides) + return parts + + def rst_to_latex(rst, top_heading_level=0): """Basic markup of reST to LaTeX code. @@ -124,41 +165,14 @@ def rst_to_latex(rst, top_heading_level=0): The reST text parsed into LaTeX markup. """ - heading_latex = { - 0: 'section*', - 1: 'subsection*', - 2: 'subsubsection*', - 3: 'paragraph*', - 4: 'subparagraph*', - } if rst is None: # No reST, so return an empty string tex = "" else: - tex = rst - # Allow literal backslashes - for c in ['\\']: - tex = tex.replace(c, '\\' + c) - # Lists - for list_rst, list_items in _parse_rst_lists(tex): - list_tex = "\n\\begin{itemize}\n" - for item in list_items: - list_tex += f"\\item{{{item}}}\n" - list_tex += "\\end{itemize}\n" - tex = tex.replace(list_rst, list_tex) - # Inline text formatting - tex = bold_re.sub(r'\\textbf{\1}', tex) - tex = it_re.sub(r'\\textit{\1}', tex) - tex = verb_re.sub(r'\\begin{verbatim}{\1}\\end{verbatim}', tex) - tex = dice_re.sub(r'\\texttt{\1}', tex) - # Headings - for heading_rst, heading, heading_level in _parse_rst_headings(tex): - heading_level = min(heading_level + top_heading_level, max(heading_latex.keys())) - tex = tex.replace(heading_rst, f"\\{heading_latex[heading_level]}{{{heading}}}") - # Escape any remaining characters that have meaning in LaTeX - for c in ['#', '$', '%', '&', '~', '_', '^']: - tex = tex.replace(c, '\\' + c) - + tex_parts = latex_parts(rst) + tex = tex_parts['body'] + # Check for currently un-supported LaTeX commands + assert "longtable" not in tex return tex diff --git a/setup.py b/setup.py index 828bdf4..54fd47c 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ setup(name='dungeonsheets', '../VERSION'] }, install_requires=[ - 'fdfgen', 'npyscreen', 'jinja2', 'pdfrw', + 'fdfgen', 'npyscreen', 'jinja2', 'pdfrw', 'sphinx', ], entry_points={ 'console_scripts': [ diff --git a/tests/test_make_sheets.py b/tests/test_make_sheets.py index 0a05d6b..f03e31a 100644 --- a/tests/test_make_sheets.py +++ b/tests/test_make_sheets.py @@ -44,7 +44,7 @@ class MarkdownTestCase(unittest.TestCase): def test_rst_bold(self): text = make_sheets.rst_to_latex('**hello**') - self.assertEqual(text, '\\textbf{hello}') + self.assertEqual(text, '\n\\textbf{hello}\n') def test_hit_dice(self): text = make_sheets.rst_to_latex('1d6+3') @@ -56,7 +56,7 @@ class MarkdownTestCase(unittest.TestCase): def test_verbatim(self): text = make_sheets.rst_to_latex('``hello, world``') - self.assertIn(r'\begin{verbatim}', text) + self.assertIn(r'\texttt{hello, world}', text) def test_literal_backslash(self): text = make_sheets.rst_to_latex('\\') @@ -114,7 +114,6 @@ class MarkdownTestCase(unittest.TestCase): """ tex = make_sheets.rst_to_latex(md_list) - print(tex) self.assertIn("\\begin{itemize}", tex)