Pythonify and cleanup rest_formatter.py helper script
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 23 Sep 2015 14:24:25 +0000 (17:24 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 23 Sep 2015 15:42:18 +0000 (18:42 +0300)
python/helpers/rest_formatter.py

index 24b0c7bcbb0b78f705ae86839dee69ce79465403..09ce081b0330184013e75e5dc74183a9bd95eabd 100644 (file)
@@ -5,227 +5,226 @@ from docutils import nodes
 from docutils.nodes import Text
 from docutils.writers.html4css1 import HTMLTranslator
 from epydoc.markup import DocstringLinker
-from epydoc.markup.restructuredtext import ParsedRstDocstring, _EpydocHTMLTranslator, _DocumentPseudoWriter, _EpydocReader
+from epydoc.markup.restructuredtext import ParsedRstDocstring, _EpydocHTMLTranslator, \
+    _DocumentPseudoWriter, _EpydocReader
 
 
 class RestHTMLTranslator(_EpydocHTMLTranslator):
-  def visit_field_name(self, node):
-    atts = {}
-    if self.in_docinfo:
-      atts['class'] = 'docinfo-name'
-    else:
-      atts['class'] = 'field-name'
-
-    self.context.append('')
-    atts['align'] = "right"
-    self.body.append(self.starttag(node, 'th', '', **atts))
-
-  def visit_field_body(self, node):
-    self.body.append(self.starttag(node, 'td', '', CLASS='field-body'))
-    parent_text = node.parent[0][0].astext()
-    if hasattr(node.parent, "type"):
-      self.body.append("(")
-      self.body.append(self.starttag(node, 'a', '',
-                                     **{"href": 'psi_element://#typename#' + node.parent.type}))
-      self.body.append(node.parent.type)
-      self.body.append("</a>")
-      self.body.append(") ")
-    elif parent_text.startswith("type "):
-      index = parent_text.index("type ")
-      type_string = parent_text[index + 5]
-      self.body.append(self.starttag(node, 'a', '',
-                                     **{"href": 'psi_element://#typename#' + type_string}))
-    elif parent_text.startswith("rtype"):
-      type_string = node.children[0][0].astext()
-      self.body.append(self.starttag(node, 'a', '',
-                                     **{"href": 'psi_element://#typename#' + type_string}))
-
-    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):
-    if node.parent[0][0].astext().startswith("type "):
-      self.body.append("</a>")
-    HTMLTranslator.depart_field_body(self, node)
-
-  def visit_reference(self, node):
-    atts = {}
-    if 'refuri' in node:
-      atts['href'] = node['refuri']
-      if self.settings.cloak_email_addresses and atts['href'].startswith('mailto:'):
-        atts['href'] = self.cloak_mailto(atts['href'])
-        self.in_mailto = True
-      # atts['class'] += ' external'
-    else:
-      assert 'refid' in node, 'References must have "refuri" or "refid" attribute.'
-      atts['href'] = '#' + node['refid']
-      atts['class'] += ' internal'
-    if not isinstance(node.parent, nodes.TextElement):
-      assert len(node) == 1 and isinstance(node[0], nodes.image)
-      atts['class'] += ' image-reference'
-    self.body.append(self.starttag(node, 'a', '', **atts))
-
-  def starttag(self, node, tagname, suffix='\n', **attributes):
-    attr_dicts = [attributes]
-    if isinstance(node, nodes.Node):
-        attr_dicts.append(node.attributes)
-    if isinstance(node, dict):
-        attr_dicts.append(node)
-    # Munge each attribute dictionary.  Unfortunately, we need to
-    # iterate through attributes one at a time because some
-    # versions of docutils don't case-normalize attributes.
-    for attr_dict in attr_dicts:
-        for (key, val) in attr_dict.items():
-            # Prefix all CSS classes with "rst-"; and prefix all
-            # names with "rst-" to avoid conflicts.
-            if key.lower() in ('class', 'id', 'name'):
-                attr_dict[key] = 'rst-%s' % val
-            elif key.lower() in ('classes', 'ids', 'names'):
-                attr_dict[key] = ['rst-%s' % cls for cls in val]
-            elif key.lower() == 'href':
-                if attr_dict[key][:1]=='#':
-                    attr_dict[key] = '#rst-%s' % attr_dict[key][1:]
-                else:
-                    pass
-    # For headings, use class="heading"
-    if re.match(r'^h\d+$', tagname):
-        attributes['class'] = ' '.join([attributes.get('class',''),
-                                        'heading']).strip()
-
-    return HTMLTranslator.starttag(self, node, tagname, suffix,
-                                       **attributes)
-
-
-  def visit_field_list(self, node):
-    fields = {}
-    for n in node.children:
-      if len(n.children) == 0: continue
-      child = n.children[0]
-      rawsource = child.rawsource
-      if rawsource.startswith("param "):
-        index = rawsource.index("param ")
-        if len(child.children) == 0: continue
-        # Strip leading escaped asterisks for vararg parameters in Google code style docstrings
-        trimmed_name = re.sub(r'\\\*', '*', rawsource[index + 6:])
-        child.children[0] = Text(trimmed_name)
-        fields[trimmed_name] = n
-      if rawsource == "return":
-        fields["return"] = n
-
-    for n in node.children:
-      if len(n.children) == 0: continue
-      child = n.children[0]
-      rawsource = child.rawsource
-      if rawsource.startswith("type "):
-        index = rawsource.index("type ")
-        name = re.sub(r'\\\*', '*', rawsource[index + 5:])
-        if fields.has_key(name):
-          fields[name].type = n.children[1][0][0]
-          node.children.remove(n)
-      if rawsource == "rtype":
-        if fields.has_key("return"):
-          fields["return"].type = n.children[1][0][0]
-          node.children.remove(n)
-
-    HTMLTranslator.visit_field_list(self, node)
-
-
-  def unknown_visit(self, node):
-    """ Ignore unknown nodes """
-
-  def unknown_departure(self, node):
-    """ Ignore unknown nodes """
-
-  def visit_problematic(self, node):
-    """Don't insert hyperlinks to nowhere for e.g. unclosed asterisks."""
-    # Note that children text elements will be visited anyway
-
-  def depart_problematic(self, node):
-    pass
-
-  def visit_block_quote(self, node):
-    self.body.append(self.emptytag(node, "br"))
-
-  def depart_block_quote(self, node):
-    pass
-
-  def visit_literal(self, node):
-    """Process text to prevent tokens from wrapping."""
-    self.body.append(
-      self.starttag(node, 'tt', '', CLASS='docutils literal'))
-    text = node.astext()
-    for token in self.words_and_spaces.findall(text):
-      if token.strip():
-        self.body.append('<code>%s</code>'
-                         % self.encode(token))
-      elif token in ('\n', ' '):
-        # Allow breaks at whitespace:
-        self.body.append(token)
-      else:
-        # Protect runs of multiple spaces; the last space can wrap:
-        self.body.append('&nbsp;' * (len(token) - 1) + ' ')
-    self.body.append('</tt>')
-    raise nodes.SkipNode
+    def visit_field_name(self, node):
+        atts = {}
+        if self.in_docinfo:
+            atts['class'] = 'docinfo-name'
+        else:
+            atts['class'] = 'field-name'
+
+        self.context.append('')
+        atts['align'] = "right"
+        self.body.append(self.starttag(node, 'th', '', **atts))
+
+    def visit_field_body(self, node):
+        self.body.append(self.starttag(node, 'td', '', CLASS='field-body'))
+        parent_text = node.parent[0][0].astext()
+        if hasattr(node.parent, "type"):
+            self.body.append("(")
+            self.body.append(self.starttag(node, 'a', '',
+                                           href='psi_element://#typename#' + node.parent.type))
+            self.body.append(node.parent.type)
+            self.body.append("</a>")
+            self.body.append(") ")
+        elif parent_text.startswith("type "):
+            index = parent_text.index("type ")
+            type_string = parent_text[index + 5]
+            self.body.append(self.starttag(node, 'a', '',
+                                           href='psi_element://#typename#' + type_string))
+        elif parent_text.startswith("rtype"):
+            type_string = node.children[0][0].astext()
+            self.body.append(self.starttag(node, 'a', '',
+                                           href='psi_element://#typename#' + type_string))
+
+        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):
+        if node.parent[0][0].astext().startswith("type "):
+            self.body.append("</a>")
+        HTMLTranslator.depart_field_body(self, node)
+
+    def visit_reference(self, node):
+        atts = {}
+        if 'refuri' in node:
+            atts['href'] = node['refuri']
+            if self.settings.cloak_email_addresses and atts['href'].startswith('mailto:'):
+                atts['href'] = self.cloak_mailto(atts['href'])
+                self.in_mailto = True
+                # atts['class'] += ' external'
+        else:
+            assert 'refid' in node, 'References must have "refuri" or "refid" attribute.'
+            atts['href'] = '#' + node['refid']
+            atts['class'] += ' internal'
+        if not isinstance(node.parent, nodes.TextElement):
+            assert len(node) == 1 and isinstance(node[0], nodes.image)
+            atts['class'] += ' image-reference'
+        self.body.append(self.starttag(node, 'a', '', **atts))
+
+    def starttag(self, node, tagname, suffix='\n', **attributes):
+        attr_dicts = [attributes]
+        if isinstance(node, nodes.Node):
+            attr_dicts.append(node.attributes)
+        if isinstance(node, dict):
+            attr_dicts.append(node)
+        # Munge each attribute dictionary.  Unfortunately, we need to
+        # iterate through attributes one at a time because some
+        # versions of docutils don't case-normalize attributes.
+        for attr_dict in attr_dicts:
+            for (key, val) in attr_dict.items():
+                # Prefix all CSS classes with "rst-"; and prefix all
+                # names with "rst-" to avoid conflicts.
+                if key.lower() in ('class', 'id', 'name'):
+                    attr_dict[key] = 'rst-%s' % val
+                elif key.lower() in ('classes', 'ids', 'names'):
+                    attr_dict[key] = ['rst-%s' % cls for cls in val]
+                elif key.lower() == 'href':
+                    if attr_dict[key][:1] == '#':
+                        attr_dict[key] = '#rst-%s' % attr_dict[key][1:]
+                    else:
+                        pass
+        # For headings, use class="heading"
+        if re.match(r'^h\d+$', tagname):
+            attributes['class'] = ' '.join([attributes.get('class', ''), 'heading']).strip()
+
+        return HTMLTranslator.starttag(self, node, tagname, suffix, **attributes)
+
+    def visit_field_list(self, node):
+        fields = {}
+        for n in node.children:
+            if not n.children:
+                continue
+            child = n.children[0]
+            rawsource = child.rawsource
+            if rawsource.startswith("param "):
+                index = rawsource.index("param ")
+                if not child.children:
+                    continue
+                # Strip leading escaped asterisks for vararg parameters in Google code style docstrings
+                trimmed_name = re.sub(r'\\\*', '*', rawsource[index + 6:])
+                child.children[0] = Text(trimmed_name)
+                fields[trimmed_name] = n
+            if rawsource == "return":
+                fields["return"] = n
+
+        for n in node.children:
+            if not n.children:
+                continue
+            child = n.children[0]
+            rawsource = child.rawsource
+            if rawsource.startswith("type "):
+                index = rawsource.index("type ")
+                name = re.sub(r'\\\*', '*', rawsource[index + 5:])
+                if name in fields:
+                    fields[name].type = n.children[1][0][0]
+                    node.children.remove(n)
+            if rawsource == "rtype":
+                if "return" in fields:
+                    fields["return"].type = n.children[1][0][0]
+                    node.children.remove(n)
+
+        HTMLTranslator.visit_field_list(self, node)
+
+    def unknown_visit(self, node):
+        """ Ignore unknown nodes """
+
+    def unknown_departure(self, node):
+        """ Ignore unknown nodes """
+
+    def visit_problematic(self, node):
+        """Don't insert hyperlinks to nowhere for e.g. unclosed asterisks."""
+        # Note that children text elements will be visited anyway
+
+    def depart_problematic(self, node):
+        pass
+
+    def visit_block_quote(self, node):
+        self.body.append(self.emptytag(node, "br"))
+
+    def depart_block_quote(self, node):
+        pass
+
+    def visit_literal(self, node):
+        """Process text to prevent tokens from wrapping."""
+        self.body.append(self.starttag(node, 'tt', '', CLASS='docutils literal'))
+        text = node.astext()
+        for token in self.words_and_spaces.findall(text):
+            if token.strip():
+                self.body.append('<code>%s</code>'
+                                 % self.encode(token))
+            elif token in ('\n', ' '):
+                # Allow breaks at whitespace:
+                self.body.append(token)
+            else:
+                # Protect runs of multiple spaces; the last space can wrap:
+                self.body.append('&nbsp;' * (len(token) - 1) + ' ')
+        self.body.append('</tt>')
+        raise nodes.SkipNode
 
 
 class MyParsedRstDocstring(ParsedRstDocstring):
