phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. |
| 3 | # |
| 4 | # Use of this source code is governed by a BSD-style license |
| 5 | # that can be found in the LICENSE file in the root of the source |
| 6 | # tree. An additional intellectual property rights grant can be found |
| 7 | # in the file PATENTS. All contributing project authors may |
| 8 | # be found in the AUTHORS file in the root of the source tree. |
| 9 | |
| 10 | """WebRTC reformat script. |
| 11 | |
| 12 | This script is used to reformat WebRTC code from the old code style to Google |
| 13 | C++ code style. |
| 14 | |
| 15 | You need to have astyle (http://astyle.sourceforge.net/) in your path. You also |
| 16 | need to put the following contents into ~/.astylerc: |
| 17 | |
| 18 | # =======================COPY============================================== |
| 19 | # Google C++ style guide settings. |
| 20 | indent=spaces=2 # Indentation uses two spaces. |
| 21 | style=attach # Attach braces. |
| 22 | |
| 23 | indent-switches |
| 24 | indent-preprocessor # Indent preprocessor continuation lines. |
| 25 | min-conditional-indent=0 # Align conditional continuation with "(". |
| 26 | # e.g. if (foo && |
| 27 | # bar |
| 28 | max-instatement-indent=80 # Try not to mess with current alignment. |
| 29 | |
| 30 | pad-oper # Padding around operators. |
| 31 | pad-header # Padding after if, for etc. |
| 32 | unpad-paren # No padding around parentheses. |
| 33 | align-pointer=type # e.g. int* foo |
| 34 | |
| 35 | convert-tabs # Convert non-indentation tabs as well. |
| 36 | |
| 37 | # The following are available in the unreleased svn repo. |
| 38 | # Behvaiour isn't quite what we'd like; more testing needed. |
| 39 | #max-code-length=80 |
| 40 | #break-after-logical |
| 41 | |
| 42 | lineend=linux |
| 43 | # ========================================================================= |
| 44 | """ |
| 45 | |
| 46 | # TODO(mflodman) |
| 47 | # x s/type *var/type* var/g |
| 48 | # x : list indention -> 4 spaces. |
| 49 | |
| 50 | __author__ = 'mflodman@webrtc.org (Magnus Flodman)' |
| 51 | |
| 52 | import fnmatch |
| 53 | import os |
| 54 | import re |
| 55 | import subprocess |
| 56 | import sys |
| 57 | |
| 58 | |
| 59 | def LowerWord(obj): |
| 60 | """Helper for DeCamelCase.""" |
phoglund@webrtc.org | 943770b | 2013-01-02 15:46:43 +0000 | [diff] [blame] | 61 | optional_last_letters = obj.group(3) or '' |
| 62 | return obj.group(1) + '_' + obj.group(2).lower() + optional_last_letters |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 63 | |
| 64 | |
| 65 | def DeCamelCase(text): |
| 66 | """De-camelize variable names.""" |
phoglund@webrtc.org | 943770b | 2013-01-02 15:46:43 +0000 | [diff] [blame] | 67 | pattern = re.compile(r'(?<=[ _*\(\&\!])([a-z]+)(?<!k)([A-Z]+)([a-z])?') |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 68 | while re.search(pattern, text): |
| 69 | text = re.sub(pattern, LowerWord, text) |
| 70 | return text |
| 71 | |
| 72 | |
| 73 | def TrimLineEndings(text): |
| 74 | """Removes trailing white spaces.""" |
| 75 | pattern = re.compile(r'[ ]+(\n)') |
| 76 | return re.sub(pattern, r'\1', text) |
| 77 | |
| 78 | |
| 79 | def MoveUnderScore(text): |
| 80 | """Moves the underscore from beginning of variable name to the end.""" |
| 81 | # TODO(mflodman) Replace \1 with ?-expression. |
| 82 | # We don't want to change macros and #defines though, so don't do anything |
| 83 | # if the first character is uppercase (normal variables shouldn't have that). |
phoglund@webrtc.org | 943770b | 2013-01-02 15:46:43 +0000 | [diff] [blame] | 84 | pattern = r'([ \*\!\&\(\[\]])_(?!_)(?![A-Z])(\w+)' |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 85 | return re.sub(pattern, r'\1\2_', text) |
| 86 | |
| 87 | |
phoglund@webrtc.org | 943770b | 2013-01-02 15:46:43 +0000 | [diff] [blame] | 88 | def PostfixToPrefixInForLoops(text): |
| 89 | """Converts x++ to ++x in the increment part of a for loop.""" |
| 90 | pattern = r'(for \(.*;.*;) (\w+)\+\+\)' |
| 91 | return re.sub(pattern, r'\1++\2)', text) |
| 92 | |
| 93 | |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 94 | def RemoveMultipleEmptyLines(text): |
| 95 | """Remove all multiple blank lines.""" |
| 96 | pattern = r'[\n]{3,}' |
| 97 | return re.sub(pattern, '\n\n', text) |
| 98 | |
| 99 | |
| 100 | def CPPComments(text): |
| 101 | """Remove all C-comments and replace with C++ comments.""" |
| 102 | |
| 103 | # Keep the copyright header style. |
| 104 | line_list = text.splitlines(True) |
| 105 | copyright_list = line_list[0:10] |
| 106 | code_list = line_list[10:] |
| 107 | copy_text = ''.join(copyright_list) |
| 108 | code_text = ''.join(code_list) |
| 109 | |
| 110 | # Remove */ for C-comments, don't care about trailing blanks. |
| 111 | comment_end = re.compile(r'\n[ ]*\*/[ ]*') |
| 112 | code_text = re.sub(comment_end, '', code_text) |
| 113 | comment_end = re.compile(r'\*/') |
| 114 | code_text = re.sub(comment_end, '', code_text) |
| 115 | # Remove comment lines in the middle of comments, replace with C++ comments. |
phoglund@webrtc.org | 78bec2d | 2012-12-03 08:48:07 +0000 | [diff] [blame] | 116 | comment_star = re.compile(r'(?<=\n)[ ]*(?!\*\w)\*[ ]*') |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 117 | code_text = re.sub(comment_star, r'// ', code_text) |
| 118 | # Remove start of C comment and replace with C++ comment. |
| 119 | comment_start = re.compile(r'/\*[ ]*\n') |
| 120 | code_text = re.sub(comment_start, '', code_text) |
| 121 | comment_start = re.compile(r'/\*[ ]*(.)') |
| 122 | code_text = re.sub(comment_start, r'// \1', code_text) |
| 123 | |
| 124 | # Add copyright info. |
| 125 | return copy_text + code_text |
| 126 | |
| 127 | |
| 128 | def SortIncludeHeaders(text, filename): |
| 129 | """Sorts all include headers in alphabetic order. |
| 130 | |
| 131 | The file's own header goes first, followed by system headers and then |
| 132 | project headers. This function will exit if we detect any fancy #ifdef logic |
| 133 | among the includes - that's a lot harder to sort. |
| 134 | |
| 135 | Args: |
| 136 | text: The file text. |
| 137 | filename: The file we are reformatting. |
| 138 | |
| 139 | Returns: |
| 140 | The text with includes sorted. |
| 141 | """ |
| 142 | # Get all includes in file. |
| 143 | include_pattern = re.compile('#include.+\n') |
| 144 | includes = re.findall(include_pattern, text) |
| 145 | |
| 146 | # Sort system headers and project headers separately. |
| 147 | sys_includes = [] |
| 148 | project_includes = [] |
| 149 | self_include = '' |
| 150 | sys_pattern = re.compile('#include <') |
phoglund@webrtc.org | e3b2bc6 | 2012-11-23 09:09:59 +0000 | [diff] [blame] | 151 | h_filename, _ = os.path.splitext(os.path.basename(filename)) |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 152 | |
| 153 | for item in includes: |
| 154 | if re.search(h_filename, item): |
| 155 | self_include = item |
| 156 | elif re.search(sys_pattern, item): |
| 157 | sys_includes.append(item) |
| 158 | else: |
| 159 | project_includes.append(item) |
| 160 | |
| 161 | sys_includes = sorted(sys_includes) |
| 162 | project_includes = sorted(project_includes) |
| 163 | headers = (self_include + '\n' + ''.join(sys_includes) + '\n' + |
| 164 | ''.join(project_includes)) |
| 165 | |
| 166 | # Replace existing headers with the sorted string. |
| 167 | text_no_hdrs = re.sub(include_pattern, r'???', text) |
| 168 | |
phoglund@webrtc.org | 943770b | 2013-01-02 15:46:43 +0000 | [diff] [blame] | 169 | # Insert sorted headers unless we detect #ifdefs right next to the headers. |
| 170 | if re.search(r'(#ifdef|#ifndef|#if).*\s*\?{3,}\s*#endif', text_no_hdrs): |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 171 | print 'WARNING: Include headers not sorted in ' + filename |
| 172 | return text |
| 173 | |
| 174 | return_text = re.sub(r'\?{3,}', headers, text_no_hdrs, 1) |
| 175 | if re.search(r'\?{3,}', text_no_hdrs): |
| 176 | # Remove possible remaining ???. |
| 177 | return_text = re.sub(r'\?{3,}', r'', return_text) |
| 178 | |
| 179 | return return_text |
| 180 | |
| 181 | |
phoglund@webrtc.org | e3b2bc6 | 2012-11-23 09:09:59 +0000 | [diff] [blame] | 182 | def AddPath(match): |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 183 | """Helper for adding file path for WebRTC header files, ignoring other.""" |
phoglund@webrtc.org | e3b2bc6 | 2012-11-23 09:09:59 +0000 | [diff] [blame] | 184 | file_to_examine = match.group(1) + '.h' |
| 185 | # TODO(mflodman) Use current directory and find webrtc/. |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 186 | for path, _, files in os.walk('./webrtc'): |
| 187 | for filename in files: |
phoglund@webrtc.org | e3b2bc6 | 2012-11-23 09:09:59 +0000 | [diff] [blame] | 188 | if fnmatch.fnmatch(filename, file_to_examine): |
| 189 | path_name = os.path.join(path, filename).replace('./', '') |
| 190 | return '#include "%s"\n' % path_name |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 191 | |
| 192 | # No path found, return original string. |
phoglund@webrtc.org | e3b2bc6 | 2012-11-23 09:09:59 +0000 | [diff] [blame] | 193 | return '#include "'+ file_to_examine + '"\n' |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 194 | |
| 195 | |
| 196 | def AddHeaderPath(text): |
phoglund@webrtc.org | e3b2bc6 | 2012-11-23 09:09:59 +0000 | [diff] [blame] | 197 | """Add path to all included header files that have no path yet.""" |
| 198 | headers = re.compile('#include "(.+).h"\n') |
| 199 | return re.sub(headers, AddPath, text) |
| 200 | |
| 201 | |
| 202 | def AddWebrtcToOldSrcRelativePath(match): |
| 203 | file_to_examine = match.group(1) + '.h' |
| 204 | path, filename = os.path.split(file_to_examine) |
| 205 | dirs_in_webrtc = [name for name in os.listdir('./webrtc') |
| 206 | if os.path.isdir(os.path.join('./webrtc', name))] |
| 207 | for dir_in_webrtc in dirs_in_webrtc: |
| 208 | if path.startswith(dir_in_webrtc): |
| 209 | return '#include "%s"\n' % os.path.join('webrtc', path, filename) |
| 210 | return '#include "%s"\n' % file_to_examine |
| 211 | |
| 212 | def AddWebrtcPrefixToOldSrcRelativePaths(text): |
| 213 | """For all paths starting with for instance video_engine, add webrtc/.""" |
| 214 | headers = re.compile('#include "(.+).h"\n') |
| 215 | return re.sub(headers, AddWebrtcToOldSrcRelativePath, text) |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 216 | |
| 217 | |
| 218 | def IndentLabels(text): |
| 219 | """Indent public, protected and private one step (astyle doesn't).""" |
| 220 | pattern = re.compile('(?<=\n)(public:|protected:|private:)') |
| 221 | return re.sub(pattern, r' \1', text) |
| 222 | |
| 223 | |
| 224 | def FixIncludeGuards(text, file_name): |
| 225 | """Change include guard according to the stantard.""" |
| 226 | # Remove a possible webrtc/ from the path. |
| 227 | file_name = re.sub(r'(webrtc\/)(.+)', r'\2', file_name) |
| 228 | new_guard = 'WEBRTC_' + file_name |
| 229 | new_guard = new_guard.upper() |
| 230 | new_guard = re.sub(r'([/\.])', r'_', new_guard) |
| 231 | new_guard += '_' |
| 232 | |
| 233 | text = re.sub(r'#ifndef WEBRTC_.+\n', r'#ifndef ' + new_guard + '\n', text, 1) |
| 234 | text = re.sub(r'#define WEBRTC_.+\n', r'#define ' + new_guard + '\n', text, 1) |
| 235 | text = re.sub(r'#endif *\/\/ *WEBRTC_.+\n', r'#endif // ' + new_guard + '\n', |
| 236 | text, 1) |
| 237 | |
| 238 | return text |
| 239 | |
| 240 | |
| 241 | def SaveFile(filename, text): |
| 242 | os.remove(filename) |
| 243 | f = open(filename, 'w') |
| 244 | f.write(text) |
| 245 | f.close() |
| 246 | |
| 247 | |
| 248 | def main(): |
| 249 | args = sys.argv[1:] |
| 250 | if not args: |
| 251 | print 'Usage: %s <filename>' % sys.argv[0] |
| 252 | sys.exit(1) |
| 253 | |
| 254 | for filename in args: |
| 255 | f = open(filename) |
| 256 | text = f.read() |
| 257 | f.close() |
| 258 | |
| 259 | text = DeCamelCase(text) |
| 260 | text = MoveUnderScore(text) |
phoglund@webrtc.org | 943770b | 2013-01-02 15:46:43 +0000 | [diff] [blame] | 261 | text = PostfixToPrefixInForLoops(text) |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 262 | text = CPPComments(text) |
| 263 | text = AddHeaderPath(text) |
phoglund@webrtc.org | e3b2bc6 | 2012-11-23 09:09:59 +0000 | [diff] [blame] | 264 | text = AddWebrtcPrefixToOldSrcRelativePaths(text) |
phoglund@webrtc.org | 3ec52c0 | 2012-11-21 13:28:52 +0000 | [diff] [blame] | 265 | text = SortIncludeHeaders(text, filename) |
| 266 | text = RemoveMultipleEmptyLines(text) |
| 267 | text = TrimLineEndings(text) |
| 268 | |
| 269 | # Remove the original file and re-create it with the reformatted content. |
| 270 | SaveFile(filename, text) |
| 271 | |
| 272 | # Fix tabs, indentation and '{' using astyle. |
| 273 | astyle_cmd = 'astyle' |
| 274 | if sys.platform == 'win32': |
| 275 | astyle_cmd += '.exe' |
| 276 | subprocess.call([astyle_cmd, '-n', '-q', filename]) |
| 277 | |
| 278 | if filename.endswith('.h'): |
| 279 | f = open(filename) |
| 280 | text = f.read() |
| 281 | f.close() |
| 282 | text = IndentLabels(text) |
| 283 | text = FixIncludeGuards(text, filename) |
| 284 | SaveFile(filename, text) |
| 285 | |
| 286 | print filename + ' done.' |
| 287 | |
| 288 | |
| 289 | if __name__ == '__main__': |
| 290 | main() |