|
1 | | -import datetime |
2 | | -import filecmp |
3 | | -import fnmatch |
4 | | -import os |
| 1 | +from datetime import datetime |
| 2 | +from pathlib import Path |
5 | 3 | import re |
6 | | -import shutil |
7 | 4 | import sys |
8 | | -import tempfile |
9 | | -import textwrap |
10 | | - |
11 | | -copyright_str = textwrap.dedent(''' |
12 | | - /* |
13 | | - * Copyright (c) {} Roc Streaming authors |
14 | | - * |
15 | | - * This Source Code Form is subject to the terms of the Mozilla Public |
16 | | - * License, v. 2.0. If a copy of the MPL was not distributed with this |
17 | | - * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
18 | | - */ |
19 | | -''').format(datetime.datetime.now().year) |
20 | | - |
21 | | -def is_header(path): |
22 | | - '''True if `path` is a C/C++ header file; False otherwise.''' |
23 | | - return re.search(r'\.h$', path) |
24 | | - |
25 | | -def is_test(path): |
26 | | - '''True if `path` is a C/C++ test file; False otherwise.''' |
27 | | - _, basename = os.path.split(path) |
28 | | - for s in [basename, os.path.basename(os.path.dirname(path))]: |
29 | | - if s.startswith('test_'): |
30 | | - return True |
31 | | - return False |
32 | | - |
33 | | -def is_public_api(path): |
34 | | - '''True if `path` is a public API C/C++ header file that resides in a |
35 | | - public_api/include/roc/ directory. |
36 | | - ''' |
37 | | - rootname = os.path.basename( |
38 | | - os.path.dirname( |
39 | | - os.path.dirname( |
40 | | - os.path.abspath(path)))) |
41 | | - return rootname == 'public_api' |
42 | | - |
43 | | -def make_guard(path): |
44 | | - '''Generate include guard macro name basing on `path` to header file. |
45 | | - The resulting macro looks like this: `PATH_TO_FILE_`. |
46 | | - ''' |
47 | | - dirpath, basename = os.path.split(path) |
48 | | - dirname = os.path.basename(dirpath) |
49 | | - if is_public_api(path): |
50 | | - if is_test(path): |
51 | | - arr = ['roc', 'public_api', dirname, basename] |
52 | | - else: |
53 | | - arr = ['roc', 'public_api', basename] |
54 | | - elif dirname == '.': |
55 | | - arr = ['roc', basename] |
| 5 | + |
| 6 | +COPYRIGHT_HEADER = """ |
| 7 | +/* |
| 8 | + * Copyright (c) {year} Roc Streaming authors |
| 9 | + * |
| 10 | + * This Source Code Form is subject to the terms of the Mozilla Public |
| 11 | + * License, v. 2.0. If a copy of the MPL was not distributed with this |
| 12 | + * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 13 | + */ |
| 14 | +""".lstrip() |
| 15 | + |
| 16 | +DOXYGEN_HEADER = """ |
| 17 | +//! @file {path} |
| 18 | +//! @brief TODO: short file description. |
| 19 | +""".lstrip() |
| 20 | + |
| 21 | +PRAGMA_HEADER = """ |
| 22 | +#pragma once |
| 23 | +""".lstrip() |
| 24 | + |
| 25 | +H_REGEXP = re.compile(r''' |
| 26 | + ^ |
| 27 | + (?P<copyright> /\*.*?Copyright.*?\*/\n )? |
| 28 | + \n* |
| 29 | + (?P<span1> .*?\n )?? |
| 30 | + \n* |
| 31 | + (?P<doxygen> //!\s*@file.*?\n //!\s*@brief.*?\n )? |
| 32 | + \n* |
| 33 | + (?P<span2> .*?\n )?? |
| 34 | + \n* |
| 35 | + (?P<pragma> \#pragma\s+once\n | \#ifndef\s+ROC_.*?_H_\n \#define\s+ROC_.*?_H_\n )? |
| 36 | + \n* |
| 37 | + (?P<body> .* ) |
| 38 | + $ |
| 39 | +''', re.DOTALL | re.VERBOSE) |
| 40 | + |
| 41 | +CPP_REGEXP = re.compile(r''' |
| 42 | + ^ |
| 43 | + (?P<copyright> /\*.*?Copyright.*?\*/\n )? |
| 44 | + \n* |
| 45 | + (?P<body> .* )? |
| 46 | + $ |
| 47 | +''', re.DOTALL | re.VERBOSE) |
| 48 | + |
| 49 | +def process_h(content, file_path, doxygen_path): |
| 50 | + m = H_REGEXP.match(content) |
| 51 | + |
| 52 | + if m.group('copyright') and (m.group('doxygen') or not doxygen_path) \ |
| 53 | + and m.group('pragma'): |
| 54 | + return None |
| 55 | + |
| 56 | + result = '' |
| 57 | + |
| 58 | + if m.group('copyright'): |
| 59 | + result += m.group('copyright') + '\n' |
| 60 | + else: |
| 61 | + result += COPYRIGHT_HEADER.format(year=datetime.now().year) + '\n' |
| 62 | + |
| 63 | + if m.group('span1'): |
| 64 | + result += m.group('span1') |
| 65 | + |
| 66 | + if m.group('doxygen'): |
| 67 | + result += m.group('doxygen') + '\n' |
| 68 | + elif doxygen_path: |
| 69 | + result += DOXYGEN_HEADER.format(path=doxygen_path) + '\n' |
| 70 | + |
| 71 | + if m.group('span2'): |
| 72 | + result += m.group('span2') |
| 73 | + |
| 74 | + if m.group('pragma'): |
| 75 | + result += m.group('pragma') + '\n' |
56 | 76 | else: |
57 | | - arr = [dirname, basename] |
58 | | - while not arr[0].startswith('roc_') and arr[0] != 'roc': |
59 | | - dirpath = os.path.dirname(dirpath) |
60 | | - arr = [os.path.basename(dirpath)] + arr |
61 | | - return '_'.join(arr).replace('.', '_').upper() + '_' |
62 | | - |
63 | | -def make_doxygen_path(path): |
64 | | - '''Generate doxygen `@file` command using `path` to the file.''' |
65 | | - path = '/'.join(path.split(os.sep)) # switch to '/' separator |
66 | | - path = re.sub(r'^\.?/', '', path) # remove leading "./" |
67 | | - return '@file ' + path |
68 | | - |
69 | | -def make_doxygen_brief(text): |
70 | | - '''Generate doxygen `@brief` command with a given `text` as a payload.''' |
71 | | - if not text.endswith('.'): |
72 | | - text += '.' |
73 | | - return '@brief ' + text |
74 | | - |
75 | | -def format_file(output, path): |
76 | | - '''Read file `path` and write its contents to the `output` stream applying |
77 | | - formatting modifications if needed: add copyright, include gards, Doxygen |
78 | | - documentation lines, and so on. |
79 | | - ''' |
80 | | - |
81 | | - def fprint(s): |
82 | | - output.write(s + '\n') |
83 | | - |
84 | | - with open(path) as fp: |
85 | | - lines = fp.read().splitlines() |
86 | | - |
87 | | - has_copyright, has_doxygen, has_guard, is_autogen = False, False, False, False |
88 | | - |
89 | | - section = 'copyright' |
90 | | - brief = 'TODO' |
91 | | - |
92 | | - original_lines = [] |
93 | | - body_start = True |
94 | | - |
95 | | - if is_header(path): |
96 | | - while re.match(r'^\s*$', lines[-1]): |
97 | | - lines.pop() |
98 | | - |
99 | | - if re.match(r'^\s*#\s*endif.*$', lines[-1]): |
100 | | - lines.pop() |
101 | | - |
102 | | - while re.match(r'^\s*$', lines[-1]): |
103 | | - lines.pop() |
104 | | - |
105 | | - for line in lines: |
106 | | - m = re.search(r'@brief\s+(.*)', line) |
107 | | - if m: |
108 | | - brief = m.group(1) |
109 | | - break |
110 | | - |
111 | | - if lines and re.match(r'^//.*AUTO-GENERATED.*', lines[0]): |
112 | | - is_autogen = True |
113 | | - section = 'body' |
114 | | - |
115 | | - while lines: |
116 | | - line = lines.pop(0) |
117 | | - |
118 | | - if section in ['copyright', 'doxygen', 'guard']: |
119 | | - if re.match(r'^\s*$', line): |
120 | | - continue |
121 | | - |
122 | | - if section == 'copyright': |
123 | | - original_lines += [line] |
124 | | - |
125 | | - if re.match(r'^\s*/?\*', line): |
126 | | - if re.match(r'^\s*/?\*.*AUTO-GENERATED.*', line): |
127 | | - is_autogen = True |
128 | | - |
129 | | - if re.match(r'^\s*/?\*\s*(Copyright|Mozilla)', line): |
130 | | - has_copyright = True |
131 | | - |
132 | | - if re.match(r'^\s*\*/', line): |
133 | | - if is_autogen or has_copyright: |
134 | | - for p in original_lines: |
135 | | - fprint(p) |
136 | | - fprint('') |
137 | | - else: |
138 | | - fprint(copyright_str.strip()) |
139 | | - fprint('') |
140 | | - |
141 | | - if not has_copyright: |
142 | | - for p in original_lines: |
143 | | - fprint(p) |
144 | | - fprint('') |
145 | | - |
146 | | - section = 'doxygen' if is_header(path) else 'body' |
147 | | - continue |
148 | | - else: |
149 | | - fprint(copyright_str.strip()) |
150 | | - fprint('') |
151 | | - |
152 | | - lines = original_lines + lines |
153 | | - section = 'doxygen' if is_header(path) else 'body' |
154 | | - |
155 | | - continue |
156 | | - |
157 | | - if section == 'doxygen': |
158 | | - if re.match(r'^\s*/?\*', line) or re.match(r'^\s*//', line): |
159 | | - continue |
160 | | - |
161 | | - if re.match(r'^\s*//!', line): |
162 | | - if not is_header(path) or is_test(path) or is_autogen: |
163 | | - continue |
164 | | - |
165 | | - if re.match(r'^\s*//!\s*@file', line): |
166 | | - fprint('//! {}'.format(make_doxygen_path(path))) |
167 | | - else: |
168 | | - fprint(line) |
169 | | - |
170 | | - has_doxygen = True |
171 | | - continue |
172 | | - else: |
173 | | - if is_public_api(path) or is_test(path) or is_autogen: |
174 | | - section = 'guard' if is_header(path) else 'body' |
175 | | - else: |
176 | | - if not has_doxygen: |
177 | | - if is_header(path): |
178 | | - fprint('//! {}'.format(make_doxygen_path(path))) |
179 | | - fprint('//! {}'.format(make_doxygen_brief(brief))) |
180 | | - section = 'guard' |
181 | | - else: |
182 | | - section = 'body' |
183 | | - |
184 | | - if is_header(path): |
185 | | - fprint('') |
186 | | - |
187 | | - if section == 'guard': |
188 | | - m = re.match(r'#\s*(ifndef|define)', line) |
189 | | - if m: |
190 | | - has_guard = True |
191 | | - fprint('#{} {}'.format(m.group(1), make_guard(path))) |
192 | | - continue |
193 | | - else: |
194 | | - if not has_guard: |
195 | | - fprint('#ifndef {}'.format(make_guard(path))) |
196 | | - fprint('#define {}'.format(make_guard(path))) |
197 | | - fprint('') |
198 | | - section = 'body' |
199 | | - |
200 | | - if section == 'body': |
201 | | - if body_start and re.match(r'^\s*$', line): |
202 | | - continue |
203 | | - body_start = False |
204 | | - fprint(line) |
205 | | - |
206 | | - if is_header(path): |
207 | | - fprint('') |
208 | | - fprint('#endif // {}'.format(make_guard(path))) |
209 | | - |
210 | | -def walk_dir(directory, patterns): |
211 | | - for root, dirs, files in os.walk(directory): |
212 | | - for basename in files: |
213 | | - for pattern in patterns: |
214 | | - if fnmatch.fnmatch(basename, pattern): |
215 | | - filename = os.path.join(root, basename) |
216 | | - yield filename |
217 | | - break |
218 | | - |
219 | | -if len(sys.argv) > 1: |
220 | | - os.chdir(sys.argv[1]) |
221 | | - |
222 | | -for path in walk_dir('.', ['*.h', '*.cpp']): |
223 | | - with tempfile.NamedTemporaryFile('w+') as fp: |
224 | | - format_file(fp, path) |
225 | | - fp.flush() |
226 | | - if not filecmp.cmp(path, fp.name): |
227 | | - shutil.copy(fp.name, path) |
| 77 | + result += PRAGMA_HEADER + '\n' |
| 78 | + |
| 79 | + if m.group('body'): |
| 80 | + result += m.group('body') |
| 81 | + |
| 82 | + return result |
| 83 | + |
| 84 | +def process_cpp(content): |
| 85 | + m = CPP_REGEXP.match(content) |
| 86 | + |
| 87 | + if m.group('copyright'): |
| 88 | + return None |
| 89 | + |
| 90 | + result = '' |
| 91 | + |
| 92 | + if m.group('copyright'): |
| 93 | + result += m.group('copyright') + '\n' |
| 94 | + else: |
| 95 | + result += COPYRIGHT_HEADER.format(year=datetime.now().year) + '\n' |
| 96 | + |
| 97 | + if m.group('body'): |
| 98 | + result += m.group('body') |
| 99 | + |
| 100 | + return result |
| 101 | + |
| 102 | +def process_file(file_path): |
| 103 | + file_path = Path(file_path).absolute().relative_to(Path.cwd()).as_posix() |
| 104 | + |
| 105 | + if file_path.startswith('src/internal_modules/'): |
| 106 | + doxygen_path = file_path.replace('src/internal_modules/', '') |
| 107 | + else: |
| 108 | + doxygen_path = None |
| 109 | + |
| 110 | + with open(file_path, 'r') as fp: |
| 111 | + content = fp.read() |
| 112 | + |
| 113 | + if file_path.endswith('.h'): |
| 114 | + new_content = process_h(content, file_path, doxygen_path) |
| 115 | + else: |
| 116 | + new_content = process_cpp(content) |
| 117 | + |
| 118 | + if new_content and new_content != content: |
| 119 | + with open(file_path, 'w') as fp: |
| 120 | + fp.write(new_content) |
| 121 | + |
| 122 | +for file_path in sys.argv[1:]: |
| 123 | + process_file(file_path) |
0 commit comments