diff --git a/python/helpers/rest_formatter.py b/python/helpers/rest_formatter.py index 24b0c7bcbb0b..09ce081b0330 100644 --- a/python/helpers/rest_formatter.py +++ b/python/helpers/rest_formatter.py @@ -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' + 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)) + 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("") - 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})) + 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("") + 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) + 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("") - HTMLTranslator.depart_field_body(self, node) + def depart_field_body(self, node): + if node.parent[0][0].astext().startswith("type "): + self.body.append("") + 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 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() + 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) + 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 - 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 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) - 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) - HTMLTranslator.visit_field_list(self, node) + def unknown_visit(self, node): + """ Ignore unknown nodes """ + def unknown_departure(self, node): + """ Ignore unknown nodes """ - def unknown_visit(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 unknown_departure(self, node): - """ Ignore unknown nodes """ + def depart_problematic(self, node): + pass - 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 visit_block_quote(self, node): + self.body.append(self.emptytag(node, "br")) - def depart_problematic(self, node): - pass + def depart_block_quote(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('%s' - % 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(' ' * (len(token) - 1) + ' ') - self.body.append('') - raise nodes.SkipNode + 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('%s' + % 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(' ' * (len(token) - 1) + ' ') + self.body.append('') + 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()