-  def __init__(self, document):
-    ParsedRstDocstring.__init__(self, document)
+    def __init__(self, document):
+        ParsedRstDocstring.__init__(self, document)
 
-  def to_html(self, docstring_linker, directory=None,
-              docindex=None, context=None, **options):
-    visitor = RestHTMLTranslator(self._document, docstring_linker,
-                                 directory, docindex, context)
-    self._document.walkabout(visitor)
-    return ''.join(visitor.body)
+    def to_html(self, docstring_linker, directory=None,
+                docindex=None, context=None, **options):
+        visitor = RestHTMLTranslator(self._document, docstring_linker,
+                                     directory, docindex, context)
+        self._document.walkabout(visitor)
+        return ''.join(visitor.body)
 
 
 def parse_docstring(docstring, errors, **options):
-  writer = _DocumentPseudoWriter()
-  reader = _EpydocReader(errors) # Outputs errors to the list.
-  publish_string(docstring, writer=writer, reader=reader,
-                 settings_overrides={'report_level': 10000,
-                                     'halt_level': 10000,
-                                     'warning_stream': None})
-  return MyParsedRstDocstring(writer.document)
+    writer = _DocumentPseudoWriter()
+    reader = _EpydocReader(errors)  # Outputs errors to the list.
+    publish_string(docstring, writer=writer, reader=reader,
+                   settings_overrides={'report_level': 10000,
+                                       'halt_level': 10000,
+                                       'warning_stream': None})
+    return MyParsedRstDocstring(writer.document)
 
 
 def main(text=None):
