Skip to content

Commit 70e1f52

Browse files
authored
clang-format hook support --verbose output (#79)
* support verbose output * fix test failure * fix lint failure * refactor clang-format * update testing run.sh * update testing run.sh * update testing run.sh * update README.md * update README.md * update project descrtion * update readme.md * add no cover to main
1 parent 0339700 commit 70e1f52

File tree

6 files changed

+180
-58
lines changed

6 files changed

+180
-58
lines changed

README.md

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,26 @@
55
[![codecov](https://codecov.io/gh/cpp-linter/cpp-linter-hooks/branch/main/graph/badge.svg?token=L74Z3HZ4Y5)](https://codecov.io/gh/cpp-linter/cpp-linter-hooks)
66
[![Test](https://github.com/cpp-linter/cpp-linter-hooks/actions/workflows/test.yml/badge.svg)](https://github.com/cpp-linter/cpp-linter-hooks/actions/workflows/test.yml)
77
[![CodeQL](https://github.com/cpp-linter/cpp-linter-hooks/actions/workflows/codeql.yml/badge.svg)](https://github.com/cpp-linter/cpp-linter-hooks/actions/workflows/codeql.yml)
8-
<!-- [![PyPI - Downloads](https://img.shields.io/pypi/dw/cpp-linter-hooks)](https://pypi.org/project/cpp-linter-hooks/) -->
98

10-
cpp-linter-hooks is a [pre-commit](https://pre-commit.com/) hook that uses `clang-format` and `clang-tidy` to format C/C++ code.
9+
A powerful [pre-commit](https://pre-commit.com/) hook for auto-formatting and linting C/C++ code with `clang-format` and `clang-tidy`.
1110

12-
> [!NOTE]
13-
> This hook automatically downloads specific versions of `clang-format` or `clang-tidy` [static-binaries](https://github.com/cpp-linter/clang-tools-static-binaries) and installs them on your system.
11+
## Table of Contents
1412

15-
## Usage
13+
- [Quick Start](#quick-start)
14+
- [Custom Configuration Files](#custom-configuration-files)
15+
- [Custom Clang Tool Version](#custom-clang-tool-version)
16+
- [Output](#output)
17+
- [clang-format Output](#clang-format-output)
18+
- [clang-tidy Output](#clang-tidy-output)
19+
- [Troubleshooting](#troubleshooting)
20+
- [Performance Optimization](#performance-optimization)
21+
- [Verbose Output](#verbose-output)
22+
- [Contributing](#contributing)
23+
- [License](#license)
1624

17-
To use cpp-linter-hooks, add the following configuration to your `.pre-commit-config.yaml`:
25+
## Quick Start
1826

19-
### Basic Configuration
27+
Add this configuration to your `.pre-commit-config.yaml` file:
2028

2129
```yaml
2230
repos:
@@ -29,7 +37,7 @@ repos:
2937
args: [--checks='boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*,clang-analyzer-*,cppcoreguidelines-*']
3038
```
3139
32-
### Custom Configuration
40+
### Custom Configuration Files
3341
3442
To use custom configurations like `.clang-format` and `.clang-tidy`:
3543

@@ -44,6 +52,8 @@ repos:
4452
args: [--checks=.clang-tidy] # Loads checks from .clang-tidy file
4553
```
4654

55+
### Custom Clang Tool Version
56+
4757
To use specific versions of [clang-tools](https://github.com/cpp-linter/clang-tools-pip?tab=readme-ov-file#supported-versions):
4858

4959
```yaml
@@ -57,33 +67,9 @@ repos:
5767
args: [--checks=.clang-tidy, --version=18] # Specifies version
5868
```
5969

60-
> [!IMPORTANT]
61-
> If your `pre-commit` runs longer than expected, it is highly recommended to add `files` in `.pre-commit-config.yaml` to limit the scope of the hook. This helps improve performance by reducing the number of files being checked and avoids unnecessary processing. Here's an example configuration:
62-
63-
64-
```yaml
65-
- repo: https://github.com/cpp-linter/cpp-linter-hooks
66-
rev: v0.8.1
67-
hooks:
68-
- id: clang-format
69-
args: [--style=file, --version=18]
70-
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$ # Limits to specific dirs and file types
71-
- id: clang-tidy
72-
args: [--checks=.clang-tidy, --version=18]
73-
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$
74-
```
75-
76-
Alternatively, if you want to run the hooks manually on only the changed files, you can use the following command:
77-
78-
```bash
79-
pre-commit run --files $(git diff --name-only)
80-
```
81-
82-
This approach ensures that only modified files are checked, further speeding up the linting process during development.
83-
8470
## Output
8571

86-
### clang-format Example
72+
### clang-format Output
8773

8874
```bash
8975
clang-format.............................................................Failed
@@ -106,8 +92,8 @@ Here’s a sample diff showing the formatting applied:
10692
+ return 0;
10793
+}
10894
```
109-
110-
Use `--dry-run` in `args` of `clang-format` to print instead of changing the format, e.g.:
95+
> [!NOTE]
96+
> Use `--dry-run` in `args` of `clang-format` to print instead of changing the format, e.g.:
11197

11298
```bash
11399
clang-format.............................................................Failed
@@ -134,7 +120,7 @@ int main() {for (;;) break; printf("Hello world!\n");return 0;}
134120
^
135121
```
136122

137-
### clang-tidy Example
123+
### clang-tidy Output
138124

139125
```bash
140126
clang-tidy...............................................................Failed
@@ -151,10 +137,51 @@ Use -header-filter=.* to display errors from all non-system headers. Use -system
151137
152138
```
153139

140+
## Troubleshooting
141+
142+
### Performance Optimization
143+
144+
> [!WARNING]
145+
> If your `pre-commit` runs longer than expected, it is highly recommended to add `files` in `.pre-commit-config.yaml` to limit the scope of the hook. This helps improve performance by reducing the number of files being checked and avoids unnecessary processing. Here's an example configuration:
146+
147+
```yaml
148+
- repo: https://github.com/cpp-linter/cpp-linter-hooks
149+
rev: v0.8.1
150+
hooks:
151+
- id: clang-format
152+
args: [--style=file, --version=18]
153+
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$ # Limits to specific dirs and file types
154+
- id: clang-tidy
155+
args: [--checks=.clang-tidy, --version=18]
156+
files: ^(src|include)/.*\.(cpp|cc|cxx|h|hpp)$
157+
```
158+
159+
Alternatively, if you want to run the hooks manually on only the changed files, you can use the following command:
160+
161+
```bash
162+
pre-commit run --files $(git diff --name-only)
163+
```
164+
165+
This approach ensures that only modified files are checked, further speeding up the linting process during development.
166+
167+
### Verbose Output
168+
169+
> [!NOTE]
170+
> Use `-v` or `--verbose` in `args` of `clang-format` to show the list of processed files e.g.:
171+
172+
```yaml
173+
repos:
174+
- repo: https://github.com/cpp-linter/cpp-linter-hooks
175+
rev: v0.8.1
176+
hooks:
177+
- id: clang-format
178+
args: [--style=file, --version=18, --verbose] # Add -v or --verbose for detailed output
179+
```
180+
154181
## Contributing
155182

156183
We welcome contributions! Whether it's fixing issues, suggesting improvements, or submitting pull requests, your support is greatly appreciated.
157184

158185
## License
159186

160-
cpp-linter-hooks is licensed under the [MIT License](LICENSE)
187+
This project is licensed under the [MIT License](LICENSE).

cpp_linter_hooks/clang_format.py

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import subprocess
2+
import sys
23
from argparse import ArgumentParser
34
from typing import Tuple
45

@@ -7,34 +8,67 @@
78

89
parser = ArgumentParser()
910
parser.add_argument("--version", default=DEFAULT_CLANG_VERSION)
11+
parser.add_argument(
12+
"-v", "--verbose", action="store_true", help="Enable verbose output"
13+
)
1014

1115

1216
def run_clang_format(args=None) -> Tuple[int, str]:
1317
hook_args, other_args = parser.parse_known_args(args)
1418
path = ensure_installed("clang-format", hook_args.version)
1519
command = [str(path), "-i"]
20+
21+
# Add verbose flag if requested
22+
if hook_args.verbose:
23+
command.append("--verbose")
24+
1625
command.extend(other_args)
1726

18-
retval = 0
19-
output = ""
2027
try:
28+
# Run the clang-format command with captured output
29+
sp = subprocess.run(
30+
command,
31+
stdout=subprocess.PIPE,
32+
stderr=subprocess.PIPE,
33+
encoding="utf-8",
34+
)
35+
36+
# Combine stdout and stderr for complete output
37+
output = (sp.stdout or "") + (sp.stderr or "")
38+
39+
# Handle special case for dry-run mode
2140
if "--dry-run" in command:
22-
sp = subprocess.run(command, stdout=subprocess.PIPE, encoding="utf-8")
23-
retval = -1 # Not a fail just identify it's a dry-run.
24-
output = sp.stdout
41+
retval = -1 # Special code to identify dry-run mode
2542
else:
26-
retval = subprocess.run(command, stdout=subprocess.PIPE).returncode
43+
retval = sp.returncode
44+
45+
# Print verbose information if requested
46+
if hook_args.verbose:
47+
_print_verbose_info(command, retval, output)
48+
2749
return retval, output
28-
except FileNotFoundError as stderr:
29-
retval = 1
30-
return retval, str(stderr)
50+
51+
except FileNotFoundError as e:
52+
return 1, str(e)
53+
54+
55+
def _print_verbose_info(command: list, retval: int, output: str) -> None:
56+
"""Print verbose debugging information to stderr."""
57+
print(f"Command executed: {' '.join(command)}", file=sys.stderr)
58+
print(f"Exit code: {retval}", file=sys.stderr)
59+
if output.strip():
60+
print(f"Output: {output}", file=sys.stderr)
3161

3262

3363
def main() -> int:
34-
retval, output = run_clang_format()
35-
if retval != 0:
64+
retval, output = run_clang_format() # pragma: no cover
65+
66+
# Print output for errors, but not for dry-run mode
67+
if retval != 0 and retval != -1 and output.strip(): # pragma: no cover
3668
print(output)
37-
return retval
69+
70+
# Convert dry-run special code to success
71+
return 0 if retval == -1 else retval # pragma: no cover
3872

3973

4074
if __name__ == "__main__":

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ requires-python = ">=3.9"
66

77
[project]
88
name = "cpp_linter_hooks"
9-
description = "Automatically check c/c++ code with clang-format and clang-tidy"
9+
description = "Automatically formats and lints C/C++ code using clang-format and clang-tidy"
1010
readme = "README.md"
1111
keywords = ["clang", "clang-format", "clang-tidy", "pre-commit", "pre-commit-hooks"]
1212
license = "MIT"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
repos:
2+
- repo: .
3+
rev: HEAD
4+
hooks:
5+
- id: clang-format
6+
args: [--style=file, --version=16, --verbose] # test with verbose output
7+
- repo: .
8+
rev: HEAD
9+
hooks:
10+
- id: clang-format
11+
args: [--style=file, --version=16, -v] # test with verbose output

testing/run.sh

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,40 @@
1-
rm -f result.txt
1+
#!/bin/bash
2+
echo "==========================="
3+
echo "Test pre-commit-config.yaml"
4+
echo "==========================="
5+
pre-commit clean
6+
pre-commit run -c testing/pre-commit-config.yaml --files testing/main.c | tee -a result.txt || true
27
git restore testing/main.c
38

4-
for config in testing/pre-commit-config.yaml testing/pre-commit-config-version.yaml; do
5-
pre-commit clean
6-
pre-commit run -c $config --files testing/main.c | tee -a result.txt || true
7-
git restore testing/main.c
8-
done
9+
echo "===================================="
10+
echo "Test pre-commit-config-version.yaml"
11+
echo "===================================="
12+
pre-commit clean
13+
pre-commit run -c testing/pre-commit-config-version.yaml --files testing/main.c | tee -a result.txt || true
14+
git restore testing/main.c
15+
16+
echo "===================================="
17+
echo "Test pre-commit-config-verbose.yaml"
18+
echo "===================================="
19+
pre-commit clean
20+
pre-commit run -c testing/pre-commit-config-verbose.yaml --files testing/main.c | tee -a result.txt || true
21+
git restore testing/main.c
22+
23+
echo "=================================================================================="
24+
echo "print result.txt"
25+
cat result.txt
26+
echo "=================================================================================="
927

1028
failed_cases=`grep -c "Failed" result.txt`
1129

1230
echo $failed_cases " cases failed."
1331

14-
if [ $failed_cases -eq 9 ]; then
32+
if [ $failed_cases -eq 10 ]; then
1533
echo "=============================="
1634
echo "Test cpp-linter-hooks success."
1735
echo "=============================="
18-
exit 0
1936
rm result.txt
37+
exit 0
2038
else
2139
echo "============================="
2240
echo "Test cpp-linter-hooks failed."

tests/test_clang_format.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,35 @@ def test_run_clang_format_dry_run(args, expected_retval, tmp_path):
6464
test_file = tmp_path / "main.c"
6565
ret, _ = run_clang_format(["--dry-run", str(test_file)])
6666
assert ret == -1 # Dry run should not fail
67+
68+
69+
def test_run_clang_format_verbose(tmp_path):
70+
"""Test that verbose option works and provides detailed output."""
71+
# copy test file to tmp_path to prevent modifying repo data
72+
test_file = tmp_path / "main.c"
73+
test_file.write_bytes(Path("testing/main.c").read_bytes())
74+
75+
# Test with verbose flag
76+
ret, _ = run_clang_format(["--verbose", "--style=Google", str(test_file)])
77+
78+
# Should succeed
79+
assert ret == 0
80+
# Should have verbose output (will be printed to stderr, not returned)
81+
# The function should still return successfully
82+
assert test_file.read_text() == Path("testing/good.c").read_text()
83+
84+
85+
def test_run_clang_format_verbose_error(tmp_path):
86+
"""Test that verbose option provides useful error information."""
87+
test_file = tmp_path / "main.c"
88+
test_file.write_bytes(Path("testing/main.c").read_bytes())
89+
90+
# Test with verbose flag and invalid style
91+
ret, output = run_clang_format(
92+
["--verbose", "--style=InvalidStyle", str(test_file)]
93+
)
94+
95+
# Should fail
96+
assert ret != 0
97+
# Should have error message in output
98+
assert "Invalid value for -style" in output

0 commit comments

Comments
 (0)