Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/reuse 2.0 implementation #17

Merged
merged 23 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6ce4d76
fixed implementation of add_license_headers for reuse 2.0
klmcadams Jul 28, 2023
d736190
style fix
klmcadams Jul 28, 2023
1f60ab0
adjusted unit tests for add_license_headers
klmcadams Jul 28, 2023
a0f935c
made reuse dependency >=2
klmcadams Jul 28, 2023
ab28647
make gitpython dependency flexible - pyproject.toml
klmcadams Jul 31, 2023
bbcce8a
set specific dependencies for testing - pyproject.toml
klmcadams Jul 31, 2023
cb6ab35
fixed docstrings
klmcadams Jul 31, 2023
adf4a48
Merge branch 'fix/reuse-2.0-implementation' of https://github.com/ans…
klmcadams Jul 31, 2023
9178903
changed sphinx & sphinx-theme back to newer versions
klmcadams Jul 31, 2023
4a9ae69
changing ansys-sphinx-theme to 9.7
klmcadams Jul 31, 2023
a6551ef
changed sphinx to 5.3.0
klmcadams Jul 31, 2023
7cdfec1
updated sphinx versions
klmcadams Jul 31, 2023
052d822
added linkcheck_ignore for time being
klmcadams Jul 31, 2023
efe2c13
fixed style for conf.py
klmcadams Jul 31, 2023
b911cff
added wget code - ci_cd.yml
klmcadams Aug 1, 2023
c1fb5c4
revert ci_cd.yml
klmcadams Aug 1, 2023
1cb9188
changed regex - conf.py
klmcadams Aug 1, 2023
6dd914c
add reminder to doc/source/conf.py
klmcadams Aug 1, 2023
a00d582
adjusted return sections in docstrings
klmcadams Aug 1, 2023
3ba557b
Merge branch 'main' into fix/reuse-2.0-implementation
RobPasMue Aug 3, 2023
dee262f
feat: genearl improvements
RobPasMue Aug 3, 2023
22d5211
feat: documenting API
RobPasMue Aug 3, 2023
b3cbe1c
feat: add ignored files
RobPasMue Aug 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,6 @@ cython_debug/
#.idea/

# End of https://www.toptal.com/developers/gitignore/api/python

# IDE
.vscode
13 changes: 13 additions & 0 deletions doc/source/_autoapi_templates/index.rst
Original file line number Diff line number Diff line change
@@ -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 %}
19 changes: 18 additions & 1 deletion doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"autoapi.extension",
"sphinx_autodoc_typehints",
"numpydoc",
"sphinx.ext.intersphinx",
"sphinx_copybutton",
Expand Down Expand Up @@ -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
Expand All @@ -90,7 +92,22 @@
# The master toctree document.
master_doc = "index"

# TODO: Once the repo goes public... remove these links
linkcheck_ignore = [
klmcadams marked this conversation as resolved.
Show resolved Hide resolved
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"]
6 changes: 6 additions & 0 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@
here.

.. include:: ../../README.rst

.. toctree::
:hidden:
:maxdepth: 3

autoapi/index
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@ classifiers = [
]
dependencies = [
"importlib-metadata >=4.0",
"reuse <2",
"reuse>=2",
"GitPython>=3",
]

[project.optional-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.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",
]

Expand Down
2 changes: 1 addition & 1 deletion src/ansys/pre_commit_hooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".", "-"))
236 changes: 160 additions & 76 deletions src/ansys/pre_commit_hooks/add_license_headers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python

# Copyright (C) 2023 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
Expand All @@ -24,98 +23,183 @@

"""Run reuse for files missing license headers."""
import argparse
import datetime
from datetime import date as dt
import json
import os
import subprocess
import sys
from tempfile import NamedTemporaryFile

from reuse import lint
from reuse.project import Project
from reuse.report import ProjectReport
import git
from reuse import header, lint, project


def get_args(args):
"""Retrieve value of --loc argument."""
parser = argparse.ArgumentParser(description="Get repository location.")
parser.add_argument(
"--loc", type=str, required=True, help="Path to repository location", default="src"
)
return parser.parse_args(args).loc
def set_lint_args(parser):
"""
Add arguments to parser for reuse lint.

Parameters
----------
parser: argparse.ArgumentParser
Parser without any arguments.

Returns
-------
argparse.Namespace
Parser Namespace containing lint arguments.
"""
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()


def get_files(loc):
def list_noncompliant_files(args, proj):
"""
Generate report containing files without the license header.
Retrieve list of noncompliant files.

Parameters
----------
loc : str
Path to repository location.
args: argparse.Namespace
Namespace of arguments with their values.
proj: project.Project
Project reuse runs on.

Returns
-------
list
List of files without license headers.
"""
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."""
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)
return 1
# 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.
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(filename)

return missing_headers


def find_files_missing_header():
"""
Retrieve files without license header.

Returns
-------
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
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 = dt.today().year

# If there are files missing headers, run reuse and return 1
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 zero if all files have license header
return 0


def check_reuse_dir():
"""
Check .reuse directory exists in root of git repository.

Returns
-------
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)
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}")
klmcadams marked this conversation as resolved.
Show resolved Hide resolved
return 1

return git_root


def run_reuse(parser, year, loc, missing_headers):
"""
Run reuse command on files without license headers.

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
-------
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,
# multi-line, explicit-license, force-dot-license, recursive, no-replace,
# skip-unrecognized, skip-existing
header.add_arguments(parser)

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
klmcadams marked this conversation as resolved.
Show resolved Hide resolved

# Add missing license header to each file in the list
for file in missing_headers:
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():
"""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."""
return find_files_missing_header()


if __name__ == "__main__":
Expand Down
Loading
Loading