From 6ce4d76b42cda9bf4342a33cab9262290818a6d0 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Fri, 28 Jul 2023 09:31:59 -0400 Subject: [PATCH 01/21] fixed implementation of add_license_headers for reuse 2.0 --- pyproject.toml | 9 +- .../pre_commit_hooks/add_license_headers.py | 176 +++++++++--------- 2 files changed, 98 insertions(+), 87 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 76ad245a..91bc224a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,19 +25,20 @@ classifiers = [ ] dependencies = [ "importlib-metadata >=4.0", - "reuse <2", + "reuse", + "GitPython==3.1.32", ] [project.optional-dependencies] tests = [ "pytest==7.3.0", "pytest-cov==4.0.0", - "reuse <2", + "reuse", ] doc = [ - "ansys-sphinx-theme==0.10.0", + "ansys-sphinx-theme==0.9.7", "numpydoc==1.5.0", - "sphinx==7.1.1", + "sphinx==5.3.0", "sphinx-copybutton==0.5.1", ] diff --git a/src/ansys/pre_commit_hooks/add_license_headers.py b/src/ansys/pre_commit_hooks/add_license_headers.py index a543b814..9e5e0100 100644 --- a/src/ansys/pre_commit_hooks/add_license_headers.py +++ b/src/ansys/pre_commit_hooks/add_license_headers.py @@ -1,5 +1,4 @@ #!/usr/bin/env python - # Copyright (C) 2023 ANSYS, Inc. and/or its affiliates. # SPDX-License-Identifier: MIT # @@ -21,102 +20,113 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - """Run reuse for files missing license headers.""" import argparse import datetime import os -import subprocess import sys +import json +import git from tempfile import NamedTemporaryFile +from reuse import lint, header, project -from reuse import lint -from reuse.project import Project -from reuse.report import ProjectReport - - -def get_args(args): - """Retrieve value of --loc argument.""" - parser = argparse.ArgumentParser(description="Get repository location.") +def find_files_missing_header(): + """ + Retrieve files without license header. + + Returns: + 0: No files exist that are missing license header. + 1: Files exist that are missing license header. + """ + # Set up argparse for location, parser, and lint + # Lint contains 4 args: quiet, json, plain, and no_multiprocessing + parser = argparse.ArgumentParser() parser.add_argument( "--loc", type=str, required=True, help="Path to repository location", default="src" ) - return parser.parse_args(args).loc - - -def get_files(loc): - """ - Generate report containing files without the license header. - - Parameters - ---------- - loc : str - Path to repository location. - """ - project = Project(loc) - report = ProjectReport.generate(project) - - with NamedTemporaryFile(mode="w") as tmp: - output = lint.lint_files_without_copyright_and_licensing(report, tmp) - - return output - - -def get_files_missing_header(loc): - """Retrieve files without license header.""" - try: - # Run reuse lint command to find files without license header - return get_files(loc) - except NotADirectoryError as e: # When loc is an invalid path - return str(e) + " for a repository" - - -def run_reuse_cmd(file): - """Run the reuse command for files missing the license header.""" + parser.add_argument( + "--parser", + ) + parser.add_argument("--no_multiprocessing", action="store_true") + lint.add_arguments(parser) + + args = parser.parse_args([sys.argv[1]]) + proj = project.Project(fr'{args.loc}') + + # Create a temporary file containing lint.run json output + with NamedTemporaryFile(mode="w", delete=False) as tmp: + args.json = True + lint.run(args, proj, tmp) + # Open the temporary file, load the json, and find files missing copyright and/or licensing info. + file = open(tmp.name , "rb" ) + lint_json = json.load(file) + file.close() + missing_headers = list(set(lint_json['non_compliant']['missing_copyright_info'] + lint_json['non_compliant']['missing_licensing_info'])) + + # Remove temporary file + os.remove(tmp.name) + + # Get current year for license file year = datetime.date.today().year - cmd = [ - "reuse", - "annotate", - "--year", - rf"{year}", - "--copyright-style", - "string-c", - "--merge-copyright", - "--template=ansys", - "-l", - "MIT", - "-c", - "ANSYS, Inc. and/or its affiliates.", - "--skip-unrecognised", - rf"{file}", - ] - subprocess.check_call(cmd, stdout=subprocess.PIPE) - - -def run_reuse_on_files(loc): - """Run the reuse annotate command on all files without license header.""" - missing_header = get_files_missing_header(loc) - if missing_header: - # Run reuse command for files without license header - for file in missing_header: - print(f"License header added to {file}") - if os.path.isfile(file): - run_reuse_cmd(file) + + # If there are files missing headers, run reuse and return 1 + if missing_headers != []: + run_reuse(parser, year, args.loc, missing_headers) return 1 - - # Return zero if all files have license header return 0 - + + +def run_reuse(parser, year, loc, missing_headers): + """ + Run reuse command on files without license headers. + Args: + parser (argparse.ArgumentParser): Parser containing previously set arguments. + year (int): Current year for license header. + loc (str): Location to search for files missing headers. + missing_headers (list): List of files that are missing headers. + Returns: + 1: Fails pre-commit hook on return 1 + """ + + # Add header arguments to parser which include: copyright, license, contributor, year, + # style, copyright-style, template, exclude-year, merge-copyrights, single-line, + # multi-line, explicit-license, force-dot-license, recursive, no-replace, + # skip-unrecognized, skip-existing + header.add_arguments(parser) + + # Get root directory of current git repository + git_repo = git.Repo(os.getcwd(), search_parent_directories=True) + git_root = git_repo.git.rev_parse("--show-toplevel") + + # If .reuse folder does not exist in git repository root, throw error + if not os.path.isdir(os.path.join(git_root,".reuse")): + print(f"Please ensure the .reuse directory is in {git_root}") + return 1 + + # Add missing license header to each file in the list + for file in missing_headers: + args = parser.parse_args([fr'--loc={loc}', file]) + args.year = [ str(year) ] + args.copyright_style = 'string-c' + args.copyright = ['ANSYS, Inc. and/or its affiliates.'] + args.merge_copyrights = True + args.template = 'ansys' + args.license = ['MIT'] + args.skip_unrecognised = True + args.parser = parser + + # Requires .reuse directory to be in git_root directory + proj = project.Project(git_root) + header.run(args, proj) + + return 1 def main(): - """Run reuse on files without license headers.""" - try: - # Get repository location argument - loc = get_args([sys.argv[1]]) - return run_reuse_on_files(loc) - except IndexError as e: - print("Please provide the --loc argument. IndexError: " + str(e)) - + """Find files missing license header & run reuse on them.""" + status = find_files_missing_header() + if status == 1: + return 1 + return 0 if __name__ == "__main__": - raise SystemExit(main()) # pragma: no cover + raise SystemExit(main()) # pragma: no cover \ No newline at end of file From d736190abce39e53e04ac8595a85f67ecc2befb7 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Fri, 28 Jul 2023 09:57:11 -0400 Subject: [PATCH 02/21] style fix --- .../pre_commit_hooks/add_license_headers.py | 83 ++++++++++--------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/src/ansys/pre_commit_hooks/add_license_headers.py b/src/ansys/pre_commit_hooks/add_license_headers.py index 9e5e0100..f1c7623f 100644 --- a/src/ansys/pre_commit_hooks/add_license_headers.py +++ b/src/ansys/pre_commit_hooks/add_license_headers.py @@ -23,17 +23,19 @@ """Run reuse for files missing license headers.""" import argparse import datetime +import json import os import sys -import json -import git from tempfile import NamedTemporaryFile -from reuse import lint, header, project + +import git +from reuse import header, lint, project + def find_files_missing_header(): """ Retrieve files without license header. - + Returns: 0: No files exist that are missing license header. 1: Files exist that are missing license header. @@ -45,82 +47,88 @@ def find_files_missing_header(): "--loc", type=str, required=True, help="Path to repository location", default="src" ) parser.add_argument( - "--parser", + "--parser", ) parser.add_argument("--no_multiprocessing", action="store_true") lint.add_arguments(parser) - + args = parser.parse_args([sys.argv[1]]) - proj = project.Project(fr'{args.loc}') - + proj = project.Project(rf"{args.loc}") + # Create a temporary file containing lint.run json output with NamedTemporaryFile(mode="w", delete=False) as tmp: args.json = True lint.run(args, proj, tmp) - # Open the temporary file, load the json, and find files missing copyright and/or licensing info. - file = open(tmp.name , "rb" ) + # Open the temporary file, load the json, and find files missing copyright & licensing info. + file = open(tmp.name, "rb") lint_json = json.load(file) file.close() - missing_headers = list(set(lint_json['non_compliant']['missing_copyright_info'] + lint_json['non_compliant']['missing_licensing_info'])) - + missing_headers = list( + set( + lint_json["non_compliant"]["missing_copyright_info"] + + lint_json["non_compliant"]["missing_licensing_info"] + ) + ) + # Remove temporary file os.remove(tmp.name) - + # Get current year for license file year = datetime.date.today().year - + # If there are files missing headers, run reuse and return 1 if missing_headers != []: run_reuse(parser, year, args.loc, missing_headers) return 1 return 0 - - + + def run_reuse(parser, year, loc, missing_headers): """ Run reuse command on files without license headers. + Args: - parser (argparse.ArgumentParser): Parser containing previously set arguments. + parser (argparse.ArgumentParser): Parser containing previously set arguments. year (int): Current year for license header. loc (str): Location to search for files missing headers. missing_headers (list): List of files that are missing headers. Returns: 1: Fails pre-commit hook on return 1 """ - - # Add header arguments to parser which include: copyright, license, contributor, year, - # style, copyright-style, template, exclude-year, merge-copyrights, single-line, - # multi-line, explicit-license, force-dot-license, recursive, no-replace, + # Add header arguments to parser which include: copyright, license, contributor, year, + # style, copyright-style, template, exclude-year, merge-copyrights, single-line, + # multi-line, explicit-license, force-dot-license, recursive, no-replace, # skip-unrecognized, skip-existing - header.add_arguments(parser) - + header.add_arguments(parser) + # Get root directory of current git repository git_repo = git.Repo(os.getcwd(), search_parent_directories=True) git_root = git_repo.git.rev_parse("--show-toplevel") - + # If .reuse folder does not exist in git repository root, throw error - if not os.path.isdir(os.path.join(git_root,".reuse")): - print(f"Please ensure the .reuse directory is in {git_root}") + if not os.path.isdir(os.path.join(git_root, ".reuse")): + print(f"Please ensure the .reuse directory is in {git_root}") return 1 - + # Add missing license header to each file in the list for file in missing_headers: - args = parser.parse_args([fr'--loc={loc}', file]) - args.year = [ str(year) ] - args.copyright_style = 'string-c' - args.copyright = ['ANSYS, Inc. and/or its affiliates.'] - args.merge_copyrights = True - args.template = 'ansys' - args.license = ['MIT'] + args = parser.parse_args([rf"--loc={loc}", file]) + args.year = [str(year)] + args.copyright_style = "string-c" + args.copyright = ["ANSYS, Inc. and/or its affiliates."] + args.merge_copyrights = True + args.template = "ansys" + args.license = ["MIT"] args.skip_unrecognised = True args.parser = parser - + # Requires .reuse directory to be in git_root directory proj = project.Project(git_root) header.run(args, proj) - + return 1 + def main(): """Find files missing license header & run reuse on them.""" status = find_files_missing_header() @@ -128,5 +136,6 @@ def main(): return 1 return 0 + if __name__ == "__main__": - raise SystemExit(main()) # pragma: no cover \ No newline at end of file + raise SystemExit(main()) # pragma: no cover From 1f60ab07666d6b7452fdf0ce5539185b27c1a024 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Fri, 28 Jul 2023 11:31:02 -0400 Subject: [PATCH 03/21] adjusted unit tests for add_license_headers --- .../pre_commit_hooks/add_license_headers.py | 88 ++++++++++---- tests/test_reuse.py | 110 ++++++++++++++---- 2 files changed, 152 insertions(+), 46 deletions(-) diff --git a/src/ansys/pre_commit_hooks/add_license_headers.py b/src/ansys/pre_commit_hooks/add_license_headers.py index f1c7623f..99ee31a6 100644 --- a/src/ansys/pre_commit_hooks/add_license_headers.py +++ b/src/ansys/pre_commit_hooks/add_license_headers.py @@ -20,6 +20,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. + """Run reuse for files missing license headers.""" import argparse import datetime @@ -32,17 +33,16 @@ from reuse import header, lint, project -def find_files_missing_header(): +def set_lint_args(parser): """ - Retrieve files without license header. + Add arguments to parser for reuse lint. + + Args: + parser (argparse.ArgumentParser): Parser without any arguments. Returns: - 0: No files exist that are missing license header. - 1: Files exist that are missing license header. + parser.parse_args (argparse.Namespace): Parser Namespace containing lint arguments. """ - # Set up argparse for location, parser, and lint - # Lint contains 4 args: quiet, json, plain, and no_multiprocessing - parser = argparse.ArgumentParser() parser.add_argument( "--loc", type=str, required=True, help="Path to repository location", default="src" ) @@ -52,13 +52,25 @@ def find_files_missing_header(): parser.add_argument("--no_multiprocessing", action="store_true") lint.add_arguments(parser) - args = parser.parse_args([sys.argv[1]]) - proj = project.Project(rf"{args.loc}") + return parser.parse_args([sys.argv[1]]) + +def list_noncompliant_files(args, proj): + """ + Retrieve list of noncompliant files. + + Args: + args (argparse.Namespace): Namespace of arguments with their values. + proj (project.Project): Project reuse runs on. + + Returns: + missing_headers (list): List of files without license headers. + """ # Create a temporary file containing lint.run json output with NamedTemporaryFile(mode="w", delete=False) as tmp: args.json = True lint.run(args, proj, tmp) + # Open the temporary file, load the json, and find files missing copyright & licensing info. file = open(tmp.name, "rb") lint_json = json.load(file) @@ -73,16 +85,54 @@ def find_files_missing_header(): # Remove temporary file os.remove(tmp.name) + return missing_headers + + +def find_files_missing_header(): + """ + Retrieve files without license header. + + Returns: + 0: No files exist that are missing license header. + 1: Files exist that are missing license header. + """ + # Set up argparse for location, parser, and lint + # Lint contains 4 args: quiet, json, plain, and no_multiprocessing + parser = argparse.ArgumentParser() + args = set_lint_args(parser) + proj = project.Project(rf"{args.loc}") + + missing_headers = list_noncompliant_files(args, proj) + # Get current year for license file year = datetime.date.today().year # If there are files missing headers, run reuse and return 1 if missing_headers != []: - run_reuse(parser, year, args.loc, missing_headers) - return 1 + # Returns 1 if reuse changes all noncompliant files + # Returns 2 if .reuse directory does not exist + return run_reuse(parser, year, args.loc, missing_headers) return 0 +def check_reuse_dir(): + """ + Check .reuse directory exists in root of git repository. + + Returns: + git_root (str): Root path of git repository. + """ + # Get root directory of current git repository + git_repo = git.Repo(os.getcwd(), search_parent_directories=True) + git_root = git_repo.git.rev_parse("--show-toplevel") + + # If .reuse folder does not exist in git repository root, return 1 + if not os.path.isdir(os.path.join(git_root, ".reuse")): + print(f"Please ensure the .reuse directory is in {git_root}") + return 1 + return git_root + + def run_reuse(parser, year, loc, missing_headers): """ Run reuse command on files without license headers. @@ -101,14 +151,9 @@ def run_reuse(parser, year, loc, missing_headers): # skip-unrecognized, skip-existing header.add_arguments(parser) - # Get root directory of current git repository - git_repo = git.Repo(os.getcwd(), search_parent_directories=True) - git_root = git_repo.git.rev_parse("--show-toplevel") - - # If .reuse folder does not exist in git repository root, throw error - if not os.path.isdir(os.path.join(git_root, ".reuse")): - print(f"Please ensure the .reuse directory is in {git_root}") - return 1 + git_root = check_reuse_dir() + if git_root == 1: + return 2 # Add missing license header to each file in the list for file in missing_headers: @@ -131,10 +176,7 @@ def run_reuse(parser, year, loc, missing_headers): def main(): """Find files missing license header & run reuse on them.""" - status = find_files_missing_header() - if status == 1: - return 1 - return 0 + return find_files_missing_header() if __name__ == "__main__": diff --git a/tests/test_reuse.py b/tests/test_reuse.py index 0a47652b..ab7d933e 100644 --- a/tests/test_reuse.py +++ b/tests/test_reuse.py @@ -1,30 +1,51 @@ +import argparse import os import sys +import git import pytest +from reuse import project import ansys.pre_commit_hooks.add_license_headers as hook def test_argparse_passes(): - """Test argparse parses loc argument correctly.""" + """Test argparse passes given loc.""" sys.argv[1:] = ["--loc=./"] - result = hook.get_args(sys.argv[1:]) - assert result == "./" + parser = argparse.ArgumentParser() + args = hook.set_lint_args(parser) + + # Assert loc argument is same as set above + assert args.loc == "./" def test_argparse_fails(): """Test argparse throws error if loc argument is empty.""" sys.argv[1:] = [""] + parser = argparse.ArgumentParser() try: - hook.get_args(sys.argv[1:]) + hook.set_lint_args(parser) passes = True - except SystemExit as e: + except: passes = False + # Assert error is thrown for empty loc argument assert not passes +def test_all_files_compliant(tmp_path: pytest.TempPathFactory): + """Test no noncompliant files are found.""" + sys.argv[1:] = [rf"--loc={tmp_path}"] + parser = argparse.ArgumentParser() + args = hook.set_lint_args(parser) + proj = project.Project(rf"{args.loc}") + + missing_headers = hook.list_noncompliant_files(args, proj) + + # Assert all files are compliant + assert missing_headers == [] + + def create_test_file(tmp_path): """Create temporary file for reuse testing.""" test_file = os.path.join(tmp_path, "test.py") @@ -37,40 +58,83 @@ def create_test_file(tmp_path): return test_file -def test_reuse_runs(tmp_path: pytest.TempPathFactory): - """Test reuse annotate command is successful.""" +def test_noncompliant_files_found(tmp_path: pytest.TempPathFactory): + """Test noncompliant file is found.""" test_file = create_test_file(tmp_path) - loc = hook.get_args([rf"--loc={tmp_path}"]) + sys.argv[1:] = [rf"--loc={tmp_path}"] + parser = argparse.ArgumentParser() + args = hook.set_lint_args(parser) + proj = project.Project(rf"{args.loc}") - # Run reuse on temporary file without header - hook.run_reuse_on_files(loc) + missing_headers = hook.list_noncompliant_files(args, proj) - # Assert reuse added headers to all files - assert hook.get_files(tmp_path) == set() + # Assert noncompliant file is found + assert missing_headers != [] # Remove test file from git repo os.remove(test_file) -def test_files_without_headers(tmp_path: pytest.TempPathFactory): - """Test reuse detects files without license headers.""" +def test_find_files_missing_header_passes(tmp_path: pytest.TempPathFactory): + """Test no noncompliant files are found.""" + sys.argv[1:] = [rf"--loc={tmp_path}"] + result = hook.find_files_missing_header() + + # Assert all files are compliant + assert result == 0 + + +def test_find_files_missing_header_fails(tmp_path: pytest.TempPathFactory): + """Test noncompliant file is found.""" + sys.argv[1:] = [rf"--loc={tmp_path}"] + # Create noncompliant file in tmp_path test_file = create_test_file(tmp_path) + result = hook.find_files_missing_header() - # Assert reuse detects file without header - assert hook.get_files(tmp_path) != set() + # Assert noncompliant file is found + assert result == 1 # Remove test file from git repo os.remove(test_file) -def test_invalid_project_loc(): - """Test reuse is being used on invalid repository.""" - out = hook.get_files_missing_header("./bad_repo") - assert out == "bad_repo is no valid path for a repository" +def test_reuse_dir_dne(tmp_path: pytest.TempPathFactory): + """Test reuse directory does not exist.""" + sys.argv[1:] = [rf"--loc={tmp_path}"] + # Create noncompliant file in tmp_path + test_file = create_test_file(tmp_path) + + # Rename .reuse to invalid name + git_repo = git.Repo(os.getcwd(), search_parent_directories=True) + git_root = git_repo.git.rev_parse("--show-toplevel") + os.rename(os.path.join(git_root, ".reuse"), os.path.join(git_root, "invalid_reuse")) + result = hook.find_files_missing_header() + + # Assert .reuse directory does not exist + assert result == 2 + + # Restore original environment + os.rename(os.path.join(git_root, "invalid_reuse"), os.path.join(git_root, ".reuse")) + os.remove(test_file) + + +def test_main_passes(): + """Test all files are compliant.""" + sys.argv[1:] = ["--loc=src"] -def test_main(): - """Test reuse is being used on valid repository.""" - sys.argv[1:] = ["--loc=./"] res = hook.main() assert res == 0 + + +def test_main_fails(tmp_path: pytest.TempPathFactory): + """Test reuse is being run on noncompliant file.""" + sys.argv[1:] = [rf"--loc={tmp_path}"] + test_file = create_test_file(tmp_path) + + # Run main given non_compliant file + res = hook.main() + assert res == 1 + + # Remove test file from git repo + os.remove(test_file) From a0f935cfb6349f3febe0d4a3430af2300b9c8a4d Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Fri, 28 Jul 2023 11:40:54 -0400 Subject: [PATCH 04/21] made reuse dependency >=2 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 91bc224a..05d9bb3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ ] dependencies = [ "importlib-metadata >=4.0", - "reuse", + "reuse>=2", "GitPython==3.1.32", ] @@ -33,7 +33,7 @@ dependencies = [ tests = [ "pytest==7.3.0", "pytest-cov==4.0.0", - "reuse", + "reuse>=2", ] doc = [ "ansys-sphinx-theme==0.9.7", From ab286472377a2f9117704ae67bb1ee05d469d713 Mon Sep 17 00:00:00 2001 From: Kerry McAdams <58492561+klmcadams@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:16:20 -0400 Subject: [PATCH 05/21] make gitpython dependency flexible - pyproject.toml Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 05d9bb3b..ddf08ad7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "importlib-metadata >=4.0", "reuse>=2", - "GitPython==3.1.32", + "GitPython>=3", ] [project.optional-dependencies] From bbcce8aeca488139b7859325a66875a9fe63010b Mon Sep 17 00:00:00 2001 From: Kerry McAdams <58492561+klmcadams@users.noreply.github.com> Date: Mon, 31 Jul 2023 08:16:45 -0400 Subject: [PATCH 06/21] set specific dependencies for testing - pyproject.toml Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ddf08ad7..c885c07d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,8 @@ dependencies = [ tests = [ "pytest==7.3.0", "pytest-cov==4.0.0", - "reuse>=2", + "reuse==2.1.0", + "GitPython==3.1.32", ] doc = [ "ansys-sphinx-theme==0.9.7", From cb6ab35ed033cf2800baf895a389ea7e9848ab2f Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Mon, 31 Jul 2023 08:36:27 -0400 Subject: [PATCH 07/21] fixed docstrings --- .../pre_commit_hooks/add_license_headers.py | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/ansys/pre_commit_hooks/add_license_headers.py b/src/ansys/pre_commit_hooks/add_license_headers.py index 99ee31a6..2d26a267 100644 --- a/src/ansys/pre_commit_hooks/add_license_headers.py +++ b/src/ansys/pre_commit_hooks/add_license_headers.py @@ -37,11 +37,15 @@ def set_lint_args(parser): """ Add arguments to parser for reuse lint. - Args: - parser (argparse.ArgumentParser): Parser without any arguments. - - Returns: - parser.parse_args (argparse.Namespace): Parser Namespace containing lint arguments. + Parameters + ---------- + parser: argparse.ArgumentParser + Parser without any arguments. + + Returns + ------- + parser.parse_args: argparse.Namespace + Parser Namespace containing lint arguments. """ parser.add_argument( "--loc", type=str, required=True, help="Path to repository location", default="src" @@ -59,12 +63,17 @@ def list_noncompliant_files(args, proj): """ Retrieve list of noncompliant files. - Args: - args (argparse.Namespace): Namespace of arguments with their values. - proj (project.Project): Project reuse runs on. - - Returns: - missing_headers (list): List of files without license headers. + Parameters + ---------- + args: argparse.Namespace + Namespace of arguments with their values. + proj: project.Project + Project reuse runs on. + + Returns + ------- + missing_headers: list + List of files without license headers. """ # Create a temporary file containing lint.run json output with NamedTemporaryFile(mode="w", delete=False) as tmp: @@ -92,9 +101,12 @@ def find_files_missing_header(): """ Retrieve files without license header. - Returns: - 0: No files exist that are missing license header. - 1: Files exist that are missing license header. + Returns + ------- + 0: int + No files exist that are missing license header. + 1: int + Files exist that are missing license header. """ # Set up argparse for location, parser, and lint # Lint contains 4 args: quiet, json, plain, and no_multiprocessing @@ -119,8 +131,10 @@ def check_reuse_dir(): """ Check .reuse directory exists in root of git repository. - Returns: - git_root (str): Root path of git repository. + Returns + ------- + git_root: str + Root path of git repository. """ # Get root directory of current git repository git_repo = git.Repo(os.getcwd(), search_parent_directories=True) @@ -137,13 +151,21 @@ def run_reuse(parser, year, loc, missing_headers): """ Run reuse command on files without license headers. - Args: - parser (argparse.ArgumentParser): Parser containing previously set arguments. - year (int): Current year for license header. - loc (str): Location to search for files missing headers. - missing_headers (list): List of files that are missing headers. - Returns: - 1: Fails pre-commit hook on return 1 + Parameters + ---------- + parser: argparse.ArgumentParser + Parser containing previously set arguments. + year: int + Current year for license header. + loc: str + Location to search for files missing headers. + missing_headers: list + List of files that are missing headers. + + Returns + ---------- + 1: int + Fails pre-commit hook on return 1 """ # Add header arguments to parser which include: copyright, license, contributor, year, # style, copyright-style, template, exclude-year, merge-copyrights, single-line, From 91789035761b3993161bed0ceca0e184e0d6ae4d Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Mon, 31 Jul 2023 08:58:50 -0400 Subject: [PATCH 08/21] changed sphinx & sphinx-theme back to newer versions --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c885c07d..47e21926 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,9 @@ tests = [ "GitPython==3.1.32", ] doc = [ - "ansys-sphinx-theme==0.9.7", + "ansys-sphinx-theme==0.10.0", "numpydoc==1.5.0", - "sphinx==5.3.0", + "sphinx==7.1.1", "sphinx-copybutton==0.5.1", ] From 4a9ae69c13f4e15cbebe0dd33c0399efaf89473f Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Mon, 31 Jul 2023 09:21:31 -0400 Subject: [PATCH 09/21] changing ansys-sphinx-theme to 9.7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 47e21926..08e46e9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ tests = [ "GitPython==3.1.32", ] doc = [ - "ansys-sphinx-theme==0.10.0", + "ansys-sphinx-theme==0.9.7", "numpydoc==1.5.0", "sphinx==7.1.1", "sphinx-copybutton==0.5.1", From a6551ef1b6c78d66a7104df79405113556e91b39 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Mon, 31 Jul 2023 09:31:05 -0400 Subject: [PATCH 10/21] changed sphinx to 5.3.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 08e46e9b..c885c07d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ tests = [ doc = [ "ansys-sphinx-theme==0.9.7", "numpydoc==1.5.0", - "sphinx==7.1.1", + "sphinx==5.3.0", "sphinx-copybutton==0.5.1", ] From 7cdfec151cc71efd3423ff602fdb00d14ef2320d Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Mon, 31 Jul 2023 11:16:13 -0400 Subject: [PATCH 11/21] updated sphinx versions --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c885c07d..47e21926 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,9 @@ tests = [ "GitPython==3.1.32", ] doc = [ - "ansys-sphinx-theme==0.9.7", + "ansys-sphinx-theme==0.10.0", "numpydoc==1.5.0", - "sphinx==5.3.0", + "sphinx==7.1.1", "sphinx-copybutton==0.5.1", ] From 052d8225a573f5df0f479655a82f47a0b4c8de36 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Mon, 31 Jul 2023 11:20:40 -0400 Subject: [PATCH 12/21] added linkcheck_ignore for time being --- doc/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 62da8cac..344c9564 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -89,3 +89,5 @@ # The master toctree document. master_doc = "index" + +linkcheck_ignore = [r'https://github.com/ansys/pre-commit-hooks:\d+/', r'https://pypi.org/project/ansys-pre-commit-hooks/:\d+/'] \ No newline at end of file From efe2c13f1a0a15ee243ddda6ce658164b2679476 Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Mon, 31 Jul 2023 11:23:17 -0400 Subject: [PATCH 13/21] fixed style for conf.py --- doc/source/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 344c9564..61f071b3 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -90,4 +90,7 @@ # The master toctree document. master_doc = "index" -linkcheck_ignore = [r'https://github.com/ansys/pre-commit-hooks:\d+/', r'https://pypi.org/project/ansys-pre-commit-hooks/:\d+/'] \ No newline at end of file +linkcheck_ignore = [ + r"https://github.com/ansys/pre-commit-hooks:\d+/", + r"https://pypi.org/project/ansys-pre-commit-hooks/:\d+/", +] From b911cff0163388cd5eb52a0e1043b4d8e9d26ca5 Mon Sep 17 00:00:00 2001 From: Kerry McAdams <58492561+klmcadams@users.noreply.github.com> Date: Mon, 31 Jul 2023 22:49:00 -0400 Subject: [PATCH 14/21] added wget code - ci_cd.yml --- .github/workflows/ci_cd.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 0d5ce2a6..26d38616 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -45,6 +45,10 @@ jobs: os: [ubuntu-latest, windows-latest] python-version: ['3.8', '3.9', '3.10', '3.11'] steps: + - name: "Install wget on Windows" + if: runner.os == 'Windows' + shell: bash + run: choco install wget - uses: ansys/actions/build-wheelhouse@v4 with: library-name: ${{ env.LIBRARY_NAME }} From c1fb5c4d24f6bbf72d63bb0994ee65b6c89c391d Mon Sep 17 00:00:00 2001 From: Kerry McAdams <58492561+klmcadams@users.noreply.github.com> Date: Mon, 31 Jul 2023 22:57:54 -0400 Subject: [PATCH 15/21] revert ci_cd.yml --- .github/workflows/ci_cd.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 26d38616..0d5ce2a6 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -45,10 +45,6 @@ jobs: os: [ubuntu-latest, windows-latest] python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - - name: "Install wget on Windows" - if: runner.os == 'Windows' - shell: bash - run: choco install wget - uses: ansys/actions/build-wheelhouse@v4 with: library-name: ${{ env.LIBRARY_NAME }} From 1cb91887a23334ea7bedc961eb2e375b705619b2 Mon Sep 17 00:00:00 2001 From: Kerry McAdams <58492561+klmcadams@users.noreply.github.com> Date: Mon, 31 Jul 2023 23:03:41 -0400 Subject: [PATCH 16/21] changed regex - conf.py --- doc/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 61f071b3..fdc84371 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -91,6 +91,6 @@ master_doc = "index" linkcheck_ignore = [ - r"https://github.com/ansys/pre-commit-hooks:\d+/", - r"https://pypi.org/project/ansys-pre-commit-hooks/:\d+/", + r"https://github.com/ansys/pre-commit-hooks/*", + r"https://pypi.org/project/ansys-pre-commit-hooks/*", ] From 6dd914cd140e96fffe7b3003a828655ea951815a Mon Sep 17 00:00:00 2001 From: Kerry McAdams <58492561+klmcadams@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:57:31 -0400 Subject: [PATCH 17/21] add reminder to doc/source/conf.py Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> --- doc/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index fdc84371..a7383711 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -90,6 +90,7 @@ # The master toctree document. master_doc = "index" +# TODO: Once the repo goes public... remove these links linkcheck_ignore = [ r"https://github.com/ansys/pre-commit-hooks/*", r"https://pypi.org/project/ansys-pre-commit-hooks/*", From a00d582b8d5aa5a68e6b9a2fc84ba77d54f2d29e Mon Sep 17 00:00:00 2001 From: Kerry McAdams Date: Tue, 1 Aug 2023 11:03:40 -0400 Subject: [PATCH 18/21] adjusted return sections in docstrings --- src/ansys/pre_commit_hooks/add_license_headers.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ansys/pre_commit_hooks/add_license_headers.py b/src/ansys/pre_commit_hooks/add_license_headers.py index 2d26a267..5c8ec4ea 100644 --- a/src/ansys/pre_commit_hooks/add_license_headers.py +++ b/src/ansys/pre_commit_hooks/add_license_headers.py @@ -44,7 +44,7 @@ def set_lint_args(parser): Returns ------- - parser.parse_args: argparse.Namespace + argparse.Namespace Parser Namespace containing lint arguments. """ parser.add_argument( @@ -72,7 +72,7 @@ def list_noncompliant_files(args, proj): Returns ------- - missing_headers: list + list List of files without license headers. """ # Create a temporary file containing lint.run json output @@ -103,10 +103,9 @@ def find_files_missing_header(): Returns ------- - 0: int - No files exist that are missing license header. - 1: int - Files exist that are missing license header. + int + Returns non-zero int if one or more files are noncompliant, + or if the .reuse directory is missing. """ # Set up argparse for location, parser, and lint # Lint contains 4 args: quiet, json, plain, and no_multiprocessing @@ -133,7 +132,7 @@ def check_reuse_dir(): Returns ------- - git_root: str + str Root path of git repository. """ # Get root directory of current git repository @@ -164,7 +163,7 @@ def run_reuse(parser, year, loc, missing_headers): Returns ---------- - 1: int + int Fails pre-commit hook on return 1 """ # Add header arguments to parser which include: copyright, license, contributor, year, From dee262f763501aae8a525daa153de4ec5963c0bd Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:07:44 +0200 Subject: [PATCH 19/21] feat: genearl improvements --- src/ansys/pre_commit_hooks/__init__.py | 2 +- .../pre_commit_hooks/add_license_headers.py | 44 ++++++++++--------- tests/test_reuse.py | 32 ++++++++------ 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/ansys/pre_commit_hooks/__init__.py b/src/ansys/pre_commit_hooks/__init__.py index 16a359b9..26b74c76 100644 --- a/src/ansys/pre_commit_hooks/__init__.py +++ b/src/ansys/pre_commit_hooks/__init__.py @@ -24,7 +24,7 @@ try: import importlib.metadata as importlib_metadata -except ModuleNotFoundError: +except ModuleNotFoundError: # pragma: no cover import importlib_metadata __version__ = importlib_metadata.version(__name__.replace(".", "-")) diff --git a/src/ansys/pre_commit_hooks/add_license_headers.py b/src/ansys/pre_commit_hooks/add_license_headers.py index 5c8ec4ea..cb21b649 100644 --- a/src/ansys/pre_commit_hooks/add_license_headers.py +++ b/src/ansys/pre_commit_hooks/add_license_headers.py @@ -23,10 +23,9 @@ """Run reuse for files missing license headers.""" import argparse -import datetime +from datetime import date as dt import json import os -import sys from tempfile import NamedTemporaryFile import git @@ -47,16 +46,12 @@ def set_lint_args(parser): argparse.Namespace Parser Namespace containing lint arguments. """ - parser.add_argument( - "--loc", type=str, required=True, help="Path to repository location", default="src" - ) - parser.add_argument( - "--parser", - ) + parser.add_argument("--loc", type=str, help="Path to repository location", default="src") + parser.add_argument("--parser") parser.add_argument("--no_multiprocessing", action="store_true") lint.add_arguments(parser) - return parser.parse_args([sys.argv[1]]) + return parser.parse_args() def list_noncompliant_files(args, proj): @@ -76,23 +71,24 @@ def list_noncompliant_files(args, proj): List of files without license headers. """ # Create a temporary file containing lint.run json output + filename = None with NamedTemporaryFile(mode="w", delete=False) as tmp: args.json = True lint.run(args, proj, tmp) + filename = tmp.name # Open the temporary file, load the json, and find files missing copyright & licensing info. - file = open(tmp.name, "rb") - lint_json = json.load(file) - file.close() - missing_headers = list( - set( - lint_json["non_compliant"]["missing_copyright_info"] - + lint_json["non_compliant"]["missing_licensing_info"] - ) + lint_json = None + with open(filename, "rb") as file: + lint_json = json.load(file) + + missing_headers = set( + lint_json["non_compliant"]["missing_copyright_info"] + + lint_json["non_compliant"]["missing_licensing_info"] ) # Remove temporary file - os.remove(tmp.name) + os.remove(filename) return missing_headers @@ -116,13 +112,14 @@ def find_files_missing_header(): missing_headers = list_noncompliant_files(args, proj) # Get current year for license file - year = datetime.date.today().year + year = dt.today().year # If there are files missing headers, run reuse and return 1 - if missing_headers != []: + if missing_headers: # Returns 1 if reuse changes all noncompliant files # Returns 2 if .reuse directory does not exist return run_reuse(parser, year, args.loc, missing_headers) + return 0 @@ -134,6 +131,8 @@ def check_reuse_dir(): ------- str Root path of git repository. + int + If .reuse directory does not exist at root path of git repository. """ # Get root directory of current git repository git_repo = git.Repo(os.getcwd(), search_parent_directories=True) @@ -143,6 +142,7 @@ def check_reuse_dir(): if not os.path.isdir(os.path.join(git_root, ".reuse")): print(f"Please ensure the .reuse directory is in {git_root}") return 1 + return git_root @@ -162,7 +162,7 @@ def run_reuse(parser, year, loc, missing_headers): List of files that are missing headers. Returns - ---------- + ------- int Fails pre-commit hook on return 1 """ @@ -174,6 +174,8 @@ def run_reuse(parser, year, loc, missing_headers): git_root = check_reuse_dir() if git_root == 1: + # Previous check_reuse_dir() function returned error because .reuse + # directory does not exist... returning 2 return 2 # Add missing license header to each file in the list diff --git a/tests/test_reuse.py b/tests/test_reuse.py index ab7d933e..b51e87e0 100644 --- a/tests/test_reuse.py +++ b/tests/test_reuse.py @@ -19,18 +19,15 @@ def test_argparse_passes(): assert args.loc == "./" -def test_argparse_fails(): - """Test argparse throws error if loc argument is empty.""" - sys.argv[1:] = [""] +def test_argparse_default(): + """Test argparse returns default if loc argument is not provided.""" + sys.argv[1:] = [] parser = argparse.ArgumentParser() - try: - hook.set_lint_args(parser) - passes = True - except: - passes = False + + args = hook.set_lint_args(parser) # Assert error is thrown for empty loc argument - assert not passes + assert args.loc == "src" def test_all_files_compliant(tmp_path: pytest.TempPathFactory): @@ -43,7 +40,7 @@ def test_all_files_compliant(tmp_path: pytest.TempPathFactory): missing_headers = hook.list_noncompliant_files(args, proj) # Assert all files are compliant - assert missing_headers == [] + assert len(missing_headers) == 0 def create_test_file(tmp_path): @@ -109,15 +106,24 @@ def test_reuse_dir_dne(tmp_path: pytest.TempPathFactory): git_root = git_repo.git.rev_parse("--show-toplevel") os.rename(os.path.join(git_root, ".reuse"), os.path.join(git_root, "invalid_reuse")) - result = hook.find_files_missing_header() + store_err = None + try: + result = hook.find_files_missing_header() + + # Assert .reuse directory does not exist + assert result == 2 - # Assert .reuse directory does not exist - assert result == 2 + except Exception as err: + store_err = err + pass # Restore original environment os.rename(os.path.join(git_root, "invalid_reuse"), os.path.join(git_root, ".reuse")) os.remove(test_file) + if store_err: + raise store_err + def test_main_passes(): """Test all files are compliant.""" From 22d5211d8c0357db55c74254181470f677916f23 Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:08:05 +0200 Subject: [PATCH 20/21] feat: documenting API --- doc/source/_autoapi_templates/index.rst | 13 +++++++++++++ doc/source/conf.py | 18 +++++++++++++++++- doc/source/index.rst | 6 ++++++ pyproject.toml | 2 ++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 doc/source/_autoapi_templates/index.rst diff --git a/doc/source/_autoapi_templates/index.rst b/doc/source/_autoapi_templates/index.rst new file mode 100644 index 00000000..c446e3a3 --- /dev/null +++ b/doc/source/_autoapi_templates/index.rst @@ -0,0 +1,13 @@ +API reference +============= + +This page contains the ``ansys-pre-commit-hooks`` API reference. + +.. toctree:: + :titlesonly: + + {% for page in pages %} + {% if page.top_level_object and page.display %} + {{ page.include_path }} + {% endif %} + {% endfor %} diff --git a/doc/source/conf.py b/doc/source/conf.py index 35470fee..c2b0485d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -38,6 +38,8 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosummary", + "autoapi.extension", + "sphinx_autodoc_typehints", "numpydoc", "sphinx.ext.intersphinx", "sphinx_copybutton", @@ -65,7 +67,7 @@ numpydoc_validation_checks = { "GL06", # Found unknown section "GL07", # Sections are in the wrong order. - "GL08", # The object does not have a docstring + # "GL08", # The object does not have a docstring "GL09", # Deprecation warning should precede extended summary "GL10", # reST directives {directives} must be followed by two colons "SS01", # No summary found @@ -95,3 +97,17 @@ r"https://github.com/ansys/pre-commit-hooks/*", r"https://pypi.org/project/ansys-pre-commit-hooks", ] + +# Configuration for Sphinx autoapi +autoapi_type = "python" +autoapi_dirs = ["../../src/ansys/pre_commit_hooks/"] +autoapi_options = [ + "members", + "undoc-members", + "show-inheritance", + "show-module-summary", + "imported-members", +] +autoapi_template_dir = "_autoapi_templates" +autoapi_python_use_implicit_namespaces = True +exclude_patterns = ["_autoapi_templates/index.rst"] diff --git a/doc/source/index.rst b/doc/source/index.rst index d2824a5f..9b5b5d92 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,3 +4,9 @@ here. .. include:: ../../README.rst + +.. toctree:: + :hidden: + :maxdepth: 3 + + autoapi/index diff --git a/pyproject.toml b/pyproject.toml index 0571a6db..04917399 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,8 @@ doc = [ "ansys-sphinx-theme==0.10.0", "numpydoc==1.5.0", "sphinx==7.1.2", + "sphinx-autoapi==2.1.1", + "sphinx-autodoc-typehints==1.22.0", "sphinx-copybutton==0.5.1", ] From b3cbe1caca5c3ced08f541bac9f8ee567041a03e Mon Sep 17 00:00:00 2001 From: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:08:12 +0200 Subject: [PATCH 21/21] feat: add ignored files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 852dd375..3cbb50cf 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,6 @@ cython_debug/ #.idea/ # End of https://www.toptal.com/developers/gitignore/api/python + +# IDE +.vscode \ No newline at end of file