From 493ff3017c403f30678527ee5e2b5b1172786118 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 23 Jan 2025 10:26:56 -0500 Subject: [PATCH] add tool for checking whether fmt::print() statements have crept in --- src/Makefile | 8 +- tools/coding_standard/fmtlib.py | 167 ++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 tools/coding_standard/fmtlib.py diff --git a/src/Makefile b/src/Makefile index 4d8b02458a..3de8eb85d5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -480,7 +480,7 @@ tar: @cd STUBS; $(MAKE) @echo "Created $(ROOT)_src.tar.gz" -check: check-whitespace check-permissions check-homepage check-errordocs check-docs check-version +check: check-whitespace check-permissions check-homepage check-errordocs check-fmtlib check-docs check-version check-whitespace: $(PYTHON) ../tools/coding_standard/whitespace.py .. @@ -506,6 +506,12 @@ check-errordocs: fix-errordocs: $(PYTHON) ../tools/coding_standard/errordocs.py .. -f +check-fmtlib: + $(PYTHON) ../tools/coding_standard/fmtlib.py .. + +fix-fmtlib: + $(PYTHON) ../tools/coding_standard/fmtlib.py .. -f + check-docs: $(MAKE) $(MFLAGS) -C ../doc anchor_check style_check package_check role_check diff --git a/tools/coding_standard/fmtlib.py b/tools/coding_standard/fmtlib.py new file mode 100644 index 0000000000..8128a3c747 --- /dev/null +++ b/tools/coding_standard/fmtlib.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# Utility for detecting fmtlib related issues +# +# Currently it checks for the following issues +# use of fmt::print() instead of utils::print() +# +# Written by Axel Kohlmeyer (Temple University) +from __future__ import print_function +import sys + +if sys.version_info.major < 3: + sys.exit('This script must be run with Python 3.5 or later') + +if sys.version_info.minor < 5: + sys.exit('This script must be run with Python 3.5 or later') + +import os +import glob +import re +import yaml +import argparse +import shutil + +DEFAULT_CONFIG = """ +recursive: true +include: + - src/** +exclude: + - "src/fmt/" + - "src/fmtlib" + - "src/utils" +patterns: + - "*.h" + - "*.cpp" +""" + +def check_fmtprint(f): + pattern = re.compile(r'[ \t\n\r]*fmt::print\(') + lineno = 1 + errors = set() + + for line in f: + if pattern.match(line): + errors.add(lineno) + lineno += 1 + + return errors + +def check_file(path): + if path.find('fmtlib.py') >= 0: return { 'fmtlib_errors' : '' } + encoding = 'UTF-8' + fmtprint_errors = set() + try: + with open(path, 'r') as f: + fmtprint_errors = check_fmtprint(f) + except UnicodeDecodeError: + encoding = 'ISO-8859-1' + try: + with open(path, 'r', encoding=encoding) as f: + fmtprint_errors = check_fmtprint(f) + except Exception: + encoding = 'unknown' + + return { + 'fmtprint_errors': fmtprint_errors, + 'encoding': encoding + } + +def fix_file(path, check_result): + if path.find('fmtlib.py') >= 0: return + newfile = path + ".modified" + pattern = re.compile(r'fmt::print\(', re.DOTALL) + with open(newfile, 'w', encoding='UTF-8') as out: + with open(path, 'r', encoding=check_result['encoding']) as src: + filetxt = re.sub(pattern,'utils::print(', src.read()); + print(filetxt, end='', file=out) + shutil.copymode(path, newfile) + shutil.move(newfile, path) + +def check_folder(directory, config, fix=False, verbose=False): + success = True + files = [] + + # compile list of files to check + 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']) + + # prune list of files to skip from list + for pattern in config['exclude']: + path = os.path.join(directory, pattern) + remove = [] + for file in files: + if path not in file: continue + remove += [file] + for rm in remove: + files.remove(rm) + + 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['fmtprint_errors']: + print("[Error] Found LAMMPS fmt::print @ {}:{}".format(path, lineno)) + has_resolvable_errors = True + + if has_resolvable_errors: + if fix: + print("Applying automatic fixes to file:", path) + fix_file(path, result) + else: + success = False + + return success + +def main(): + parser = argparse.ArgumentParser(description='Utility for detecting and fixing fmtlib 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 URLs') + parser.add_argument('-v', '--verbose', action='store_true', help='verbose output') + parser.add_argument('DIRECTORY', help='directory (or file) that should be checked') + args = parser.parse_args() + lammpsdir = os.path.abspath(os.path.expanduser(args.DIRECTORY)) + + 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) + + if os.path.isdir(lammpsdir): + if not check_folder(lammpsdir, config, args.fix, args.verbose): + sys.exit(1) + else: + success = True + path = os.path.normpath(lammpsdir) + + if args.verbose: + print("Checking file:", path) + + result = check_file(path) + + has_resolvable_errors = False + + for lineno in result['fmtprint_errors']: + print("[Error] Found LAMMPS fmt::print @ {}:{}".format(path, lineno)) + has_resolvable_errors = True + + if has_resolvable_errors: + if args.fix: + print("Applying automatic fixes to file:", path) + fix_file(path, result) + else: + success = False + + if not success: + sys.exit(1) + +if __name__ == "__main__": + main()