# Copyright © 2021 Lee Yingtong Li
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.https://mozilla.org/MPL/2.0/.
def setup(app):
app.add_builder(POStandaloneHTMLBuilder, override=True)
app.add_builder(POLaTeXBuilder, override=True)
app.add_builder(POEpub3Builder, override=True)
app.add_role('mref', MRefRole())
app.add_role('mdoc', MDocRole())
app.add_role('subref', SubRefRole())
app.add_role('msubref', MSubRefRole())
app.add_source_parser(PORSTParser, override=True)
# ===================================================================
# HTML Builder
# ===================================================================
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.environment.adapters.toctree import TocTree
from sphinx.writers.html5 import HTML5Translator
import docutils.nodes
import sphinx.addnodes
from sphinx.util import logging
logger = logging.getLogger(__name__)
class POHTML5Translator(HTML5Translator):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.paragraph_num = 0
self.footnote_order = []
def visit_paragraph(self, node):
super().visit_paragraph(node)
if node.parent.tagname == 'section':
# Add paragraph numbers
self.paragraph_num += 1
if self.builder.name == 'html':
self.body.append('{0}'.format(self.paragraph_num))
else: # epub (or mobi)
# Hardcode the brackets, as MOBI6 does not support CSS
self.body.append('[{0}] '.format(self.paragraph_num))
def visit_footnote_reference(self, node):
super().visit_footnote_reference(node)
# Verify order
if node['refid'] not in self.footnote_order:
self.footnote_order.append(node['refid'])
def visit_footnote(self, node):
super().visit_footnote(node)
# Verify order
if not self.footnote_order:
pass
elif node['ids'][0] == self.footnote_order[0]:
del self.footnote_order[0]
else:
logger.warning('footnote defined out of sequence: expecting {}'.format(self.footnote_order[0]), location=node)
self.footnote_order = None # Ignore future errors
def visit_download_reference(self, node):
# Set URI (for epub/mobi output)
if self.builder.name != 'html':
node['refuri'] = 'https://yingtongli.me/pointsoforder/_downloads/' + node['filename']
# Remove code literal
node.children = node.children[0].children
super().visit_download_reference(node)
class POStandaloneHTMLBuilder(StandaloneHTMLBuilder):
default_translator_class = POHTML5Translator
__TocTree_resolve = TocTree.resolve
def _TocTree_resolve(self, *args, **kwargs):
result = __TocTree_resolve(self, *args, **kwargs)
if self.env.app.builder.name == 'html' or self.env.app.builder.name == 'epub':
# Add Index TOC entry
bullet_list = result[1]
reference = docutils.nodes.reference('', '', internal=True, refuri='genindex.xhtml' if self.env.app.builder.name == 'epub' else 'genindex.html', anchorname='', *[docutils.nodes.Text('Index')])
compact_paragraph = sphinx.addnodes.compact_paragraph('', '', reference)
list_item = docutils.nodes.list_item('', classes=['toctree-l1'], *[compact_paragraph])
bullet_list.append(list_item)
return result
TocTree.resolve = _TocTree_resolve
# ===================================================================
# LaTeX Builder
# ===================================================================
from sphinx.builders.latex import LaTeXBuilder
from sphinx.writers.latex import LaTeXTranslator
class POLaTeXTranslator(LaTeXTranslator):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.paragraph_num = 0
def visit_title(self, node):
super().visit_title(node)
if self.sectionlevel == 1:
# Reset paragraph numbers
self.paragraph_num = 0
def visit_paragraph(self, node):
super().visit_paragraph(node)
if node.parent.tagname == 'section':
# Add paragraph numbers
self.paragraph_num += 1
#self.body.append(r'\makebox[0pt][r]{{\small\textcolor[HTML]{{AAAAAA}}{{¶{0}}}\hspace{{0.8em}}}}'.format(self.paragraph_num))
#self.body.append(r'{{\small\textcolor[HTML]{{AAAAAA}}{{[¶{0}]}}}} '.format(self.paragraph_num))
self.body.append(r'{{\small\bfseries\sffamily[{0}]}} '.format(self.paragraph_num))
def visit_reference(self, node):
uri = node.get('refuri', '')
if 'po_mref' in node.attributes:
# Force page reference
self.builder.config.latex_show_pagerefs = True
super().visit_reference(node)
self.builder.config.latex_show_pagerefs = False
else:
super().visit_reference(node)
if '://doi.org/' in uri:
# Fix formatting of DOI references
self.body.append('\\seqsplit{')
def depart_reference(self, node):
uri = node.get('refuri', '')
if '://doi.org/' in uri:
# Fix formatting of DOI references
self.body.append('}')
super().depart_reference(node)
def visit_download_reference(self, node):
# Set URI
node['refuri'] = 'https://yingtongli.me/pointsoforder/_downloads/' + node['filename']
# Remove code literal
node.children = node.children[0].children
super().visit_reference(node)
def depart_download_reference(self, node):
super().depart_reference(node)
class POLaTeXBuilder(LaTeXBuilder):
default_translator_class = POLaTeXTranslator
# Don't escape "-"
import sphinx.util.texescape
sphinx.util.texescape.ascii_tex_replacements.remove(('-', r'\sphinxhyphen{}'))
__texescape_init = sphinx.util.texescape.init
def _texescape_init():
__texescape_init()
del sphinx.util.texescape._tex_escape_map_without_unicode[ord('-')]
sphinx.util.texescape.init = _texescape_init
# ===================================================================
# Epub3 Builder
# ===================================================================
from sphinx.builders.epub3 import Epub3Builder
class POEpub3Builder(Epub3Builder):
default_translator_class = POHTML5Translator
def toc_add_files(self, refnodes):
super().toc_add_files(refnodes)
# Add Index TOC entry
refnodes.append({
'level': 1,
'refuri': 'genindex.xhtml',
'text': 'Index'
})
# ===================================================================
# Referencing roles
# ===================================================================
from sphinx.roles import XRefRole
class MRefRole(XRefRole):
# Like :ref:, but in LaTeX includes a page number
def __init__(self, *args, **kwargs):
# cf. sphinx.domains.std.StandardDomain.roles
super().__init__(lowercase=True, innernodeclass=docutils.nodes.inline, warn_dangling=True, *args, **kwargs)
def run(self):
self.name = 'std:ref'
return super().run()
def result_nodes(self, document, env, node, is_ref):
result = super().result_nodes(document, env, node, is_ref)
pending_xref = result[0][0]
pending_xref['po_mref'] = True # Flag for StandardDomain._resolve_ref_xref
return result
class MDocRole(MRefRole):
def __init__(self, *args, **kwargs):
# cf. sphinx.domains.std.StandardDomain.roles
XRefRole.__init__(self, innernodeclass=docutils.nodes.inline, warn_dangling=True, *args, **kwargs)
def run(self):
self.name = 'std:doc'
return XRefRole.run(self)
class SubRefRole(XRefRole):
# Like :ref: but uses a substitution text as the label - allows for nested inline formatting
def __init__(self, *args, **kwargs):
# cf. sphinx.domains.std.StandardDomain.roles
super().__init__(lowercase=True, innernodeclass=docutils.nodes.inline, warn_dangling=True, *args, **kwargs)
def run(self):
self.name = 'std:ref'
return super().run()
def result_nodes(self, document, env, node, is_ref):
result = super().result_nodes(document, env, node, is_ref)
pending_xref = result[0][0]
inline = pending_xref[0]
substitution_reference = docutils.nodes.substitution_reference('', '', refname=docutils.nodes.fully_normalize_name(self.title))
inline.clear() # Remove raw "title" text node
inline.append(substitution_reference) # Replace with substitution_reference
pending_xref['po_subref'] = True # Flag for StandardDomain._resolve_ref_xref
return result
class MSubRefRole(SubRefRole, MRefRole):
def __init__(self, *args, **kwargs):
XRefRole.__init__(self, lowercase=True, innernodeclass=docutils.nodes.inline, warn_dangling=True, *args, **kwargs)
def result_nodes(self, document, env, node, is_ref):
result = SubRefRole.result_nodes(self, document, env, node, is_ref)
pending_xref = result[0][0]
pending_xref['po_mref'] = True # Flag for StandardDomain._resolve_ref_xref
return result
from sphinx.domains.std import StandardDomain
__StandardDomain_resolve_ref_xref = StandardDomain._resolve_ref_xref
def _StandardDomain_resolve_ref_xref(self, env, fromdocname, builder, typ, target, node, contnode):
if 'po_subref' in node:
# Build reference node, preserving substitution
docname, labelid = self.anonlabels.get(target, ('', ''))
# Based on StandardDomain.build_reference_node
newnode = docutils.nodes.reference('', '', internal=True)
if docname == fromdocname:
newnode['refid'] = labelid
else:
contnode = sphinx.addnodes.pending_xref('')
contnode['refdocname'] = docname
contnode['refsectname'] = node.astext()
newnode['refuri'] = builder.get_relative_uri(fromdocname, docname)
if labelid:
newnode['refuri'] += '#' + labelid
# Copy across substitution
newnode.children.extend(node.children)
result = newnode
else:
result = __StandardDomain_resolve_ref_xref(self, env, fromdocname, builder, typ, target, node, contnode)
if 'po_mref' in node:
result['po_mref'] = True # Propagate flag for POLaTeXTranslator.visit_reference
return result
StandardDomain._resolve_ref_xref = _StandardDomain_resolve_ref_xref
__StandardDomain_resolve_doc_xref = StandardDomain._resolve_doc_xref
def _StandardDomain_resolve_doc_xref(self, env, fromdocname, builder, typ, target, node, contnode):
result = __StandardDomain_resolve_doc_xref(self, env, fromdocname, builder, typ, target, node, contnode)
if 'po_mref' in node:
result['po_mref'] = True # Propagate flag for POLaTeXTranslator.visit_reference
return result
StandardDomain._resolve_doc_xref = _StandardDomain_resolve_doc_xref
# ===================================================================
# RST Parser (unused)
# ===================================================================
from sphinx.parsers import RSTParser
class PORSTParser(RSTParser):
pass
#def parse(self, inputstring, document):
# super().parse(inputstring, document)