ansys-pre-commit-hooks 0.5.2__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/LICENSE +1 -1
  2. {ansys_pre_commit_hooks-0.5.2/src/ansys_pre_commit_hooks.egg-info → ansys_pre_commit_hooks-0.6.0}/PKG-INFO +23 -31
  3. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/pyproject.toml +58 -4
  4. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/setup.py +16 -47
  5. ansys_pre_commit_hooks-0.6.0/src/ansys/pre_commit_hooks/VERSION +1 -0
  6. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/__init__.py +1 -1
  7. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/add_license_headers.py +85 -74
  8. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/assets/licenses.json +68 -1
  9. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/tech_review.py +15 -8
  10. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/dependabot.yml +4 -0
  11. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0/src/ansys_pre_commit_hooks.egg-info}/PKG-INFO +23 -31
  12. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys_pre_commit_hooks.egg-info/SOURCES.txt +0 -3
  13. ansys_pre_commit_hooks-0.6.0/src/ansys_pre_commit_hooks.egg-info/requires.txt +18 -0
  14. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/tests/test_add_license_headers.py +105 -113
  15. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/tests/test_metadata.py +1 -1
  16. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/tests/test_tech_review.py +1 -1
  17. ansys_pre_commit_hooks-0.5.2/AUTHORS +0 -12
  18. ansys_pre_commit_hooks-0.5.2/src/ansys/pre_commit_hooks/VERSION +0 -1
  19. ansys_pre_commit_hooks-0.5.2/src/ansys/pre_commit_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  20. ansys_pre_commit_hooks-0.5.2/src/ansys/pre_commit_hooks/__pycache__/tech_review.cpython-312.pyc +0 -0
  21. ansys_pre_commit_hooks-0.5.2/src/ansys_pre_commit_hooks.egg-info/requires.txt +0 -19
  22. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/MANIFEST.in +0 -0
  23. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/README.rst +0 -0
  24. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/setup.cfg +0 -0
  25. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/assets/.reuse/templates/ansys.jinja2 +0 -0
  26. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/assets/LICENSES/MIT.txt +0 -0
  27. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/AUTHORS +0 -0
  28. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/CODE_OF_CONDUCT.md +0 -0
  29. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/CONTRIBUTING.md +0 -0
  30. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/CONTRIBUTORS.md +0 -0
  31. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/LICENSE +0 -0
  32. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/README.md +0 -0
  33. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys/pre_commit_hooks/templates/README.rst +0 -0
  34. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys_pre_commit_hooks.egg-info/dependency_links.txt +0 -0
  35. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys_pre_commit_hooks.egg-info/entry_points.txt +0 -0
  36. {ansys_pre_commit_hooks-0.5.2 → ansys_pre_commit_hooks-0.6.0}/src/ansys_pre_commit_hooks.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
3
+ Copyright (c) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
@@ -1,59 +1,51 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ansys-pre-commit-hooks
3
- Version: 0.5.2
4
- Summary: A Python wrapper to create Ansys-tailored pre-commit hooks
3
+ Version: 0.6.0
5
4
  Home-page: https://github.com/ansys/pre-commit-hooks
6
5
  Author: ANSYS, Inc.
7
- Author-email: pyansys.core@ansys.com
6
+ Author-email: "ANSYS, Inc." <pyansys.core@ansys.com>
8
7
  Maintainer: ANSYS, Inc.
9
- Maintainer-email: pyansys.core@ansys.com
10
- License: MIT
8
+ Maintainer-email: "ANSYS, Inc." <pyansys.core@ansys.com>
9
+ License-Expression: MIT
11
10
  Project-URL: Source, https://github.com/ansys/pre-commit-hooks/
