Skip to content

Commit

Permalink
Fix/reuse 2.0 implementation (#17)
Browse files Browse the repository at this point in the history
Co-authored-by: Roberto Pastor Muela <[email protected]>
  • Loading branch information
klmcadams and RobPasMue authored Aug 3, 2023
1 parent b6f42d2 commit 4ef0021
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 110 deletions.
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 = [
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}")
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

# 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

0 comments on commit 4ef0021

Please sign in to comment.