From cf5fa3959cf33425a6495d137c0f793fb21dfcd8 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Fri, 12 Jun 2020 21:42:17 -0400 Subject: [PATCH] Add first version of whitespace utility --- cmake/CMakeLists.txt | 1 + cmake/Modules/CodingStandard.cmake | 21 +++++ tools/coding_standard/whitespace.py | 132 ++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 cmake/Modules/CodingStandard.cmake create mode 100644 tools/coding_standard/whitespace.py diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 2407d03a80..c0563565e0 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -676,6 +676,7 @@ endif() include(Testing) include(CodeCoverage) +include(CodingStandard) ############################################################################### # Print package summary diff --git a/cmake/Modules/CodingStandard.cmake b/cmake/Modules/CodingStandard.cmake new file mode 100644 index 0000000000..d7440fba9d --- /dev/null +++ b/cmake/Modules/CodingStandard.cmake @@ -0,0 +1,21 @@ +if(CMAKE_VERSION VERSION_LESS 3.12) + find_package(PythonInterp) # Deprecated since version 3.12 + if(PYTHONINTERP_FOUND) + set(Python_EXECUTABLE ${PYTHON_EXECUTABLE}) + endif() +else() + find_package(Python COMPONENTS Interpreter) +endif() + +if (Python_EXECUTABLE) + add_custom_target( + check-whitespace + ${Python_EXECUTABLE} ${LAMMPS_TOOLS_DIR}/coding_standard/whitespace.py . + WORKING_DIRECTORY ${LAMMPS_DIR} + COMMENT "Check for whitespace errors") + add_custom_target( + fix-whitespace + ${Python_EXECUTABLE} ${LAMMPS_TOOLS_DIR}/coding_standard/whitespace.py -f . + WORKING_DIRECTORY ${LAMMPS_DIR} + COMMENT "Fix whitespace errors") +endif() diff --git a/tools/coding_standard/whitespace.py b/tools/coding_standard/whitespace.py new file mode 100644 index 0000000000..f9923893a0 --- /dev/null +++ b/tools/coding_standard/whitespace.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# Utility for detecting and fixing whitespace issues in LAMMPS +# +# Written by Richard Berger (Temple University) +import os +import glob +import re +import yaml +import argparse +import shutil + +DEFAULT_CONFIG = """ +recursive: true +include: + - cmake/** + - doc + - doc/src/** + - python + - src/** +patterns: + - ".gitignore" + - "README" + - "requirements.txt" + - "*.c" + - "*.cpp" + - "*.h" + - "*.sh" + - "*.py" + - "*.md" + - "*.rst" + - "*.cmake" +""" + +def check_trailing_whitespace(f): + pattern = re.compile(r'\s+\n$') + last_line = "\n" + lineno = 1 + errors = set() + + for line in f: + if pattern.match(line): + errors.add(lineno) + last_line = line + lineno += 1 + + return errors, last_line + +def check_file(path): + encoding = 'UTF-8' + last_line = "\n" + whitespace_errors = set() + try: + with open(path, 'r') as f: + whitespace_errors, last_line = check_trailing_whitespace(f) + except UnicodeDecodeError: + encoding = 'ISO-8859-1' + try: + with open(path, 'r', encoding=encoding) as f: + whitespace_errors, last_line = check_trailing_whitespace(f) + except Exception: + encoding = 'unknown' + + return { + 'whitespace_errors': whitespace_errors, + 'encoding': encoding, + 'eof_error': not last_line.endswith('\n') + } + +def fix_file(path, check_result): + newfile = path + ".modified" + with open(newfile, 'w', encoding='UTF-8') as out: + with open(path, 'r', encoding=check_result['encoding']) as src: + for line in src: + print(line.rstrip(), file=out) + shutil.move(newfile, path) + +def check_folder(directory, config, fix=False, verbose=False): + files = [] + + for base_path in config['include']: + for pattern in config['patterns']: + path = os.path.join(directory, base_path, pattern) + files += glob.glob(path, recursive=config['recursive']) + + for f in files: + path = os.path.normpath(f) + + if verbose: + print("Checking file:", path) + + result = check_file(path) + + has_resolvable_errors = False + + for lineno in result['whitespace_errors']: + print("[Error] Trailing whitespace @ {}:{}".format(path, lineno)) + has_resolvable_errors = True + + if result['eof_error']: + print("[Error] Missing newline at end of file @ {}".format(path)) + has_resolvable_errors = True + + if result['encoding'] == 'unknown': + print("[Error] Unknown text encoding @ {}".format(path)) + has_resolvable_errors = False + elif result['encoding'] == 'ISO-8859-1': + print("[Error] Found ISO-8859-1 encoding instead of UTF-8 @ {}".format(path)) + has_resolvable_errors = True + + if has_resolvable_errors and fix: + print("Applying automatic fixes to file:", path) + fix_file(path, result) + + +def main(): + parser = argparse.ArgumentParser(description='Utility for detecting and fixing whitespace issues in LAMMPS') + parser.add_argument('-c', '--config', metavar='CONFIG_FILE', help='location of a optional configuration file') + parser.add_argument('-f', '--fix', action='store_true', help='automatically fix common issues') + parser.add_argument('-v', '--verbose', action='store_true', help='verbose output') + parser.add_argument('DIRECTORY', help='directory that should be checked') + args = parser.parse_args() + + if args.config: + with open(args.config, 'r') as cfile: + config = yaml.load(cfile, Loader=yaml.FullLoader) + else: + config = yaml.load(DEFAULT_CONFIG, Loader=yaml.FullLoader) + + check_folder(args.DIRECTORY, config, args.fix, args.verbose) + +if __name__ == "__main__": + main()