[ci] Add a script for checking whitespace endings of files
Signed-off-by: Miguel Young de la Sota <mcyoung@google.com>
diff --git a/util/fix_trailing_whitespace.py b/util/fix_trailing_whitespace.py
new file mode 100755
index 0000000..34f0112
--- /dev/null
+++ b/util/fix_trailing_whitespace.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# fix_trailing_whitespace.py script ensures that all files passed into it satisfy
+# various requirements in terms of whitespace:
+# - There is no leading whitespace in the file.
+# - Lines do not have trailing non-newline whitespace and have UNIX-style line endings.
+# - The file ends in a single newline.
+
+import argparse
+import sys
+import re
+import subprocess
+
+from pathlib import Path
+
+# This file is $REPO_TOP/util/fix_trailing_newlines.py, so it takes two parent()
+# calls to get back to the top.
+REPO_TOP = Path(__file__).resolve().parent.parent
+
+def is_ignored(path):
+ return subprocess.run(['git', 'check-ignore', path]).returncode == 0
+
+def walk_tree(paths=[REPO_TOP]):
+ for path in paths:
+ if isinstance(path, str):
+ path = Path(path)
+
+ if path.is_symlink() or is_ignored(path) or 'LICENSE' in path.parts:
+ continue
+
+ if path.is_dir() and 'vendor' not in path.parts:
+ yield from walk_tree(path.iterdir())
+ else:
+ yield path
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--dry-run',
+ action='store_true',
+ help='report writes which would have happened')
+ parser.add_argument(
+ '--recursive', '-r',
+ action='store_true',
+ default=False,
+ help='traverse the entire tree modolo .gitignore'
+ )
+ parser.add_argument(
+ '--verbose', '-v',
+ action='store_true',
+ help='verbose output')
+ parser.add_argument(
+ 'files',
+ type=str,
+ nargs='*',
+ help='files to fix whitespace for')
+ args = parser.parse_args()
+
+ files = args.files
+ if args.recursive:
+ files = walk_tree(args.files)
+
+ total_fixable = 0
+ for path in files:
+ path = Path(path).resolve().relative_to(REPO_TOP)
+ if not path.is_file() or path.is_symlink():
+ continue
+ if 'vendor' in path.parts or path.suffix in ['.patch', '.svg']:
+ continue
+ if args.verbose:
+ print(f'Checking: "{path}"')
+
+ try:
+ old_text = path.read_text()
+ except UnicodeDecodeError:
+ print(f'Binary file: "{path}"')
+ continue
+ new_text = "\n".join([line.rstrip() for line in old_text.strip().split("\n")]) + "\n"
+
+ if old_text != new_text:
+ print(f'Fixing file: "{path}"', file=sys.stdout)
+ total_fixable += 1
+ if not args.dry_run:
+ path.write_text(new_text)
+
+ if total_fixable:
+ verb = 'Would have fixed' if args.dry_run else 'Fixed'
+ print(f'{verb} {total_fixable} files.', file=sys.stderr)
+
+ # Pass if we fixed everything or there was nothing to fix.
+ return 1 if total_fixable > 0 else 0
+
+if __name__ == '__main__':
+ sys.exit(main())