master
py 56 lines 2 KB
Raw
1 import argparse
2 import os.path
3 import subprocess
4
5 EXTENSIONS = ['.c', '.cpp', '.h', '.hpp', '.idl', '.resw', '.cmake']
6 FILENAMES = ['cmakelists.txt']
7
8 def is_source_file(path: str) -> bool:
9 folded = path.casefold()
10 return os.path.basename(folded) in FILENAMES or any(folded.endswith(e) for e in EXTENSIONS)
11
12 def has_crlf_mismatch(content: bytes) -> bool:
13 # Strip all CRLF pairs, then any remaining lone '\n' or '\r' is a mismatch.
14 stripped = content.replace(b'\r\n', b'')
15 return b'\n' in stripped or b'\r' in stripped
16
17 def to_crlf(content: bytes) -> bytes:
18 # Normalize every line ending (CRLF, lone CR, lone LF) to a single CRLF.
19 return content.replace(b'\r\n', b'\n').replace(b'\r', b'\n').replace(b'\n', b'\r\n')
20
21 def main(path: str, fix: bool):
22 tracked = subprocess.run(
23 ['git', '-C', path, 'ls-files', '-z'], check=True, stdout=subprocess.PIPE).stdout.decode('utf-8').split('\0')
24 source_files = [os.path.join(path, e) for e in tracked if e and is_source_file(e)]
25
26 mismatches = []
27 for e in source_files:
28 with open(e, 'rb') as fd:
29 content = fd.read()
30
31 if not has_crlf_mismatch(content):
32 continue
33
34 mismatches.append(e)
35 if fix:
36 with open(e, 'wb') as fd:
37 fd.write(to_crlf(content))
38
39 if not mismatches:
40 print(f'All {len(source_files)} files use CRLF line endings')
41 return
42
43 listed = '\n'.join(mismatches)
44 if fix:
45 print(f'Converted {len(mismatches)} files to CRLF:\n{listed}')
46 else:
47 print(f'{len(mismatches)} files have non-CRLF line endings:\n{listed}')
48 raise SystemExit(1)
49
50 if __name__ == '__main__':
51 parser = argparse.ArgumentParser(description='Validate that source files use CRLF line endings.')
52 parser.add_argument('path', help='Path to validate (must be inside the repo).')
53 parser.add_argument('--fix', action='store_true', help='Convert mismatching files to CRLF line endings.')
54 args = parser.parse_args()
55
56 main(args.path, args.fix)