readability-cli 0.8.6__tar.gz → 0.9.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.8.6 → readability_cli-0.9.0}/.github/workflows/update-guides.yml +1 -1
- {readability_cli-0.8.6 → readability_cli-0.9.0}/PKG-INFO +40 -38
- {readability_cli-0.8.6 → readability_cli-0.9.0}/README.md +39 -37
- {readability_cli-0.8.6 → readability_cli-0.9.0}/pyproject.toml +4 -7
- readability_cli-0.9.0/readability/__init__.py +0 -0
- readability_cli-0.9.0/readability/checking.py +265 -0
- readability_cli-0.9.0/readability/cli.py +264 -0
- readability_cli-0.9.0/readability/guide.py +250 -0
- readability_cli-0.9.0/readability/outline.py +681 -0
- readability_cli-0.9.0/readability/tools.py +407 -0
- readability_cli-0.9.0/test_checking.py +1427 -0
- readability_cli-0.9.0/test_guide.py +223 -0
- readability_cli-0.9.0/test_outline.py +952 -0
- readability_cli-0.9.0/test_package.py +25 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0}/uv.lock +1 -1
- readability_cli-0.8.6/.prettierignore +0 -2
- readability_cli-0.8.6/readability.py +0 -1815
- readability_cli-0.8.6/test_readability.py +0 -2450
- {readability_cli-0.8.6 → readability_cli-0.9.0}/.github/workflows/ci.yml +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0}/.github/workflows/publish.yml +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0}/.gitignore +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0}/.python-version +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0}/LICENSE +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/configs/biome-default.json +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/configs/pyrefly.toml +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/configs/ruff.toml +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/Rguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/cppguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/csharp-style.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/docguide-style.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/go-guide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/htmlcssguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/javaguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/jsguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/jsoncstyleguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/objcguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/pyguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/shellguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/tsguide.md +0 -0
- {readability_cli-0.8.6 → readability_cli-0.9.0/readability}/guides/vimscriptguide.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: readability-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -23,9 +23,9 @@ quick access to style conventions without browsing HTML pages.
|
|
|
23
23
|
## Features
|
|
24
24
|
|
|
25
25
|
- **Linting & Formatting**: A `check` command that automatically detects and
|
|
26
|
-
runs relevant tools (Ruff, Pyrefly, Biome,
|
|
27
|
-
- **
|
|
28
|
-
|
|
26
|
+
runs relevant tools (Ruff, Pyrefly, Biome, and gofmt) for your project.
|
|
27
|
+
- **Predictable Ownership**: Every supported format has fixed tools, so
|
|
28
|
+
configuration customizes checks without changing which formatter runs.
|
|
29
29
|
- **Style Guides**: A `guide` command that fetches the latest Google style
|
|
30
30
|
guides (Python, Shell, C++, Java, JS/TS, Go, etc.) converted to Markdown, and
|
|
31
31
|
outlines, addresses, and searches them by section rather than serving 200 KB
|
|
@@ -78,8 +78,8 @@ uv run readability sync
|
|
|
78
78
|
## Checking and Formatting
|
|
79
79
|
|
|
80
80
|
The `check` command identifies and runs relevant linting and formatting tools
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
solely from file extensions. Configuration can customize the assigned tools,
|
|
82
|
+
but cannot opt another tool into a format:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
85
|
# Run checks on the current directory
|
|
@@ -92,21 +92,22 @@ readability check src/ tests/ main.py
|
|
|
92
92
|
readability check . --fix
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
### Supported
|
|
95
|
+
### Supported Formats
|
|
96
96
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
| **Prettier** | `.js`, `.ts`, `.jsx`, `.tsx`, `.json`, `.css`, `.scss`, `.html`, `.md`, `.yml`, `.yaml` | none for Markdown, YAML, and SCSS | `npx` |
|
|
103
|
-
| **gofmt** | `.go` | `go.mod` | — |
|
|
97
|
+
| Formats | Owners | Project configuration |
|
|
98
|
+
| ---------------------------------------------------------------- | --------------------------------------- | ------------------------------------- |
|
|
99
|
+
| `.py` | Ruff lint/format; Pyrefly type checking | Ruff and Pyrefly native configuration |
|
|
100
|
+
| `.js`, `.jsx`, `.ts`, `.tsx`, `.json`, `.jsonc`, `.css`, `.html` | Biome | `biome.json` or `biome.jsonc` |
|
|
101
|
+
| `.go` | gofmt | None |
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
Markdown, YAML, SCSS, JSONL, and extensions not listed above are unsupported.
|
|
104
|
+
An unsupported-only path reports that nothing was checked and exits
|
|
105
|
+
successfully.
|
|
106
|
+
|
|
107
|
+
Biome first checks a project's `node_modules/.bin`. Ruff, Pyrefly, and Biome
|
|
108
|
+
then use `PATH`, followed by `uvx` for the Python tools or `npx` for Biome. The
|
|
109
|
+
runners cache downloads, so subsequent runs work offline. Gofmt ships with Go
|
|
110
|
+
and must be available on `PATH`.
|
|
110
111
|
|
|
111
112
|
That keeps this package at ~4 MB rather than the ~54 MB it would take to carry
|
|
112
113
|
Ruff and Pyrefly itself — a cost that would fall on everyone using only `guide`.
|
|
@@ -123,18 +124,17 @@ over a fetched one.
|
|
|
123
124
|
Set `UV_OFFLINE=1` to forbid fetching. A tool that then cannot be reached fails
|
|
124
125
|
the run rather than passing it.
|
|
125
126
|
|
|
126
|
-
Ruff, Pyrefly, and Biome ship with bundled configurations, so they run on
|
|
127
|
-
file they
|
|
128
|
-
|
|
129
|
-
formats into Prettier too. Gofmt waits for a `go.mod` file.
|
|
127
|
+
Ruff, Pyrefly, and Biome ship with bundled configurations, so they run on every
|
|
128
|
+
file they own without project setup. Gofmt runs on `.go` files even when there
|
|
129
|
+
is no `go.mod`.
|
|
130
130
|
|
|
131
131
|
A tool that could not be reached at all is never skipped quietly:
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
134
|
# Some tools ran, so the result stands, but coverage was partial
|
|
135
135
|
$ readability check src/
|
|
136
|
-
Warning: not installed, so not run:
|
|
137
|
-
No findings in 1 path(s) (ruff
|
|
136
|
+
Warning: not installed, so not run: pyrefly.
|
|
137
|
+
No findings in 1 path(s) (ruff).
|
|
138
138
|
|
|
139
139
|
# Nothing ran, so there is no result to report
|
|
140
140
|
$ readability check src/
|
|
@@ -157,7 +157,7 @@ or exit-code decisions:
|
|
|
157
157
|
```python
|
|
158
158
|
from pathlib import Path
|
|
159
159
|
|
|
160
|
-
from readability import check_paths
|
|
160
|
+
from readability.checking import check_paths
|
|
161
161
|
|
|
162
162
|
report = check_paths(["src", Path("tests")], project_root=Path.cwd())
|
|
163
163
|
if report.findings or report.failed or not report.ran:
|
|
@@ -171,22 +171,23 @@ raises `FileNotFoundError` rather than being misreported as a finding. Relative
|
|
|
171
171
|
paths remain relative to the process working directory; `project_root` controls
|
|
172
172
|
configuration discovery only.
|
|
173
173
|
|
|
174
|
-
###
|
|
174
|
+
### Configuring Formats
|
|
175
175
|
|
|
176
176
|
Ruff and Pyrefly defaults follow the
|
|
177
177
|
[Google Python style guide](https://google.github.io/styleguide/pyguide.html):
|
|
178
178
|
80-column lines, Google docstrings, import ordering, and full type checking. The
|
|
179
179
|
Biome default applies the 80-column lines and two-space indentation of the
|
|
180
180
|
[Google JavaScript style guide](https://google.github.io/styleguide/jsguide.html)
|
|
181
|
-
and enables Biome's recommended lint rules.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
and enables Biome's recommended lint rules. Project `biome.json` and
|
|
182
|
+
`biome.jsonc` files replace those bundled defaults for Biome-owned formats.
|
|
183
|
+
|
|
184
|
+
For Python, add `[tool.ruff]` or `[tool.pyrefly]` to `pyproject.toml`, or use
|
|
185
|
+
`ruff.toml`, `.ruff.toml`, or `pyrefly.toml`. Ruff, Pyrefly, and Biome load
|
|
186
|
+
their native project configurations in place of bundled defaults. Readability
|
|
187
|
+
does not interpret EditorConfig itself; a canonical tool such as Biome may opt
|
|
188
|
+
into it through that tool's native configuration. Gofmt has no project settings.
|
|
189
|
+
The bundled Biome file requires Biome 2.5 or later, matching the fallback
|
|
190
|
+
runner's version floor.
|
|
190
191
|
|
|
191
192
|
## Style Guides
|
|
192
193
|
|
|
@@ -338,14 +339,15 @@ readability sync
|
|
|
338
339
|
|
|
339
340
|
### Offline Mode
|
|
340
341
|
|
|
341
|
-
The tool stores local copies of the style guides in the
|
|
342
|
+
The tool stores local copies of the style guides in the
|
|
343
|
+
`readability/guides/` directory and
|
|
342
344
|
the `guide` command uses these local files when they exist. The bundled copies
|
|
343
345
|
are automatically synchronized weekly from the official
|
|
344
346
|
[Google Style Guides](https://google.github.io/styleguide/) repository via
|
|
345
347
|
GitHub Actions, and you can refresh your local cache at any time with the `sync`
|
|
346
348
|
command.
|
|
347
349
|
|
|
348
|
-
You can override the default
|
|
350
|
+
You can override the default guide directory by setting the
|
|
349
351
|
`READABILITY_CACHE` environment variable. This is useful if you want to store
|
|
350
352
|
the guides in a specific location or share them across different installations:
|
|
351
353
|
|
|
@@ -9,9 +9,9 @@ quick access to style conventions without browsing HTML pages.
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- **Linting & Formatting**: A `check` command that automatically detects and
|
|
12
|
-
runs relevant tools (Ruff, Pyrefly, Biome,
|
|
13
|
-
- **
|
|
14
|
-
|
|
12
|
+
runs relevant tools (Ruff, Pyrefly, Biome, and gofmt) for your project.
|
|
13
|
+
- **Predictable Ownership**: Every supported format has fixed tools, so
|
|
14
|
+
configuration customizes checks without changing which formatter runs.
|
|
15
15
|
- **Style Guides**: A `guide` command that fetches the latest Google style
|
|
16
16
|
guides (Python, Shell, C++, Java, JS/TS, Go, etc.) converted to Markdown, and
|
|
17
17
|
outlines, addresses, and searches them by section rather than serving 200 KB
|
|
@@ -64,8 +64,8 @@ uv run readability sync
|
|
|
64
64
|
## Checking and Formatting
|
|
65
65
|
|
|
66
66
|
The `check` command identifies and runs relevant linting and formatting tools
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
solely from file extensions. Configuration can customize the assigned tools,
|
|
68
|
+
but cannot opt another tool into a format:
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
71
|
# Run checks on the current directory
|
|
@@ -78,21 +78,22 @@ readability check src/ tests/ main.py
|
|
|
78
78
|
readability check . --fix
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
### Supported
|
|
81
|
+
### Supported Formats
|
|
82
82
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
| **Prettier** | `.js`, `.ts`, `.jsx`, `.tsx`, `.json`, `.css`, `.scss`, `.html`, `.md`, `.yml`, `.yaml` | none for Markdown, YAML, and SCSS | `npx` |
|
|
89
|
-
| **gofmt** | `.go` | `go.mod` | — |
|
|
83
|
+
| Formats | Owners | Project configuration |
|
|
84
|
+
| ---------------------------------------------------------------- | --------------------------------------- | ------------------------------------- |
|
|
85
|
+
| `.py` | Ruff lint/format; Pyrefly type checking | Ruff and Pyrefly native configuration |
|
|
86
|
+
| `.js`, `.jsx`, `.ts`, `.tsx`, `.json`, `.jsonc`, `.css`, `.html` | Biome | `biome.json` or `biome.jsonc` |
|
|
87
|
+
| `.go` | gofmt | None |
|
|
90
88
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
Markdown, YAML, SCSS, JSONL, and extensions not listed above are unsupported.
|
|
90
|
+
An unsupported-only path reports that nothing was checked and exits
|
|
91
|
+
successfully.
|
|
92
|
+
|
|
93
|
+
Biome first checks a project's `node_modules/.bin`. Ruff, Pyrefly, and Biome
|
|
94
|
+
then use `PATH`, followed by `uvx` for the Python tools or `npx` for Biome. The
|
|
95
|
+
runners cache downloads, so subsequent runs work offline. Gofmt ships with Go
|
|
96
|
+
and must be available on `PATH`.
|
|
96
97
|
|
|
97
98
|
That keeps this package at ~4 MB rather than the ~54 MB it would take to carry
|
|
98
99
|
Ruff and Pyrefly itself — a cost that would fall on everyone using only `guide`.
|
|
@@ -109,18 +110,17 @@ over a fetched one.
|
|
|
109
110
|
Set `UV_OFFLINE=1` to forbid fetching. A tool that then cannot be reached fails
|
|
110
111
|
the run rather than passing it.
|
|
111
112
|
|
|
112
|
-
Ruff, Pyrefly, and Biome ship with bundled configurations, so they run on
|
|
113
|
-
file they
|
|
114
|
-
|
|
115
|
-
formats into Prettier too. Gofmt waits for a `go.mod` file.
|
|
113
|
+
Ruff, Pyrefly, and Biome ship with bundled configurations, so they run on every
|
|
114
|
+
file they own without project setup. Gofmt runs on `.go` files even when there
|
|
115
|
+
is no `go.mod`.
|
|
116
116
|
|
|
117
117
|
A tool that could not be reached at all is never skipped quietly:
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
120
|
# Some tools ran, so the result stands, but coverage was partial
|
|
121
121
|
$ readability check src/
|
|
122
|
-
Warning: not installed, so not run:
|
|
123
|
-
No findings in 1 path(s) (ruff
|
|
122
|
+
Warning: not installed, so not run: pyrefly.
|
|
123
|
+
No findings in 1 path(s) (ruff).
|
|
124
124
|
|
|
125
125
|
# Nothing ran, so there is no result to report
|
|
126
126
|
$ readability check src/
|
|
@@ -143,7 +143,7 @@ or exit-code decisions:
|
|
|
143
143
|
```python
|
|
144
144
|
from pathlib import Path
|
|
145
145
|
|
|
146
|
-
from readability import check_paths
|
|
146
|
+
from readability.checking import check_paths
|
|
147
147
|
|
|
148
148
|
report = check_paths(["src", Path("tests")], project_root=Path.cwd())
|
|
149
149
|
if report.findings or report.failed or not report.ran:
|
|
@@ -157,22 +157,23 @@ raises `FileNotFoundError` rather than being misreported as a finding. Relative
|
|
|
157
157
|
paths remain relative to the process working directory; `project_root` controls
|
|
158
158
|
configuration discovery only.
|
|
159
159
|
|
|
160
|
-
###
|
|
160
|
+
### Configuring Formats
|
|
161
161
|
|
|
162
162
|
Ruff and Pyrefly defaults follow the
|
|
163
163
|
[Google Python style guide](https://google.github.io/styleguide/pyguide.html):
|
|
164
164
|
80-column lines, Google docstrings, import ordering, and full type checking. The
|
|
165
165
|
Biome default applies the 80-column lines and two-space indentation of the
|
|
166
166
|
[Google JavaScript style guide](https://google.github.io/styleguide/jsguide.html)
|
|
167
|
-
and enables Biome's recommended lint rules.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
and enables Biome's recommended lint rules. Project `biome.json` and
|
|
168
|
+
`biome.jsonc` files replace those bundled defaults for Biome-owned formats.
|
|
169
|
+
|
|
170
|
+
For Python, add `[tool.ruff]` or `[tool.pyrefly]` to `pyproject.toml`, or use
|
|
171
|
+
`ruff.toml`, `.ruff.toml`, or `pyrefly.toml`. Ruff, Pyrefly, and Biome load
|
|
172
|
+
their native project configurations in place of bundled defaults. Readability
|
|
173
|
+
does not interpret EditorConfig itself; a canonical tool such as Biome may opt
|
|
174
|
+
into it through that tool's native configuration. Gofmt has no project settings.
|
|
175
|
+
The bundled Biome file requires Biome 2.5 or later, matching the fallback
|
|
176
|
+
runner's version floor.
|
|
176
177
|
|
|
177
178
|
## Style Guides
|
|
178
179
|
|
|
@@ -324,14 +325,15 @@ readability sync
|
|
|
324
325
|
|
|
325
326
|
### Offline Mode
|
|
326
327
|
|
|
327
|
-
The tool stores local copies of the style guides in the
|
|
328
|
+
The tool stores local copies of the style guides in the
|
|
329
|
+
`readability/guides/` directory and
|
|
328
330
|
the `guide` command uses these local files when they exist. The bundled copies
|
|
329
331
|
are automatically synchronized weekly from the official
|
|
330
332
|
[Google Style Guides](https://google.github.io/styleguide/) repository via
|
|
331
333
|
GitHub Actions, and you can refresh your local cache at any time with the `sync`
|
|
332
334
|
command.
|
|
333
335
|
|
|
334
|
-
You can override the default
|
|
336
|
+
You can override the default guide directory by setting the
|
|
335
337
|
`READABILITY_CACHE` environment variable. This is useful if you want to store
|
|
336
338
|
the guides in a specific location or share them across different installations:
|
|
337
339
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "readability-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.9.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"
|
|
@@ -16,17 +16,13 @@ Homepage = "https://github.com/owahltinez/readability"
|
|
|
16
16
|
Repository = "https://github.com/owahltinez/readability"
|
|
17
17
|
|
|
18
18
|
[project.scripts]
|
|
19
|
-
readability = "readability:main"
|
|
19
|
+
readability = "readability.cli:main"
|
|
20
20
|
|
|
21
21
|
[tool.uv]
|
|
22
22
|
package = true
|
|
23
23
|
|
|
24
24
|
[tool.hatch.build.targets.wheel]
|
|
25
|
-
|
|
26
|
-
"/readability.py",
|
|
27
|
-
"/guides",
|
|
28
|
-
"/configs",
|
|
29
|
-
]
|
|
25
|
+
packages = ["readability"]
|
|
30
26
|
|
|
31
27
|
[build-system]
|
|
32
28
|
requires = ["hatchling"]
|
|
@@ -54,3 +50,4 @@ convention = "google"
|
|
|
54
50
|
[tool.ruff.lint.per-file-ignores]
|
|
55
51
|
"test_*.py" = ["D"]
|
|
56
52
|
"*_test.py" = ["D"]
|
|
53
|
+
"readability/__init__.py" = ["D104"]
|
|
File without changes
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Run readability checks and report their coverage."""
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import logging
|
|
5
|
+
import subprocess
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from readability.tools import (
|
|
12
|
+
ToolPlan,
|
|
13
|
+
_command_batches,
|
|
14
|
+
_get_tool_definitions,
|
|
15
|
+
_should_run_tool,
|
|
16
|
+
_tool_is_installed,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("readability")
|
|
20
|
+
DEFAULT_TIMEOUT = 60
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclasses.dataclass
|
|
24
|
+
class CheckReport:
|
|
25
|
+
"""What a check actually did, as opposed to what it was asked to do.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
findings: Whether any tool that ran reported something.
|
|
29
|
+
ran: Names of the tools that processed at least one file. This records
|
|
30
|
+
what happened, not what was intended. A tool that resolved but
|
|
31
|
+
never started belongs in failed, and a tool that explicitly
|
|
32
|
+
reported processing no files is not recorded as having run.
|
|
33
|
+
skipped: Names of the tools that were applicable but not installed.
|
|
34
|
+
Tools that own no requested file are in neither set, so passing
|
|
35
|
+
without them is not a gap in coverage.
|
|
36
|
+
failed: Names of the tools that started and could not finish, by
|
|
37
|
+
failing to exec or by running past the timeout.
|
|
38
|
+
unverified_paths: Requested paths for which no tool processed a file.
|
|
39
|
+
Keeping these paths through aggregation prevents a checked path
|
|
40
|
+
from hiding another path that a tool ignored.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
findings: bool = False
|
|
44
|
+
ran: set[str] = dataclasses.field(default_factory=set)
|
|
45
|
+
skipped: set[str] = dataclasses.field(default_factory=set)
|
|
46
|
+
failed: set[str] = dataclasses.field(default_factory=set)
|
|
47
|
+
unverified_paths: list[Path] = dataclasses.field(default_factory=list)
|
|
48
|
+
|
|
49
|
+
def absorb(self, other: "CheckReport") -> None:
|
|
50
|
+
"""Fold another report into this one.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
other: The report to merge, typically for one more path.
|
|
54
|
+
"""
|
|
55
|
+
self.findings |= other.findings
|
|
56
|
+
self.ran |= other.ran
|
|
57
|
+
self.skipped |= other.skipped
|
|
58
|
+
self.failed |= other.failed
|
|
59
|
+
self.unverified_paths.extend(other.unverified_paths)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_paths(
|
|
63
|
+
paths: Sequence[str | Path],
|
|
64
|
+
project_root: Path | None = None,
|
|
65
|
+
fix: bool = False,
|
|
66
|
+
) -> CheckReport:
|
|
67
|
+
"""Run relevant checks for paths and aggregate what the tools did.
|
|
68
|
+
|
|
69
|
+
Detailed findings from the underlying tools are written as they are for
|
|
70
|
+
the command-line interface. This function does not print CLI status prose
|
|
71
|
+
or turn the report into a process exit code; callers decide how to handle
|
|
72
|
+
the result.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
paths: Files or directories to check, as strings or paths. Relative
|
|
76
|
+
paths are interpreted from the current working directory.
|
|
77
|
+
project_root: Root used only to discover tool configuration. Defaults
|
|
78
|
+
to the current working directory; it does not rebase paths.
|
|
79
|
+
fix: Whether to apply automatic fixes.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A report aggregated across all provided paths.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
FileNotFoundError: If any requested path does not exist. Every path is
|
|
86
|
+
validated before any tools run.
|
|
87
|
+
"""
|
|
88
|
+
root = Path.cwd() if project_root is None else project_root
|
|
89
|
+
requested_paths = [Path(path) for path in paths]
|
|
90
|
+
missing_path = next(
|
|
91
|
+
(path for path in requested_paths if not path.exists()), None
|
|
92
|
+
)
|
|
93
|
+
if missing_path is not None:
|
|
94
|
+
raise FileNotFoundError(f"Path does not exist: {missing_path}")
|
|
95
|
+
|
|
96
|
+
report = CheckReport()
|
|
97
|
+
for path in requested_paths:
|
|
98
|
+
path_report = _check_path(path, root, fix=fix)
|
|
99
|
+
if not path_report.ran:
|
|
100
|
+
path_report.unverified_paths.append(path)
|
|
101
|
+
report.absorb(path_report)
|
|
102
|
+
return report
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _check_path(
|
|
106
|
+
path: Path, project_root: Path, fix: bool = False
|
|
107
|
+
) -> CheckReport:
|
|
108
|
+
"""Apply relevant tools to a single path.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
path: The path (file or directory) to check.
|
|
112
|
+
project_root: The root used for native tool configuration discovery.
|
|
113
|
+
fix: Whether to apply automatic fixes.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
What the tools applicable to this path did.
|
|
117
|
+
"""
|
|
118
|
+
logger.info("Checking path: %s", path)
|
|
119
|
+
|
|
120
|
+
# Iterate through all supported tool definitions
|
|
121
|
+
report = CheckReport()
|
|
122
|
+
for tool in _get_tool_definitions(path, project_root):
|
|
123
|
+
if not _should_run_tool(tool, path):
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
# A tool that is wanted but absent leaves a hole in the coverage,
|
|
127
|
+
# which is not the same as a clean result
|
|
128
|
+
if not _tool_is_installed(tool):
|
|
129
|
+
report.skipped.add(tool.name)
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
report.absorb(_run_tool(tool, fix=fix))
|
|
133
|
+
|
|
134
|
+
return report
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _run_tool(
|
|
138
|
+
tool: ToolPlan,
|
|
139
|
+
fix: bool = False,
|
|
140
|
+
) -> CheckReport:
|
|
141
|
+
"""Orchestrate the execution of a specific formatting or linting tool.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
tool: The executable plan to run.
|
|
145
|
+
fix: Whether to apply automatic fixes.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
What the tool did: whether it reported findings, and whether it
|
|
149
|
+
completed at all. A tool that could not be started or ran past the
|
|
150
|
+
timeout verified nothing, and saying so is the difference between a
|
|
151
|
+
pass and a command that only looks like one.
|
|
152
|
+
"""
|
|
153
|
+
logger.info("Running %s...", tool.name)
|
|
154
|
+
|
|
155
|
+
report = CheckReport()
|
|
156
|
+
target_count = len(tool.targets or ())
|
|
157
|
+
try:
|
|
158
|
+
if fix:
|
|
159
|
+
# Formatters rewrite files, fixers apply what they can. Both
|
|
160
|
+
# exit non-zero when something is left over, which is a finding
|
|
161
|
+
# rather than a failure, so the check below still gets to run
|
|
162
|
+
# and report what they could not deal with.
|
|
163
|
+
for phase in ("format", "fix"):
|
|
164
|
+
configured_command = getattr(tool, phase)
|
|
165
|
+
if configured_command:
|
|
166
|
+
commands = _command_batches(
|
|
167
|
+
configured_command, target_count
|
|
168
|
+
)
|
|
169
|
+
for command in commands:
|
|
170
|
+
result = _capture_tool_command(command, cwd=tool.cwd)
|
|
171
|
+
if _tool_checked_files(tool.name, result):
|
|
172
|
+
report.ran.add(tool.name)
|
|
173
|
+
if result.returncode != 0:
|
|
174
|
+
report.findings = True
|
|
175
|
+
finding_type = (
|
|
176
|
+
"formatting findings"
|
|
177
|
+
if phase == "format"
|
|
178
|
+
else "findings"
|
|
179
|
+
)
|
|
180
|
+
click.echo(
|
|
181
|
+
f"--- {tool.name} {finding_type} ---\n"
|
|
182
|
+
f"{result.stdout}\n{result.stderr}"
|
|
183
|
+
)
|
|
184
|
+
elif tool.check_format:
|
|
185
|
+
for command in _command_batches(tool.check_format, target_count):
|
|
186
|
+
result = _capture_tool_command(command, cwd=tool.cwd)
|
|
187
|
+
if _tool_checked_files(tool.name, result):
|
|
188
|
+
report.ran.add(tool.name)
|
|
189
|
+
# gofmt reports by naming files rather than by exit code
|
|
190
|
+
if result.returncode != 0 or (
|
|
191
|
+
tool.name == "gofmt" and result.stdout.strip()
|
|
192
|
+
):
|
|
193
|
+
report.findings = True
|
|
194
|
+
click.echo(
|
|
195
|
+
f"--- {tool.name} formatting findings ---\n"
|
|
196
|
+
f"{result.stdout}\n{result.stderr}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if tool.check:
|
|
200
|
+
for command in _command_batches(tool.check, target_count):
|
|
201
|
+
result = _capture_tool_command(command, cwd=tool.cwd)
|
|
202
|
+
if _tool_checked_files(tool.name, result):
|
|
203
|
+
report.ran.add(tool.name)
|
|
204
|
+
if result.returncode != 0:
|
|
205
|
+
report.findings = True
|
|
206
|
+
click.echo(
|
|
207
|
+
f"--- {tool.name} findings ---\n"
|
|
208
|
+
f"{result.stdout}\n{result.stderr}"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Failing to start, or running past the timeout, means this tool checked
|
|
212
|
+
# nothing. Whatever it managed before that stays in ran.
|
|
213
|
+
except (subprocess.SubprocessError, OSError) as e:
|
|
214
|
+
logger.warning("Could not run %s: %s", tool.name, e)
|
|
215
|
+
report.failed.add(tool.name)
|
|
216
|
+
|
|
217
|
+
return report
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _tool_checked_files(
|
|
221
|
+
tool_name: str, result: subprocess.CompletedProcess
|
|
222
|
+
) -> bool:
|
|
223
|
+
"""Report whether a completed command actually processed any files.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
tool_name: The tool whose command completed.
|
|
227
|
+
result: The completed subprocess.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
False when Biome explicitly reports that it checked zero files.
|
|
231
|
+
"""
|
|
232
|
+
if tool_name != "biome":
|
|
233
|
+
return True
|
|
234
|
+
output = f"{result.stdout or ''}\n{result.stderr or ''}"
|
|
235
|
+
# Biome 2.x uses these summaries for an unmatched target.
|
|
236
|
+
zero_file_summaries = ("Checked 0 files", "Formatted 0 files")
|
|
237
|
+
return not any(summary in output for summary in zero_file_summaries)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _capture_tool_command(
|
|
241
|
+
cmd: list[str], cwd: Path | None = None
|
|
242
|
+
) -> subprocess.CompletedProcess:
|
|
243
|
+
"""Run a tool and capture what it said, whatever its exit code.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
cmd: The command list to execute.
|
|
247
|
+
cwd: Directory in which the tool should run. Defaults to the caller's
|
|
248
|
+
current working directory.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
The completed process, including captured output and its exit code.
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
subprocess.SubprocessError: If the command outlives the timeout.
|
|
255
|
+
OSError: If the command cannot be started.
|
|
256
|
+
"""
|
|
257
|
+
logger.debug("Executing: %s", " ".join(cmd))
|
|
258
|
+
return subprocess.run(
|
|
259
|
+
cmd,
|
|
260
|
+
capture_output=True,
|
|
261
|
+
text=True,
|
|
262
|
+
check=False,
|
|
263
|
+
timeout=DEFAULT_TIMEOUT,
|
|
264
|
+
cwd=cwd,
|
|
265
|
+
)
|