12
- Project-URL: Tracker, https://github.com/ansys/pre-commit-hooks/issues
13
- Project-URL: Homepage, https://github.com/ansys/pre-commit-hooks
11
+ Project-URL: Issues, https://github.com/ansys/pre-commit-hooks/issues
12
+ Project-URL: Discussions, https://github.com/ansys/pre-commit-hooks/discussions
14
13
  Project-URL: Documentation, https://pre-commit-hooks.docs.pyansys.com
14
+ Project-URL: Releases, https://github.com/ansys/pre-commit-hooks/releases
15
+ Project-URL: Changelog, https://github.com/ansys/pre-commit-hooks/blob/main/doc/source/changelog.rst
15
16
  Classifier: Development Status :: 4 - Beta
16
17
  Classifier: Intended Audience :: Science/Research
17
18
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
18
- Classifier: License :: OSI Approved :: MIT License
19
19
  Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3.9
21
20
  Classifier: Programming Language :: Python :: 3.10
22
21
  Classifier: Programming Language :: Python :: 3.11
23
22
  Classifier: Programming Language :: Python :: 3.12
24
- Requires-Python: >=3.9,<4
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Requires-Python: >=3.10,<4
25
+ Description-Content-Type: text/x-rst
25
26
  License-File: LICENSE
26
- License-File: AUTHORS
27
- Requires-Dist: GitPython==3.1.44
28
- Requires-Dist: importlib-metadata==8.6.1
29
- Requires-Dist: Jinja2==3.1.5
30
- Requires-Dist: reuse==5.0.2
31
- Requires-Dist: requests==2.32.3
27
+ Requires-Dist: GitPython==3.1.46
28
+ Requires-Dist: importlib-metadata==8.7.1
29
+ Requires-Dist: Jinja2==3.1.6
30
+ Requires-Dist: reuse==6.2.0
31
+ Requires-Dist: requests==2.32.5
32
32
  Requires-Dist: semver==3.0.4
33
33
  Requires-Dist: toml==0.10.2
34
34
  Provides-Extra: doc
35
- Requires-Dist: ansys-sphinx-theme[autoapi]==1.3.2; extra == "doc"
36
- Requires-Dist: numpydoc==1.8.0; extra == "doc"
37
- Requires-Dist: sphinx==8.2.1; extra == "doc"
35
+ Requires-Dist: ansys-sphinx-theme[autoapi]==1.7.2; extra == "doc"
36
+ Requires-Dist: numpydoc==1.10.0; extra == "doc"
37
+ Requires-Dist: sphinx==8.2.3; extra == "doc"
38
38
  Requires-Dist: sphinx-autodoc-typehints==3.1.0; extra == "doc"
39
39
  Requires-Dist: sphinx-copybutton==0.5.1; extra == "doc"
40
- Requires-Dist: sphinx_design==0.6.1; extra == "doc"
41
40
  Provides-Extra: tests
42
- Requires-Dist: pytest==8.3.4; extra == "tests"
43
- Requires-Dist: pytest-cov==6.0.0; extra == "tests"
41
+ Requires-Dist: pytest==9.0.2; extra == "tests"
42
+ Requires-Dist: pytest-cov==7.0.0; extra == "tests"
44
43
  Dynamic: author
45
- Dynamic: author-email
46
44
  Dynamic: classifier
47
- Dynamic: description
48
45
  Dynamic: home-page
49
- Dynamic: license
46
+ Dynamic: license-file
50
47
  Dynamic: maintainer
51
- Dynamic: maintainer-email
52
- Dynamic: project-url
53
- Dynamic: provides-extra
54
- Dynamic: requires-dist
55
48
  Dynamic: requires-python
56
- Dynamic: summary
57
49
 
58
50
  Ansys pre-commit hooks
59
51
  ======================
@@ -1,16 +1,70 @@
1
1
  [build-system]
2
2
  requires = [
3
3
  "setuptools>=42.0",
4
- "GitPython==3.1.44",
5
- "Jinja2==3.1.5",
6
- "reuse==5.0.2",
7
- "requests==2.32.3",
4
+ "GitPython==3.1.46",
5
+ "Jinja2==3.1.6",
6
+ "reuse==6.2.0",
7
+ "requests==2.32.5",
8
8
  "semver==3.0.4",
9
9
  "toml==0.10.2",
10
10
  "wheel",
11
11
  ]
