1 | #!/usr/bin/env python |
2 | # A tool to parse the FormatStyle struct from Format.h and update the |
3 | # documentation in ../ClangFormatStyleOptions.rst automatically. |
4 | # Run from the directory in which this file is located to update the docs. |
5 | |
6 | import collections |
7 | import os |
8 | import re |
9 | |
10 | CLANG_DIR = os.path.join(os.path.dirname(__file__), '../..') |
11 | FORMAT_STYLE_FILE = os.path.join(CLANG_DIR, 'include/clang/Format/Format.h') |
12 | INCLUDE_STYLE_FILE = os.path.join(CLANG_DIR, 'include/clang/Tooling/Inclusions/IncludeStyle.h') |
13 | DOC_FILE = os.path.join(CLANG_DIR, 'docs/ClangFormatStyleOptions.rst') |
14 | |
15 | |
16 | def substitute(text, tag, contents): |
17 | replacement = '\n.. START_%s\n\n%s\n\n.. END_%s\n' % (tag, contents, tag) |
18 | pattern = r'\n\.\. START_%s\n.*\n\.\. END_%s\n' % (tag, tag) |
19 | return re.sub(pattern, '%s', text, flags=re.S) % replacement |
20 | |
21 | def doxygen2rst(text): |
22 | text = re.sub(r'<tt>\s*(.*?)\s*<\/tt>', r'``\1``', text) |
23 | text = re.sub(r'\\c ([^ ,;\.]+)', r'``\1``', text) |
24 | text = re.sub(r'\\\w+ ', '', text) |
25 | return text |
26 | |
27 | def indent(text, columns, indent_first_line=True): |
28 | indent = ' ' * columns |
29 | s = re.sub(r'\n([^\n])', '\n' + indent + '\\1', text, flags=re.S) |
30 | if not indent_first_line or s.startswith('\n'): |
31 | return s |
32 | return indent + s |
33 | |
34 | class Option(object): |
35 | def __init__(self, name, type, comment): |
36 | self.name = name |
37 | self.type = type |
38 | self.comment = comment.strip() |
39 | self.enum = None |
40 | self.nested_struct = None |
41 | |
42 | def __str__(self): |
43 | s = '**%s** (``%s``)\n%s' % (self.name, self.type, |
44 | doxygen2rst(indent(self.comment, 2))) |
45 | if self.enum: |
46 | s += indent('\n\nPossible values:\n\n%s\n' % self.enum, 2) |
47 | if self.nested_struct: |
48 | s += indent('\n\nNested configuration flags:\n\n%s\n' %self.nested_struct, |
49 | 2) |
50 | return s |
51 | |
52 | class NestedStruct(object): |
53 | def __init__(self, name, comment): |
54 | self.name = name |
55 | self.comment = comment.strip() |
56 | self.values = [] |
57 | |
58 | def __str__(self): |
59 | return '\n'.join(map(str, self.values)) |
60 | |
61 | class NestedField(object): |
62 | def __init__(self, name, comment): |
63 | self.name = name |
64 | self.comment = comment.strip() |
65 | |
66 | def __str__(self): |
67 | return '\n* ``%s`` %s' % ( |
68 | self.name, |
69 | doxygen2rst(indent(self.comment, 2, indent_first_line=False))) |
70 | |
71 | class Enum(object): |
72 | def __init__(self, name, comment): |
73 | self.name = name |
74 | self.comment = comment.strip() |
75 | self.values = [] |
76 | |
77 | def __str__(self): |
78 | return '\n'.join(map(str, self.values)) |
79 | |
80 | class EnumValue(object): |
81 | def __init__(self, name, comment): |
82 | self.name = name |
83 | self.comment = comment |
84 | |
85 | def __str__(self): |
86 | return '* ``%s`` (in configuration: ``%s``)\n%s' % ( |
87 | self.name, |
88 | re.sub('.*_', '', self.name), |
89 | doxygen2rst(indent(self.comment, 2))) |
90 | |
91 | def clean_comment_line(line): |
92 | match = re.match(r'^/// \\code(\{.(\w+)\})?$', line) |
93 | if match: |
94 | lang = match.groups()[1] |
95 | if not lang: |
96 | lang = 'c++' |
97 | return '\n.. code-block:: %s\n\n' % lang |
98 | if line == '/// \\endcode': |
99 | return '' |
100 | return line[4:] + '\n' |
101 | |
102 | def read_options(header): |
103 | class State(object): |
104 | BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComent, \ |
105 | InFieldComment, InEnum, InEnumMemberComment = range(8) |
106 | state = State.BeforeStruct |
107 | |
108 | options = [] |
109 | enums = {} |
110 | nested_structs = {} |
111 | comment = '' |
112 | enum = None |
113 | nested_struct = None |
114 | |
115 | for line in header: |
116 | line = line.strip() |
117 | if state == State.BeforeStruct: |
118 | if line == 'struct FormatStyle {' or line == 'struct IncludeStyle {': |
119 | state = State.InStruct |
120 | elif state == State.InStruct: |
121 | if line.startswith('///'): |
122 | state = State.InFieldComment |
123 | comment = clean_comment_line(line) |
124 | elif line == '};': |
125 | state = State.Finished |
126 | break |
127 | elif state == State.InFieldComment: |
128 | if line.startswith('///'): |
129 | comment += clean_comment_line(line) |
130 | elif line.startswith('enum'): |
131 | state = State.InEnum |
132 | name = re.sub(r'enum\s+(\w+)\s*\{', '\\1', line) |
133 | enum = Enum(name, comment) |
134 | elif line.startswith('struct'): |
135 | state = State.InNestedStruct |
136 | name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line) |
137 | nested_struct = NestedStruct(name, comment) |
138 | elif line.endswith(';'): |
139 | state = State.InStruct |
140 | field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);', |
141 | line).groups() |
142 | option = Option(str(field_name), str(field_type), comment) |
143 | options.append(option) |
144 | else: |
145 | raise Exception('Invalid format, expected comment, field or enum') |
146 | elif state == State.InNestedStruct: |
147 | if line.startswith('///'): |
148 | state = State.InNestedFieldComent |
149 | comment = clean_comment_line(line) |
150 | elif line == '};': |
151 | state = State.InStruct |
152 | nested_structs[nested_struct.name] = nested_struct |
153 | elif state == State.InNestedFieldComent: |
154 | if line.startswith('///'): |
155 | comment += clean_comment_line(line) |
156 | else: |
157 | state = State.InNestedStruct |
158 | nested_struct.values.append(NestedField(line.replace(';', ''), comment)) |
159 | elif state == State.InEnum: |
160 | if line.startswith('///'): |
161 | state = State.InEnumMemberComment |
162 | comment = clean_comment_line(line) |
163 | elif line == '};': |
164 | state = State.InStruct |
165 | enums[enum.name] = enum |
166 | else: |
167 | raise Exception('Invalid format, expected enum field comment or };') |
168 | elif state == State.InEnumMemberComment: |
169 | if line.startswith('///'): |
170 | comment += clean_comment_line(line) |
171 | else: |
172 | state = State.InEnum |
173 | enum.values.append(EnumValue(line.replace(',', ''), comment)) |
174 | if state != State.Finished: |
175 | raise Exception('Not finished by the end of file') |
176 | |
177 | for option in options: |
178 | if not option.type in ['bool', 'unsigned', 'int', 'std::string', |
179 | 'std::vector<std::string>', |
180 | 'std::vector<IncludeCategory>', |
181 | 'std::vector<RawStringFormat>']: |
182 | if option.type in enums: |
183 | option.enum = enums[option.type] |
184 | elif option.type in nested_structs: |
185 | option.nested_struct = nested_structs[option.type] |
186 | else: |
187 | raise Exception('Unknown type: %s' % option.type) |
188 | return options |
189 | |
190 | options = read_options(open(FORMAT_STYLE_FILE)) |
191 | options += read_options(open(INCLUDE_STYLE_FILE)) |
192 | |
193 | options = sorted(options, key=lambda x: x.name) |
194 | options_text = '\n\n'.join(map(str, options)) |
195 | |
196 | contents = open(DOC_FILE).read() |
197 | |
198 | contents = substitute(contents, 'FORMAT_STYLE_OPTIONS', options_text) |
199 | |
200 | with open(DOC_FILE, 'wb') as output: |
201 | output.write(contents) |
202 | |