# 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)