From 2806c95c8331259ef62e84076359a2475643f37b Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Sun, 1 Jan 2017 15:32:29 -0500 Subject: [PATCH 1/2] Respect the html_use_smartypants config option Previously, this was ignored because the config option is implemented by using a different HTMLTranslator in the case that the option is True (which is the default). This PR switches from subclassing the non-SmartyPants HTMLTranslator and instead uses calls to `app.add_node` to override the visit/depart functions. This removes the need to use `app.set_translator`, making our Sphinx requirement more flexible. I tested building the demo with the current version of Sphinx and earlier, and it worked all the way to Sphinx 1.1, which failed for something unrelated to this change. `setup.py` was updated accordingly. --- guzzle_sphinx_theme/__init__.py | 191 ++++++++++++++++++-------------- setup.py | 2 +- 2 files changed, 107 insertions(+), 86 deletions(-) diff --git a/guzzle_sphinx_theme/__init__.py b/guzzle_sphinx_theme/__init__.py index 78363a2..09a5df4 100644 --- a/guzzle_sphinx_theme/__init__.py +++ b/guzzle_sphinx_theme/__init__.py @@ -4,8 +4,8 @@ import xml.etree.ElementTree as ET from docutils import nodes +from sphinx import version_info as sphinx_version from sphinx.locale import admonitionlabels -from sphinx.writers.html import HTMLTranslator as SphinxHTMLTranslator from pygments.style import Style from pygments.token import Keyword, Name, Comment, String, Error, \ @@ -16,7 +16,20 @@ def setup(app): """Setup conntects events to the sitemap builder""" app.connect('html-page-context', add_html_link) app.connect('build-finished', create_sitemap) - app.set_translator('html', HTMLTranslator) + + # Override several HTML translator methods. + # In Sphinx 1.4 and above, a warning is raised when adding a node that + # already exists, unless you pass `override=True`. + kwargs = {} if sphinx_version[:2] <= (1, 3) else {"override": True} + app.add_node(nodes.table, html=(visit_table, depart_table), **kwargs) + app.add_node(nodes.field, html=(visit_field, depart_field), **kwargs) + app.add_node(nodes.field_name, + html=(visit_field_name, depart_field_name), **kwargs) + app.add_node(nodes.field_body, + html=(visit_field_body, depart_field_body), **kwargs) + app.add_node(nodes.field_list, + html=(visit_field_list, depart_field_list), **kwargs) + app.sitemap_links = [] @@ -51,88 +64,96 @@ def html_theme_path(): return [os.path.dirname(os.path.abspath(__file__))] -class HTMLTranslator(SphinxHTMLTranslator): +# The subsequent functions affect how the document is translated to HTML, +# in most cases to work nicely with bootstrap. + +def visit_table(self, node, name=''): + """ + Override docutils default table formatter to not include a border + and to use Bootstrap CSS + See: http://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/docutils/writetml4css1/__init__.py#l1550 + """ + self.context.append(self.compact_p) + self.compact_p = True + classes = 'table table-bordered ' + self.settings.table_style + classes = classes.strip() + self.body.append( + self.starttag(node, 'table', CLASS=classes)) + + +def depart_table(self, node): """ - Handle translating to bootstrap structure. + This needs overridin' too """ - def visit_table(self, node, name=''): - """ - Override docutils default table formatter to not include a border - and to use Bootstrap CSS - See: http://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/docutils/writers/html4css1/__init__.py#l1550 - """ - self.context.append(self.compact_p) - self.compact_p = True - classes = 'table table-bordered ' + self.settings.table_style - classes = classes.strip() - self.body.append( - self.starttag(node, 'table', CLASS=classes)) - - def depart_table(self, node): - """ - This needs overridin' too - """ - self.compact_p = self.context.pop() - self.body.append('\n') - - def visit_field(self, node): - pass - - def depart_field(self, node): - pass - - def visit_field_name(self, node): - atts = {} - if self.in_docinfo: - atts['class'] = 'docinfo-name' - else: - atts['class'] = 'field-name' - self.context.append('') - self.body.append(self.starttag(node, 'dt', '', **atts)) - - def depart_field_name(self, node): - self.body.append('') - self.body.append(self.context.pop()) - - def visit_field_body(self, node): - self.body.append(self.starttag(node, 'dd', '', CLASS='field-body')) - self.set_class_on_child(node, 'first', 0) - field = node.parent - if (self.compact_field_list or - isinstance(field.parent, nodes.docinfo) or - field.parent.index(field) == len(field.parent) - 1): - # If we are in a compact list, the docinfo, or if this is - # the last field of the field list, do not add vertical - # space after last element. - self.set_class_on_child(node, 'last', -1) - - def depart_field_body(self, node): - self.body.append('\n') - - def visit_field_list(self, node): - self.context.append((self.compact_field_list, self.compact_p)) - self.compact_p = None - if 'compact' in node['classes']: - self.compact_field_list = True - elif (self.settings.compact_field_lists - and 'open' not in node['classes']): - self.compact_field_list = True - if self.compact_field_list: - for field in node: - field_body = field[-1] - assert isinstance(field_body, nodes.field_body) - children = [n for n in field_body - if not isinstance(n, nodes.Invisible)] - if not (len(children) == 0 or - len(children) == 1 and - isinstance(children[0], - (nodes.paragraph, nodes.line_block))): - self.compact_field_list = False - break - self.body.append(self.starttag(node, 'dl', frame='void', - rules='none', - CLASS='docutils field-list')) - - def depart_field_list(self, node): - self.body.append('\n') - self.compact_field_list, self.compact_p = self.context.pop() + self.compact_p = self.context.pop() + self.body.append('\n') + + +def visit_field(self, node): + pass + + +def depart_field(self, node): + pass + + +def visit_field_name(self, node): + atts = {} + if self.in_docinfo: + atts['class'] = 'docinfo-name' + else: + atts['class'] = 'field-name' + self.context.append('') + self.body.append(self.starttag(node, 'dt', '', **atts)) + + +def depart_field_name(self, node): + self.body.append('') + self.body.append(self.context.pop()) + + +def visit_field_body(self, node): + self.body.append(self.starttag(node, 'dd', '', CLASS='field-body')) + self.set_class_on_child(node, 'first', 0) + field = node.parent + if (self.compact_field_list or + isinstance(field.parent, nodes.docinfo) or + field.parent.index(field) == len(field.parent) - 1): + # If we are in a compact list, the docinfo, or if this is + # the last field of the field list, do not add vertical + # space after last element. + self.set_class_on_child(node, 'last', -1) + + +def depart_field_body(self, node): + self.body.append('\n') + + +def visit_field_list(self, node): + self.context.append((self.compact_field_list, self.compact_p)) + self.compact_p = None + if 'compact' in node['classes']: + self.compact_field_list = True + elif (self.settings.compact_field_lists + and 'open' not in node['classes']): + self.compact_field_list = True + if self.compact_field_list: + for field in node: + field_body = field[-1] + assert isinstance(field_body, nodes.field_body) + children = [n for n in field_body + if not isinstance(n, nodes.Invisible)] + if not (len(children) == 0 or + len(children) == 1 and + isinstance(children[0], + (nodes.paragraph, nodes.line_block))): + self.compact_field_list = False + break + self.body.append(self.starttag(node, 'dl', frame='void', + rules='none', + CLASS='docutils field-list')) + + +def depart_field_list(self, node): + self.body.append('\n') + self.compact_field_list, self.compact_p = self.context.pop() diff --git a/setup.py b/setup.py index ca2964b..147a753 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ url='https://github.com/guzzle/guzzle_sphinx_theme', packages=['guzzle_sphinx_theme'], include_package_data=True, - install_requires=['Sphinx>1.3'], + install_requires=['Sphinx>=1.2'], classifiers=( 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', From 302bf83969ee6a61ac6a000a096d064452fc7e0f Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Tue, 31 Jan 2017 13:06:16 -0500 Subject: [PATCH 2/2] Remove some unused imports And fixed some indentation to follow PEP8 style. --- guzzle_sphinx_theme/__init__.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/guzzle_sphinx_theme/__init__.py b/guzzle_sphinx_theme/__init__.py index 09a5df4..799550a 100644 --- a/guzzle_sphinx_theme/__init__.py +++ b/guzzle_sphinx_theme/__init__.py @@ -5,11 +5,6 @@ from docutils import nodes from sphinx import version_info as sphinx_version -from sphinx.locale import admonitionlabels - -from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal def setup(app): @@ -43,8 +38,8 @@ def add_html_link(app, pagename, templatename, context, doctree): def create_sitemap(app, exception): """Generates the sitemap.xml from the collected HTML page links""" if (not app.config['html_theme_options'].get('base_url', '') or - exception is not None or - not app.sitemap_links): + exception is not None or + not app.sitemap_links): return filename = app.outdir + "/sitemap.xml" @@ -117,8 +112,8 @@ def visit_field_body(self, node): self.set_class_on_child(node, 'first', 0) field = node.parent if (self.compact_field_list or - isinstance(field.parent, nodes.docinfo) or - field.parent.index(field) == len(field.parent) - 1): + isinstance(field.parent, nodes.docinfo) or + field.parent.index(field) == len(field.parent) - 1): # If we are in a compact list, the docinfo, or if this is # the last field of the field list, do not add vertical # space after last element.