readability-cli 0.4.0__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.
- {readability_cli-0.4.0 → readability_cli-0.6.0}/PKG-INFO +28 -5
- {readability_cli-0.4.0 → readability_cli-0.6.0}/README.md +26 -3
- {readability_cli-0.4.0 → readability_cli-0.6.0}/pyproject.toml +1 -1
- {readability_cli-0.4.0 → readability_cli-0.6.0}/readability.py +82 -9
- {readability_cli-0.4.0 → readability_cli-0.6.0}/test_readability.py +150 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/uv.lock +1 -1
- {readability_cli-0.4.0 → readability_cli-0.6.0}/.github/workflows/ci.yml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/.github/workflows/publish.yml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/.github/workflows/update-guides.yml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/.gitignore +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/.python-version +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/LICENSE +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/configs/pyrefly.toml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/configs/ruff.toml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/Rguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/cppguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/csharp-style.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/docguide-style.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/go-guide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/htmlcssguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/javaguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/jsguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/jsoncstyleguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/objcguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/pyguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/shellguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/tsguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.6.0}/guides/vimscriptguide.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: readability-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A CLI to lint, format, and type-check code with Google-style defaults, and pull Google style guides in markdown format.
|
|
5
5
|
Project-URL: Homepage, https://github.com/owahltinez/readability
|
|
6
6
|
Project-URL: Repository, https://github.com/owahltinez/readability
|
|
@@ -37,10 +37,10 @@ You can run the tool directly without installing it using `uvx`:
|
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
# Check and fix formatting for the current directory
|
|
40
|
-
uvx --from
|
|
40
|
+
uvx --from readability-cli readability check . --fix
|
|
41
41
|
|
|
42
42
|
# Get the Python style guide
|
|
43
|
-
uvx --from
|
|
43
|
+
uvx --from readability-cli readability guide python
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
## Installation
|
|
@@ -49,7 +49,7 @@ Install it as a global tool with `uv`:
|
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
# Install the readability tool
|
|
52
|
-
uv tool install
|
|
52
|
+
uv tool install readability-cli
|
|
53
53
|
|
|
54
54
|
# Use it anywhere
|
|
55
55
|
readability check .
|
|
@@ -128,10 +128,19 @@ readability guide python --remote
|
|
|
128
128
|
# Save a style guide to a file
|
|
129
129
|
readability guide cpp --output cpp-style.md
|
|
130
130
|
|
|
131
|
+
# Print where the guide already lives, to search it without copying it
|
|
132
|
+
readability guide python --path
|
|
133
|
+
grep -n "Naming" "$(readability guide python --path)"
|
|
134
|
+
|
|
131
135
|
# Synchronize all supported style guides to the local cache
|
|
132
136
|
readability sync
|
|
133
137
|
```
|
|
134
138
|
|
|
139
|
+
A guide can exceed 100 KB, so `--path` is the cheap way to consult one: the
|
|
140
|
+
file is already on disk, and searching it beats reading it whole or writing
|
|
141
|
+
out a second copy. It is also what a coding agent should use, rather than
|
|
142
|
+
redirecting the guide into a repository.
|
|
143
|
+
|
|
135
144
|
### Supported Languages
|
|
136
145
|
|
|
137
146
|
Use `readability languages` to see a full list of supported languages and
|
|
@@ -190,3 +199,17 @@ Check code style with `ruff`:
|
|
|
190
199
|
uv run ruff check .
|
|
191
200
|
uv run ruff format .
|
|
192
201
|
```
|
|
202
|
+
|
|
203
|
+
### Releasing
|
|
204
|
+
|
|
205
|
+
Releases are published to PyPI as
|
|
206
|
+
[`readability-cli`](https://pypi.org/project/readability-cli/) via trusted
|
|
207
|
+
publishing: pushing a `v*` tag triggers the `publish.yml` GitHub Actions
|
|
208
|
+
workflow, which builds the package with `uv build` and uploads it.
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# 1. Bump the version in pyproject.toml, commit, and push
|
|
212
|
+
# 2. Tag the release and push the tag
|
|
213
|
+
git tag v0.4.1
|
|
214
|
+
git push origin v0.4.1
|
|
215
|
+
```
|
|
@@ -23,10 +23,10 @@ You can run the tool directly without installing it using `uvx`:
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
# Check and fix formatting for the current directory
|
|
26
|
-
uvx --from
|
|
26
|
+
uvx --from readability-cli readability check . --fix
|
|
27
27
|
|
|
28
28
|
# Get the Python style guide
|
|
29
|
-
uvx --from
|
|
29
|
+
uvx --from readability-cli readability guide python
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Installation
|
|
@@ -35,7 +35,7 @@ Install it as a global tool with `uv`:
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
# Install the readability tool
|
|
38
|
-
uv tool install
|
|
38
|
+
uv tool install readability-cli
|
|
39
39
|
|
|
40
40
|
# Use it anywhere
|
|
41
41
|
readability check .
|
|
@@ -114,10 +114,19 @@ readability guide python --remote
|
|
|
114
114
|
# Save a style guide to a file
|
|
115
115
|
readability guide cpp --output cpp-style.md
|
|
116
116
|
|
|
117
|
+
# Print where the guide already lives, to search it without copying it
|
|
118
|
+
readability guide python --path
|
|
119
|
+
grep -n "Naming" "$(readability guide python --path)"
|
|
120
|
+
|
|
117
121
|
# Synchronize all supported style guides to the local cache
|
|
118
122
|
readability sync
|
|
119
123
|
```
|
|
120
124
|
|
|
125
|
+
A guide can exceed 100 KB, so `--path` is the cheap way to consult one: the
|
|
126
|
+
file is already on disk, and searching it beats reading it whole or writing
|
|
127
|
+
out a second copy. It is also what a coding agent should use, rather than
|
|
128
|
+
redirecting the guide into a repository.
|
|
129
|
+
|
|
121
130
|
### Supported Languages
|
|
122
131
|
|
|
123
132
|
Use `readability languages` to see a full list of supported languages and
|
|
@@ -176,3 +185,17 @@ Check code style with `ruff`:
|
|
|
176
185
|
uv run ruff check .
|
|
177
186
|
uv run ruff format .
|
|
178
187
|
```
|
|
188
|
+
|
|
189
|
+
### Releasing
|
|
190
|
+
|
|
191
|
+
Releases are published to PyPI as
|
|
192
|
+
[`readability-cli`](https://pypi.org/project/readability-cli/) via trusted
|
|
193
|
+
publishing: pushing a `v*` tag triggers the `publish.yml` GitHub Actions
|
|
194
|
+
workflow, which builds the package with `uv build` and uploads it.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# 1. Bump the version in pyproject.toml, commit, and push
|
|
198
|
+
# 2. Tag the release and push the tag
|
|
199
|
+
git tag v0.4.1
|
|
200
|
+
git push origin v0.4.1
|
|
201
|
+
```
|
|
@@ -253,6 +253,38 @@ def cli(ctx: click.Context, verbose: bool) -> None:
|
|
|
253
253
|
logger.setLevel(logging.DEBUG)
|
|
254
254
|
|
|
255
255
|
|
|
256
|
+
def _echo_guide_path(language: str) -> None:
|
|
257
|
+
"""Print the local path of a language's style guide.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
language: The language whose guide is being located.
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
SystemExit: If the language is unsupported or its guide is absent.
|
|
264
|
+
"""
|
|
265
|
+
filename = LANGUAGE_MAP.get(language.lower())
|
|
266
|
+
if not filename:
|
|
267
|
+
click.echo(
|
|
268
|
+
f"Error: Language '{language}' is not supported. Supported "
|
|
269
|
+
f"languages: {', '.join(sorted(LANGUAGE_MAP.keys()))}",
|
|
270
|
+
err=True,
|
|
271
|
+
)
|
|
272
|
+
sys.exit(1)
|
|
273
|
+
|
|
274
|
+
local_path = get_local_path(filename)
|
|
275
|
+
if not os.path.exists(local_path):
|
|
276
|
+
# Naming a file that is not there would send the caller to read
|
|
277
|
+
# nothing; fetching it is what `sync` is for.
|
|
278
|
+
click.echo(
|
|
279
|
+
f"Error: No local guide for '{language}' at {local_path}. "
|
|
280
|
+
"Run 'readability sync' to fetch it.",
|
|
281
|
+
err=True,
|
|
282
|
+
)
|
|
283
|
+
sys.exit(1)
|
|
284
|
+
|
|
285
|
+
click.echo(local_path)
|
|
286
|
+
|
|
287
|
+
|
|
256
288
|
@cli.command()
|
|
257
289
|
@click.argument("language")
|
|
258
290
|
@click.option(
|
|
@@ -264,9 +296,20 @@ def cli(ctx: click.Context, verbose: bool) -> None:
|
|
|
264
296
|
@click.option(
|
|
265
297
|
"--remote", "-r", is_flag=True, help="Force fetching from the web."
|
|
266
298
|
)
|
|
299
|
+
@click.option(
|
|
300
|
+
"--path",
|
|
301
|
+
"-p",
|
|
302
|
+
"show_path",
|
|
303
|
+
is_flag=True,
|
|
304
|
+
help="Print where the guide is stored instead of its contents.",
|
|
305
|
+
)
|
|
267
306
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging.")
|
|
268
307
|
def guide(
|
|
269
|
-
language: str,
|
|
308
|
+
language: str,
|
|
309
|
+
output: Optional[str],
|
|
310
|
+
remote: bool,
|
|
311
|
+
show_path: bool,
|
|
312
|
+
verbose: bool,
|
|
270
313
|
) -> None:
|
|
271
314
|
"""Fetch the style guide for a specific LANGUAGE."""
|
|
272
315
|
if verbose:
|
|
@@ -274,6 +317,13 @@ def guide(
|
|
|
274
317
|
|
|
275
318
|
logger.info("Processing style guide for: %s", language)
|
|
276
319
|
|
|
320
|
+
# A guide runs to hundreds of kilobytes, so a caller that wants a few
|
|
321
|
+
# rules is better served searching the copy already on disk than
|
|
322
|
+
# reading the whole thing or writing out a second one.
|
|
323
|
+
if show_path:
|
|
324
|
+
_echo_guide_path(language)
|
|
325
|
+
return
|
|
326
|
+
|
|
277
327
|
try:
|
|
278
328
|
# Fetch and process the style guide
|
|
279
329
|
markdown_content = get_guide(language, remote=remote)
|
|
@@ -362,32 +412,47 @@ def languages() -> None:
|
|
|
362
412
|
)
|
|
363
413
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging.")
|
|
364
414
|
def check(paths: Sequence[str], fix: bool, verbose: bool) -> None:
|
|
365
|
-
"""Run relevant formatters and linters for given paths.
|
|
415
|
+
"""Run relevant formatters and linters for given paths.
|
|
416
|
+
|
|
417
|
+
Exits with a non-zero status code if any tool reports findings, so the
|
|
418
|
+
command can gate scripts and CI.
|
|
419
|
+
"""
|
|
366
420
|
if verbose:
|
|
367
421
|
logger.setLevel(logging.DEBUG)
|
|
368
422
|
|
|
369
423
|
# Resolve project root once for trigger file checking
|
|
370
424
|
project_root = Path.cwd()
|
|
371
425
|
|
|
372
|
-
# Process each provided path independently
|
|
426
|
+
# Process each provided path independently, tracking findings across
|
|
427
|
+
# all of them so the exit code reflects the overall result
|
|
428
|
+
found_issues = False
|
|
373
429
|
for path_str in paths:
|
|
374
|
-
_check_path(Path(path_str), project_root, fix=fix)
|
|
430
|
+
found_issues |= _check_path(Path(path_str), project_root, fix=fix)
|
|
375
431
|
|
|
432
|
+
if found_issues:
|
|
433
|
+
sys.exit(1)
|
|
376
434
|
|
|
377
|
-
|
|
435
|
+
|
|
436
|
+
def _check_path(path: Path, project_root: Path, fix: bool = False) -> bool:
|
|
378
437
|
"""Apply relevant tools to a single path.
|
|
379
438
|
|
|
380
439
|
Args:
|
|
381
440
|
path: The path (file or directory) to check.
|
|
382
441
|
project_root: The root of the project for trigger file discovery.
|
|
383
442
|
fix: Whether to apply automatic fixes.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
True if any tool reported findings, False otherwise.
|
|
384
446
|
"""
|
|
385
447
|
logger.info("Checking path: %s", path)
|
|
386
448
|
|
|
387
449
|
# Iterate through all supported tool definitions
|
|
450
|
+
found_issues = False
|
|
388
451
|
for tool in _get_tool_definitions(path, project_root):
|
|
389
452
|
if _should_run_tool(tool, path, project_root):
|
|
390
|
-
_run_tool(tool["name"], tool, fix=fix)
|
|
453
|
+
found_issues |= _run_tool(tool["name"], tool, fix=fix)
|
|
454
|
+
|
|
455
|
+
return found_issues
|
|
391
456
|
|
|
392
457
|
|
|
393
458
|
def _should_run_tool(
|
|
@@ -642,13 +707,16 @@ def _run_tool(
|
|
|
642
707
|
tool_name: str,
|
|
643
708
|
tool_config: dict[str, Any],
|
|
644
709
|
fix: bool = False,
|
|
645
|
-
) ->
|
|
710
|
+
) -> bool:
|
|
646
711
|
"""Orchestrate the execution of a specific formatting or linting tool.
|
|
647
712
|
|
|
648
713
|
Args:
|
|
649
714
|
tool_name: The name of the tool to run.
|
|
650
715
|
tool_config: The tool configuration dictionary.
|
|
651
716
|
fix: Whether to apply automatic fixes.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
True if the tool reported findings, False otherwise.
|
|
652
720
|
"""
|
|
653
721
|
# Identify the primary command to check for executable availability
|
|
654
722
|
cmd = (
|
|
@@ -658,16 +726,17 @@ def _run_tool(
|
|
|
658
726
|
or tool_config.get("check_format")
|
|
659
727
|
)
|
|
660
728
|
if not cmd:
|
|
661
|
-
return
|
|
729
|
+
return False
|
|
662
730
|
|
|
663
731
|
executable = str(cmd[0])
|
|
664
732
|
if not shutil.which(executable):
|
|
665
733
|
logger.debug(
|
|
666
734
|
"Tool %s (%s) not found in PATH, skipping.", tool_name, executable
|
|
667
735
|
)
|
|
668
|
-
return
|
|
736
|
+
return False
|
|
669
737
|
|
|
670
738
|
logger.info("Running %s...", tool_name)
|
|
739
|
+
found_issues = False
|
|
671
740
|
try:
|
|
672
741
|
if fix:
|
|
673
742
|
# 1. Run formatters (if available) - these are expected to
|
|
@@ -691,6 +760,7 @@ def _run_tool(
|
|
|
691
760
|
if result.returncode != 0 or (
|
|
692
761
|
tool_name == "go fmt" and result.stdout.strip()
|
|
693
762
|
):
|
|
763
|
+
found_issues = True
|
|
694
764
|
click.echo(
|
|
695
765
|
f"--- {tool_name} formatting findings ---\n"
|
|
696
766
|
f"{result.stdout}\n{result.stderr}"
|
|
@@ -707,6 +777,7 @@ def _run_tool(
|
|
|
707
777
|
timeout=DEFAULT_TIMEOUT,
|
|
708
778
|
)
|
|
709
779
|
if result.returncode != 0:
|
|
780
|
+
found_issues = True
|
|
710
781
|
click.echo(
|
|
711
782
|
f"--- {tool_name} findings ---\n"
|
|
712
783
|
f"{result.stdout}\n{result.stderr}"
|
|
@@ -721,6 +792,8 @@ def _run_tool(
|
|
|
721
792
|
except (subprocess.SubprocessError, OSError) as e:
|
|
722
793
|
logger.warning("Unexpected error while running %s: %s", tool_name, e)
|
|
723
794
|
|
|
795
|
+
return found_issues
|
|
796
|
+
|
|
724
797
|
|
|
725
798
|
def _execute_tool_command(cmd: list[str]) -> None:
|
|
726
799
|
"""Execute a tool command, raising if it exits with a non-zero code.
|
|
@@ -151,6 +151,52 @@ def test_cli_output_file(mock_guide: MagicMock, tmp_path: Path) -> None:
|
|
|
151
151
|
assert output_file.read_text() == "# Style Guide"
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
def test_cli_path_prints_the_location(tmp_path: Path, monkeypatch) -> None:
|
|
155
|
+
"""Tests that --path reports where the guide already lives on disk.
|
|
156
|
+
|
|
157
|
+
Guides ship with the package, so a caller wanting to search one needs
|
|
158
|
+
its location, not another copy of its contents.
|
|
159
|
+
"""
|
|
160
|
+
monkeypatch.setenv("READABILITY_CACHE", str(tmp_path))
|
|
161
|
+
(tmp_path / "pyguide.md").write_text("# Style Guide")
|
|
162
|
+
|
|
163
|
+
runner = CliRunner()
|
|
164
|
+
result = runner.invoke(cli, ["guide", "python", "--path"])
|
|
165
|
+
|
|
166
|
+
assert result.exit_code == 0
|
|
167
|
+
assert result.output.strip() == str(tmp_path / "pyguide.md")
|
|
168
|
+
assert "# Style Guide" not in result.output
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_cli_path_reports_a_guide_not_yet_fetched(
|
|
172
|
+
tmp_path: Path, monkeypatch
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Tests that a missing local guide fails instead of naming a phantom.
|
|
175
|
+
|
|
176
|
+
Printing a path to a file that is not there would send the caller to
|
|
177
|
+
read nothing at all.
|
|
178
|
+
"""
|
|
179
|
+
monkeypatch.setenv("READABILITY_CACHE", str(tmp_path))
|
|
180
|
+
|
|
181
|
+
runner = CliRunner()
|
|
182
|
+
result = runner.invoke(cli, ["guide", "python", "--path"])
|
|
183
|
+
|
|
184
|
+
assert result.exit_code == 1
|
|
185
|
+
assert "sync" in result.output
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_cli_path_rejects_an_unsupported_language(
|
|
189
|
+
tmp_path: Path, monkeypatch
|
|
190
|
+
) -> None:
|
|
191
|
+
"""Tests that --path validates the language like the default path does."""
|
|
192
|
+
monkeypatch.setenv("READABILITY_CACHE", str(tmp_path))
|
|
193
|
+
|
|
194
|
+
runner = CliRunner()
|
|
195
|
+
result = runner.invoke(cli, ["guide", "nonexistent", "--path"])
|
|
196
|
+
|
|
197
|
+
assert result.exit_code == 1
|
|
198
|
+
|
|
199
|
+
|
|
154
200
|
@patch("readability.get_guide")
|
|
155
201
|
def test_cli_verbose(mock_guide: MagicMock) -> None:
|
|
156
202
|
"""Tests CLI with verbose flag.
|
|
@@ -559,3 +605,107 @@ def test_bundled_default_configs_are_valid(tmp_path: Path) -> None:
|
|
|
559
605
|
ruff_config = tomllib.loads(_bundled_config("ruff").read_text())
|
|
560
606
|
assert ruff_config["line-length"] == 80
|
|
561
607
|
assert ruff_config["lint"]["pydocstyle"]["convention"] == "google"
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
@patch("shutil.which")
|
|
611
|
+
@patch("subprocess.run")
|
|
612
|
+
def test_check_command_exits_nonzero_on_format_findings(
|
|
613
|
+
mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path
|
|
614
|
+
) -> None:
|
|
615
|
+
"""Tests that formatting findings produce a non-zero exit code.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
mock_run: The mocked subprocess.run function.
|
|
619
|
+
mock_which: The mocked shutil.which function.
|
|
620
|
+
tmp_path: The temporary directory fixture.
|
|
621
|
+
"""
|
|
622
|
+
mock_which.side_effect = lambda x: x if x == "ruff" else None
|
|
623
|
+
|
|
624
|
+
# Only `ruff format --check` reports findings
|
|
625
|
+
def run_side_effect(cmd, **kwargs):
|
|
626
|
+
if "format" in cmd:
|
|
627
|
+
return MagicMock(
|
|
628
|
+
returncode=1, stdout="Would reformat: script.py", stderr=""
|
|
629
|
+
)
|
|
630
|
+
return MagicMock(returncode=0, stdout="", stderr="")
|
|
631
|
+
|
|
632
|
+
mock_run.side_effect = run_side_effect
|
|
633
|
+
|
|
634
|
+
runner = CliRunner()
|
|
635
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
636
|
+
Path("pyproject.toml").touch()
|
|
637
|
+
Path("script.py").touch()
|
|
638
|
+
|
|
639
|
+
result = runner.invoke(cli, ["check", "script.py"])
|
|
640
|
+
|
|
641
|
+
assert "formatting findings" in result.output
|
|
642
|
+
assert result.exit_code == 1
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
@patch("shutil.which")
|
|
646
|
+
@patch("subprocess.run")
|
|
647
|
+
def test_check_command_exits_nonzero_on_check_findings(
|
|
648
|
+
mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path
|
|
649
|
+
) -> None:
|
|
650
|
+
"""Tests that linter findings produce a non-zero exit code.
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
mock_run: The mocked subprocess.run function.
|
|
654
|
+
mock_which: The mocked shutil.which function.
|
|
655
|
+
tmp_path: The temporary directory fixture.
|
|
656
|
+
"""
|
|
657
|
+
mock_which.side_effect = lambda x: x if x == "ruff" else None
|
|
658
|
+
|
|
659
|
+
# Only `ruff check` reports findings
|
|
660
|
+
def run_side_effect(cmd, **kwargs):
|
|
661
|
+
if "check" in cmd and "format" not in cmd:
|
|
662
|
+
return MagicMock(
|
|
663
|
+
returncode=1, stdout="E501 line too long", stderr=""
|
|
664
|
+
)
|
|
665
|
+
return MagicMock(returncode=0, stdout="", stderr="")
|
|
666
|
+
|
|
667
|
+
mock_run.side_effect = run_side_effect
|
|
668
|
+
|
|
669
|
+
runner = CliRunner()
|
|
670
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
671
|
+
Path("pyproject.toml").touch()
|
|
672
|
+
Path("script.py").touch()
|
|
673
|
+
|
|
674
|
+
result = runner.invoke(cli, ["check", "script.py"])
|
|
675
|
+
|
|
676
|
+
assert "findings" in result.output
|
|
677
|
+
assert result.exit_code == 1
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
@patch("shutil.which")
|
|
681
|
+
@patch("subprocess.run")
|
|
682
|
+
def test_check_command_fix_exits_nonzero_on_remaining_findings(
|
|
683
|
+
mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path
|
|
684
|
+
) -> None:
|
|
685
|
+
"""Tests that findings remaining after --fix produce a non-zero exit.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
mock_run: The mocked subprocess.run function.
|
|
689
|
+
mock_which: The mocked shutil.which function.
|
|
690
|
+
tmp_path: The temporary directory fixture.
|
|
691
|
+
"""
|
|
692
|
+
mock_which.side_effect = lambda x: x if x == "ruff" else None
|
|
693
|
+
|
|
694
|
+
# Fixers succeed, but the check step still reports findings
|
|
695
|
+
def run_side_effect(cmd, **kwargs):
|
|
696
|
+
if "check" in cmd and "--fix" not in cmd:
|
|
697
|
+
return MagicMock(
|
|
698
|
+
returncode=1, stdout="E501 line too long", stderr=""
|
|
699
|
+
)
|
|
700
|
+
return MagicMock(returncode=0, stdout="", stderr="")
|
|
701
|
+
|
|
702
|
+
mock_run.side_effect = run_side_effect
|
|
703
|
+
|
|
704
|
+
runner = CliRunner()
|
|
705
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
706
|
+
Path("pyproject.toml").touch()
|
|
707
|
+
Path("script.py").touch()
|
|
708
|
+
|
|
709
|
+
result = runner.invoke(cli, ["check", "--fix", "script.py"])
|
|
710
|
+
|
|
711
|
+
assert result.exit_code == 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|