You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
373 lines
10 KiB
373 lines
10 KiB
4 years ago
|
from .depth import Depth
|
||
|
from .doctree2md import Translator, Writer
|
||
|
from docutils import nodes
|
||
|
from pydash import _
|
||
|
import html2text
|
||
|
import os
|
||
|
import sys
|
||
|
|
||
|
h = html2text.HTML2Text()
|
||
|
|
||
|
class MarkdownTranslator(Translator):
|
||
|
depth = Depth()
|
||
|
enumerated_count = {}
|
||
|
table_entries = []
|
||
|
table_rows = []
|
||
|
tables = []
|
||
|
tbodys = []
|
||
|
theads = []
|
||
|
|
||
|
def __init__(self, document, builder=None):
|
||
|
Translator.__init__(self, document, builder=None)
|
||
|
self.builder = builder
|
||
|
|
||
|
@property
|
||
|
def rows(self):
|
||
|
rows = []
|
||
|
if not len(self.tables):
|
||
|
return rows
|
||
|
for node in self.tables[len(self.tables) - 1].children:
|
||
|
if isinstance(node, nodes.row):
|
||
|
rows.append(node)
|
||
|
else:
|
||
|
for node in node.children:
|
||
|
if isinstance(node, nodes.row):
|
||
|
rows.append(node)
|
||
|
return rows
|
||
|
|
||
|
def visit_document(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_document(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_title(self, node):
|
||
|
self.add((self.section_level) * '#' + ' ')
|
||
|
|
||
|
def visit_desc(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_desc(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_desc_annotation(self, node):
|
||
|
# annotation, e.g 'method', 'class'
|
||
|
pass
|
||
|
|
||
|
def depart_desc_annotation(self, node):
|
||
|
# annotation, e.g 'method', 'class'
|
||
|
pass
|
||
|
|
||
|
def visit_desc_addname(self, node):
|
||
|
# module preroll for class/method
|
||
|
pass
|
||
|
|
||
|
def depart_desc_addname(self, node):
|
||
|
# module preroll for class/method
|
||
|
pass
|
||
|
|
||
|
def visit_desc_name(self, node):
|
||
|
# name of the class/method
|
||
|
# Escape "__" which is a formating string for markdown
|
||
|
if node.rawsource.startswith("__"):
|
||
|
self.add('\\')
|
||
|
pass
|
||
|
|
||
|
def depart_desc_name(self, node):
|
||
|
# name of the class/method
|
||
|
self.add('(')
|
||
|
|
||
|
def visit_desc_content(self, node):
|
||
|
# the description of the class/method
|
||
|
pass
|
||
|
|
||
|
def depart_desc_content(self, node):
|
||
|
# the description of the class/method
|
||
|
pass
|
||
|
|
||
|
def visit_desc_signature(self, node):
|
||
|
# the main signature of class/method
|
||
|
# We dont want methods to be at the same level as classes,
|
||
|
# If signature has a non null class, thats means it is a signature
|
||
|
# of a class method
|
||
|
if ("class" in node.attributes and node.attributes["class"]):
|
||
|
self.add('\n#### ')
|
||
|
else:
|
||
|
self.add('\n### ')
|
||
|
|
||
|
def depart_desc_signature(self, node):
|
||
|
# the main signature of class/method
|
||
|
self.add(')\n')
|
||
|
|
||
|
def visit_desc_parameterlist(self, node):
|
||
|
# method/class ctor param list
|
||
|
pass
|
||
|
|
||
|
def depart_desc_parameterlist(self, node):
|
||
|
# method/class ctor param list
|
||
|
pass
|
||
|
|
||
|
def visit_desc_parameter(self, node):
|
||
|
# single method/class ctr param
|
||
|
pass
|
||
|
|
||
|
def depart_desc_parameter(self, node):
|
||
|
# single method/class ctr param
|
||
|
# if there are additional params, include a comma
|
||
|
if node.next_node(descend=False, siblings=True):
|
||
|
self.add(', ')
|
||
|
|
||
|
# list of parameters/return values/exceptions
|
||
|
#
|
||
|
# field_list
|
||
|
# field
|
||
|
# field_name (e.g 'returns/parameters/raises')
|
||
|
#
|
||
|
|
||
|
def visit_field_list(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_field_list(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_field(self, node):
|
||
|
self.add('\n')
|
||
|
|
||
|
def depart_field(self, node):
|
||
|
self.add('\n')
|
||
|
|
||
|
def visit_field_name(self, node):
|
||
|
# field name, e.g 'returns', 'parameters'
|
||
|
self.add('* **')
|
||
|
|
||
|
def depart_field_name(self, node):
|
||
|
self.add('**')
|
||
|
|
||
|
def visit_literal_strong(self, node):
|
||
|
self.add('**')
|
||
|
|
||
|
def depart_literal_strong(self, node):
|
||
|
self.add('**')
|
||
|
|
||
|
def visit_literal_emphasis(self, node):
|
||
|
self.add('*')
|
||
|
|
||
|
def depart_literal_emphasis(self, node):
|
||
|
self.add('*')
|
||
|
|
||
|
def visit_title_reference(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_title_reference(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_versionmodified(self, node):
|
||
|
# deprecation and compatibility messages
|
||
|
# type will hold something like 'deprecated'
|
||
|
self.add('**%s:** ' % node.attributes['type'].capitalize())
|
||
|
|
||
|
def depart_versionmodified(self, node):
|
||
|
# deprecation and compatibility messages
|
||
|
pass
|
||
|
|
||
|
def visit_warning(self, node):
|
||
|
"""Sphinx warning directive."""
|
||
|
self.add('**WARNING**: ')
|
||
|
|
||
|
def depart_warning(self, node):
|
||
|
"""Sphinx warning directive."""
|
||
|
pass
|
||
|
|
||
|
def visit_note(self, node):
|
||
|
"""Sphinx note directive."""
|
||
|
self.add('**NOTE**: ')
|
||
|
|
||
|
def depart_note(self, node):
|
||
|
"""Sphinx note directive."""
|
||
|
pass
|
||
|
|
||
|
def visit_rubric(self, node):
|
||
|
"""Sphinx Rubric, a heading without relation to the document sectioning
|
||
|
http://docutils.sourceforge.net/docs/ref/rst/directives.html#rubric."""
|
||
|
self.add('### ')
|
||
|
|
||
|
def depart_rubric(self, node):
|
||
|
"""Sphinx Rubric, a heading without relation to the document sectioning
|
||
|
http://docutils.sourceforge.net/docs/ref/rst/directives.html#rubric."""
|
||
|
self.add('\n\n')
|
||
|
|
||
|
def visit_image(self, node):
|
||
|
"""Image directive."""
|
||
|
uri = node.attributes['uri']
|
||
|
doc_folder = os.path.dirname(self.builder.current_docname)
|
||
|
if uri.startswith(doc_folder):
|
||
|
# drop docname prefix
|
||
|
uri = uri[len(doc_folder):]
|
||
|
if uri.startswith('/'):
|
||
|
uri = '.' + uri
|
||
|
self.add('\n\n![image](%s)\n\n' % uri)
|
||
|
|
||
|
def depart_image(self, node):
|
||
|
"""Image directive."""
|
||
|
pass
|
||
|
|
||
|
def visit_autosummary_table(self, node):
|
||
|
"""Sphinx autosummary See http://www.sphinx-
|
||
|
doc.org/en/master/usage/extensions/autosummary.html."""
|
||
|
pass
|
||
|
|
||
|
def depart_autosummary_table(self, node):
|
||
|
"""Sphinx autosummary See http://www.sphinx-
|
||
|
doc.org/en/master/usage/extensions/autosummary.html."""
|
||
|
pass
|
||
|
|
||
|
################################################################################
|
||
|
# tables
|
||
|
#
|
||
|
# docutils.nodes.table
|
||
|
# docutils.nodes.tgroup [cols=x]
|
||
|
# docutils.nodes.colspec
|
||
|
#
|
||
|
# docutils.nodes.thead
|
||
|
# docutils.nodes.row
|
||
|
# docutils.nodes.entry
|
||
|
# docutils.nodes.entry
|
||
|
# docutils.nodes.entry
|
||
|
#
|
||
|
# docutils.nodes.tbody
|
||
|
# docutils.nodes.row
|
||
|
# docutils.nodes.entry
|
||
|
|
||
|
def visit_math_block(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_math_block(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_raw(self, node):
|
||
|
self.descend('raw')
|
||
|
|
||
|
def depart_raw(self, node):
|
||
|
self.ascend('raw')
|
||
|
|
||
|
def visit_table(self, node):
|
||
|
self.tables.append(node)
|
||
|
|
||
|
def depart_table(self, node):
|
||
|
self.tables.pop()
|
||
|
|
||
|
def visit_tabular_col_spec(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_tabular_col_spec(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_colspec(self, node):
|
||
|
pass
|
||
|
|
||
|
def depart_colspec(self, node):
|
||
|
pass
|
||
|
|
||
|
def visit_tgroup(self, node):
|
||
|
self.descend('tgroup')
|
||
|
|
||
|
def depart_tgroup(self, node):
|
||
|
self.ascend('tgroup')
|
||
|
|
||
|
def visit_thead(self, node):
|
||
|
if not len(self.tables):
|
||
|
raise nodes.SkipNode
|
||
|
self.theads.append(node)
|
||
|
|
||
|
def depart_thead(self, node):
|
||
|
for i in range(len(self.table_entries)):
|
||
|
length = 0
|
||
|
for row in self.table_rows:
|
||
|
if len(row.children) > i:
|
||
|
entry_length = len(row.children[i].astext())
|
||
|
if entry_length > length:
|
||
|
length = entry_length
|
||
|
self.add('| ' + ''.join(_.map(range(length), lambda: '-')) + ' ')
|
||
|
self.add('|\n')
|
||
|
self.table_entries = []
|
||
|
self.theads.pop()
|
||
|
|
||
|
def visit_tbody(self, node):
|
||
|
if not len(self.tables):
|
||
|
raise nodes.SkipNode
|
||
|
self.tbodys.append(node)
|
||
|
|
||
|
def depart_tbody(self, node):
|
||
|
self.tbodys.pop()
|
||
|
|
||
|
def visit_row(self, node):
|
||
|
if not len(self.theads) and not len(self.tbodys):
|
||
|
raise nodes.SkipNode
|
||
|
self.table_rows.append(node)
|
||
|
|
||
|
def depart_row(self, node):
|
||
|
self.add('|\n')
|
||
|
if not len(self.theads):
|
||
|
self.table_entries = []
|
||
|
|
||
|
def visit_enumerated_list(self, node):
|
||
|
self.depth.descend('list')
|
||
|
self.depth.descend('enumerated_list')
|
||
|
|
||
|
def depart_enumerated_list(self, node):
|
||
|
self.enumerated_count[self.depth.get('list')] = 0
|
||
|
self.depth.ascend('enumerated_list')
|
||
|
self.depth.ascend('list')
|
||
|
|
||
|
def visit_bullet_list(self, node):
|
||
|
self.depth.descend('list')
|
||
|
self.depth.descend('bullet_list')
|
||
|
|
||
|
def depart_bullet_list(self, node):
|
||
|
self.depth.ascend('bullet_list')
|
||
|
self.depth.ascend('list')
|
||
|
|
||
|
def visit_list_item(self, node):
|
||
|
self.depth.descend('list_item')
|
||
|
depth = self.depth.get('list')
|
||
|
depth_padding = ''.join([' ' for i in range(depth - 1)])
|
||
|
marker = '*'
|
||
|
if node.parent.tagname == 'enumerated_list':
|
||
|
if depth not in self.enumerated_count:
|
||
|
self.enumerated_count[depth] = 1
|
||
|
else:
|
||
|
self.enumerated_count[depth] = self.enumerated_count[depth] + 1
|
||
|
marker = str(self.enumerated_count[depth]) + '.'
|
||
|
self.add('\n' + depth_padding + marker + ' ')
|
||
|
|
||
|
def depart_list_item(self, node):
|
||
|
self.depth.ascend('list_item')
|
||
|
|
||
|
def visit_entry(self, node):
|
||
|
if not len(self.table_rows):
|
||
|
raise nodes.SkipNode
|
||
|
self.table_entries.append(node)
|
||
|
self.add('| ')
|
||
|
|
||
|
def depart_entry(self, node):
|
||
|
length = 0
|
||
|
i = len(self.table_entries) - 1
|
||
|
for row in self.table_rows:
|
||
|
if len(row.children) > i:
|
||
|
entry_length = len(row.children[i].astext())
|
||
|
if entry_length > length:
|
||
|
length = entry_length
|
||
|
padding = ''.join(
|
||
|
_.map(range(length - len(node.astext())), lambda: ' ')
|
||
|
)
|
||
|
self.add(padding + ' ')
|
||
|
|
||
|
def descend(self, node_name):
|
||
|
self.depth.descend(node_name)
|
||
|
|
||
|
def ascend(self, node_name):
|
||
|
self.depth.ascend(node_name)
|
||
|
|
||
|
class MarkdownWriter(Writer):
|
||
|
translator_class = MarkdownTranslator
|