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 |
| 7 | import re |
| 8 | import urllib2 |
| 9 | |
| 10 | FORMAT_STYLE_FILE = '../../include/clang/Format/Format.h' |
| 11 | DOC_FILE = '../ClangFormatStyleOptions.rst' |
| 12 | |
| 13 | |
| 14 | def substitute(text, tag, contents): |
| 15 | replacement = '\n.. START_%s\n\n%s\n\n.. END_%s\n' % (tag, contents, tag) |
| 16 | pattern = r'\n\.\. START_%s\n.*\n\.\. END_%s\n' % (tag, tag) |
| 17 | return re.sub(pattern, '%s', text, flags=re.S) % replacement |
| 18 | |
| 19 | def doxygen2rst(text): |
Daniel Jasper | 7f43266 | 2014-12-02 14:21:16 +0000 | [diff] [blame] | 20 | text = re.sub(r'([^/\*])\*', r'\1\\*', text) |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 21 | text = re.sub(r'<tt>\s*(.*?)\s*<\/tt>', r'``\1``', text) |
| 22 | text = re.sub(r'\\c ([^ ,;\.]+)', r'``\1``', text) |
| 23 | text = re.sub(r'\\\w+ ', '', text) |
| 24 | return text |
| 25 | |
| 26 | def indent(text, columns): |
| 27 | indent = ' ' * columns |
| 28 | s = re.sub(r'\n([^\n])', '\n' + indent + '\\1', text, flags=re.S) |
| 29 | if s.startswith('\n'): |
| 30 | return s |
| 31 | return indent + s |
| 32 | |
| 33 | class Option: |
| 34 | def __init__(self, name, type, comment): |
| 35 | self.name = name |
| 36 | self.type = type |
| 37 | self.comment = comment.strip() |
| 38 | self.enum = None |
| 39 | |
| 40 | def __str__(self): |
| 41 | s = '**%s** (``%s``)\n%s' % (self.name, self.type, |
| 42 | doxygen2rst(indent(self.comment, 2))) |
| 43 | if self.enum: |
| 44 | s += indent('\n\nPossible values:\n\n%s\n' % self.enum, 2) |
| 45 | return s |
| 46 | |
| 47 | class Enum: |
| 48 | def __init__(self, name, comment): |
| 49 | self.name = name |
| 50 | self.comment = comment.strip() |
| 51 | self.values = [] |
| 52 | |
| 53 | def __str__(self): |
| 54 | return '\n'.join(map(str, self.values)) |
| 55 | |
| 56 | class EnumValue: |
| 57 | def __init__(self, name, comment): |
| 58 | self.name = name |
| 59 | self.comment = comment.strip() |
| 60 | |
| 61 | def __str__(self): |
| 62 | return '* ``%s`` (in configuration: ``%s``)\n%s' % ( |
| 63 | self.name, |
| 64 | re.sub('.*_', '', self.name), |
| 65 | doxygen2rst(indent(self.comment, 2))) |
| 66 | |
| 67 | def clean_comment_line(line): |
| 68 | return line[3:].strip() + '\n' |
| 69 | |
| 70 | def read_options(header): |
| 71 | class State: |
| 72 | BeforeStruct, Finished, InStruct, InFieldComment, InEnum, \ |
| 73 | InEnumMemberComment = range(6) |
| 74 | state = State.BeforeStruct |
| 75 | |
| 76 | options = [] |
| 77 | enums = {} |
| 78 | comment = '' |
| 79 | enum = None |
| 80 | |
| 81 | for line in header: |
| 82 | line = line.strip() |
| 83 | if state == State.BeforeStruct: |
| 84 | if line == 'struct FormatStyle {': |
| 85 | state = State.InStruct |
| 86 | elif state == State.InStruct: |
| 87 | if line.startswith('///'): |
| 88 | state = State.InFieldComment |
| 89 | comment = clean_comment_line(line) |
| 90 | elif line == '};': |
| 91 | state = State.Finished |
| 92 | break |
| 93 | elif state == State.InFieldComment: |
| 94 | if line.startswith('///'): |
| 95 | comment += clean_comment_line(line) |
| 96 | elif line.startswith('enum'): |
| 97 | state = State.InEnum |
| 98 | name = re.sub(r'enum\s+(\w+)\s*\{', '\\1', line) |
| 99 | enum = Enum(name, comment) |
| 100 | elif line.endswith(';'): |
| 101 | state = State.InStruct |
Daniel Jasper | b552482 | 2014-04-09 14:05:49 +0000 | [diff] [blame] | 102 | field_type, field_name = re.match(r'([<>:\w]+)\s+(\w+);', line).groups() |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 103 | option = Option(str(field_name), str(field_type), comment) |
| 104 | options.append(option) |
| 105 | else: |
| 106 | raise Exception('Invalid format, expected comment, field or enum') |
| 107 | elif state == State.InEnum: |
| 108 | if line.startswith('///'): |
| 109 | state = State.InEnumMemberComment |
| 110 | comment = clean_comment_line(line) |
| 111 | elif line == '};': |
| 112 | state = State.InStruct |
| 113 | enums[enum.name] = enum |
| 114 | else: |
| 115 | raise Exception('Invalid format, expected enum field comment or };') |
| 116 | elif state == State.InEnumMemberComment: |
| 117 | if line.startswith('///'): |
| 118 | comment += clean_comment_line(line) |
| 119 | else: |
| 120 | state = State.InEnum |
| 121 | enum.values.append(EnumValue(line.replace(',', ''), comment)) |
| 122 | if state != State.Finished: |
| 123 | raise Exception('Not finished by the end of file') |
| 124 | |
| 125 | for option in options: |
Daniel Jasper | b552482 | 2014-04-09 14:05:49 +0000 | [diff] [blame] | 126 | if not option.type in ['bool', 'unsigned', 'int', 'std::string', |
| 127 | 'std::vector<std::string>']: |
Alexander Kornienko | d278e0e | 2013-09-04 15:09:13 +0000 | [diff] [blame] | 128 | if enums.has_key(option.type): |
| 129 | option.enum = enums[option.type] |
| 130 | else: |
| 131 | raise Exception('Unknown type: %s' % option.type) |
| 132 | return options |
| 133 | |
| 134 | options = read_options(open(FORMAT_STYLE_FILE)) |
| 135 | |
| 136 | options = sorted(options, key=lambda x: x.name) |
| 137 | options_text = '\n\n'.join(map(str, options)) |
| 138 | |
| 139 | contents = open(DOC_FILE).read() |
| 140 | |
| 141 | contents = substitute(contents, 'FORMAT_STYLE_OPTIONS', options_text) |
| 142 | |
| 143 | with open(DOC_FILE, 'w') as output: |
| 144 | output.write(contents) |
| 145 | |