12
12
  build-backend = "setuptools.build_meta"
13
13
 
14
+ [project]
15
+ name = "ansys-pre-commit-hooks"
16
+ dynamic = ["version", "classifiers", "description", "readme", "scripts"]
17
+ requires-python = ">=3.10,<4"
18
+ license = "MIT"
19
+ license-files = ["LICENSE"]
20
+ authors = [{ name = "ANSYS, Inc.", email = "pyansys.core@ansys.com" }]
21
+ maintainers = [{ name = "ANSYS, Inc.", email = "pyansys.core@ansys.com" }]
22
+ dependencies = [
23
+ "GitPython==3.1.46",
24
+ "importlib-metadata==8.7.1",
25
+ "Jinja2==3.1.6",
26
+ "reuse==6.2.0",
27
+ "requests==2.32.5",
28
+ "semver==3.0.4",
29
+ "toml==0.10.2",
30
+ ]
31
+
32
+ [tool.setuptools.dynamic]
33
+ version = {file = "src/ansys/pre_commit_hooks/VERSION"}
34
+ readme = {file = ["README.rst"]}
35
+ description = {file = "A Python wrapper to create Ansys-tailored pre-commit hooks"}
36
+ classifiers = {file = """
37
+ "Development Status :: 4 - Beta",
38
+ "Intended Audience :: Science/Research",
39
+ "Topic :: Scientific/Engineering :: Information Analysis",
40
+ "Operating System :: OS Independent",
41
+ "Programming Language :: Python :: 3.10",
42
+ "Programming Language :: Python :: 3.11",
43
+ "Programming Language :: Python :: 3.12",
44
+ "Programming Language :: Python :: 3.13",
45
+ """}
46
+
47
+ [project.optional-dependencies]
48
+ doc = [
49
+ "ansys-sphinx-theme[autoapi]==1.7.2",
50
+ "numpydoc==1.10.0",
51
+ "sphinx==8.2.3",
52
+ "sphinx-autodoc-typehints==3.1.0",
53
+ "sphinx-copybutton==0.5.1",
54
+ ]
55
+ tests = [
56
+ "pytest==9.0.2",
57
+ "pytest-cov==7.0.0",
58
+ ]
59
+
60
+ [project.urls]
61
+ Source = "https://github.com/ansys/pre-commit-hooks/"
62
+ Issues = "https://github.com/ansys/pre-commit-hooks/issues"
63
+ Discussions = "https://github.com/ansys/pre-commit-hooks/discussions"
64
+ Documentation = "https://pre-commit-hooks.docs.pyansys.com"
65
+ Releases = "https://github.com/ansys/pre-commit-hooks/releases"
66
+ Changelog = "https://github.com/ansys/pre-commit-hooks/blob/main/doc/source/changelog.rst"
67
+
14
68
  [tool.black]
15
69
  line-length = 100
16
70
 
@@ -1,36 +1,8 @@
1
1
  """Installation file for ansys-pre-commit-hooks."""
2
2
 
3
3
  import os
4
- import sys
5
4
 
6
5
  from setuptools import find_namespace_packages, setup
7
- from setuptools.command.develop import develop
8
- from setuptools.command.install import install
9
-
10
- SCRIPT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "src", "ansys")
11
-
12
- sys.path.append(os.path.dirname(SCRIPT_DIR))
13
-
14
- from ansys.pre_commit_hooks.tech_review import JSON_URL, LICENSES_JSON, download_license_json
15
-
16
-
17
- class CustomInstallCommand(install):
18
- """Custom install command to download the license json file."""
19
-
20
- def run(self):
21
- """Download the license json file."""
22
- install.run(self)
23
- download_license_json(JSON_URL, LICENSES_JSON)
24
-
25
-
26
- class CustomDevelopCommand(develop):
27
- """Custom develop command to download the license json file."""
28
-
29
- def run(self):
30
- """Download the license json file."""
31
- develop.run(self)
32
- download_license_json(JSON_URL, LICENSES_JSON)
33
-
34
6
 
