]>
Commit | Line | Data |
---|---|---|
9f95a23c | 1 | #!/usr/bin/env python |
11fdf7f2 TL |
2 | # reStructuredText (RST) to GitHub-flavored Markdown converter |
3 | ||
9f95a23c | 4 | import re, sys |
11fdf7f2 TL |
5 | from docutils import core, nodes, writers |
6 | ||
7 | ||
8 | def is_github_ref(node): | |
9 | return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri']) | |
10 | ||
11 | ||
12 | class 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 | ||
9f95a23c TL |
39 | def visit_title_reference(self, node): |
40 | raise Exception(node) | |
41 | ||
11fdf7f2 TL |
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): | |
1e59de90 | 68 | self.write('\n\n') |
11fdf7f2 TL |
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 | ||
141 | class 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 | ||
153 | def convert(rst_path): | |
154 | """Converts RST file to Markdown.""" | |
155 | return core.publish_file(source_path=rst_path, writer=MDWriter()) | |
9f95a23c TL |
156 | |
157 | ||
158 | if __name__ == '__main__': | |
159 | convert(sys.argv[1]) |