• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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