2"""A wrapper script around clang-format, suitable for linting multiple files
3and to use for continuous integration.
5This is an alternative API for the clang-format command line.
6It runs over multiple files and directories in parallel.
7A diff output is produced and a sensible exit code is returned.
9Adopted from https://github.com/Sarcasm/run-clang-format/tree/master
12from __future__
import print_function, unicode_literals
27from functools
import partial
30 from subprocess
import DEVNULL
32 DEVNULL = open(os.devnull,
"wb")
35DEFAULT_EXTENSIONS =
'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx'
36DEFAULT_CLANG_FORMAT_IGNORE =
'.clang-format-ignore'
44def excludes_from_file(ignore_file):
47 with io.open(ignore_file,
'r', encoding=
'utf-8')
as f:
49 if line.startswith(
'#'):
52 pattern = line.rstrip()
56 excludes.append(pattern)
57 except EnvironmentError
as e:
58 if e.errno != errno.ENOENT:
62def list_files(files, recursive=False, extensions=None, exclude=None):
63 if extensions
is None:
70 if recursive
and os.path.isdir(file):
71 for dirpath, dnames, fnames
in os.walk(file):
72 fpaths = [os.path.join(dirpath, fname)
for fname
in fnames]
73 for pattern
in exclude:
80 not fnmatch.fnmatch(os.path.join(dirpath, x), pattern)
83 x
for x
in fpaths
if not fnmatch.fnmatch(x, pattern)
86 ext = os.path.splitext(f)[1][1:]
94def make_diff(file, original, reformatted):
99 fromfile=
'{}\t(original)'.format(file),
100 tofile=
'{}\t(reformatted)'.format(file),
104class DiffError(Exception):
106 super(DiffError, self).
__init__(message)
107 self.
errs = errs
or []
112 super(UnexpectedError, self).
__init__(message)
123 except Exception
as e:
130 with io.open(file,
'r', encoding=
'utf-8')
as f:
131 original = f.readlines()
132 except IOError
as exc:
136 invocation = [args.clang_format_executable,
'-i', file]
138 invocation = [args.clang_format_executable, file]
141 invocation.extend([
'--style', args.style])
144 print(
" ".join(invocation))
166 if sys.version_info[0] >= 3:
167 encoding_py3[
'encoding'] =
'utf-8'
170 proc = subprocess.Popen(
172 stdout=subprocess.PIPE,
173 stderr=subprocess.PIPE,
174 universal_newlines=
True,
176 except OSError
as exc:
178 "Command '{}' failed to start: {}".format(
179 subprocess.list2cmdline(invocation), exc
182 proc_stdout = proc.stdout
183 proc_stderr = proc.stderr
184 if sys.version_info[0] < 3:
188 proc_stdout = codecs.getreader(encoding)(proc_stdout)
189 proc_stderr = codecs.getreader(encoding)(proc_stderr)
191 outs = list(proc_stdout.readlines())
192 errs = list(proc_stderr.readlines())
196 "Command '{}' returned non-zero exit status {}".format(
197 subprocess.list2cmdline(invocation), proc.returncode
203 return make_diff(file, original, outs), errs
207 return '\x1b[1m\x1b[31m' + s +
'\x1b[0m'
212 return '\x1b[1m' + s +
'\x1b[0m'
215 return '\x1b[36m' + s +
'\x1b[0m'
218 return '\x1b[32m' + s +
'\x1b[0m'
221 return '\x1b[31m' + s +
'\x1b[0m'
223 for line
in diff_lines:
224 if line[:4]
in [
'--- ',
'+++ ']:
226 elif line.startswith(
'@@ '):
228 elif line.startswith(
'+'):
230 elif line.startswith(
'-'):
239 if sys.version_info[0] < 3:
240 sys.stdout.writelines((l.encode(
'utf-8')
for l
in diff_lines))
242 sys.stdout.writelines(diff_lines)
246 error_text =
'error:'
249 print(
"{}: {} {}".format(prog, error_text, message), file=sys.stderr)
253 parser = argparse.ArgumentParser(description=__doc__)
255 '--clang-format-executable',
256 metavar=
'EXECUTABLE',
257 help=
'path to the clang-format executable',
258 default=
'clang-format')
261 help=
'comma separated list of file extensions (default: {})'.format(
263 default=DEFAULT_EXTENSIONS)
268 help=
'run recursively over directories')
273 help=
'just print the list of files')
278 help=
'format file instead of printing differences')
279 parser.add_argument(
'files', metavar=
'file', nargs=
'+')
284 help=
"disable output, useful for the exit code")
290 help=
'run N clang-format jobs in parallel'
291 ' (default number of cpus + 1)')
295 choices=[
'auto',
'always',
'never'],
296 help=
'show colored diff (default: auto)')
303 help=
'exclude paths matching the given glob-like pattern(s)'
304 ' from recursive search')
307 help=
'formatting style to apply (LLVM, Google, Chromium, Mozilla, WebKit)')
309 args = parser.parse_args()
313 signal.signal(signal.SIGINT, signal.SIG_DFL)
316 except AttributeError:
320 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
322 colored_stdout =
False
323 colored_stderr =
False
324 if args.color ==
'always':
325 colored_stdout =
True
326 colored_stderr =
True
327 elif args.color ==
'auto':
328 colored_stdout = sys.stdout.isatty()
329 colored_stderr = sys.stderr.isatty()
331 version_invocation = [args.clang_format_executable, str(
"--version")]
333 subprocess.check_call(version_invocation, stdout=DEVNULL)
334 except subprocess.CalledProcessError
as e:
335 print_trouble(parser.prog, str(e), use_colors=colored_stderr)
336 return ExitStatus.TROUBLE
340 "Command '{}' failed to start: {}".format(
341 subprocess.list2cmdline(version_invocation), e
343 use_colors=colored_stderr,
345 return ExitStatus.TROUBLE
347 retcode = ExitStatus.SUCCESS
350 excludes.extend(args.exclude)
354 recursive=args.recursive,
356 extensions=args.extensions.split(
','))
363 njobs = multiprocessing.cpu_count() + 1
364 njobs =
min(len(files), njobs)
372 pool = multiprocessing.Pool(njobs)
373 it = pool.imap_unordered(
374 partial(run_clang_format_diff_wrapper, args), files)
378 outs, errs = next(it)
379 except StopIteration:
381 except DiffError
as e:
382 print_trouble(parser.prog, str(e), use_colors=colored_stderr)
383 retcode = ExitStatus.TROUBLE
384 sys.stderr.writelines(e.errs)
385 except UnexpectedError
as e:
386 print_trouble(parser.prog, str(e), use_colors=colored_stderr)
387 sys.stderr.write(e.formatted_traceback)
388 retcode = ExitStatus.TROUBLE
396 sys.stderr.writelines(errs)
401 if retcode == ExitStatus.SUCCESS:
402 retcode = ExitStatus.DIFF
408if __name__ ==
'__main__':
friend F32vec4 min(const F32vec4 &a, const F32vec4 &b)