Skip to content

Commit 241ced4

Browse files
dchaudhari7177claudegaborbernat
authored
Add a --check flag that reports formatting without writing (#361)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Bernát Gábor <gaborjbernat@gmail.com>
1 parent 0734df6 commit 241ced4

6 files changed

Lines changed: 102 additions & 27 deletions

File tree

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ See [pre-commit](https://github.com/pre-commit/pre-commit) for instructions
2020
Sample `.pre-commit-config.yaml`:
2121

2222
```yaml
23-
- repo: https://github.com/tox-dev/tox-ini-fmt
24-
rev: "1.3.1"
25-
hooks:
26-
- id: tox-ini-fmt
27-
args: ["-p", "fix_lint,type"]
23+
- repo: https://github.com/tox-dev/tox-ini-fmt
24+
rev: 1.3.1
25+
hooks:
26+
- id: tox-ini-fmt
27+
args: [-p, 'fix_lint,type']
2828
```
2929
3030
## cli
@@ -33,14 +33,15 @@ Consult the help for the latest usage:
3333
3434
```console
3535
$ tox-ini-fmt --help
36-
usage: tox-ini-fmt [-h] [-s] [-p toxenv] tox_ini
36+
usage: tox-ini-fmt [-h] [-s | --check] [-p toxenv] tox_ini [tox_ini ...]
3737

3838
positional arguments:
39-
tox_ini tox ini file to format
39+
tox_ini tox ini files to format
4040

41-
optional arguments:
41+
options:
4242
-h, --help show this help message and exit
4343
-s, --stdout print the formatted text to the stdout (instead of update in-place)
44+
--check check files are formatted without writing them back (exit code 1 on change)
4445
-p toxenv tox environments that pin to the start of the envlist (comma separated)
4546
```
4647

pyproject.toml

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ test = [
6464
"covdefaults>=2.3",
6565
"pytest>=8.4.2",
6666
"pytest-cov>=7",
67+
"pytest-mock>=3.15.1",
6768
]
6869
type = [
6970
"ty>=0.0.17",
@@ -99,23 +100,23 @@ lint.select = [
99100
"ALL",
100101
]
101102
lint.ignore = [
102-
"COM812", # Conflict with formatter
103-
"CPY", # No copyright statements
104-
"D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible
105-
"D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible
106-
"DOC", # no support for sphinx
107-
"ISC001", # Conflict with formatter
108-
"RUF067", # `__init__` module should only contain docstrings and re-exports
103+
"CPY", # No copyright statements
104+
"DOC", # no support for sphinx
105+
"incorrect-blank-line-before-class", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible
106+
"missing-trailing-comma", # Conflict with formatter
107+
"multi-line-summary-first-line", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible
108+
"non-empty-init-module", # `__init__` module should only contain docstrings and re-exports
109+
"single-line-implicit-string-concatenation", # Conflict with formatter
109110
]
110111
lint.per-file-ignores."tests/**/*.py" = [
111-
"D", # don't care about documentation in tests
112-
"FBT", # don't care about booleans as positional arguments in tests
113-
"INP001", # no implicit namespace
114-
"PLC2701", # private import is fine
115-
"PLR0917", # Too many positional argument
116-
"PLR2004", # Magic value used in comparison, consider replacing with a constant variable
117-
"S", # no safety concerns
118-
"S101", # asserts allowed in tests
112+
"assert", # asserts allowed in tests
113+
"D", # don't care about documentation in tests
114+
"FBT", # don't care about booleans as positional arguments in tests
115+
"implicit-namespace-package", # no implicit namespace
116+
"import-private-name", # private import is fine
117+
"magic-value-comparison", # Magic value used in comparison, consider replacing with a constant variable
118+
"S", # no safety concerns
119+
"too-many-positional-arguments", # Too many positional argument
119120
]
120121
lint.isort = { known-first-party = [
121122
"tox_uv",

src/tox_ini_fmt/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def run(args: Sequence[str] | None = None) -> int:
5353
if opts.stdout: # stdout just prints new format to stdout
5454
print(formatted, end="") # ruff:ignore[print]
5555
else:
56-
if before != formatted:
56+
if before != formatted and not opts.check:
5757
with tox_ini.open("wt", encoding="utf-8", newline=original_newlines) as file:
5858
file.write(formatted)
5959
try:

src/tox_ini_fmt/cli.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ToxIniFmtNamespace(Namespace):
1616

1717
tox_ini: list[Path]
1818
stdout: bool
19+
check: bool
1920
pin_toxenvs: list[str]
2021

2122

@@ -50,12 +51,18 @@ def cli_args(args: Sequence[str]) -> ToxIniFmtNamespace:
5051
:return: the parsed options
5152
"""
5253
parser = ArgumentParser()
53-
parser.add_argument(
54+
output_mode = parser.add_mutually_exclusive_group()
55+
output_mode.add_argument(
5456
"-s",
5557
"--stdout",
5658
action="store_true",
5759
help="print the formatted text to the stdout (instead of update in-place)",
5860
)
61+
output_mode.add_argument(
62+
"--check",
63+
action="store_true",
64+
help="check files are formatted without writing them back (exit code 1 on change)",
65+
)
5966

6067
class CommaSeparatedStr(Action):
6168
def __call__(

tests/test_cli.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,27 @@ def test_tox_ini_resolved(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> No
7979
path.write_text("")
8080
result = cli_args(["tox.ini"])
8181
assert result.tox_ini[0] == path
82+
83+
84+
@pytest.mark.parametrize(
85+
("args", "check"),
86+
[
87+
pytest.param([], False, id="off"),
88+
pytest.param(["--check"], True, id="on"),
89+
],
90+
)
91+
def test_cli_check(tmp_path: Path, args: list[str], check: bool) -> None:
92+
path = tmp_path / "tox.ini"
93+
path.write_text("")
94+
assert cli_args([str(path), *args]).check is check
95+
96+
97+
def test_cli_check_and_stdout_are_exclusive(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
98+
path = tmp_path / "tox.ini"
99+
path.write_text("")
100+
with pytest.raises(SystemExit) as context:
101+
cli_args([str(path), "--check", "--stdout"])
102+
assert context.value.code != 0
103+
out, err = capsys.readouterr()
104+
assert not out
105+
assert "not allowed with argument" in err

tests/test_main.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
import pytest
99

10-
import tox_ini_fmt.__main__
1110
from tox_ini_fmt.__main__ import GREEN, RED, RESET, color_diff, run
1211

1312
if TYPE_CHECKING:
1413
from pathlib import Path
1514

15+
from pytest_mock import MockerFixture
16+
1617

1718
def test_color_diff() -> None:
1819
# Arrange
@@ -96,8 +97,9 @@ def test_main( # ruff:ignore[too-many-arguments]
9697
outcome: str,
9798
output: str,
9899
monkeypatch: pytest.MonkeyPatch,
100+
mocker: MockerFixture,
99101
) -> None:
100-
monkeypatch.setattr(tox_ini_fmt.__main__, "color_diff", no_color)
102+
mocker.patch("tox_ini_fmt.__main__.color_diff", no_color)
101103
if cwd:
102104
monkeypatch.chdir(tmp_path)
103105
tox_ini = tmp_path / "tox.ini"
@@ -150,3 +152,43 @@ def test_non_ascii_ignores_locale_encoding(tmp_path: Path) -> None:
150152
"tox_ini_fmt",
151153
str(tox_ini),
152154
])
155+
156+
157+
@pytest.mark.parametrize(
158+
("start", "code", "output"),
159+
[
160+
pytest.param(
161+
"[tox]\nrequires =\n tox>=4.2\nenv_list=py311,py310",
162+
1,
163+
"--- tox.ini\n\n+++ tox.ini\n\n@@ -1,4 +1,6 @@\n\n "
164+
"[tox]\n requires =\n tox>=4.2\n-env_list=py311,py310\n+env_list =\n+ py311\n+ py310\n",
165+
id="change",
166+
),
167+
pytest.param(
168+
"[tox]\nrequires =\n tox>=4.2\nenv_list =\n py311\n py310\n",
169+
0,
170+
"no change for tox.ini\n",
171+
id="no-change",
172+
),
173+
],
174+
)
175+
def test_main_check( # ruff:ignore[too-many-arguments]
176+
tmp_path: Path,
177+
capsys: pytest.CaptureFixture[str],
178+
monkeypatch: pytest.MonkeyPatch,
179+
mocker: MockerFixture,
180+
start: str,
181+
code: int,
182+
output: str,
183+
) -> None:
184+
mocker.patch("tox_ini_fmt.__main__.color_diff", no_color)
185+
monkeypatch.chdir(tmp_path)
186+
tox_ini = tmp_path / "tox.ini"
187+
tox_ini.write_text(start)
188+
189+
assert run([str(tox_ini), "--check"]) == code
190+
191+
assert tox_ini.read_text() == start
192+
out, err = capsys.readouterr()
193+
assert not err
194+
assert out == output

0 commit comments

Comments
 (0)