readability-cli 0.4.0__tar.gz → 0.5.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.5.0}/PKG-INFO +18 -4
- {readability_cli-0.4.0 → readability_cli-0.5.0}/README.md +17 -3
- {readability_cli-0.4.0 → readability_cli-0.5.0}/pyproject.toml +1 -1
- {readability_cli-0.4.0 → readability_cli-0.5.0}/readability.py +31 -8
- {readability_cli-0.4.0 → readability_cli-0.5.0}/test_readability.py +104 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/uv.lock +1 -1
- {readability_cli-0.4.0 → readability_cli-0.5.0}/.github/workflows/ci.yml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/.github/workflows/publish.yml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/.github/workflows/update-guides.yml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/.gitignore +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/.python-version +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/LICENSE +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/configs/pyrefly.toml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/configs/ruff.toml +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/Rguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/cppguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/csharp-style.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/docguide-style.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/go-guide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/htmlcssguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/javaguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/jsguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/jsoncstyleguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/objcguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/pyguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/shellguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/tsguide.md +0 -0
- {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/vimscriptguide.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: readability-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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 .
|
|
@@ -190,3 +190,17 @@ Check code style with `ruff`:
|
|
|
190
190
|
uv run ruff check .
|
|
191
191
|
uv run ruff format .
|
|
192
192
|
```
|
|
193
|
+
|
|
194
|
+
### Releasing
|
|
195
|
+
|
|
196
|
+
Releases are published to PyPI as
|
|
197
|
+
[`readability-cli`](https://pypi.org/project/readability-cli/) via trusted
|
|
198
|
+
publishing: pushing a `v*` tag triggers the `publish.yml` GitHub Actions
|
|
199
|
+
workflow, which builds the package with `uv build` and uploads it.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# 1. Bump the version in pyproject.toml, commit, and push
|
|
203
|
+
# 2. Tag the release and push the tag
|
|
204
|
+
git tag v0.4.1
|
|
205
|
+
git push origin v0.4.1
|
|
206
|
+
```
|
|
@@ -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 .
|
|
@@ -176,3 +176,17 @@ Check code style with `ruff`:
|
|
|
176
176
|
uv run ruff check .
|
|
177
177
|
uv run ruff format .
|
|
178
178
|
```
|
|
179
|
+
|
|
180
|
+
### Releasing
|
|
181
|
+
|
|
182
|
+
Releases are published to PyPI as
|
|
183
|
+
[`readability-cli`](https://pypi.org/project/readability-cli/) via trusted
|
|
184
|
+
publishing: pushing a `v*` tag triggers the `publish.yml` GitHub Actions
|
|
185
|
+
workflow, which builds the package with `uv build` and uploads it.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# 1. Bump the version in pyproject.toml, commit, and push
|
|
189
|
+
# 2. Tag the release and push the tag
|
|
190
|
+
git tag v0.4.1
|
|
191
|
+
git push origin v0.4.1
|
|
192
|
+
```
|
|
@@ -362,32 +362,47 @@ def languages() -> None:
|
|
|
362
362
|
)
|
|
363
363
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging.")
|
|
364
364
|
def check(paths: Sequence[str], fix: bool, verbose: bool) -> None:
|
|
365
|
-
"""Run relevant formatters and linters for given paths.
|
|
365
|
+
"""Run relevant formatters and linters for given paths.
|
|
366
|
+
|
|
367
|
+
Exits with a non-zero status code if any tool reports findings, so the
|
|
368
|
+
command can gate scripts and CI.
|
|
369
|
+
"""
|
|
366
370
|
if verbose:
|
|
367
371
|
logger.setLevel(logging.DEBUG)
|
|
368
372
|
|
|
369
373
|
# Resolve project root once for trigger file checking
|
|
370
374
|
project_root = Path.cwd()
|
|
371
375
|
|
|
372
|
-
# Process each provided path independently
|
|
376
|
+
# Process each provided path independently, tracking findings across
|
|
377
|
+
# all of them so the exit code reflects the overall result
|
|
378
|
+
found_issues = False
|
|
373
379
|
for path_str in paths:
|
|
374
|
-
_check_path(Path(path_str), project_root, fix=fix)
|
|
380
|
+
found_issues |= _check_path(Path(path_str), project_root, fix=fix)
|
|
381
|
+
|
|
382
|
+
if found_issues:
|
|
383
|
+
sys.exit(1)
|
|
375
384
|
|
|
376
385
|
|
|
377
|
-
def _check_path(path: Path, project_root: Path, fix: bool = False) ->
|
|
386
|
+
def _check_path(path: Path, project_root: Path, fix: bool = False) -> bool:
|
|
378
387
|
"""Apply relevant tools to a single path.
|
|
379
388
|
|
|
380
389
|
Args:
|
|
381
390
|
path: The path (file or directory) to check.
|
|
382
391
|
project_root: The root of the project for trigger file discovery.
|
|
383
392
|
fix: Whether to apply automatic fixes.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
True if any tool reported findings, False otherwise.
|
|
384
396
|
"""
|
|
385
397
|
logger.info("Checking path: %s", path)
|
|
386
398
|
|
|
387
399
|
# Iterate through all supported tool definitions
|
|
400
|
+
found_issues = False
|
|
388
401
|
for tool in _get_tool_definitions(path, project_root):
|
|
389
402
|
if _should_run_tool(tool, path, project_root):
|
|
390
|
-
_run_tool(tool["name"], tool, fix=fix)
|
|
403
|
+
found_issues |= _run_tool(tool["name"], tool, fix=fix)
|
|
404
|
+
|
|
405
|
+
return found_issues
|
|
391
406
|
|
|
392
407
|
|
|
393
408
|
def _should_run_tool(
|
|
@@ -642,13 +657,16 @@ def _run_tool(
|
|
|
642
657
|
tool_name: str,
|
|
643
658
|
tool_config: dict[str, Any],
|
|
644
659
|
fix: bool = False,
|
|
645
|
-
) ->
|
|
660
|
+
) -> bool:
|
|
646
661
|
"""Orchestrate the execution of a specific formatting or linting tool.
|
|
647
662
|
|
|
648
663
|
Args:
|
|
649
664
|
tool_name: The name of the tool to run.
|
|
650
665
|
tool_config: The tool configuration dictionary.
|
|
651
666
|
fix: Whether to apply automatic fixes.
|
|
667
|
+
|
|
668
|
+
Returns:
|
|
669
|
+
True if the tool reported findings, False otherwise.
|
|
652
670
|
"""
|
|
653
671
|
# Identify the primary command to check for executable availability
|
|
654
672
|
cmd = (
|
|
@@ -658,16 +676,17 @@ def _run_tool(
|
|
|
658
676
|
or tool_config.get("check_format")
|
|
659
677
|
)
|
|
660
678
|
if not cmd:
|
|
661
|
-
return
|
|
679
|
+
return False
|
|
662
680
|
|
|
663
681
|
executable = str(cmd[0])
|
|
664
682
|
if not shutil.which(executable):
|
|
665
683
|
logger.debug(
|
|
666
684
|
"Tool %s (%s) not found in PATH, skipping.", tool_name, executable
|
|
667
685
|
)
|
|
668
|
-
return
|
|
686
|
+
return False
|
|
669
687
|
|
|
670
688
|
logger.info("Running %s...", tool_name)
|
|
689
|
+
found_issues = False
|
|
671
690
|
try:
|
|
672
691
|
if fix:
|
|
673
692
|
# 1. Run formatters (if available) - these are expected to
|
|
@@ -691,6 +710,7 @@ def _run_tool(
|
|
|
691
710
|
if result.returncode != 0 or (
|
|
692
711
|
tool_name == "go fmt" and result.stdout.strip()
|
|
693
712
|
):
|
|
713
|
+
found_issues = True
|
|
694
714
|
click.echo(
|
|
695
715
|
f"--- {tool_name} formatting findings ---\n"
|
|
696
716
|
f"{result.stdout}\n{result.stderr}"
|
|
@@ -707,6 +727,7 @@ def _run_tool(
|
|
|
707
727
|
timeout=DEFAULT_TIMEOUT,
|
|
708
728
|
)
|
|
709
729
|
if result.returncode != 0:
|
|
730
|
+
found_issues = True
|
|
710
731
|
click.echo(
|
|
711
732
|
f"--- {tool_name} findings ---\n"
|
|
712
733
|
f"{result.stdout}\n{result.stderr}"
|
|
@@ -721,6 +742,8 @@ def _run_tool(
|
|
|
721
742
|
except (subprocess.SubprocessError, OSError) as e:
|
|
722
743
|
logger.warning("Unexpected error while running %s: %s", tool_name, e)
|
|
723
744
|
|
|
745
|
+
return found_issues
|
|
746
|
+
|
|
724
747
|
|
|
725
748
|
def _execute_tool_command(cmd: list[str]) -> None:
|
|
726
749
|
"""Execute a tool command, raising if it exits with a non-zero code.
|
|
@@ -559,3 +559,107 @@ def test_bundled_default_configs_are_valid(tmp_path: Path) -> None:
|
|
|
559
559
|
ruff_config = tomllib.loads(_bundled_config("ruff").read_text())
|
|
560
560
|
assert ruff_config["line-length"] == 80
|
|
561
561
|
assert ruff_config["lint"]["pydocstyle"]["convention"] == "google"
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
@patch("shutil.which")
|
|
565
|
+
@patch("subprocess.run")
|
|
566
|
+
def test_check_command_exits_nonzero_on_format_findings(
|
|
567
|
+
mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path
|
|
568
|
+
) -> None:
|
|
569
|
+
"""Tests that formatting findings produce a non-zero exit code.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
mock_run: The mocked subprocess.run function.
|
|
573
|
+
mock_which: The mocked shutil.which function.
|
|
574
|
+
tmp_path: The temporary directory fixture.
|
|
575
|
+
"""
|
|
576
|
+
mock_which.side_effect = lambda x: x if x == "ruff" else None
|
|
577
|
+
|
|
578
|
+
# Only `ruff format --check` reports findings
|
|
579
|
+
def run_side_effect(cmd, **kwargs):
|
|
580
|
+
if "format" in cmd:
|
|
581
|
+
return MagicMock(
|
|
582
|
+
returncode=1, stdout="Would reformat: script.py", stderr=""
|
|
583
|
+
)
|
|
584
|
+
return MagicMock(returncode=0, stdout="", stderr="")
|
|
585
|
+
|
|
586
|
+
mock_run.side_effect = run_side_effect
|
|
587
|
+
|
|
588
|
+
runner = CliRunner()
|
|
589
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
590
|
+
Path("pyproject.toml").touch()
|
|
591
|
+
Path("script.py").touch()
|
|
592
|
+
|
|
593
|
+
result = runner.invoke(cli, ["check", "script.py"])
|
|
594
|
+
|
|
595
|
+
assert "formatting findings" in result.output
|
|
596
|
+
assert result.exit_code == 1
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
@patch("shutil.which")
|
|
600
|
+
@patch("subprocess.run")
|
|
601
|
+
def test_check_command_exits_nonzero_on_check_findings(
|
|
602
|
+
mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path
|
|
603
|
+
) -> None:
|
|
604
|
+
"""Tests that linter findings produce a non-zero exit code.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
mock_run: The mocked subprocess.run function.
|
|
608
|
+
mock_which: The mocked shutil.which function.
|
|
609
|
+
tmp_path: The temporary directory fixture.
|
|
610
|
+
"""
|
|
611
|
+
mock_which.side_effect = lambda x: x if x == "ruff" else None
|
|
612
|
+
|
|
613
|
+
# Only `ruff check` reports findings
|
|
614
|
+
def run_side_effect(cmd, **kwargs):
|
|
615
|
+
if "check" in cmd and "format" not in cmd:
|
|
616
|
+
return MagicMock(
|
|
617
|
+
returncode=1, stdout="E501 line too long", stderr=""
|
|
618
|
+
)
|
|
619
|
+
return MagicMock(returncode=0, stdout="", stderr="")
|
|
620
|
+
|
|
621
|
+
mock_run.side_effect = run_side_effect
|
|
622
|
+
|
|
623
|
+
runner = CliRunner()
|
|
624
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
625
|
+
Path("pyproject.toml").touch()
|
|
626
|
+
Path("script.py").touch()
|
|
627
|
+
|
|
628
|
+
result = runner.invoke(cli, ["check", "script.py"])
|
|
629
|
+
|
|
630
|
+
assert "findings" in result.output
|
|
631
|
+
assert result.exit_code == 1
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@patch("shutil.which")
|
|
635
|
+
@patch("subprocess.run")
|
|
636
|
+
def test_check_command_fix_exits_nonzero_on_remaining_findings(
|
|
637
|
+
mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path
|
|
638
|
+
) -> None:
|
|
639
|
+
"""Tests that findings remaining after --fix produce a non-zero exit.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
mock_run: The mocked subprocess.run function.
|
|
643
|
+
mock_which: The mocked shutil.which function.
|
|
644
|
+
tmp_path: The temporary directory fixture.
|
|
645
|
+
"""
|
|
646
|
+
mock_which.side_effect = lambda x: x if x == "ruff" else None
|
|
647
|
+
|
|
648
|
+
# Fixers succeed, but the check step still reports findings
|
|
649
|
+
def run_side_effect(cmd, **kwargs):
|
|
650
|
+
if "check" in cmd and "--fix" not in cmd:
|
|
651
|
+
return MagicMock(
|
|
652
|
+
returncode=1, stdout="E501 line too long", stderr=""
|
|
653
|
+
)
|
|
654
|
+
return MagicMock(returncode=0, stdout="", stderr="")
|
|
655
|
+
|
|
656
|
+
mock_run.side_effect = run_side_effect
|
|
657
|
+
|
|
658
|
+
runner = CliRunner()
|
|
659
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
660
|
+
Path("pyproject.toml").touch()
|
|
661
|
+
Path("script.py").touch()
|
|
662
|
+
|
|
663
|
+
result = runner.invoke(cli, ["check", "--fix", "script.py"])
|
|
664
|
+
|
|
665
|
+
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
|