1#!/usr/bin/env python3 2# Copyright 2021 gRPC authors. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16# Eliminate the kind of redundant namespace qualifiers that tend to 17# creep in when converting C to C++. 18 19import collections 20import os 21import re 22import sys 23 24 25def find_closing_mustache(contents, initial_depth): 26 """Find the closing mustache for a given number of open mustaches.""" 27 depth = initial_depth 28 start_len = len(contents) 29 while contents: 30 # Skip over strings. 31 if contents[0] == '"': 32 contents = contents[1:] 33 while contents[0] != '"': 34 if contents.startswith('\\\\'): 35 contents = contents[2:] 36 elif contents.startswith('\\"'): 37 contents = contents[2:] 38 else: 39 contents = contents[1:] 40 contents = contents[1:] 41 # And characters that might confuse us. 42 elif contents.startswith("'{'") or contents.startswith( 43 "'\"'") or contents.startswith("'}'"): 44 contents = contents[3:] 45 # Skip over comments. 46 elif contents.startswith("//"): 47 contents = contents[contents.find('\n'):] 48 elif contents.startswith("/*"): 49 contents = contents[contents.find('*/') + 2:] 50 # Count up or down if we see a mustache. 51 elif contents[0] == '{': 52 contents = contents[1:] 53 depth += 1 54 elif contents[0] == '}': 55 contents = contents[1:] 56 depth -= 1 57 if depth == 0: 58 return start_len - len(contents) 59 # Skip over everything else. 60 else: 61 contents = contents[1:] 62 return None 63 64 65def is_a_define_statement(match, body): 66 """See if the matching line begins with #define""" 67 # This does not yet help with multi-line defines 68 m = re.search(r"^#define.*{}$".format(match.group(0)), body[:match.end()], 69 re.MULTILINE) 70 return m is not None 71 72 73def update_file(contents, namespaces): 74 """Scan the contents of a file, and for top-level namespaces in namespaces remove redundant usages.""" 75 output = '' 76 while contents: 77 m = re.search(r'namespace ([a-zA-Z0-9_]*) {', contents) 78 if not m: 79 output += contents 80 break 81 output += contents[:m.end()] 82 contents = contents[m.end():] 83 end = find_closing_mustache(contents, 1) 84 if end is None: 85 print('Failed to find closing mustache for namespace {}'.format( 86 m.group(1))) 87 print('Remaining text:') 88 print(contents) 89 sys.exit(1) 90 body = contents[:end] 91 namespace = m.group(1) 92 if namespace in namespaces: 93 while body: 94 # Find instances of 'namespace::' 95 m = re.search(r'\b' + namespace + r'::\b', body) 96 if not m: 97 break 98 # Ignore instances of '::namespace::' -- these are usually meant to be there. 99 if m.start() >= 2 and body[m.start() - 2:].startswith('::'): 100 output += body[:m.end()] 101 # Ignore #defines, since they may be used anywhere 102 elif is_a_define_statement(m, body): 103 output += body[:m.end()] 104 else: 105 output += body[:m.start()] 106 body = body[m.end():] 107 output += body 108 contents = contents[end:] 109 return output 110 111 112# self check before doing anything 113_TEST = """ 114namespace bar { 115 namespace baz { 116 } 117} 118namespace foo {} 119namespace foo { 120 foo::a; 121 ::foo::a; 122} 123""" 124_TEST_EXPECTED = """ 125namespace bar { 126 namespace baz { 127 } 128} 129namespace foo {} 130namespace foo { 131 a; 132 ::foo::a; 133} 134""" 135output = update_file(_TEST, ['foo']) 136if output != _TEST_EXPECTED: 137 import difflib 138 print('FAILED: self check') 139 print('\n'.join( 140 difflib.ndiff(_TEST_EXPECTED.splitlines(1), output.splitlines(1)))) 141 sys.exit(1) 142 143# Main loop. 144Config = collections.namedtuple('Config', ['dirs', 'namespaces']) 145 146_CONFIGURATION = (Config(['src/core', 'test/core'], ['grpc_core']),) 147 148changed = [] 149 150for config in _CONFIGURATION: 151 for dir in config.dirs: 152 for root, dirs, files in os.walk(dir): 153 for file in files: 154 if file.endswith('.cc') or file.endswith('.h'): 155 path = os.path.join(root, file) 156 try: 157 with open(path) as f: 158 contents = f.read() 159 except IOError: 160 continue 161 updated = update_file(contents, config.namespaces) 162 if updated != contents: 163 changed.append(path) 164 with open(os.path.join(root, file), 'w') as f: 165 f.write(updated) 166 167if changed: 168 print('The following files were changed:') 169 for path in changed: 170 print(' ' + path) 171 sys.exit(1) 172