From 9cec0e0af52ad245fe79452cf23e12705523544b Mon Sep 17 00:00:00 2001 From: Tyler Veness Date: Thu, 3 Aug 2023 08:57:14 -0700 Subject: [PATCH 1/3] Rename .styleguide configs to .wpiformat to match tool name --- .github/workflows/ci.yml | 8 ++++---- .styleguide => .wpiformat | 0 .styleguide-license => .wpiformat-license | 0 wpiformat/README.rst | 16 ++++++++-------- wpiformat/examples/{.styleguide => .wpiformat} | 0 .../{.styleguide-license => .wpiformat-license} | 0 wpiformat/wpiformat/__init__.py | 8 ++++---- wpiformat/wpiformat/licenseupdate.py | 2 +- wpiformat/wpiformat/test/test_config.py | 2 +- wpiformat/wpiformat/test/test_licenseupdate.py | 10 +++++----- wpiformat/wpiformat/test/test_tasktest.py | 2 +- 11 files changed, 24 insertions(+), 24 deletions(-) rename .styleguide => .wpiformat (100%) rename .styleguide-license => .wpiformat-license (100%) rename wpiformat/examples/{.styleguide => .wpiformat} (100%) rename wpiformat/examples/{.styleguide-license => .wpiformat-license} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db7557e7..f041c9aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,8 +90,8 @@ jobs: rm -rf branch-test mkdir branch-test && cd branch-test && git init git checkout -b master - touch .styleguide - git add .styleguide && git commit -q -m "Initial commit" + touch .wpiformat + git add .wpiformat && git commit -q -m "Initial commit" wpiformat # Verify wpiformat reports success if "main" exists @@ -101,8 +101,8 @@ jobs: rm -rf branch-test mkdir branch-test && cd branch-test && git init git checkout -b main - touch .styleguide - git add .styleguide && git commit -q -m "Initial commit" + touch .wpiformat + git add .wpiformat && git commit -q -m "Initial commit" wpiformat - name: Delete branch-test folder diff --git a/.styleguide b/.wpiformat similarity index 100% rename from .styleguide rename to .wpiformat diff --git a/.styleguide-license b/.wpiformat-license similarity index 100% rename from .styleguide-license rename to .wpiformat-license diff --git a/wpiformat/README.rst b/wpiformat/README.rst index 0fa784a1..7ea62d80 100644 --- a/wpiformat/README.rst +++ b/wpiformat/README.rst @@ -22,14 +22,14 @@ On Linux/OSX, execute:: Project Setup ************* -To use these tools with a new project, copy `.styleguide`_, and `.styleguide-license`_ from the examples folder into the project and create a new ``.clang-format`` file based on the desired C/C++ style. +To use these tools with a new project, copy `.wpiformat`_, and `.wpiformat-license`_ from the examples folder into the project and create a new ``.clang-format`` file based on the desired C/C++ style. Note: Since wpiformat already handles include ordering, it is recommended to use ``SortIncludes: false`` in ``.clang-format``. -.styleguide ------------ +.wpiformat +---------- -wpiformat checks the current directory for the ``.styleguide`` file. If one doesn't exist, all parent directories are tried as well. See the ``.styleguide`` file in the docs/examples directory for all possible groups. +wpiformat checks the current directory for the ``.wpiformat`` file. If one doesn't exist, all parent directories are tried as well. See the ``.wpiformat`` file in the docs/examples directory for all possible groups. This file contains groups of file name regular expressions. There are two groups of regexes which prevent tasks (i.e., formatters and linters) from running on matching files: @@ -54,12 +54,12 @@ The regex for C system headers produces false positives on headers from "other l ``NOLINT`` can be appended in a comment to a header include to prevent wpiformat's header include sorter from modifying it and to maintain its relative ordering with other header includes. This will, in effect, treat it as a barrier across which no header includes will be moved. Header includes on each side of the barrier will still be sorted as normal. -.styleguide-license -------------------- +.wpiformat-license +------------------ -This file contains the license header template. It should contain ``Copyright (c)`` followed by the company name and the string ``{year}``. See the ``.styleguide-license`` file in the docs/examples directory. +This file contains the license header template. It should contain ``Copyright (c)`` followed by the company name and the string ``{year}``. See the ``.wpiformat-license`` file in the docs/examples directory. -wpiformat checks the currently processed file's directory for a ``.styleguide`` file first and traverses up the directory tree if one isn't found. This allows templates which are closer to the processed file to override a project's main template. +wpiformat checks the currently processed file's directory for a ``.wpiformat`` file first and traverses up the directory tree if one isn't found. This allows templates which are closer to the processed file to override a project's main template. The license header is always at the beginning of the file and ends after two newlines. If there isn't one, or it doesn't contain the required copyright contents, wpiformat inserts a new one containing the current year. diff --git a/wpiformat/examples/.styleguide b/wpiformat/examples/.wpiformat similarity index 100% rename from wpiformat/examples/.styleguide rename to wpiformat/examples/.wpiformat diff --git a/wpiformat/examples/.styleguide-license b/wpiformat/examples/.wpiformat-license similarity index 100% rename from wpiformat/examples/.styleguide-license rename to wpiformat/examples/.wpiformat-license diff --git a/wpiformat/wpiformat/__init__.py b/wpiformat/wpiformat/__init__.py index e2e00096..bea4d96d 100644 --- a/wpiformat/wpiformat/__init__.py +++ b/wpiformat/wpiformat/__init__.py @@ -87,7 +87,7 @@ def proc_pipeline(name): Keyword arguments: name -- file name string """ - config_file = Config(os.path.dirname(name), ".styleguide") + config_file = Config(os.path.dirname(name), ".wpiformat") if verbose1 or verbose2: with print_lock: print("Processing", name) @@ -129,7 +129,7 @@ def proc_standalone(name): Keyword arguments: name -- file name string """ - config_file = Config(os.path.dirname(name), ".styleguide") + config_file = Config(os.path.dirname(name), ".wpiformat") if verbose2: with print_lock: print("Processing", name) @@ -179,7 +179,7 @@ def proc_batch(files): for subtask in task_pipeline: work = [] for name in files: - config_file = Config(os.path.dirname(name), ".styleguide") + config_file = Config(os.path.dirname(name), ".wpiformat") if subtask.should_process_file(config_file, name): work.append(name) @@ -480,7 +480,7 @@ def main(): # Don't run tasks on modifiable or generated files work = [] for name in files: - config_file = Config(os.path.dirname(name), ".styleguide") + config_file = Config(os.path.dirname(name), ".wpiformat") if config_file.is_modifiable_file(name): continue diff --git a/wpiformat/wpiformat/licenseupdate.py b/wpiformat/wpiformat/licenseupdate.py index 2175bddc..90a9a3c6 100644 --- a/wpiformat/wpiformat/licenseupdate.py +++ b/wpiformat/wpiformat/licenseupdate.py @@ -136,7 +136,7 @@ def run_pipeline(self, config_file, name, lines): linesep = super().get_linesep(lines) _, license_template = Config.read_file( - os.path.dirname(os.path.abspath(name)), ".styleguide-license" + os.path.dirname(os.path.abspath(name)), ".wpiformat-license" ) # Get year when file was most recently modified in Git history diff --git a/wpiformat/wpiformat/test/test_config.py b/wpiformat/wpiformat/test/test_config.py index 922c9613..e4f05e35 100644 --- a/wpiformat/wpiformat/test/test_config.py +++ b/wpiformat/wpiformat/test/test_config.py @@ -4,7 +4,7 @@ def test_config(): - config_file = Config(os.path.abspath(os.getcwd()), ".styleguide") + config_file = Config(os.path.abspath(os.getcwd()), ".wpiformat") assert config_file.is_modifiable_file( "." + os.sep + "wpiformat" + os.sep + "javaguidelink.png" ) diff --git a/wpiformat/wpiformat/test/test_licenseupdate.py b/wpiformat/wpiformat/test/test_licenseupdate.py index d5348c8e..1e8debdb 100644 --- a/wpiformat/wpiformat/test/test_licenseupdate.py +++ b/wpiformat/wpiformat/test/test_licenseupdate.py @@ -307,7 +307,7 @@ def test_licenseupdate(): ) # Ensure excluded files won't be processed - config_file = Config(os.path.abspath(os.getcwd()), ".styleguide") + config_file = Config(os.path.abspath(os.getcwd()), ".wpiformat") assert not task.should_process_file(config_file, "./Excluded.h") # Create git repo to test license years for commits @@ -315,12 +315,12 @@ def test_licenseupdate(): subprocess.run(["git", "init", "-q"]) # Add base files - with open(".styleguide-license", "w") as file: + with open(".wpiformat-license", "w") as file: file.write("// Copyright (c) {year}") - with open(".styleguide", "w") as file: + with open(".wpiformat", "w") as file: file.write("cppSrcFileInclude {\n" + r"\.cpp$") - subprocess.run(["git", "add", ".styleguide-license"]) - subprocess.run(["git", "add", ".styleguide"]) + subprocess.run(["git", "add", ".wpiformat-license"]) + subprocess.run(["git", "add", ".wpiformat"]) subprocess.run(["git", "commit", "-q", "-m", '"Initial commit"']) # Add file with commit date of last year and range through this year diff --git a/wpiformat/wpiformat/test/test_tasktest.py b/wpiformat/wpiformat/test/test_tasktest.py index 1d55433d..2863bafa 100644 --- a/wpiformat/wpiformat/test/test_tasktest.py +++ b/wpiformat/wpiformat/test/test_tasktest.py @@ -68,7 +68,7 @@ def run(self, output_type): """ assert len(self.inputs) == len(self.outputs) - config_file = Config(os.path.abspath(os.getcwd()), ".styleguide") + config_file = Config(os.path.abspath(os.getcwd()), ".wpiformat") for i in range(len(self.inputs)): if self.task.should_process_file(config_file, self.inputs[i][0]): From 9e4619462ee1ecbe88f1140fb38b1e2368efb851 Mon Sep 17 00:00:00 2001 From: Tyler Veness Date: Thu, 3 Aug 2023 10:33:50 -0700 Subject: [PATCH 2/3] Look for deprecated configs and warn about them --- wpiformat/wpiformat/__init__.py | 35 ++++++++++++++++++++++++---- wpiformat/wpiformat/config.py | 10 +++----- wpiformat/wpiformat/licenseupdate.py | 15 +++++++++--- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/wpiformat/wpiformat/__init__.py b/wpiformat/wpiformat/__init__.py index bea4d96d..5935a4d0 100644 --- a/wpiformat/wpiformat/__init__.py +++ b/wpiformat/wpiformat/__init__.py @@ -87,7 +87,13 @@ def proc_pipeline(name): Keyword arguments: name -- file name string """ - config_file = Config(os.path.dirname(name), ".wpiformat") + try: + config_file = Config(os.path.dirname(name), ".wpiformat") + except OSError: + # TODO: Remove handling for deprecated .styleguide file + config_file = Config(os.path.dirname(name), ".styleguide") + print("Warning: found deprecated '.styleguide' file. Rename to '.wpiformat'.") + if verbose1 or verbose2: with print_lock: print("Processing", name) @@ -129,7 +135,13 @@ def proc_standalone(name): Keyword arguments: name -- file name string """ - config_file = Config(os.path.dirname(name), ".wpiformat") + try: + config_file = Config(os.path.dirname(name), ".wpiformat") + except OSError: + # TODO: Remove handling for deprecated .styleguide file + config_file = Config(os.path.dirname(name), ".styleguide") + print("Warning: found deprecated '.styleguide' file. Rename to '.wpiformat'.") + if verbose2: with print_lock: print("Processing", name) @@ -179,7 +191,15 @@ def proc_batch(files): for subtask in task_pipeline: work = [] for name in files: - config_file = Config(os.path.dirname(name), ".wpiformat") + try: + config_file = Config(os.path.dirname(name), ".wpiformat") + except OSError: + # TODO: Remove handling for deprecated .styleguide file + config_file = Config(os.path.dirname(name), ".styleguide") + print( + "Warning: found deprecated '.styleguide' file. Rename to '.wpiformat'." + ) + if subtask.should_process_file(config_file, name): work.append(name) @@ -480,7 +500,14 @@ def main(): # Don't run tasks on modifiable or generated files work = [] for name in files: - config_file = Config(os.path.dirname(name), ".wpiformat") + try: + config_file = Config(os.path.dirname(name), ".wpiformat") + except OSError: + # TODO: Remove handling for deprecated .styleguide file + config_file = Config(os.path.dirname(name), ".styleguide") + print( + "Warning: found deprecated '.styleguide' file. Rename to '.wpiformat'." + ) if config_file.is_modifiable_file(name): continue diff --git a/wpiformat/wpiformat/config.py b/wpiformat/wpiformat/config.py index 15643910..ca8d9b56 100644 --- a/wpiformat/wpiformat/config.py +++ b/wpiformat/wpiformat/config.py @@ -44,17 +44,13 @@ def read_file(directory, file_name): os.path.join(directory, file_name), file_contents.read().splitlines(), ) - except OSError: + except OSError as e: # .git files are ignored, which are created within submodules if os.path.isdir(directory + os.sep + ".git"): print( - "Error: config file '" - + file_name - + "' not found in '" - + directory - + "'" + f"Error: config file '{file_name}' not found in '{directory}'" ) - sys.exit(1) + raise e directory = os.path.dirname(directory) def group(self, group_name): diff --git a/wpiformat/wpiformat/licenseupdate.py b/wpiformat/wpiformat/licenseupdate.py index 90a9a3c6..b5502fd0 100644 --- a/wpiformat/wpiformat/licenseupdate.py +++ b/wpiformat/wpiformat/licenseupdate.py @@ -135,9 +135,18 @@ def __try_string_search(self, lines, last_year, license_template): def run_pipeline(self, config_file, name, lines): linesep = super().get_linesep(lines) - _, license_template = Config.read_file( - os.path.dirname(os.path.abspath(name)), ".wpiformat-license" - ) + try: + _, license_template = Config.read_file( + os.path.dirname(os.path.abspath(name)), ".wpiformat-license" + ) + except OSError: + # TODO: Remove handling for deprecated .styleguide-license file + _, license_template = Config.read_file( + os.path.dirname(os.path.abspath(name)), ".styleguide-license" + ) + print( + "Warning: found deprecated '.styleguide-license' file. Rename to '.wpiformat-license'." + ) # Get year when file was most recently modified in Git history # From 3f28c0b58c0e89c64d7d064d845e3863e2f5ca92 Mon Sep 17 00:00:00 2001 From: Tyler Veness Date: Wed, 16 Aug 2023 13:48:03 -0700 Subject: [PATCH 3/3] Cache config file contents --- wpiformat/wpiformat/__init__.py | 3 +++ wpiformat/wpiformat/config.py | 29 ++++++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/wpiformat/wpiformat/__init__.py b/wpiformat/wpiformat/__init__.py index 5935a4d0..91347c0c 100644 --- a/wpiformat/wpiformat/__init__.py +++ b/wpiformat/wpiformat/__init__.py @@ -90,6 +90,9 @@ def proc_pipeline(name): try: config_file = Config(os.path.dirname(name), ".wpiformat") except OSError: + print( + "Warning: '.wpiformat' file not found. Looking for deprecated '.styleguide' file." + ) # TODO: Remove handling for deprecated .styleguide file config_file = Config(os.path.dirname(name), ".styleguide") print("Warning: found deprecated '.styleguide' file. Rename to '.wpiformat'.") diff --git a/wpiformat/wpiformat/config.py b/wpiformat/wpiformat/config.py index ca8d9b56..8260f5b9 100644 --- a/wpiformat/wpiformat/config.py +++ b/wpiformat/wpiformat/config.py @@ -1,12 +1,14 @@ """This class is for handling wpiformat config files.""" import os -import sys import regex class Config: + # Dict from filepath to file contents + config_cache: dict[str, list[str]] = {} + def __init__(self, directory, file_name): """Constructor for Config object. @@ -35,22 +37,23 @@ def read_file(directory, file_name): Returns tuple of file name and list containing file contents or triggers program exit. """ - file_found = False - while not file_found: + while True: + filepath = os.path.join(directory, file_name) try: - with open(directory + os.sep + file_name, "r") as file_contents: - file_found = True - return ( - os.path.join(directory, file_name), - file_contents.read().splitlines(), - ) + # If filepath in config cache, return cached version instead + if filepath in Config.config_cache: + return filepath, Config.config_cache[filepath] + + with open(filepath, "r") as file_contents: + contents = file_contents.read().splitlines() + Config.config_cache[filepath] = contents + return filepath, contents except OSError as e: # .git files are ignored, which are created within submodules if os.path.isdir(directory + os.sep + ".git"): - print( - f"Error: config file '{file_name}' not found in '{directory}'" - ) - raise e + raise OSError( + f"config file '{file_name}' not found in '{directory}'" + ) from e directory = os.path.dirname(directory) def group(self, group_name):