35
7
  HERE = os.path.abspath(os.path.dirname(__file__))
36
8
  with open(os.path.join(HERE, "src", "ansys", "pre_commit_hooks", "VERSION"), encoding="utf-8") as f:
@@ -58,49 +30,46 @@ setup(
58
30
  "Development Status :: 4 - Beta",
59
31
  "Intended Audience :: Science/Research",
60
32
  "Topic :: Scientific/Engineering :: Information Analysis",
61
- "License :: OSI Approved :: MIT License",
62
33
  "Operating System :: OS Independent",
63
- "Programming Language :: Python :: 3.9",
64
34
  "Programming Language :: Python :: 3.10",
65
35
  "Programming Language :: Python :: 3.11",
66
36
  "Programming Language :: Python :: 3.12",
37
+ "Programming Language :: Python :: 3.13",
67
38
  ],
68
39
  url="https://github.com/ansys/pre-commit-hooks",
69
- python_requires=">=3.9,<4",
40
+ python_requires=">=3.10,<4",
70
41
  install_requires=[
71
- "GitPython==3.1.44",
72
- "importlib-metadata==8.6.1",
73
- "Jinja2==3.1.5",
74
- "reuse==5.0.2",
75
- "requests==2.32.3",
42
+ "GitPython==3.1.46",
43
+ "importlib-metadata==8.7.1",
44
+ "Jinja2==3.1.6",
45
+ "reuse==6.2.0",
46
+ "requests==2.32.5",
76
47
  "semver==3.0.4",
77
48
  "toml==0.10.2",
78
49
  ],
79
50
  extras_require={
80
51
  "doc": [
81
- "ansys-sphinx-theme[autoapi]==1.3.2",
82
- "numpydoc==1.8.0",
83
- "sphinx==8.2.1",
52
+ "ansys-sphinx-theme[autoapi]==1.7.2",
53
+ "numpydoc==1.10.0",
54
+ "sphinx==8.2.3",
84
55
  "sphinx-autodoc-typehints==3.1.0",
85
56
  "sphinx-copybutton==0.5.1",
86
57
  "sphinx_design==0.6.1",
87
58
  ],
88
59
  "tests": [
89
- "pytest==8.3.4",
90
- "pytest-cov==6.0.0",
60
+ "pytest==9.0.2",
61
+ "pytest-cov==7.0.0",
91
62
  ],
92
63
  },
93
64
  project_urls={
94
65
  "Source": "https://github.com/ansys/pre-commit-hooks/",
95
- "Tracker": "https://github.com/ansys/pre-commit-hooks/issues",
96
- "Homepage": "https://github.com/ansys/pre-commit-hooks",
66
+ "Issues": "https://github.com/ansys/pre-commit-hooks/issues",
67
+ "Discussions": "https://github.com/ansys/pre-commit-hooks/discussions",
97
68
  "Documentation": "https://pre-commit-hooks.docs.pyansys.com",
69
+ "Releases": "https://github.com/ansys/pre-commit-hooks/releases",
70
+ "Changelog": "https://github.com/ansys/pre-commit-hooks/blob/main/doc/source/changelog.rst",
98
71
  },
99
72
  include_package_data=True,