-  try:
-    src = sys.stdin.read() if text is None else text
+    try:
+        src = sys.stdin.read() if text is None else text
 
-    errors = []
+        errors = []
 
-    class EmptyLinker(DocstringLinker):
-      def translate_indexterm(self, indexterm):
-        return ""
+        class EmptyLinker(DocstringLinker):
+            def translate_indexterm(self, indexterm):
+                return ""
 
-      def translate_identifier_xref(self, identifier, label=None):
-        return identifier
+            def translate_identifier_xref(self, identifier, label=None):
+                return identifier
 
-    docstring = parse_docstring(src, errors)
-    html = docstring.to_html(EmptyLinker())
+        docstring = parse_docstring(src, errors)
+        html = docstring.to_html(EmptyLinker())
 
-    if errors and not html:
-      sys.stderr.write("Error parsing docstring:\n")
-      for error in errors:
-        sys.stderr.write(str(error) + "\n")
-      sys.exit(1)
+        if errors and not html:
+            sys.stderr.write("Error parsing docstring:\n")
+            for error in errors:
+                sys.stderr.write(str(error) + "\n")
+            sys.exit(1)
 
-    sys.stdout.write(html)
-    sys.stdout.flush()
-  except:
-    exc_type, exc_value, exc_traceback = sys.exc_info()
-    sys.stderr.write("Error calculating docstring: " + str(exc_value))
-    sys.exit(1)
+        sys.stdout.write(html)
+        sys.stdout.flush()
+    except:
+        exc_type, exc_value, exc_traceback = sys.exc_info()
+        sys.stderr.write("Error calculating docstring: " + str(exc_value))
+        sys.exit(1)
 
 
 if __name__ == '__main__':
-  main()
+    main()