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.
Files changed (28) hide show
  1. {readability_cli-0.4.0 → readability_cli-0.5.0}/PKG-INFO +18 -4
  2. {readability_cli-0.4.0 → readability_cli-0.5.0}/README.md +17 -3
  3. {readability_cli-0.4.0 → readability_cli-0.5.0}/pyproject.toml +1 -1
  4. {readability_cli-0.4.0 → readability_cli-0.5.0}/readability.py +31 -8
  5. {readability_cli-0.4.0 → readability_cli-0.5.0}/test_readability.py +104 -0
  6. {readability_cli-0.4.0 → readability_cli-0.5.0}/uv.lock +1 -1
  7. {readability_cli-0.4.0 → readability_cli-0.5.0}/.github/workflows/ci.yml +0 -0
  8. {readability_cli-0.4.0 → readability_cli-0.5.0}/.github/workflows/publish.yml +0 -0
  9. {readability_cli-0.4.0 → readability_cli-0.5.0}/.github/workflows/update-guides.yml +0 -0
  10. {readability_cli-0.4.0 → readability_cli-0.5.0}/.gitignore +0 -0
  11. {readability_cli-0.4.0 → readability_cli-0.5.0}/.python-version +0 -0
  12. {readability_cli-0.4.0 → readability_cli-0.5.0}/LICENSE +0 -0
  13. {readability_cli-0.4.0 → readability_cli-0.5.0}/configs/pyrefly.toml +0 -0
  14. {readability_cli-0.4.0 → readability_cli-0.5.0}/configs/ruff.toml +0 -0
  15. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/Rguide.md +0 -0
  16. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/cppguide.md +0 -0
  17. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/csharp-style.md +0 -0
  18. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/docguide-style.md +0 -0
  19. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/go-guide.md +0 -0
  20. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/htmlcssguide.md +0 -0
  21. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/javaguide.md +0 -0
  22. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/jsguide.md +0 -0
  23. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/jsoncstyleguide.md +0 -0
  24. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/objcguide.md +0 -0
  25. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/pyguide.md +0 -0
  26. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/shellguide.md +0 -0
  27. {readability_cli-0.4.0 → readability_cli-0.5.0}/guides/tsguide.md +0 -0
  28. {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.4.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 git+https://github.com/owahltinez/readability.git readability check . --fix
40
+ uvx --from readability-cli readability check . --fix
41
41
 
42
42
  # Get the Python style guide
43
- uvx --from git+https://github.com/owahltinez/readability.git readability guide python
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 git+https://github.com/owahltinez/readability.git
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 git+https://github.com/owahltinez/readability.git readability check . --fix
26
+ uvx --from readability-cli readability check . --fix
27
27
 
28
28
  # Get the Python style guide
29
- uvx --from git+https://github.com/owahltinez/readability.git readability guide python
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 git+https://github.com/owahltinez/readability.git
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
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "readability-cli"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "A CLI to lint, format, and type-check code with Google-style defaults, and pull Google style guides in markdown format."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -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) -> None:
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
- ) -> None:
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
@@ -194,7 +194,7 @@ wheels = [
194
194
 
195
195
  [[package]]
196
196
  name = "readability-cli"
197
- version = "0.4.0"
197
+ version = "0.5.0"
198
198
  source = { editable = "." }
199
199
  dependencies = [
200
200
  { name = "beautifulsoup4" },
File without changes