# -*- coding: utf-8 -*- """ sphinx.writer ~~~~~~~~~~~~~ docutils writers handling Sphinx' custom nodes. :copyright: 2007 by Georg Brandl. :license: Python license. """ from docutils import nodes from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator from .smartypants import sphinx_smarty_pants class HTMLWriter(Writer): def __init__(self, config): Writer.__init__(self) if config.get('use_smartypants', False): self.translator_class = SmartyPantsHTMLTranslator else: self.translator_class = HTMLTranslator version_text = { 'deprecated': 'Deprecated in version %s', 'versionchanged': 'Changed in version %s', 'versionadded': 'New in version %s', } class HTMLTranslator(BaseTranslator): """ Our custom HTML translator. """ def __init__(self, *args, **kwds): self.no_smarty = 0 BaseTranslator.__init__(self, *args, **kwds) self.highlightlang = 'python' def visit_desc(self, node): self.body.append(self.starttag(node, 'dl', CLASS=node['desctype'])) def depart_desc(self, node): self.body.append('\n\n') def visit_desc_signature(self, node): # the id is set automatically self.body.append(self.starttag(node, 'dt')) # anchor for per-desc interactive data if node.parent['desctype'] != 'describe' and node['ids'] and node['first']: self.body.append('' % node['ids'][0]) if node.parent['desctype'] in ('class', 'exception'): self.body.append('%s ' % node.parent['desctype']) def depart_desc_signature(self, node): self.body.append('\n') def visit_desc_classname(self, node): self.body.append(self.starttag(node, 'tt', '', CLASS='descclassname')) def depart_desc_classname(self, node): self.body.append('') def visit_desc_name(self, node): self.body.append(self.starttag(node, 'tt', '', CLASS='descname')) def depart_desc_name(self, node): self.body.append('') def visit_desc_parameterlist(self, node): self.body.append('(') self.first_param = 1 def depart_desc_parameterlist(self, node): self.body.append(')') def visit_desc_parameter(self, node): if not self.first_param: self.body.append(', ') else: self.first_param = 0 if not node.hasattr('noemph'): self.body.append('') def depart_desc_parameter(self, node): if not node.hasattr('noemph'): self.body.append('') def visit_desc_optional(self, node): self.body.append('[') def depart_desc_optional(self, node): self.body.append(']') def visit_desc_content(self, node): self.body.append(self.starttag(node, 'dd', '')) def depart_desc_content(self, node): self.body.append('') def visit_refcount(self, node): self.body.append(self.starttag(node, 'em', '', CLASS='refcount')) def depart_refcount(self, node): self.body.append('') def visit_versionmodified(self, node): self.body.append(self.starttag(node, 'p')) text = version_text[node['type']] % node['version'] if len(node): text += ': ' else: text += '.' self.body.append('%s' % text) def depart_versionmodified(self, node): self.body.append('
\n') # overwritten -- we don't want source comments to show up in the HTML def visit_comment(self, node): raise nodes.SkipNode # overwritten def visit_admonition(self, node, name=''): self.body.append(self.start_tag_with_title( node, 'div', CLASS=('admonition ' + name))) if name and name != 'seealso': node.insert(0, nodes.title(name, self.language.labels[name])) self.set_first_last(node) def visit_seealso(self, node): self.visit_admonition(node, 'seealso') def depart_seealso(self, node): self.depart_admonition(node) # overwritten def visit_title(self, node, move_ids=1): # if we have a section we do our own processing in order # to have ids in the hN-tags and not in additional a-tags if isinstance(node.parent, nodes.section): h_level = self.section_level + self.initial_header_level - 1 if node.parent.get('ids'): attrs = {'ids': node.parent['ids']} else: attrs = {} self.body.append(self.starttag(node, 'h%d' % h_level, '', **attrs)) self.context.append('\n' % h_level) else: BaseTranslator.visit_title(self, node, move_ids) # overwritten def visit_literal_block(self, node): from .highlighting import highlight_block self.body.append(highlight_block(node.rawsource, self.highlightlang)) raise nodes.SkipNode def visit_productionlist(self, node): self.body.append(self.starttag(node, 'pre')) names = [] for production in node: names.append(production['tokenname']) maxlen = max(len(name) for name in names) for production in node: if production['tokenname']: self.body.append(self.starttag(production, 'strong', '')) self.body.append(production['tokenname'].ljust(maxlen) + ' ::= ') lastname = production['tokenname'] else: self.body.append('%s ' % (' '*len(lastname))) production.walkabout(self) self.body.append('\n') self.body.append('\n') raise nodes.SkipNode def depart_productionlist(self, node): pass def visit_production(self, node): pass def depart_production(self, node): pass def visit_centered(self, node): self.body.append(self.starttag(node, 'center') + '') def depart_centered(self, node): self.body.append('') def visit_compact_paragraph(self, node): pass def depart_compact_paragraph(self, node): pass def visit_highlightlang(self, node): self.highlightlang = node['lang'] def depart_highlightlang(self, node): pass def visit_toctree(self, node): # this only happens when formatting a toc from env.tocs -- in this # case we don't want to include the subtree raise nodes.SkipNode def visit_index(self, node): raise nodes.SkipNode class SmartyPantsHTMLTranslator(HTMLTranslator): """ Handle ordinary text via smartypants, converting quotes and dashes to the correct entities. """ def __init__(self, *args, **kwds): self.no_smarty = 0 HTMLTranslator.__init__(self, *args, **kwds) def visit_literal(self, node): self.no_smarty += 1 try: # this raises SkipNode HTMLTranslator.visit_literal(self, node) finally: self.no_smarty -= 1 def visit_productionlist(self, node): self.no_smarty += 1 try: HTMLTranslator.visit_productionlist(self, node) finally: self.no_smarty -= 1 def encode(self, text): text = HTMLTranslator.encode(self, text) if self.no_smarty <= 0: text = sphinx_smarty_pants(text) return text