1#!/usr/bin/env python 2# reStructuredText (RST) to GitHub-flavored Markdown converter 3 4import re, sys 5from docutils import core, nodes, writers 6 7 8def is_github_ref(node): 9 return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri']) 10 11 12class Translator(nodes.NodeVisitor): 13 def __init__(self, document): 14 nodes.NodeVisitor.__init__(self, document) 15 self.output = '' 16 self.indent = 0 17 self.preserve_newlines = False 18 19 def write(self, text): 20 self.output += text.replace('\n', '\n' + ' ' * self.indent) 21 22 def visit_document(self, node): 23 pass 24 25 def depart_document(self, node): 26 pass 27 28 def visit_section(self, node): 29 pass 30 31 def depart_section(self, node): 32 # Skip all sections except the first one. 33 raise nodes.StopTraversal 34 35 def visit_title(self, node): 36 self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1) 37 raise nodes.SkipChildren 38 39 def visit_title_reference(self, node): 40 raise Exception(node) 41 42 def depart_title(self, node): 43 pass 44 45 def visit_Text(self, node): 46 if not self.preserve_newlines: 47 node = node.replace('\n', ' ') 48 self.write(node) 49 50 def depart_Text(self, node): 51 pass 52 53 def visit_bullet_list(self, node): 54 pass 55 56 def depart_bullet_list(self, node): 57 pass 58 59 def visit_list_item(self, node): 60 self.write('* ') 61 self.indent += 2 62 63 def depart_list_item(self, node): 64 self.indent -= 2 65 self.write('\n\n') 66 67 def visit_paragraph(self, node): 68 pass 69 70 def depart_paragraph(self, node): 71 pass 72 73 def visit_reference(self, node): 74 if not is_github_ref(node): 75 self.write('[') 76 77 def depart_reference(self, node): 78 if not is_github_ref(node): 79 self.write('](' + node['refuri'] + ')') 80 81 def visit_target(self, node): 82 pass 83 84 def depart_target(self, node): 85 pass 86 87 def visit_literal(self, node): 88 self.write('`') 89 90 def depart_literal(self, node): 91 self.write('`') 92 93 def visit_literal_block(self, node): 94 self.write('\n\n```') 95 if 'c++' in node['classes']: 96 self.write('c++') 97 self.write('\n') 98 self.preserve_newlines = True 99 100 def depart_literal_block(self, node): 101 self.write('\n```\n') 102 self.preserve_newlines = False 103 104 def visit_inline(self, node): 105 pass 106 107 def depart_inline(self, node): 108 pass 109 110 def visit_image(self, node): 111 self.write('![](' + node['uri'] + ')') 112 113 def depart_image(self, node): 114 pass 115 116 def write_row(self, row, widths): 117 for i, entry in enumerate(row): 118 text = entry[0][0] if len(entry) > 0 else '' 119 if i != 0: 120 self.write('|') 121 self.write('{:{}}'.format(text, widths[i])) 122 self.write('\n') 123 124 def visit_table(self, node): 125 table = node.children[0] 126 colspecs = table[:-2] 127 thead = table[-2] 128 tbody = table[-1] 129 widths = [int(cs['colwidth']) for cs in colspecs] 130 sep = '|'.join(['-' * w for w in widths]) + '\n' 131 self.write('\n\n') 132 self.write_row(thead[0], widths) 133 self.write(sep) 134 for row in tbody: 135 self.write_row(row, widths) 136 raise nodes.SkipChildren 137 138 def depart_table(self, node): 139 pass 140 141class MDWriter(writers.Writer): 142 """GitHub-flavored markdown writer""" 143 144 supported = ('md',) 145 """Formats this writer supports.""" 146 147 def translate(self): 148 translator = Translator(self.document) 149 self.document.walkabout(translator) 150 self.output = (translator.output, translator.version) 151 152 153def convert(rst_path): 154 """Converts RST file to Markdown.""" 155 return core.publish_file(source_path=rst_path, writer=MDWriter()) 156 157 158if __name__ == '__main__': 159 convert(sys.argv[1]) 160