100
- cmdclass={
101
- "develop": CustomDevelopCommand,
102
- "install": CustomInstallCommand,
103
- },
104
73
  entry_points={
105
74
  "console_scripts": [
106
75
  "add-license-headers=ansys.pre_commit_hooks.add_license_headers:main",
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
1
+ # Copyright (C) 2023 - 2026 ANSYS, Inc. and/or its affiliates.
2
2
  # SPDX-License-Identifier: MIT
3
3
  #
4
4
  #
@@ -25,6 +25,7 @@ Module for running `REUSE <https://reuse.software/>`_ to add missing license hea
25
25
 
26
26
  A license header consists of the Ansys copyright statement and licensing information.
27
27
  """
28
+
28
29
  import argparse
29
30
  from datetime import date as dt
30
31
  import filecmp
@@ -35,12 +36,6 @@ import sys
35
36
  from tempfile import NamedTemporaryFile
36
37
  from typing import IO, Union
37
38
 
38
- import git
39
- from jinja2 import Environment, FileSystemLoader
40
- from reuse import extract
41
- from reuse.cli import common
42
- from reuse.cli.annotate import add_header_to_file, get_comment_style, get_reuse_info, get_template
43
-
44
39
  DEFAULT_TEMPLATE = "ansys"
45
40
  """Default template to use for license headers."""
46
41
  DEFAULT_COPYRIGHT = "ANSYS, Inc. and/or its affiliates."
@@ -190,6 +185,9 @@ def link_assets(assets: dict, git_root: str, args: argparse.Namespace) -> None:
190
185
  """
191
186
  Link the default template and/or license from the assets folder to your git repo.
192
187
 
188
+ Only creates symlinks or generates files if they don't already exist or
189
+ point to the wrong target, avoiding unnecessary filesystem churn.
190
+
193
191
  Parameters
194
192
  ----------
195
193
  assets: dict
@@ -199,9 +197,6 @@ def link_assets(assets: dict, git_root: str, args: argparse.Namespace) -> None:
199
197
  args: argparse.Namespace
200
198
  Namespace of arguments with their values.
201
199
  """
202
- # Unlink default files & remove .reuse and LICENSES folders if empty
203
- cleanup(assets, git_root)
204
-
205
200
  # Get the location of the hook in the file system
206
201
  hook_loc = Path(__file__).parent.resolve()
207
202
 
@@ -224,7 +219,8 @@ def link_assets(assets: dict, git_root: str, args: argparse.Namespace) -> None:
224
219
  repo_license_file = repo_asset_dir / value["default_file"]
225
220
  if not repo_asset_dir.is_dir():
226
221
  repo_asset_dir.mkdir(parents=True)
227
- generate_license_file(hook_license_file.parent, dt.today().year, repo_license_file)
222
+ if not repo_license_file.is_file():
223
+ generate_license_file(hook_license_file.parent, dt.today().year, repo_license_file)
228
224
 
229
225
 
230
226
  def generate_license_file(
@@ -242,6 +238,8 @@ def generate_license_file(
242
238
  license_file_name: Path
243
239
  Path to the license file in the repository to generate.
244
240
  """
241
+ from jinja2 import Environment, FileSystemLoader
242
+
245
243
  loader = FileSystemLoader(searchpath=template_parent_dir)
246
244
  env = Environment(loader=loader) # nosec
247
245
  # Get the template for the specified file
@@ -260,6 +258,9 @@ def mkdirs_and_link(
260
258
  """
261
259
  Make .reuse or LICENSES directory and create symbolic link to file.
262
260
 
261
+ Skips symlink creation if destination already points to the correct source,
262
+ avoiding unnecessary filesystem operations on every invocation.
263
+
263
264
  Parameters
264
265
  ----------
265
266
  asset_dir: str
@@ -273,67 +274,57 @@ def mkdirs_and_link(
273
274
  """
274
275
  src = Path(hook_asset_dir) / filename
275
276
  dest = Path(repo_asset_dir) / filename
277
+
278
+ # If symlink already points to the correct target, skip
279
+ if dest.is_symlink() and dest.resolve() == src.resolve():
280
+ return
281
+
276
282
  # If .reuse/templates or LICENSES directories do not exist, create them
277
283
  if not Path(asset_dir).is_dir():
278
284
  Path(asset_dir).mkdir(parents=True)
285
+
286
+ # Remove stale symlink or file before creating new one
287
+ if dest.exists() or dest.is_symlink():
288
+ dest.unlink()
289
+
279
290
  # Make symbolic links to files within the assets folder
280
291
  dest.symlink_to(src)
281
292
 
282
293
 
283
- def recursive_file_check(
284
- changed_headers: int, obj: common.ClickObj, values: dict, args: argparse.Namespace, count: int
285
- ) -> int:
286
- """Check if the committed file is missing its header.
294
+ def _has_current_header(file_path: str, copyright: list, years: str) -> bool:
295
+ """Fast check if the file already has the expected copyright and year range.
296
+
297
+ Reads only the first 1024 bytes to avoid the overhead of full REUSE parsing.
298
+ This is a heuristic — files that pass this check are very likely compliant
299
+ and don't need the expensive update_header flow.
287
300
 
288
301
  Parameters
289
302
  ----------
290
- changed_headers: int
291
- ``0`` if no headers were added or updated.
292
- ``1`` if headers were added or updated.
293
- obj: common.ClickObj
294
- A click object used in `REUSE <https://reuse.software/>`_ to annotate files.
295
- values: dict
296
- Dictionary containing the values of files, copyright,
297
- template, license, changed_headers, year, and git_repo.
298
- args: argparse.Namespace
299
- Namespace of arguments with their values.
300
- count: int
301
- Integer of the location in the files array.
303
+ file_path: str
304
+ Path to the file to check.
305
+ copyright: list
306
+ List containing the copyright string. For example, ["ANSYS, Inc. and/or its affiliates."].
307
+ years: str
308
+ The expected year span. For example, "2023 - 2026" or "2026".
302
309
 
303
310
  Returns
304
311
  -------
305
- int
306
- ``0`` if all files contain headers and are up to date.
307
- ``1`` if ``REUSE`` changed all noncompliant files.
312
+ bool
313
+ ``True`` if the file header appears to already have the correct copyright and years.
314
+ ``False`` if the file may need a header update.
308
315
  """
309
- project, template, commented, license, pre_commit_files, copyright, years = set_variables(
310
- obj, values, args
311
- )
312
-
313
- if count < len(pre_commit_files):
314
- # Get the file name at count from pre_commit_files
315
- file = pre_commit_files[count]
316
- # Get the reuse information of the file
317
- file_reuse_info = project.reuse_info_of(file)
318
-
319
- if (not file_reuse_info) or (Path(file).stat().st_size == 0):
320
- changed_headers = 1
321
- # Add the header to the file
322
- add_header(copyright, license, years, file, template, commented, sys.stdout)
323
- # Check if the next file is in missing_headers
324
- return recursive_file_check(changed_headers, obj, values, args, count + 1)
325
- elif file_reuse_info:
326
- # Update the header
327
- changed_headers = update_header(
328
- changed_headers, file, copyright, license, years, template, commented
329
- )
330
- return recursive_file_check(changed_headers, obj, values, args, count + 1)
316
+ try:
317
+ with Path(file_path).open(encoding="utf-8", errors="ignore") as f:
318
+ head = f.read(1024)
319
+ except OSError:
320
+ return False
331
321
 
332
- return changed_headers
322
+ # Check both the copyright holder and year range are present in the header
323
+ return copyright[0] in head and years in head
333
324
 
334
325
 
335
326
  def non_recursive_file_check(
336
- changed_headers: int, obj: common.ClickObj, values: dict, args: argparse.Namespace
327
+ changed_headers: int, obj: "common.ClickObj", values: dict, args: argparse.Namespace
337
328
  ) -> int:
338
329
  """
339
330
  Check if the committed file is missing its header.
@@ -362,6 +353,10 @@ def non_recursive_file_check(
362
353
  )
363
354
 
364
355
  for file in pre_commit_files:
356
+ # Fast check: skip files that already have the correct header
357
+ if _has_current_header(file, copyright, years):
358
+ continue
359
+
365
360
  # Get the reuse information of the file
366
361
  file_reuse_info = project.reuse_info_of(file)
367
362
 
@@ -369,7 +364,7 @@ def non_recursive_file_check(
369
364
  if (not file_reuse_info) or (Path(file).stat().st_size == 0):
370
365
  changed_headers = 1
371
366
  add_header(copyright, license, years, file, template, commented, sys.stdout)
372
- elif file_reuse_info:
367
+ else:
373
368
  changed_headers = update_header(
374
369
  changed_headers, file, copyright, license, years, template, commented
375
370
  )
@@ -377,7 +372,7 @@ def non_recursive_file_check(
377
372
  return changed_headers
378
373
 
379
374
 
380
- def set_variables(obj: common.ClickObj, values: dict, args: argparse.Namespace) -> tuple:
375
+ def set_variables(obj: "common.ClickObj", values: dict, args: argparse.Namespace) -> tuple:
381
376
  """Set variables to run `REUSE <https://reuse.software/>`_ on the project.
382
377
 
383
378
  Parameters
@@ -395,6 +390,8 @@ def set_variables(obj: common.ClickObj, values: dict, args: argparse.Namespace)
395
390
  tuple
396
391
  Tuple containing the project, template, commented, license, files, copyright, and years.
397
392
  """
393
+ from reuse.cli.annotate import get_template
394
+
398
395
  project = obj.project
399
396
  template, commented = get_template(values["template"], project)
400
397
 
@@ -461,16 +458,16 @@ def update_header(
461
458
  # Check if the file before add-license-headers was run is the same as the one
462
459
  # after add-license-headers was run. If not, apply the syntax changes
463
460
  # from other hooks before add-license-headers was run to the file
464
- if check_same_content(before_hook, file) == False:
461
+ if check_same_content(before_hook, file) is False:
465
462
  apply_hook_changes(before_hook, file)
466
463
 
467
464
  # Update the year span in the header if necessary
468
465
  years_list = years.split(" - ")
469
466
  if len(years_list) == 1:
470
- if years_list != DEFAULT_START_YEAR:
467
+ if int(years_list[0]) != DEFAULT_START_YEAR:
471
468
  years_list.append(DEFAULT_START_YEAR)
472
469
  else:
473
- years_list.append(years_list)
470
+ years_list.append(years_list[0])
474
471
  changed_headers = update_year_range(
475
472
  changed_headers, file, YEAR_REGEX, years_list[0], years_list[1]
476
473
  )
@@ -478,7 +475,7 @@ def update_header(
478
475
  # Check if the file content before add-license-headers was run has been changed
479
476
  # Assuming the syntax was fixed in the above if statement, this check is
480
477
  # solely for the file's content
481
- if check_same_content(before_hook, file) == False:
478
+ if check_same_content(before_hook, file) is False:
482
479
  changed_headers = 1
483
480
  print(f"Successfully changed header of {file}")
484
481
 
@@ -517,12 +514,18 @@ def add_header(
517
514
  tmp: Union[NamedTemporaryFile, IO[str]]
518
515
  Temporary file to capture the stdout of the add_header_to_file() function or ``sys.stdout``.
519
516
  """
517
+ from reuse.cli.annotate import add_header_to_file, get_comment_style, get_reuse_info
518
+ from reuse.copyright import YearRange
519
+
520
+ # Create a YearRange object from the years string to pass into the get_reuse_info function
521
+ year_range = YearRange.tuple_from_string(years)
522
+
520
523
  # Get the REUSE information from the file.
521
524
  reuse_info = get_reuse_info(
522
525
  copyrights=copyright,
523
526
  licenses=license,
524
527
  copyright_prefix="string-c",
525
- year=years,
528
+ years=year_range,
526
529
  contributors="",
527
530
  )
528
531
 
@@ -537,6 +540,15 @@ def add_header(
537
540
  out=out,
538
541
  )
539
542
 
543
+ # Add a space before and after the year range if there is not already one
544
+ with Path(file).open(encoding="utf-8", newline="", mode="r") as read_file:
545
+ content = read_file.read()
546
+ content = re.sub(r"(\d{4})-(\d{4})", r"\1 - \2", content)
547
+
548
+ # Write the updated content back to the file
549
+ with Path(file).open(encoding="utf-8", newline="", mode="w") as write_file:
550
+ write_file.write(content)
551
+
540
552
 
541
553
  def check_same_content(before_hook: str, after_hook: str) -> bool:
542
554
  """
@@ -556,12 +568,7 @@ def check_same_content(before_hook: str, after_hook: str) -> bool:
556
568
  ``False`` if the files have different content.
557
569
  """
558
570
  # Check if the files have the same content
559
- same_files = filecmp.cmp(before_hook, after_hook, shallow=False)
560
- # If the files are different, return False. Otherwise, return True
561
- if same_files == False:
562
- return False
563
- else:
564
- return True
571
+ return filecmp.cmp(before_hook, after_hook, shallow=False)
565
572
 
566
573
 
567
574
  def apply_hook_changes(before_hook: str, after_hook: str) -> None:
@@ -586,6 +593,8 @@ def apply_hook_changes(before_hook: str, after_hook: str) -> None:
586
593
  # the file after add-license-header was run.
587
594
  for line in after_hook_lines:
588
595
  # Copy the new reuse lines into the file
596
+ from reuse import extract
597
+
589
598
  if extract.contains_reuse_info(line):
590
599
  count += 1
591
600
  found_reuse_info = True
@@ -625,8 +634,8 @@ def get_content(file: str) -> str:
625
634
  str
626
635
  Content of the file.
627
636
  """
628
- read_file = Path(file).open(encoding="utf-8", newline="", mode="r")
629
- content = read_file.readlines()
637
+ with Path(file).open(encoding="utf-8", newline="", mode="r") as read_file:
638
+ content = read_file.readlines()
630
639
 
631
640
  return content
632
641
 
@@ -767,6 +776,8 @@ def main():
767
776
  else:
768
777
  raise Exception("Please ensure the start year is a number.")
769
778
 
779
+ import git
780
+
770
781
  # Get root directory of the git repository.
771
782
  git_repo = git.Repo(Path.cwd(), search_parent_directories=True)
772
783
  # Get the root of the git repository and fix the line separators
@@ -818,22 +829,22 @@ def main():
818
829
  # Update the year span in the LICENSE file
819
830
  license_return_code = update_license_file(repo_license_path, year_span)
820
831
 
832
+ from reuse.cli import common
833
+
821
834
  # Create click object for the project
822
835
  obj = common.ClickObj(git_root)
823
836
 
824
837
  # Add or update headers of required files.
825
838
  # Return 1 if files were added or updated, and return 0 if no files were altered.
826
- if len(values["files"]) <= (sys.getrecursionlimit() - 2):
827
- file_return_code = recursive_file_check(changed_headers, obj, values, args, 0)
828
- else:
829
- file_return_code = non_recursive_file_check(changed_headers, obj, values, args)
839
+ # No recursion needed because simply iterating over a flat list
840
+ file_return_code = non_recursive_file_check(changed_headers, obj, values, args)
830
841
 
831
842
  # Unlink default files & remove .reuse and LICENSES folders if empty
832
843
  cleanup(assets, git_root)
833
844
 
834
845
  # Returns 1 if REUSE changes noncompliant files or the year was updated in LICENSE
835
846
  # Returns 0 if all files are compliant
836
- return 1 if (license_return_code or file_return_code) == 1 else 0
847
+ return 1 if license_return_code or file_return_code else 0
837
848
 
838
849
 
839
850
  if __name__ == "__main__":