Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 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 |
Alexander Kornienko | 370bfb4 | 2016-02-23 16:11:43 +0000 | [diff] [blame] | 7 | import os |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 8 | import re |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 9 | |
Alexander Kornienko | 370bfb4 | 2016-02-23 16:11:43 +0000 | [diff] [blame] | 10 | CLANG_DIR = os.path.join(os.path.dirname(__file__), '../..') |
| 11 | FORMAT_STYLE_FILE = os.path.join(CLANG_DIR, 'include/clang/Format/Format.h') |
Krasimir Georgiev | cf699ad | 2018-07-25 10:21:47 +0000 | [diff] [blame] | 12 | INCLUDE_STYLE_FILE = os.path.join(CLANG_DIR, 'include/clang/Tooling/Inclusions/IncludeStyle.h') |
Alexander Kornienko | 370bfb4 | 2016-02-23 16:11:43 +0000 | [diff] [blame] | 13 | DOC_FILE = os.path.join(CLANG_DIR, 'docs/ClangFormatStyleOptions.rst') |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 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 | |
Krasimir Georgiev | 90b4ce3 | 2017-06-23 11:29:40 +0000 | [diff] [blame] | 27 | def indent(text, columns, indent_first_line=True): |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 28 | indent = ' ' * columns |
| 29 | s = re.sub(r'\n([^\n])', '\n' + indent + '\\1', text, flags=re.S) |
Krasimir Georgiev | 90b4ce3 | 2017-06-23 11:29:40 +0000 | [diff] [blame] | 30 | if not indent_first_line or s.startswith('\n'): |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 31 | return s |
| 32 | return indent + s |
| 33 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 34 | class Option(object): |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 35 | def __init__(self, name, type, comment): |
| 36 | self.name = name |
| 37 | self.type = type |
| 38 | self.comment = comment.strip() |
| 39 | self.enum = None |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 40 | self.nested_struct = None |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 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) |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 47 | if self.nested_struct: |
| 48 | s += indent('\n\nNested configuration flags:\n\n%s\n' %self.nested_struct, |
| 49 | 2) |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 50 | return s |
| 51 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 52 | class NestedStruct(object): |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 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 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 61 | class NestedField(object): |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 62 | def __init__(self, name, comment): |
| 63 | self.name = name |
| 64 | self.comment = comment.strip() |
| 65 | |
| 66 | def __str__(self): |
Krasimir Georgiev | 90b4ce3 | 2017-06-23 11:29:40 +0000 | [diff] [blame] | 67 | return '\n* ``%s`` %s' % ( |
| 68 | self.name, |
| 69 | doxygen2rst(indent(self.comment, 2, indent_first_line=False))) |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 70 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 71 | class Enum(object): |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 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 | |
paul_hoad | eb00839 | 2019-11-08 14:34:07 +0000 | [diff] [blame] | 80 | class NestedEnum(object): |
| 81 | def __init__(self, name, enumtype, comment, values): |
| 82 | self.name = name |
| 83 | self.comment = comment |
| 84 | self.values = values |
| 85 | self.type = enumtype |
| 86 | |
| 87 | def __str__(self): |
| 88 | s = '\n* ``%s %s``\n%s' % (self.type, self.name, |
| 89 | doxygen2rst(indent(self.comment, 2))) |
| 90 | s += indent('\nPossible values:\n\n', 2) |
| 91 | s += indent('\n'.join(map(str, self.values)),2) |
| 92 | return s; |
| 93 | |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 94 | class EnumValue(object): |
paulhoad | eadb65f | 2019-11-06 20:02:16 +0000 | [diff] [blame] | 95 | def __init__(self, name, comment, config): |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 96 | self.name = name |
Alexander Kornienko | 32718b6 | 2016-02-23 16:11:55 +0000 | [diff] [blame] | 97 | self.comment = comment |
paulhoad | eadb65f | 2019-11-06 20:02:16 +0000 | [diff] [blame] | 98 | self.config = config |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 99 | |
| 100 | def __str__(self): |
| 101 | return '* ``%s`` (in configuration: ``%s``)\n%s' % ( |
| 102 | self.name, |
paulhoad | eadb65f | 2019-11-06 20:02:16 +0000 | [diff] [blame] | 103 | re.sub('.*_', '', self.config), |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 104 | doxygen2rst(indent(self.comment, 2))) |
| 105 | |
| 106 | def clean_comment_line(line): |
Alexander Kornienko | 32718b6 | 2016-02-23 16:11:55 +0000 | [diff] [blame] | 107 | match = re.match(r'^/// \\code(\{.(\w+)\})?$', line) |
| 108 | if match: |
| 109 | lang = match.groups()[1] |
| 110 | if not lang: |
| 111 | lang = 'c++' |
| 112 | return '\n.. code-block:: %s\n\n' % lang |
Daniel Jasper | 8ce1b8d | 2015-10-06 11:54:18 +0000 | [diff] [blame] | 113 | if line == '/// \\endcode': |
| 114 | return '' |
| 115 | return line[4:] + '\n' |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 116 | |
| 117 | def read_options(header): |
Serge Guelton | 09616bd | 2018-12-03 12:12:48 +0000 | [diff] [blame] | 118 | class State(object): |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 119 | BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComent, \ |
| 120 | InFieldComment, InEnum, InEnumMemberComment = range(8) |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 121 | state = State.BeforeStruct |
| 122 | |
| 123 | options = [] |
| 124 | enums = {} |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 125 | nested_structs = {} |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 126 | comment = '' |
| 127 | enum = None |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 128 | nested_struct = None |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 129 | |
| 130 | for line in header: |
| 131 | line = line.strip() |
| 132 | if state == State.BeforeStruct: |
Krasimir Georgiev | cf699ad | 2018-07-25 10:21:47 +0000 | [diff] [blame] | 133 | if line == 'struct FormatStyle {' or line == 'struct IncludeStyle {': |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 134 | state = State.InStruct |
| 135 | elif state == State.InStruct: |
| 136 | if line.startswith('///'): |
| 137 | state = State.InFieldComment |
| 138 | comment = clean_comment_line(line) |
| 139 | elif line == '};': |
| 140 | state = State.Finished |
| 141 | break |
| 142 | elif state == State.InFieldComment: |
| 143 | if line.startswith('///'): |
| 144 | comment += clean_comment_line(line) |
| 145 | elif line.startswith('enum'): |
| 146 | state = State.InEnum |
| 147 | name = re.sub(r'enum\s+(\w+)\s*\{', '\\1', line) |
| 148 | enum = Enum(name, comment) |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 149 | elif line.startswith('struct'): |
| 150 | state = State.InNestedStruct |
| 151 | name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line) |
| 152 | nested_struct = NestedStruct(name, comment) |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 153 | elif line.endswith(';'): |
| 154 | state = State.InStruct |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 155 | field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);', |
| 156 | line).groups() |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 157 | option = Option(str(field_name), str(field_type), comment) |
| 158 | options.append(option) |
| 159 | else: |
| 160 | raise Exception('Invalid format, expected comment, field or enum') |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 161 | elif state == State.InNestedStruct: |
| 162 | if line.startswith('///'): |
| 163 | state = State.InNestedFieldComent |
| 164 | comment = clean_comment_line(line) |
| 165 | elif line == '};': |
| 166 | state = State.InStruct |
| 167 | nested_structs[nested_struct.name] = nested_struct |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 168 | elif state == State.InNestedFieldComent: |
| 169 | if line.startswith('///'): |
| 170 | comment += clean_comment_line(line) |
| 171 | else: |
| 172 | state = State.InNestedStruct |
paul_hoad | eb00839 | 2019-11-08 14:34:07 +0000 | [diff] [blame] | 173 | field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);',line).groups() |
| 174 | if field_type in enums: |
| 175 | nested_struct.values.append(NestedEnum(field_name,field_type,comment,enums[field_type].values)) |
| 176 | else: |
| 177 | nested_struct.values.append(NestedField(field_type + " " + field_name, comment)) |
| 178 | |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 179 | elif state == State.InEnum: |
| 180 | if line.startswith('///'): |
| 181 | state = State.InEnumMemberComment |
| 182 | comment = clean_comment_line(line) |
| 183 | elif line == '};': |
| 184 | state = State.InStruct |
| 185 | enums[enum.name] = enum |
| 186 | else: |
| 187 | raise Exception('Invalid format, expected enum field comment or };') |
| 188 | elif state == State.InEnumMemberComment: |
| 189 | if line.startswith('///'): |
| 190 | comment += clean_comment_line(line) |
| 191 | else: |
| 192 | state = State.InEnum |
paulhoad | eadb65f | 2019-11-06 20:02:16 +0000 | [diff] [blame] | 193 | val = line.replace(',', '') |
| 194 | pos = val.find(" // ") |
| 195 | if (pos != -1): |
| 196 | config = val[pos+4:] |
| 197 | val = val[:pos] |
| 198 | else: |
| 199 | config = val; |
| 200 | enum.values.append(EnumValue(val, comment,config)) |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 201 | if state != State.Finished: |
| 202 | raise Exception('Not finished by the end of file') |
| 203 | |
| 204 | for option in options: |
Daniel Jasper | b552482 | 2014-04-09 14:05:49 +0000 | [diff] [blame] | 205 | if not option.type in ['bool', 'unsigned', 'int', 'std::string', |
Daniel Jasper | c1bc38e | 2015-09-29 14:57:55 +0000 | [diff] [blame] | 206 | 'std::vector<std::string>', |
Krasimir Georgiev | 818da9b | 2017-11-09 15:41:23 +0000 | [diff] [blame] | 207 | 'std::vector<IncludeCategory>', |
| 208 | 'std::vector<RawStringFormat>']: |
Serge Guelton | 366c089 | 2018-12-18 08:22:47 +0000 | [diff] [blame] | 209 | if option.type in enums: |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 210 | option.enum = enums[option.type] |
Serge Guelton | 366c089 | 2018-12-18 08:22:47 +0000 | [diff] [blame] | 211 | elif option.type in nested_structs: |
Krasimir Georgiev | 90b4ce3 | 2017-06-23 11:29:40 +0000 | [diff] [blame] | 212 | option.nested_struct = nested_structs[option.type] |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 213 | else: |
| 214 | raise Exception('Unknown type: %s' % option.type) |
| 215 | return options |
| 216 | |
| 217 | options = read_options(open(FORMAT_STYLE_FILE)) |
Krasimir Georgiev | cf699ad | 2018-07-25 10:21:47 +0000 | [diff] [blame] | 218 | options += read_options(open(INCLUDE_STYLE_FILE)) |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 219 | |
| 220 | options = sorted(options, key=lambda x: x.name) |
| 221 | options_text = '\n\n'.join(map(str, options)) |
| 222 | |
| 223 | contents = open(DOC_FILE).read() |
| 224 | |
| 225 | contents = substitute(contents, 'FORMAT_STYLE_OPTIONS', options_text) |
| 226 | |
Benjamin Kramer | 611d33a | 2015-11-20 07:46:19 +0000 | [diff] [blame] | 227 | with open(DOC_FILE, 'wb') as output: |
mydeveloperday | 3125aa9 | 2020-05-07 19:52:12 +0100 | [diff] [blame] | 228 | output.write(contents.encode()) |