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
6import collections
7import re
8import urllib2
9
10FORMAT_STYLE_FILE = '../../include/clang/Format/Format.h'
11DOC_FILE = '../ClangFormatStyleOptions.rst'
12
13
14def 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
19def doxygen2rst(text):
20  text = re.sub(r'([^/\*])\*', r'\1\\*', text)
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
26def 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
33class 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    self.nested_struct = None
40
41  def __str__(self):
42    s = '**%s** (``%s``)\n%s' % (self.name, self.type,
43                                 doxygen2rst(indent(self.comment, 2)))
44    if self.enum:
45      s += indent('\n\nPossible values:\n\n%s\n' % self.enum, 2)
46    if self.nested_struct:
47      s += indent('\n\nNested configuration flags:\n\n%s\n' %self.nested_struct,
48                  2)
49    return s
50
51class NestedStruct:
52  def __init__(self, name, comment):
53    self.name = name
54    self.comment = comment.strip()
55    self.values = []
56
57  def __str__(self):
58    return '\n'.join(map(str, self.values))
59
60class NestedField:
61  def __init__(self, name, comment):
62    self.name = name
63    self.comment = comment.strip()
64
65  def __str__(self):
66    return '* ``%s`` %s' % (self.name, doxygen2rst(self.comment))
67
68class Enum:
69  def __init__(self, name, comment):
70    self.name = name
71    self.comment = comment.strip()
72    self.values = []
73
74  def __str__(self):
75    return '\n'.join(map(str, self.values))
76
77class EnumValue:
78  def __init__(self, name, comment):
79    self.name = name
80    self.comment = comment.strip()
81
82  def __str__(self):
83    return '* ``%s`` (in configuration: ``%s``)\n%s' % (
84        self.name,
85        re.sub('.*_', '', self.name),
86        doxygen2rst(indent(self.comment, 2)))
87
88def clean_comment_line(line):
89  if line == '/// \\code':
90    return '\n.. code-block:: c++\n\n'
91  if line == '/// \\endcode':
92    return ''
93  return line[4:] + '\n'
94
95def read_options(header):
96  class State:
97    BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComent, \
98    InFieldComment, InEnum, InEnumMemberComment = range(8)
99  state = State.BeforeStruct
100
101  options = []
102  enums = {}
103  nested_structs = {}
104  comment = ''
105  enum = None
106  nested_struct = None
107
108  for line in header:
109    line = line.strip()
110    if state == State.BeforeStruct:
111      if line == 'struct FormatStyle {':
112        state = State.InStruct
113    elif state == State.InStruct:
114      if line.startswith('///'):
115        state = State.InFieldComment
116        comment = clean_comment_line(line)
117      elif line == '};':
118        state = State.Finished
119        break
120    elif state == State.InFieldComment:
121      if line.startswith('///'):
122        comment += clean_comment_line(line)
123      elif line.startswith('enum'):
124        state = State.InEnum
125        name = re.sub(r'enum\s+(\w+)\s*\{', '\\1', line)
126        enum = Enum(name, comment)
127      elif line.startswith('struct'):
128        state = State.InNestedStruct
129        name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line)
130        nested_struct = NestedStruct(name, comment)
131      elif line.endswith(';'):
132        state = State.InStruct
133        field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);',
134                                          line).groups()
135        option = Option(str(field_name), str(field_type), comment)
136        options.append(option)
137      else:
138        raise Exception('Invalid format, expected comment, field or enum')
139    elif state == State.InNestedStruct:
140      if line.startswith('///'):
141        state = State.InNestedFieldComent
142        comment = clean_comment_line(line)
143      elif line == '};':
144        state = State.InStruct
145        nested_structs[nested_struct.name] = nested_struct
146    elif state == State.InNestedFieldComent:
147      if line.startswith('///'):
148        comment += clean_comment_line(line)
149      else:
150        state = State.InNestedStruct
151        nested_struct.values.append(NestedField(line.replace(';', ''), comment))
152    elif state == State.InEnum:
153      if line.startswith('///'):
154        state = State.InEnumMemberComment
155        comment = clean_comment_line(line)
156      elif line == '};':
157        state = State.InStruct
158        enums[enum.name] = enum
159      else:
160        raise Exception('Invalid format, expected enum field comment or };')
161    elif state == State.InEnumMemberComment:
162      if line.startswith('///'):
163        comment += clean_comment_line(line)
164      else:
165        state = State.InEnum
166        enum.values.append(EnumValue(line.replace(',', ''), comment))
167  if state != State.Finished:
168    raise Exception('Not finished by the end of file')
169
170  for option in options:
171    if not option.type in ['bool', 'unsigned', 'int', 'std::string',
172                           'std::vector<std::string>',
173                           'std::vector<IncludeCategory>']:
174      if enums.has_key(option.type):
175        option.enum = enums[option.type]
176      elif nested_structs.has_key(option.type):
177        option.nested_struct = nested_structs[option.type];
178      else:
179        raise Exception('Unknown type: %s' % option.type)
180  return options
181
182options = read_options(open(FORMAT_STYLE_FILE))
183
184options = sorted(options, key=lambda x: x.name)
185options_text = '\n\n'.join(map(str, options))
186
187contents = open(DOC_FILE).read()
188
189contents = substitute(contents, 'FORMAT_STYLE_OPTIONS', options_text)
190
191with open(DOC_FILE, 'wb') as output:
192  output.write(contents)
193
194