Home | History | Annotate | Line # | Download | only in tools
      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 and self.enum.values:
     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 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 
     94 class EnumValue(object):
     95   def __init__(self, name, comment, config):
     96     self.name = name
     97     self.comment = comment
     98     self.config = config
     99 
    100   def __str__(self):
    101     return '* ``%s`` (in configuration: ``%s``)\n%s' % (
    102         self.name,
    103         re.sub('.*_', '', self.config),
    104         doxygen2rst(indent(self.comment, 2)))
    105 
    106 def clean_comment_line(line):
    107   match = re.match(r'^/// (?P<indent> +)?\\code(\{.(?P<lang>\w+)\})?$', line)
    108   if match:
    109     indent = match.group('indent')
    110     if not indent:
    111       indent = ''
    112     lang = match.group('lang')
    113     if not lang:
    114       lang = 'c++'
    115     return '\n%s.. code-block:: %s\n\n' % (indent, lang)
    116 
    117   endcode_match = re.match(r'^/// +\\endcode$', line)
    118   if endcode_match:
    119     return ''
    120   return line[4:] + '\n'
    121 
    122 def read_options(header):
    123   class State(object):
    124     BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComent, \
    125     InFieldComment, InEnum, InEnumMemberComment = range(8)
    126   state = State.BeforeStruct
    127 
    128   options = []
    129   enums = {}
    130   nested_structs = {}
    131   comment = ''
    132   enum = None
    133   nested_struct = None
    134 
    135   for line in header:
    136     line = line.strip()
    137     if state == State.BeforeStruct:
    138       if line == 'struct FormatStyle {' or line == 'struct IncludeStyle {':
    139         state = State.InStruct
    140     elif state == State.InStruct:
    141       if line.startswith('///'):
    142         state = State.InFieldComment
    143         comment = clean_comment_line(line)
    144       elif line == '};':
    145         state = State.Finished
    146         break
    147     elif state == State.InFieldComment:
    148       if line.startswith('///'):
    149         comment += clean_comment_line(line)
    150       elif line.startswith('enum'):
    151         state = State.InEnum
    152         name = re.sub(r'enum\s+(\w+)\s*(:((\s*\w+)+)\s*)?\{', '\\1', line)
    153         enum = Enum(name, comment)
    154       elif line.startswith('struct'):
    155         state = State.InNestedStruct
    156         name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line)
    157         nested_struct = NestedStruct(name, comment)
    158       elif line.endswith(';'):
    159         state = State.InStruct
    160         field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);',
    161                                           line).groups()
    162         option = Option(str(field_name), str(field_type), comment)
    163         options.append(option)
    164       else:
    165         raise Exception('Invalid format, expected comment, field or enum')
    166     elif state == State.InNestedStruct:
    167       if line.startswith('///'):
    168         state = State.InNestedFieldComent
    169         comment = clean_comment_line(line)
    170       elif line == '};':
    171         state = State.InStruct
    172         nested_structs[nested_struct.name] = nested_struct
    173     elif state == State.InNestedFieldComent:
    174       if line.startswith('///'):
    175         comment += clean_comment_line(line)
    176       else:
    177         state = State.InNestedStruct
    178         field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);',line).groups()
    179         if field_type in enums:
    180             nested_struct.values.append(NestedEnum(field_name,field_type,comment,enums[field_type].values))
    181         else:
    182             nested_struct.values.append(NestedField(field_type + " " + field_name, comment))
    183 
    184     elif state == State.InEnum:
    185       if line.startswith('///'):
    186         state = State.InEnumMemberComment
    187         comment = clean_comment_line(line)
    188       elif line == '};':
    189         state = State.InStruct
    190         enums[enum.name] = enum
    191       else:
    192         # Enum member without documentation. Must be documented where the enum
    193         # is used.
    194         pass
    195     elif state == State.InEnumMemberComment:
    196       if line.startswith('///'):
    197         comment += clean_comment_line(line)
    198       else:
    199         state = State.InEnum
    200         val = line.replace(',', '')
    201         pos = val.find(" // ")
    202         if (pos != -1):
    203             config = val[pos+4:]
    204             val = val[:pos]
    205         else:
    206             config = val;
    207         enum.values.append(EnumValue(val, comment,config))
    208   if state != State.Finished:
    209     raise Exception('Not finished by the end of file')
    210 
    211   for option in options:
    212     if not option.type in ['bool', 'unsigned', 'int', 'std::string',
    213                            'std::vector<std::string>',
    214                            'std::vector<IncludeCategory>',
    215                            'std::vector<RawStringFormat>']:
    216       if option.type in enums:
    217         option.enum = enums[option.type]
    218       elif option.type in nested_structs:
    219         option.nested_struct = nested_structs[option.type]
    220       else:
    221         raise Exception('Unknown type: %s' % option.type)
    222   return options
    223 
    224 options = read_options(open(FORMAT_STYLE_FILE))
    225 options += read_options(open(INCLUDE_STYLE_FILE))
    226 
    227 options = sorted(options, key=lambda x: x.name)
    228 options_text = '\n\n'.join(map(str, options))
    229 
    230 contents = open(DOC_FILE).read()
    231 
    232 contents = substitute(contents, 'FORMAT_STYLE_OPTIONS', options_text)
    233 
    234 with open(DOC_FILE, 'wb') as output:
    235   output.write(contents.encode())
    236