Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ repos:
- id: validate-pyproject
stages: [pre-commit, manual]

- repo: local
hooks:
# Checks every file NOTICE.yml covers, not only the changed ones; add headers with tools/update_license_headers.py
- id: license-headers
name: license headers (NOTICE.yml)
entry: python tools/check_license_headers.py
language: python
additional_dependencies: [pyyaml]
pass_filenames: false
always_run: true
stages: [pre-commit, manual]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.6
hooks:
Expand Down
50 changes: 50 additions & 0 deletions tools/check_license_headers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#
# DeepLabCut Toolbox (deeplabcut.org)
# © A. & M.W. Mathis Labs
# https://github.com/DeepLabCut/DeepLabCut
#
# Please see AUTHORS for contributors.
# https://github.com/DeepLabCut/DeepLabCut/blob/master/AUTHORS
#
# Licensed under GNU Lesser General Public License v3.0
#

"""Check that every file NOTICE.yml covers starts with its header.

Reads only; use update_license_headers.py for writing.
"""

import sys
from itertools import takewhile
from pathlib import Path

from update_license_headers import load_config, walk_directory


def leading_comment(path: Path) -> list[str]:
"""Return the file's first comment block, with comment markers and one following space removed."""
lines = path.read_text(encoding="utf-8").splitlines()
return [line.lstrip("#").removeprefix(" ").rstrip() for line in takewhile(lambda line: line.startswith("#"), lines)]


def has_header(path: Path, header: list[str]) -> bool:
"""Return whether the header lines appear, in order and contiguous, in the file's first comment block."""
comment = leading_comment(path)
return any(comment[start : start + len(header)] == header for start in range(len(comment) - len(header) + 1))


def main(input_file: str = "NOTICE.yml") -> int:
"""Print each file missing its header and return 1 if there is any, else 0."""
missing = []
for entry in load_config(input_file):
header = [line.rstrip() for line in entry["header"].splitlines()]
missing += [name for name in walk_directory(entry) if not has_header(Path(name), header)]

for name in sorted(missing):
print(f"{name}: missing or outdated license header (see {input_file})")

return 1 if missing else 0


if __name__ == "__main__":
sys.exit(main())
Loading