1# reStructuredText (RST) to GitHub-flavored Markdown converter
2
3import re
4from docutils import core, nodes, writers
5
6
7def is_github_ref(node):
8    return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
9
10
11class Translator(nodes.NodeVisitor):
12    def __init__(self, document):
13        nodes.NodeVisitor.__init__(self, document)
14        self.output = ''
15        self.indent = 0
16        self.preserve_newlines = False
17
18    def write(self, text):
19        self.output += text.replace('\n', '\n' + ' ' * self.indent)
20
21    def visit_document(self, node):
22        pass
23
24    def depart_document(self, node):
25        pass
26
27    def visit_section(self, node):
28        pass
29
30    def depart_section(self, node):
31        # Skip all sections except the first one.
32        raise nodes.StopTraversal
33
34    def visit_title(self, node):
35        self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
36        raise nodes.SkipChildren
37
38    def depart_title(self, node):
39        pass
40
41    def visit_Text(self, node):
42        if not self.preserve_newlines:
43            node = node.replace('\n', ' ')
44        self.write(node)
45
46    def depart_Text(self, node):
47        pass
48
49    def visit_bullet_list(self, node):
50        pass
51
52    def depart_bullet_list(self, node):
53        pass
54
55    def visit_list_item(self, node):
56        self.write('* ')
57        self.indent += 2
58
59    def depart_list_item(self, node):
60        self.indent -= 2
61        self.write('\n\n')
62
63    def visit_paragraph(self, node):
64        pass
65
66    def depart_paragraph(self, node):
67        pass
68
69    def visit_reference(self, node):
70        if not is_github_ref(node):
71            self.write('[')
72
73    def depart_reference(self, node):
74        if not is_github_ref(node):
75            self.write('](' + node['refuri'] + ')')
76
77    def visit_target(self, node):
78        pass
79
80    def depart_target(self, node):
81        pass
82
83    def visit_literal(self, node):
84        self.write('`')
85
86    def depart_literal(self, node):
87        self.write('`')
88
89    def visit_literal_block(self, node):
90        self.write('\n\n```')
91        if 'c++' in node['classes']:
92            self.write('c++')
93        self.write('\n')
94        self.preserve_newlines = True
95
96    def depart_literal_block(self, node):
97        self.write('\n```\n')
98        self.preserve_newlines = False
99
100    def visit_inline(self, node):
101        pass
102
103    def depart_inline(self, node):
104        pass
105
106    def visit_image(self, node):
107        self.write('![](' + node['uri'] + ')')
108
109    def depart_image(self, node):
110        pass
111
112
113class MDWriter(writers.Writer):
114    """GitHub-flavored markdown writer"""
115
116    supported = ('md',)
117    """Formats this writer supports."""
118
119    def translate(self):
120        translator = Translator(self.document)
121        self.document.walkabout(translator)
122        self.output = (translator.output, translator.version)
123
124
125def convert(rst_path):
126    """Converts RST file to Markdown."""
127    return core.publish_file(source_path=rst_path, writer=MDWriter())
128