Index: docs/tools/dump_ast_matchers.py =================================================================== --- docs/tools/dump_ast_matchers.py +++ docs/tools/dump_ast_matchers.py @@ -13,7 +13,7 @@ # result | name | argA # The subsequent row contains the documentation and is hidden by default, # becoming visible via javascript when the user clicks the matcher name. -TD_TEMPLATE=""" +TD_TEMPLATE = """ %(result)s%(name)s%(args)s
%(comment)s
""" @@ -31,161 +31,169 @@ # Cache for doxygen urls we have already verified. doxygen_probes = {} + def esc(text): - """Escape any html in the given text.""" - text = re.sub(r'&', '&', text) - text = re.sub(r'<', '<', text) - text = re.sub(r'>', '>', text) - def link_if_exists(m): - name = m.group(1) - url = 'http://clang.llvm.org/doxygen/classclang_1_1%s.html' % name - if url not in doxygen_probes: - try: - print 'Probing %s...' % url - urllib2.urlopen(url) - doxygen_probes[url] = True - except: - doxygen_probes[url] = False - if doxygen_probes[url]: - return r'Matcher<%s>' % (url, name) - else: - return m.group(0) - text = re.sub( - r'Matcher<([^\*&]+)>', link_if_exists, text) - return text + """Escape any html in the given text.""" + text = re.sub(r'&', '&', text) + text = re.sub(r'<', '<', text) + text = re.sub(r'>', '>', text) + def link_if_exists(m): + name = m.group(1) + url = 'http://clang.llvm.org/doxygen/classclang_1_1%s.html' % name + if url not in doxygen_probes: + try: + print('Probing %s...' % url) + urllib2.urlopen(url) + doxygen_probes[url] = True + except: + doxygen_probes[url] = False + if doxygen_probes[url]: + return r'Matcher<%s>' % (url, name) + else: + return m.group(0) + text = re.sub( + r'Matcher<([^\*&]+)>', link_if_exists, text) + return text + + def extract_result_types(comment): - """Extracts a list of result types from the given comment. + """Extracts a list of result types from the given comment. - We allow annotations in the comment of the matcher to specify what - nodes a matcher can match on. Those comments have the form: - Usable as: Any Matcher | (Matcher[, Matcher[, ...]]) + We allow annotations in the comment of the matcher to specify what + nodes a matcher can match on. Those comments have the form: + Usable as: Any Matcher | (Matcher[, Matcher[, ...]]) - Returns ['*'] in case of 'Any Matcher', or ['T1', 'T2', ...]. - Returns the empty list if no 'Usable as' specification could be - parsed. - """ - result_types = [] - m = re.search(r'Usable as: Any Matcher[\s\n]*$', comment, re.S) - if m: - return ['*'] - while True: - m = re.match(r'^(.*)Matcher<([^>]+)>\s*,?[\s\n]*$', comment, re.S) - if not m: - if re.search(r'Usable as:\s*$', comment): - return result_types - else: - return None - result_types += [m.group(2)] - comment = m.group(1) + Returns ['*'] in case of 'Any Matcher', or ['T1', 'T2', ...]. + Returns the empty list if no 'Usable as' specification could be + parsed. + """ + result_types = [] + m = re.search(r'Usable as: Any Matcher[\s\n]*$', comment, re.S) + if m: + return ['*'] + while True: + m = re.match(r'^(.*)Matcher<([^>]+)>\s*,?[\s\n]*$', comment, re.S) + if not m: + if re.search(r'Usable as:\s*$', comment): + return result_types + else: + return None + result_types += [m.group(2)] + comment = m.group(1) + def strip_doxygen(comment): - """Returns the given comment without \-escaped words.""" - # If there is only a doxygen keyword in the line, delete the whole line. - comment = re.sub(r'^\\[^\s]+\n', r'', comment, flags=re.M) - - # If there is a doxygen \see command, change the \see prefix into "See also:". - # FIXME: it would be better to turn this into a link to the target instead. - comment = re.sub(r'\\see', r'See also:', comment) - - # Delete the doxygen command and the following whitespace. - comment = re.sub(r'\\[^\s]+\s+', r'', comment) - return comment + """Returns the given comment without \-escaped words.""" + # If there is only a doxygen keyword in the line, delete the whole line. + comment = re.sub(r'^\\[^\s]+\n', r'', comment, flags=re.M) + # If there is a doxygen \see command, change the \see prefix into "See also:". + # FIXME: it would be better to turn this into a link to the target instead. + comment = re.sub(r'\\see', r'See also:', comment) + + # Delete the doxygen command and the following whitespace. + comment = re.sub(r'\\[^\s]+\s+', r'', comment) + return comment + + def unify_arguments(args): - """Gets rid of anything the user doesn't care about in the argument list.""" - args = re.sub(r'internal::', r'', args) - args = re.sub(r'const\s+(.*)&', r'\1 ', args) - args = re.sub(r'&', r' ', args) - args = re.sub(r'(^|\s)M\d?(\s)', r'\1Matcher<*>\2', args) - return args + """Gets rid of anything the user doesn't care about in the argument list.""" + args = re.sub(r'internal::', r'', args) + args = re.sub(r'const\s+(.*)&', r'\1 ', args) + args = re.sub(r'&', r' ', args) + args = re.sub(r'(^|\s)M\d?(\s)', r'\1Matcher<*>\2', args) + return args + def add_matcher(result_type, name, args, comment, is_dyncast=False): - """Adds a matcher to one of our categories.""" - if name == 'id': - # FIXME: Figure out whether we want to support the 'id' matcher. - return - matcher_id = '%s%d' % (name, ids[name]) - ids[name] += 1 - args = unify_arguments(args) - matcher_html = TD_TEMPLATE % { - 'result': esc('Matcher<%s>' % result_type), - 'name': name, - 'args': esc(args), - 'comment': esc(strip_doxygen(comment)), - 'id': matcher_id, - } - if is_dyncast: - node_matchers[result_type + name] = matcher_html - # Use a heuristic to figure out whether a matcher is a narrowing or - # traversal matcher. By default, matchers that take other matchers as - # arguments (and are not node matchers) do traversal. We specifically - # exclude known narrowing matchers that also take other matchers as - # arguments. - elif ('Matcher<' not in args or - name in ['allOf', 'anyOf', 'anything', 'unless']): - narrowing_matchers[result_type + name + esc(args)] = matcher_html - else: - traversal_matchers[result_type + name + esc(args)] = matcher_html + """Adds a matcher to one of our categories.""" + if name == 'id': + # FIXME: Figure out whether we want to support the 'id' matcher. + return + matcher_id = '%s%d' % (name, ids[name]) + ids[name] += 1 + args = unify_arguments(args) + matcher_html = TD_TEMPLATE % { + 'result': esc('Matcher<%s>' % result_type), + 'name': name, + 'args': esc(args), + 'comment': esc(strip_doxygen(comment)), + 'id': matcher_id, + } + if is_dyncast: + node_matchers[result_type + name] = matcher_html + # Use a heuristic to figure out whether a matcher is a narrowing or + # traversal matcher. By default, matchers that take other matchers as + # arguments (and are not node matchers) do traversal. We specifically + # exclude known narrowing matchers that also take other matchers as + # arguments. + elif ('Matcher<' not in args or + name in ['allOf', 'anyOf', 'anything', 'unless']): + narrowing_matchers[result_type + name + esc(args)] = matcher_html + else: + traversal_matchers[result_type + name + esc(args)] = matcher_html + def act_on_decl(declaration, comment, allowed_types): - """Parse the matcher out of the given declaration and comment. + """Parse the matcher out of the given declaration and comment. - If 'allowed_types' is set, it contains a list of node types the matcher - can match on, as extracted from the static type asserts in the matcher - definition. - """ - if declaration.strip(): - # Node matchers are defined by writing: - # VariadicDynCastAllOfMatcher name; - m = re.match(r""".*Variadic(?:DynCast)?AllOfMatcher\s*< + If 'allowed_types' is set, it contains a list of node types the matcher + can match on, as extracted from the static type asserts in the matcher + definition. + """ + if declaration.strip(): + # Node matchers are defined by writing: + # VariadicDynCastAllOfMatcher name; + m = re.match(r""".*Variadic(?:DynCast)?AllOfMatcher\s*< \s*([^\s,]+)\s*(?:, \s*([^\s>]+)\s*)?> \s*([^\s;]+)\s*;\s*$""", declaration, flags=re.X) - if m: - result, inner, name = m.groups() - if not inner: - inner = result - add_matcher(result, name, 'Matcher<%s>...' % inner, - comment, is_dyncast=True) - return + if m: + result, inner, name = m.groups() + if not inner: + inner = result + add_matcher(result, name, 'Matcher<%s>...' % inner, + comment, is_dyncast=True) + return - # Parse the various matcher definition macros. - m = re.match(""".*AST_TYPE_MATCHER\( + # Parse the various matcher definition macros. + m = re.match(""".*AST_TYPE_MATCHER\( \s*([^\s,]+\s*), \s*([^\s,]+\s*) \)\s*;\s*$""", declaration, flags=re.X) - if m: - inner, name = m.groups() - add_matcher('Type', name, 'Matcher<%s>...' % inner, - comment, is_dyncast=True) - # FIXME: re-enable once we have implemented casting on the TypeLoc - # hierarchy. - # add_matcher('TypeLoc', '%sLoc' % name, 'Matcher<%sLoc>...' % inner, - # comment, is_dyncast=True) - return + if m: + inner, name = m.groups() + add_matcher('Type', name, 'Matcher<%s>...' % inner, + comment, is_dyncast=True) + # FIXME: re-enable once we have implemented casting on the TypeLoc + # hierarchy. + # add_matcher('TypeLoc', '%sLoc' % name, 'Matcher<%sLoc>...' % inner, + # comment, is_dyncast=True) + return - m = re.match(""".*AST_TYPE(LOC)?_TRAVERSE_MATCHER\( + m = re.match(""".*AST_TYPE(LOC)?_TRAVERSE_MATCHER\( \s*([^\s,]+\s*), \s*(?:[^\s,]+\s*), \s*AST_POLYMORPHIC_SUPPORTED_TYPES\(([^)]*)\) \)\s*;\s*$""", declaration, flags=re.X) - if m: - loc, name, results = m.groups()[0:3] - result_types = [r.strip() for r in results.split(',')] + if m: + loc, name, results = m.groups()[0:3] + result_types = [r.strip() for r in results.split(',')] - comment_result_types = extract_result_types(comment) - if (comment_result_types and - sorted(result_types) != sorted(comment_result_types)): - raise Exception('Inconsistent documentation for: %s' % name) - for result_type in result_types: - add_matcher(result_type, name, 'Matcher', comment) - if loc: - add_matcher('%sLoc' % result_type, '%sLoc' % name, 'Matcher', - comment) - return + comment_result_types = extract_result_types(comment) + if (comment_result_types and + sorted(result_types) != sorted(comment_result_types)): + raise Exception('Inconsistent documentation for: %s' % name) + for result_type in result_types: + add_matcher(result_type, name, 'Matcher', comment) + if loc: + add_matcher( + '%sLoc' % result_type, '%sLoc' % name, 'Matcher', + comment) + return - m = re.match(r"""^\s*AST_POLYMORPHIC_MATCHER(_P)?(.?)(?:_OVERLOAD)?\( + m = re.match(r"""^\s*AST_POLYMORPHIC_MATCHER(_P)?(.?)(?:_OVERLOAD)?\( \s*([^\s,]+)\s*, \s*AST_POLYMORPHIC_SUPPORTED_TYPES\(([^)]*)\) (?:,\s*([^\s,]+)\s* @@ -195,21 +203,21 @@ (?:,\s*\d+\s*)? \)\s*{\s*$""", declaration, flags=re.X) - if m: - p, n, name, results = m.groups()[0:4] - args = m.groups()[4:] - result_types = [r.strip() for r in results.split(',')] - if allowed_types and allowed_types != result_types: - raise Exception('Inconsistent documentation for: %s' % name) - if n not in ['', '2']: - raise Exception('Cannot parse "%s"' % declaration) - args = ', '.join('%s %s' % (args[i], args[i+1]) - for i in range(0, len(args), 2) if args[i]) - for result_type in result_types: - add_matcher(result_type, name, args, comment) - return + if m: + p, n, name, results = m.groups()[0:4] + args = m.groups()[4:] + result_types = [r.strip() for r in results.split(',')] + if allowed_types and allowed_types != result_types: + raise Exception('Inconsistent documentation for: %s' % name) + if n not in ['', '2']: + raise Exception('Cannot parse "%s"' % declaration) + args = ', '.join('%s %s' % (args[i], args[i + 1]) + for i in range(0, len(args), 2) if args[i]) + for result_type in result_types: + add_matcher(result_type, name, args, comment) + return - m = re.match(r"""^\s*AST_MATCHER_FUNCTION(_P)?(.?)(?:_OVERLOAD)?\( + m = re.match(r"""^\s*AST_MATCHER_FUNCTION(_P)?(.?)(?:_OVERLOAD)?\( (?:\s*([^\s,]+)\s*,)? \s*([^\s,]+)\s* (?:,\s*([^\s,]+)\s* @@ -218,17 +226,17 @@ ,\s*([^\s,]+)\s*)? (?:,\s*\d+\s*)? \)\s*{\s*$""", declaration, flags=re.X) - if m: - p, n, result, name = m.groups()[0:4] - args = m.groups()[4:] - if n not in ['', '2']: - raise Exception('Cannot parse "%s"' % declaration) - args = ', '.join('%s %s' % (args[i], args[i+1]) - for i in range(0, len(args), 2) if args[i]) - add_matcher(result, name, args, comment) - return + if m: + p, n, result, name = m.groups()[0:4] + args = m.groups()[4:] + if n not in ['', '2']: + raise Exception('Cannot parse "%s"' % declaration) + args = ', '.join('%s %s' % (args[i], args[i + 1]) + for i in range(0, len(args), 2) if args[i]) + add_matcher(result, name, args, comment) + return - m = re.match(r"""^\s*AST_MATCHER(_P)?(.?)(?:_OVERLOAD)?\( + m = re.match(r"""^\s*AST_MATCHER(_P)?(.?)(?:_OVERLOAD)?\( (?:\s*([^\s,]+)\s*,)? \s*([^\s,]+)\s* (?:,\s*([^,]+)\s* @@ -237,96 +245,100 @@ ,\s*([^\s,]+)\s*)? (?:,\s*\d+\s*)? \)\s*{\s*$""", declaration, flags=re.X) - if m: - p, n, result, name = m.groups()[0:4] - args = m.groups()[4:] - if not result: - if not allowed_types: - raise Exception('Did not find allowed result types for: %s' % name) - result_types = allowed_types - else: - result_types = [result] - if n not in ['', '2']: - raise Exception('Cannot parse "%s"' % declaration) - args = ', '.join('%s %s' % (args[i], args[i+1]) - for i in range(0, len(args), 2) if args[i]) - for result_type in result_types: - add_matcher(result_type, name, args, comment) - return + if m: + p, n, result, name = m.groups()[0:4] + args = m.groups()[4:] + if not result: + if not allowed_types: + raise Exception( + 'Did not find allowed result types for: %s' % name) + result_types = allowed_types + else: + result_types = [result] + if n not in ['', '2']: + raise Exception('Cannot parse "%s"' % declaration) + args = ', '.join('%s %s' % (args[i], args[i + 1]) + for i in range(0, len(args), 2) if args[i]) + for result_type in result_types: + add_matcher(result_type, name, args, comment) + return - # Parse ArgumentAdapting matchers. - m = re.match( - r"""^.*ArgumentAdaptingMatcherFunc<.*>\s*(?:LLVM_ATTRIBUTE_UNUSED\s*) + # Parse ArgumentAdapting matchers. + m = re.match( + r"""^.*ArgumentAdaptingMatcherFunc<.*>\s*(?:LLVM_ATTRIBUTE_UNUSED\s*) ([a-zA-Z]*)\s*=\s*{};$""", - declaration, flags=re.X) - if m: - name = m.groups()[0] - add_matcher('*', name, 'Matcher<*>', comment) - return + declaration, flags=re.X) + if m: + name = m.groups()[0] + add_matcher('*', name, 'Matcher<*>', comment) + return - # Parse Variadic functions. - m = re.match( - r"""^.*internal::VariadicFunction\s*<\s*([^,]+),\s*([^,]+),\s*[^>]+>\s* + # Parse Variadic functions. + m = re.match( + r"""^.*internal::VariadicFunction\s*<\s*([^,]+),\s*([^,]+),\s*[^>]+>\s* ([a-zA-Z]*)\s*=\s*{.*};$""", - declaration, flags=re.X) - if m: - result, arg, name = m.groups()[:3] - add_matcher(result, name, '%s, ..., %s' % (arg, arg), comment) - return + declaration, flags=re.X) + if m: + result, arg, name = m.groups()[:3] + add_matcher(result, name, '%s, ..., %s' % (arg, arg), comment) + return - # Parse Variadic operator matchers. - m = re.match( - r"""^.*VariadicOperatorMatcherFunc\s*<\s*([^,]+),\s*([^\s>]+)\s*>\s* + # Parse Variadic operator matchers. + m = re.match( + r"""^.*VariadicOperatorMatcherFunc\s*<\s*([^,]+),\s*([^\s>]+)\s*>\s* ([a-zA-Z]*)\s*=\s*{.*};$""", - declaration, flags=re.X) - if m: - min_args, max_args, name = m.groups()[:3] - if max_args == '1': - add_matcher('*', name, 'Matcher<*>', comment) - return - elif max_args == 'UINT_MAX': - add_matcher('*', name, 'Matcher<*>, ..., Matcher<*>', comment) - return + declaration, flags=re.X) + if m: + min_args, max_args, name = m.groups()[:3] + if max_args == '1': + add_matcher('*', name, 'Matcher<*>', comment) + return + elif max_args == 'UINT_MAX': + add_matcher('*', name, 'Matcher<*>, ..., Matcher<*>', comment) + return - - # Parse free standing matcher functions, like: - # Matcher Name(Matcher InnerMatcher) { - m = re.match(r"""^\s*(.*)\s+ + # Parse free standing matcher functions, like: + # Matcher Name(Matcher InnerMatcher) { + m = re.match(r"""^\s*(.*)\s+ ([^\s\(]+)\s*\( (.*) \)\s*{""", declaration, re.X) - if m: - result, name, args = m.groups() - args = ', '.join(p.strip() for p in args.split(',')) - m = re.match(r'.*\s+internal::(Bindable)?Matcher<([^>]+)>$', result) - if m: - result_types = [m.group(2)] - else: - result_types = extract_result_types(comment) - if not result_types: - if not comment: - # Only overloads don't have their own doxygen comments; ignore those. - print 'Ignoring "%s"' % name + if m: + result, name, args = m.groups() + args = ', '.join(p.strip() for p in args.split(',')) + m = re.match( + r'.*\s+internal::(Bindable)?Matcher<([^>]+)>$', result) + if m: + result_types = [m.group(2)] + else: + result_types = extract_result_types(comment) + if not result_types: + if not comment: + # Only overloads don't have their own doxygen comments; + # ignore those. + print('Ignoring "%s"' % name) + else: + print('Cannot determine result type for "%s"' % name) + else: + for result_type in result_types: + add_matcher(result_type, name, args, comment) else: - print 'Cannot determine result type for "%s"' % name - else: - for result_type in result_types: - add_matcher(result_type, name, args, comment) - else: - print '*** Unparsable: "' + declaration + '" ***' + print('*** Unparsable: "' + declaration + '" ***') + def sort_table(matcher_type, matcher_map): - """Returns the sorted html table for the given row map.""" - table = '' - for key in sorted(matcher_map.keys()): - table += matcher_map[key] + '\n' - return ('\n' + - '%(table)s' + - '') % { - 'type': matcher_type, - 'table': table, - } + """Returns the sorted html table for the given row map.""" + table = '' + for key in sorted(matcher_map.keys()): + table += matcher_map[key] + '\n' + return ('\n' + + '%(table)s' + + '') % { + 'type': matcher_type, + 'table': table, + } + # Parse the ast matchers. # We alternate between two modes: # body = True: We parse the definition of a matcher. We need @@ -339,33 +351,33 @@ allowed_types = [] body = False for line in open(MATCHERS_FILE).read().splitlines(): - if body: - if line.strip() and line[0] == '}': - if declaration: - act_on_decl(declaration, comment, allowed_types) - comment = '' - declaration = '' - allowed_types = [] - body = False + if body: + if line.strip() and line[0] == '}': + if declaration: + act_on_decl(declaration, comment, allowed_types) + comment = '' + declaration = '' + allowed_types = [] + body = False + else: + m = re.search(r'is_base_of<([^,]+), NodeType>', line) + if m and m.group(1): + allowed_types += [m.group(1)] + continue + if line.strip() and line.lstrip()[0] == '/': + comment += re.sub(r'/+\s?', '', line) + '\n' else: - m = re.search(r'is_base_of<([^,]+), NodeType>', line) - if m and m.group(1): - allowed_types += [m.group(1)] - continue - if line.strip() and line.lstrip()[0] == '/': - comment += re.sub(r'/+\s?', '', line) + '\n' - else: - declaration += ' ' + line - if ((not line.strip()) or - line.rstrip()[-1] == ';' or - (line.rstrip()[-1] == '{' and line.rstrip()[-3:] != '= {')): - if line.strip() and line.rstrip()[-1] == '{': - body = True - else: - act_on_decl(declaration, comment, allowed_types) - comment = '' - declaration = '' - allowed_types = [] + declaration += ' ' + line + if ((not line.strip()) or + line.rstrip()[-1] == ';' or + (line.rstrip()[-1] == '{' and line.rstrip()[-3:] != '= {')): + if line.strip() and line.rstrip()[-1] == '{': + body = True + else: + act_on_decl(declaration, comment, allowed_types) + comment = '' + declaration = '' + allowed_types = [] node_matcher_table = sort_table('DECL', node_matchers) narrowing_matcher_table = sort_table('NARROWING', narrowing_matchers) @@ -374,11 +386,12 @@ reference = open('../LibASTMatchersReference.html').read() reference = re.sub(r'', node_matcher_table, reference, flags=re.S) -reference = re.sub(r'', - narrowing_matcher_table, reference, flags=re.S) -reference = re.sub(r'', - traversal_matcher_table, reference, flags=re.S) +reference = re.sub( + r'', + narrowing_matcher_table, reference, flags=re.S) +reference = re.sub( + r'', + traversal_matcher_table, reference, flags=re.S) with open('../LibASTMatchersReference.html', 'wb') as output: - output.write(reference) - + output.write(reference) Index: docs/tools/dump_format_style.py =================================================================== --- docs/tools/dump_format_style.py +++ docs/tools/dump_format_style.py @@ -3,10 +3,8 @@ # documentation in ../ClangFormatStyleOptions.rst automatically. # Run from the directory in which this file is located to update the docs. -import collections import os import re -import urllib2 CLANG_DIR = os.path.join(os.path.dirname(__file__), '../..') FORMAT_STYLE_FILE = os.path.join(CLANG_DIR, 'include/clang/Format/Format.h') @@ -14,176 +12,195 @@ def substitute(text, tag, contents): - replacement = '\n.. START_%s\n\n%s\n\n.. END_%s\n' % (tag, contents, tag) - pattern = r'\n\.\. START_%s\n.*\n\.\. END_%s\n' % (tag, tag) - return re.sub(pattern, '%s', text, flags=re.S) % replacement + replacement = '\n.. START_%s\n\n%s\n\n.. END_%s\n' % (tag, contents, tag) + pattern = r'\n\.\. START_%s\n.*\n\.\. END_%s\n' % (tag, tag) + return re.sub(pattern, '%s', text, flags=re.S) % replacement + def doxygen2rst(text): - text = re.sub(r'\s*(.*?)\s*<\/tt>', r'``\1``', text) - text = re.sub(r'\\c ([^ ,;\.]+)', r'``\1``', text) - text = re.sub(r'\\\w+ ', '', text) - return text + text = re.sub(r'\s*(.*?)\s*<\/tt>', r'``\1``', text) + text = re.sub(r'\\c ([^ ,;\.]+)', r'``\1``', text) + text = re.sub(r'\\\w+ ', '', text) + return text + def indent(text, columns): - indent = ' ' * columns - s = re.sub(r'\n([^\n])', '\n' + indent + '\\1', text, flags=re.S) - if s.startswith('\n'): - return s - return indent + s + indent = ' ' * columns + s = re.sub(r'\n([^\n])', '\n' + indent + '\\1', text, flags=re.S) + if s.startswith('\n'): + return s + return indent + s + class Option: - def __init__(self, name, type, comment): - self.name = name - self.type = type - self.comment = comment.strip() - self.enum = None - self.nested_struct = None - def __str__(self): - s = '**%s** (``%s``)\n%s' % (self.name, self.type, - doxygen2rst(indent(self.comment, 2))) - if self.enum: - s += indent('\n\nPossible values:\n\n%s\n' % self.enum, 2) - if self.nested_struct: - s += indent('\n\nNested configuration flags:\n\n%s\n' %self.nested_struct, - 2) - return s + def __init__(self, name, type, comment): + self.name = name + self.type = type + self.comment = comment.strip() + self.enum = None + self.nested_struct = None + def __str__(self): + s = '**%s** (``%s``)\n%s' % (self.name, self.type, + doxygen2rst(indent(self.comment, 2))) + if self.enum: + s += indent('\n\nPossible values:\n\n%s\n' % self.enum, 2) + if self.nested_struct: + s += indent( + '\n\nNested configuration flags:\n\n%s\n' % self.nested_struct, + 2) + return s + + class NestedStruct: - def __init__(self, name, comment): - self.name = name - self.comment = comment.strip() - self.values = [] - def __str__(self): - return '\n'.join(map(str, self.values)) + def __init__(self, name, comment): + self.name = name + self.comment = comment.strip() + self.values = [] + def __str__(self): + return '\n'.join(map(str, self.values)) + + class NestedField: - def __init__(self, name, comment): - self.name = name - self.comment = comment.strip() - def __str__(self): - return '* ``%s`` %s' % (self.name, doxygen2rst(self.comment)) + def __init__(self, name, comment): + self.name = name + self.comment = comment.strip() + def __str__(self): + return '* ``%s`` %s' % (self.name, doxygen2rst(self.comment)) + + class Enum: - def __init__(self, name, comment): - self.name = name - self.comment = comment.strip() - self.values = [] - def __str__(self): - return '\n'.join(map(str, self.values)) + def __init__(self, name, comment): + self.name = name + self.comment = comment.strip() + self.values = [] + def __str__(self): + return '\n'.join(map(str, self.values)) + + class EnumValue: - def __init__(self, name, comment): - self.name = name - self.comment = comment - def __str__(self): - return '* ``%s`` (in configuration: ``%s``)\n%s' % ( - self.name, - re.sub('.*_', '', self.name), - doxygen2rst(indent(self.comment, 2))) + def __init__(self, name, comment): + self.name = name + self.comment = comment + def __str__(self): + return '* ``%s`` (in configuration: ``%s``)\n%s' % ( + self.name, + re.sub('.*_', '', self.name), + doxygen2rst(indent(self.comment, 2))) + + def clean_comment_line(line): - match = re.match(r'^/// \\code(\{.(\w+)\})?$', line) - if match: - lang = match.groups()[1] - if not lang: - lang = 'c++' - return '\n.. code-block:: %s\n\n' % lang - if line == '/// \\endcode': - return '' - return line[4:] + '\n' + match = re.match(r'^/// \\code(\{.(\w+)\})?$', line) + if match: + lang = match.groups()[1] + if not lang: + lang = 'c++' + return '\n.. code-block:: %s\n\n' % lang + if line == '/// \\endcode': + return '' + return line[4:] + '\n' + def read_options(header): - class State: - BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComent, \ - InFieldComment, InEnum, InEnumMemberComment = range(8) - state = State.BeforeStruct + class State: + BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComent, \ + InFieldComment, InEnum, InEnumMemberComment = range(8) + state = State.BeforeStruct - options = [] - enums = {} - nested_structs = {} - comment = '' - enum = None - nested_struct = None + options = [] + enums = {} + nested_structs = {} + comment = '' + enum = None + nested_struct = None - for line in header: - line = line.strip() - if state == State.BeforeStruct: - if line == 'struct FormatStyle {': - state = State.InStruct - elif state == State.InStruct: - if line.startswith('///'): - state = State.InFieldComment - comment = clean_comment_line(line) - elif line == '};': - state = State.Finished - break - elif state == State.InFieldComment: - if line.startswith('///'): - comment += clean_comment_line(line) - elif line.startswith('enum'): - state = State.InEnum - name = re.sub(r'enum\s+(\w+)\s*\{', '\\1', line) - enum = Enum(name, comment) - elif line.startswith('struct'): - state = State.InNestedStruct - name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line) - nested_struct = NestedStruct(name, comment) - elif line.endswith(';'): - state = State.InStruct - field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);', - line).groups() - option = Option(str(field_name), str(field_type), comment) - options.append(option) - else: - raise Exception('Invalid format, expected comment, field or enum') - elif state == State.InNestedStruct: - if line.startswith('///'): - state = State.InNestedFieldComent - comment = clean_comment_line(line) - elif line == '};': - state = State.InStruct - nested_structs[nested_struct.name] = nested_struct - elif state == State.InNestedFieldComent: - if line.startswith('///'): - comment += clean_comment_line(line) - else: - state = State.InNestedStruct - nested_struct.values.append(NestedField(line.replace(';', ''), comment)) - elif state == State.InEnum: - if line.startswith('///'): - state = State.InEnumMemberComment - comment = clean_comment_line(line) - elif line == '};': - state = State.InStruct - enums[enum.name] = enum - else: - raise Exception('Invalid format, expected enum field comment or };') - elif state == State.InEnumMemberComment: - if line.startswith('///'): - comment += clean_comment_line(line) - else: - state = State.InEnum - enum.values.append(EnumValue(line.replace(',', ''), comment)) - if state != State.Finished: - raise Exception('Not finished by the end of file') + for line in header: + line = line.strip() + if state == State.BeforeStruct: + if line == 'struct FormatStyle {': + state = State.InStruct + elif state == State.InStruct: + if line.startswith('///'): + state = State.InFieldComment + comment = clean_comment_line(line) + elif line == '};': + state = State.Finished + break + elif state == State.InFieldComment: + if line.startswith('///'): + comment += clean_comment_line(line) + elif line.startswith('enum'): + state = State.InEnum + name = re.sub(r'enum\s+(\w+)\s*\{', '\\1', line) + enum = Enum(name, comment) + elif line.startswith('struct'): + state = State.InNestedStruct + name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line) + nested_struct = NestedStruct(name, comment) + elif line.endswith(';'): + state = State.InStruct + field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);', + line).groups() + option = Option(str(field_name), str(field_type), comment) + options.append(option) + else: + raise Exception( + 'Invalid format, expected comment, field or enum') + elif state == State.InNestedStruct: + if line.startswith('///'): + state = State.InNestedFieldComent + comment = clean_comment_line(line) + elif line == '};': + state = State.InStruct + nested_structs[nested_struct.name] = nested_struct + elif state == State.InNestedFieldComent: + if line.startswith('///'): + comment += clean_comment_line(line) + else: + state = State.InNestedStruct + nested_struct.values.append( + NestedField(line.replace(';', ''), comment)) + elif state == State.InEnum: + if line.startswith('///'): + state = State.InEnumMemberComment + comment = clean_comment_line(line) + elif line == '};': + state = State.InStruct + enums[enum.name] = enum + else: + raise Exception( + 'Invalid format, expected enum field comment or };') + elif state == State.InEnumMemberComment: + if line.startswith('///'): + comment += clean_comment_line(line) + else: + state = State.InEnum + enum.values.append(EnumValue(line.replace(',', ''), comment)) + if state != State.Finished: + raise Exception('Not finished by the end of file') - for option in options: - if not option.type in ['bool', 'unsigned', 'int', 'std::string', - 'std::vector', - 'std::vector']: - if enums.has_key(option.type): - option.enum = enums[option.type] - elif nested_structs.has_key(option.type): - option.nested_struct = nested_structs[option.type]; - else: - raise Exception('Unknown type: %s' % option.type) - return options + for option in options: + if option.type not in ['bool', 'unsigned', 'int', 'std::string', + 'std::vector', + 'std::vector']: + if option.type in enums: + option.enum = enums[option.type] + elif option.type in nested_structs: + option.nested_struct = nested_structs[option.type] + else: + raise Exception('Unknown type: %s' % option.type) + return options + options = read_options(open(FORMAT_STYLE_FILE)) options = sorted(options, key=lambda x: x.name) @@ -194,5 +211,4 @@ contents = substitute(contents, 'FORMAT_STYLE_OPTIONS', options_text) with open(DOC_FILE, 'wb') as output: - output.write(contents) - + output.write(contents)