From ced63781ac520a648f323486cc702947b631c830 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 1 Nov 2025 01:36:45 +0200 Subject: [PATCH 1/5] fix(pyproject.toml): add include-package-data to true --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 3ebd612..ee3ad95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ dev = [ [tool.setuptools] zip-safe = false packages = ["cpp_linter_hooks"] +include-package-data = true [tool.setuptools_scm] # It would be nice to include the commit hash in the version, but that From 9a9eba3e299822320aea77eafdcc37ee1c105641 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 1 Nov 2025 01:42:13 +0200 Subject: [PATCH 2/5] fix: include pyproject.toml --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ee3ad95..b3a5982 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,9 @@ zip-safe = false packages = ["cpp_linter_hooks"] include-package-data = true +[tool.setuptools.package-data] +cpp_linter_hooks = ["../pyproject.toml"] + [tool.setuptools_scm] # It would be nice to include the commit hash in the version, but that # can't be done in a PEP 440-compatible way. From 20337cbf4342ec207b723122dc66de26433552f3 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Sat, 1 Nov 2025 02:02:14 +0200 Subject: [PATCH 3/5] chore: Update pre-commit.yml add types --- .github/workflows/pre-commit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 01775b0..a856754 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -4,6 +4,7 @@ on: push: branches: main pull_request: + types: [opened, synchronize, reopened, edited] branches: main workflow_dispatch: From 604c7524edc3923eb1fbd18effbc377464d254f6 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sat, 1 Nov 2025 02:11:17 +0200 Subject: [PATCH 4/5] fix: update resolve_install to public --- cpp_linter_hooks/clang_format.py | 4 ++-- cpp_linter_hooks/clang_tidy.py | 4 ++-- cpp_linter_hooks/util.py | 2 +- tests/test_util.py | 28 ++++++++++++++-------------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cpp_linter_hooks/clang_format.py b/cpp_linter_hooks/clang_format.py index 0cd0d34..da891e3 100644 --- a/cpp_linter_hooks/clang_format.py +++ b/cpp_linter_hooks/clang_format.py @@ -3,7 +3,7 @@ from argparse import ArgumentParser from typing import Tuple -from cpp_linter_hooks.util import _resolve_install, DEFAULT_CLANG_FORMAT_VERSION +from cpp_linter_hooks.util import resolve_install, DEFAULT_CLANG_FORMAT_VERSION parser = ArgumentParser() @@ -16,7 +16,7 @@ def run_clang_format(args=None) -> Tuple[int, str]: hook_args, other_args = parser.parse_known_args(args) if hook_args.version: - _resolve_install("clang-format", hook_args.version) + resolve_install("clang-format", hook_args.version) command = ["clang-format", "-i"] # Add verbose flag if requested diff --git a/cpp_linter_hooks/clang_tidy.py b/cpp_linter_hooks/clang_tidy.py index f1cb163..1a5c8f0 100644 --- a/cpp_linter_hooks/clang_tidy.py +++ b/cpp_linter_hooks/clang_tidy.py @@ -2,7 +2,7 @@ from argparse import ArgumentParser from typing import Tuple -from cpp_linter_hooks.util import _resolve_install, DEFAULT_CLANG_TIDY_VERSION +from cpp_linter_hooks.util import resolve_install, DEFAULT_CLANG_TIDY_VERSION parser = ArgumentParser() @@ -12,7 +12,7 @@ def run_clang_tidy(args=None) -> Tuple[int, str]: hook_args, other_args = parser.parse_known_args(args) if hook_args.version: - _resolve_install("clang-tidy", hook_args.version) + resolve_install("clang-tidy", hook_args.version) command = ["clang-tidy"] + other_args retval = 0 diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 37d431a..ebd48a3 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -72,7 +72,7 @@ def _install_tool(tool: str, version: str) -> Optional[Path]: return None -def _resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: +def resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: """Resolve the installation of a tool, checking for version and installing if necessary.""" user_version = _resolve_version( CLANG_FORMAT_VERSIONS if tool == "clang-format" else CLANG_TIDY_VERSIONS, diff --git a/tests/test_util.py b/tests/test_util.py index 38d5e6d..8305ba0 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -8,7 +8,7 @@ get_version_from_dependency, _resolve_version, _install_tool, - _resolve_install, + resolve_install, DEFAULT_CLANG_FORMAT_VERSION, DEFAULT_CLANG_TIDY_VERSION, ) @@ -159,22 +159,22 @@ def test_install_tool_success_but_not_found(): assert result is None -# Tests for _resolve_install +# Tests for resolve_install @pytest.mark.benchmark def test_resolve_install_tool_already_installed_correct_version(): - """Test _resolve_install when tool is already installed with correct version.""" + """Test resolve_install when tool is already installed with correct version.""" mock_path = "/usr/bin/clang-format" with ( patch("shutil.which", return_value=mock_path), ): - result = _resolve_install("clang-format", "20.1.7") + result = resolve_install("clang-format", "20.1.7") assert Path(result) == Path(mock_path) @pytest.mark.benchmark def test_resolve_install_tool_version_mismatch(): - """Test _resolve_install when tool has wrong version.""" + """Test resolve_install when tool has wrong version.""" mock_path = "/usr/bin/clang-format" with ( @@ -183,7 +183,7 @@ def test_resolve_install_tool_version_mismatch(): "cpp_linter_hooks.util._install_tool", return_value=Path(mock_path) ) as mock_install, ): - result = _resolve_install("clang-format", "20.1.7") + result = resolve_install("clang-format", "20.1.7") assert result == Path(mock_path) mock_install.assert_called_once_with("clang-format", "20.1.7") @@ -191,7 +191,7 @@ def test_resolve_install_tool_version_mismatch(): @pytest.mark.benchmark def test_resolve_install_tool_not_installed(): - """Test _resolve_install when tool is not installed.""" + """Test resolve_install when tool is not installed.""" with ( patch("shutil.which", return_value=None), patch( @@ -199,7 +199,7 @@ def test_resolve_install_tool_not_installed(): return_value=Path("/usr/bin/clang-format"), ) as mock_install, ): - result = _resolve_install("clang-format", "20.1.7") + result = resolve_install("clang-format", "20.1.7") assert result == Path("/usr/bin/clang-format") mock_install.assert_called_once_with("clang-format", "20.1.7") @@ -207,7 +207,7 @@ def test_resolve_install_tool_not_installed(): @pytest.mark.benchmark def test_resolve_install_no_version_specified(): - """Test _resolve_install when no version is specified.""" + """Test resolve_install when no version is specified.""" with ( patch("shutil.which", return_value=None), patch( @@ -215,7 +215,7 @@ def test_resolve_install_no_version_specified(): return_value=Path("/usr/bin/clang-format"), ) as mock_install, ): - result = _resolve_install("clang-format", None) + result = resolve_install("clang-format", None) assert result == Path("/usr/bin/clang-format") mock_install.assert_called_once_with( @@ -225,7 +225,7 @@ def test_resolve_install_no_version_specified(): @pytest.mark.benchmark def test_resolve_install_invalid_version(): - """Test _resolve_install with invalid version.""" + """Test resolve_install with invalid version.""" with ( patch("shutil.which", return_value=None), patch( @@ -233,7 +233,7 @@ def test_resolve_install_invalid_version(): return_value=Path("/usr/bin/clang-format"), ) as mock_install, ): - result = _resolve_install("clang-format", "invalid.version") + result = resolve_install("clang-format", "invalid.version") assert result == Path("/usr/bin/clang-format") # Should fallback to default version @@ -263,7 +263,7 @@ def test_version_lists_not_empty(): @pytest.mark.benchmark def test_resolve_install_with_none_default_version(): - """Test _resolve_install when DEFAULT versions are None.""" + """Test resolve_install when DEFAULT versions are None.""" with ( patch("shutil.which", return_value=None), patch("cpp_linter_hooks.util.DEFAULT_CLANG_FORMAT_VERSION", None), @@ -273,7 +273,7 @@ def test_resolve_install_with_none_default_version(): return_value=Path("/usr/bin/clang-format"), ) as mock_install, ): - result = _resolve_install("clang-format", None) + result = resolve_install("clang-format", None) assert result == Path("/usr/bin/clang-format") # Should fallback to hardcoded version when DEFAULT is None From b5b3f0b1a532c0b40cc593c0a184de07fe344878 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 00:16:38 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`add?= =?UTF-8?q?-include-package-data`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @shenxianpeng. * https://github.com/cpp-linter/cpp-linter-hooks/pull/138#issuecomment-3475280419 The following files were modified: * `cpp_linter_hooks/clang_format.py` * `cpp_linter_hooks/clang_tidy.py` * `cpp_linter_hooks/util.py` --- cpp_linter_hooks/clang_format.py | 16 +++++++++++++++- cpp_linter_hooks/clang_tidy.py | 13 ++++++++++++- cpp_linter_hooks/util.py | 24 +++++++++++++++++++++--- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/cpp_linter_hooks/clang_format.py b/cpp_linter_hooks/clang_format.py index da891e3..f815f38 100644 --- a/cpp_linter_hooks/clang_format.py +++ b/cpp_linter_hooks/clang_format.py @@ -14,6 +14,20 @@ def run_clang_format(args=None) -> Tuple[int, str]: + """ + Run clang-format with the given arguments and return its exit code and combined output. + + Parses known hook options from `args`, optionally ensures a specific clang-format version is installed, builds and executes a clang-format command (modifying files in-place by default), and captures stdout and stderr merged into a single output string. + + Parameters: + args (Optional[Sequence[str]]): Argument list to parse (typically sys.argv[1:]). If omitted, uses parser defaults. + + Returns: + tuple[int, str]: A pair (retval, output) where `output` is the concatenation of stdout and stderr. + `retval` is the subprocess return code, except: + - `-1` when the command included `--dry-run` (special sentinel to indicate dry-run mode), + - `1` when clang-format could not be found (FileNotFoundError converted to an exit-like code). + """ hook_args, other_args = parser.parse_known_args(args) if hook_args.version: resolve_install("clang-format", hook_args.version) @@ -73,4 +87,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file diff --git a/cpp_linter_hooks/clang_tidy.py b/cpp_linter_hooks/clang_tidy.py index 1a5c8f0..ab968f8 100644 --- a/cpp_linter_hooks/clang_tidy.py +++ b/cpp_linter_hooks/clang_tidy.py @@ -10,6 +10,17 @@ def run_clang_tidy(args=None) -> Tuple[int, str]: + """ + Run clang-tidy with the given command-line arguments and return a status code and captured output. + + Parameters: + args (Optional[Sequence[str]]): Arguments to parse and forward to clang-tidy; if None, uses sys.argv. If the parsed arguments include a --version value, the specified clang-tidy version is ensured to be installed before running. + + Returns: + Tuple[int, str]: A pair (status, output). + - status: 0 when clang-tidy executed and produced no warnings or errors; 1 when clang-tidy reports any "warning:" or "error:", or when clang-tidy cannot be executed. + - output: Captured stdout from clang-tidy, or the error text if execution failed. + """ hook_args, other_args = parser.parse_known_args(args) if hook_args.version: resolve_install("clang-tidy", hook_args.version) @@ -37,4 +48,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index ebd48a3..3cd341d 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -60,7 +60,16 @@ def parse_version(v: str): def _install_tool(tool: str, version: str) -> Optional[Path]: - """Install a tool using pip, suppressing output.""" + """ + Install the specified tool version into the current Python environment and return its executable path. + + Parameters: + tool (str): The package/executable name to install (e.g., "clang-format"). + version (str): The exact version string to install (e.g., "14.0.6"). + + Returns: + Path: Path to the installed tool's executable if the installation succeeds and the executable is found, `None` otherwise. + """ try: subprocess.check_call( [sys.executable, "-m", "pip", "install", f"{tool}=={version}"], @@ -73,7 +82,16 @@ def _install_tool(tool: str, version: str) -> Optional[Path]: def resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: - """Resolve the installation of a tool, checking for version and installing if necessary.""" + """ + Resolve and install the requested clang tool version and return its executable path. + + Parameters: + tool (str): Tool name, expected "clang-format" or "clang-tidy". + version (Optional[str]): Desired version string (exact match or prefix). If None, falls back to the default version from pyproject.toml when available. + + Returns: + Optional[Path]: Path to the installed tool executable if installation succeeded, `None` otherwise. + """ user_version = _resolve_version( CLANG_FORMAT_VERSIONS if tool == "clang-format" else CLANG_TIDY_VERSIONS, version, @@ -85,4 +103,4 @@ def resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: else DEFAULT_CLANG_TIDY_VERSION ) - return _install_tool(tool, user_version) + return _install_tool(tool, user_version) \ No newline at end of file