Skip to content

Commit 3dcd4c7

Browse files
271 implement usethis tool codespell (#281)
* Implememt `usethis tool codespell` * Update README re: codespell * Restore missing splat * Add two tests * Ignore long base-64 strings * Add tests, tweak capitalization * Add TestGetProjectDict for better coverage * Add CLIRunner test for Codespell * Add Codespell integation test for bitbucket
1 parent ba07928 commit 3dcd4c7

12 files changed

Lines changed: 339 additions & 8 deletions

File tree

.pre-commit-config.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ repos:
3434
language: system
3535
always_run: true
3636
pass_filenames: false
37+
- repo: https://github.com/codespell-project/codespell
38+
rev: v2.4.1
39+
hooks:
40+
- id: codespell
41+
additional_dependencies:
42+
- tomli
3743
- repo: local
3844
hooks:
3945
- id: import-linter
@@ -51,9 +57,3 @@ repos:
5157
types_or: [python, pyi]
5258
always_run: true
5359
require_serial: true
54-
- repo: https://github.com/codespell-project/codespell
55-
rev: v2.4.1
56-
hooks:
57-
- id: codespell
58-
additional_dependencies:
59-
- tomli

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Add a new tool to a Python project, including:
108108

109109
Currently supported tools:
110110

111+
- `usethis tool codespell`
111112
- `usethis tool coverage`
112113
- `usethis tool deptry`
113114
- `usethis tool pre-commit`

src/usethis/_core/ci.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
)
1010
from usethis._integrations.uv.init import ensure_pyproject_toml
1111
from usethis._tool import (
12+
CodespellTool,
1213
DeptryTool,
1314
PreCommitTool,
1415
PyprojectFmtTool,
@@ -26,8 +27,14 @@ def use_ci_bitbucket(*, remove: bool = False) -> None:
2627
use_ruff = RuffTool().is_used()
2728
use_deptry = DeptryTool().is_used()
2829
use_pyproject_fmt = PyprojectFmtTool().is_used()
30+
use_codespell = CodespellTool().is_used()
2931
use_any_tool = (
30-
use_pre_commit or use_pytest or use_ruff or use_deptry or use_pyproject_fmt
32+
use_pre_commit
33+
or use_pytest
34+
or use_ruff
35+
or use_deptry
36+
or use_pyproject_fmt
37+
or use_codespell
3138
)
3239

3340
add_bitbucket_pipeline_config(report_placeholder=not use_any_tool)
@@ -43,6 +50,8 @@ def use_ci_bitbucket(*, remove: bool = False) -> None:
4350
add_bitbucket_steps_in_default(RuffTool().get_bitbucket_steps())
4451
if use_deptry:
4552
add_bitbucket_steps_in_default(DeptryTool().get_bitbucket_steps())
53+
if use_codespell:
54+
add_bitbucket_steps_in_default(CodespellTool().get_bitbucket_steps())
4655

4756
if use_pytest:
4857
update_bitbucket_pytest_steps()

src/usethis/_core/tool.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from usethis._integrations.uv.init import ensure_pyproject_toml
3232
from usethis._tool import (
3333
ALL_TOOLS,
34+
CodespellTool,
3435
CoverageTool,
3536
DeptryTool,
3637
PreCommitTool,
@@ -42,6 +43,44 @@
4243
)
4344

4445

46+
def use_codespell(*, remove: bool = False) -> None:
47+
tool = CodespellTool()
48+
49+
ensure_pyproject_toml()
50+
51+
if not remove:
52+
is_pre_commit = PreCommitTool().is_used()
53+
54+
if not is_pre_commit:
55+
add_deps_to_group(tool.dev_deps, "dev")
56+
if is_bitbucket_used():
57+
add_bitbucket_steps_in_default(tool.get_bitbucket_steps())
58+
else:
59+
tool.add_pre_commit_repo_configs()
60+
61+
tool.add_pyproject_configs()
62+
63+
if not is_pre_commit:
64+
_codespell_instructions_basic()
65+
else:
66+
_codespell_instructions_pre_commit()
67+
else:
68+
remove_bitbucket_steps_from_default(tool.get_bitbucket_steps())
69+
tool.remove_pyproject_configs()
70+
tool.remove_pre_commit_repo_configs()
71+
remove_deps_from_group(tool.dev_deps, "dev")
72+
73+
74+
def _codespell_instructions_basic() -> None:
75+
box_print("Run 'codespell' to run the Codespell spellchecker.")
76+
77+
78+
def _codespell_instructions_pre_commit() -> None:
79+
box_print(
80+
"Run 'pre-commit run codespell --all-files' to run the Codespell spellchecker."
81+
)
82+
83+
4584
def use_coverage(*, remove: bool = False) -> None:
4685
tool = CoverageTool()
4786

src/usethis/_integrations/bitbucket/steps.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def _add_step_in_default_via_doc(
163163
"Run pyproject-fmt",
164164
"Run Ruff",
165165
"Run Deptry",
166+
"Run Codespell",
166167
*[f"Test on 3.{maj}" for maj in get_supported_major_python_versions()],
167168
]
168169
for step_name in step_order:

src/usethis/_integrations/pre_commit/hooks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"ruff", # ruff followed by ruff-format seems to be the recommended way by Astral
2222
"ruff-format",
2323
"deptry",
24+
"codespell",
2425
]
2526

2627
_PLACEHOLDER_ID = "placeholder"

src/usethis/_interface/tool.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from usethis._config import offline_opt, quiet_opt, usethis_config
77
from usethis._console import err_print
88
from usethis._core.tool import (
9+
use_codespell,
910
use_coverage,
1011
use_deptry,
1112
use_pre_commit,
@@ -25,6 +26,17 @@
2526
frozen_opt = typer.Option(False, "--frozen", help="Use the frozen dependencies.")
2627

2728

29+
@app.command(help="Use the codespell spellchecker: detect common spelling mistakes.")
30+
def codespell(
31+
remove: bool = remove_opt,
32+
offline: bool = offline_opt,
33+
quiet: bool = quiet_opt,
34+
frozen: bool = frozen_opt,
35+
) -> None:
36+
with usethis_config.set(offline=offline, quiet=quiet, frozen=frozen):
37+
_run_tool(use_codespell, remove=remove)
38+
39+
2840
@app.command(help="Use the coverage code coverage measurement tool.")
2941
def coverage(
3042
remove: bool = remove_opt,

src/usethis/_tool.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,60 @@ def remove_pyproject_configs(self) -> None:
191191
first_removal = False
192192

193193

194+
class CodespellTool(Tool):
195+
@property
196+
def name(self) -> str:
197+
return "Codespell"
198+
199+
@property
200+
def dev_deps(self) -> list[Dependency]:
201+
return [Dependency(name="codespell")]
202+
203+
def get_pyproject_configs(self) -> list[PyProjectConfig]:
204+
return [
205+
PyProjectConfig(
206+
id_keys=["tool", "codespell"],
207+
value={
208+
"ignore-words-list": [],
209+
"ignore-regex": [
210+
"[A-Za-z0-9+/]{100,}" # Ignore long base64 strings
211+
],
212+
},
213+
),
214+
]
215+
216+
def get_pre_commit_repos(self) -> list[LocalRepo | UriRepo]:
217+
return [
218+
UriRepo(
219+
repo="https://github.com/codespell-project/codespell",
220+
rev="v2.4.1", # Manually bump this version when necessary
221+
hooks=[
222+
HookDefinition(id="codespell", additional_dependencies=["tomli"])
223+
],
224+
)
225+
]
226+
227+
def get_pyproject_id_keys(self) -> list[list[str]]:
228+
return [["tool", "codespell"]]
229+
230+
def get_managed_files(self) -> list[Path]:
231+
return [Path(".codespellrc")]
232+
233+
def get_bitbucket_steps(self) -> list[BitbucketStep]:
234+
return [
235+
BitbucketStep(
236+
name="Run Codespell",
237+
caches=["uv"],
238+
script=BitbucketScript(
239+
[
240+
BitbucketScriptItemAnchor(name="install-uv"),
241+
"uv run codespell",
242+
]
243+
),
244+
)
245+
]
246+
247+
194248
class CoverageTool(Tool):
195249
@property
196250
def name(self) -> str:
@@ -495,6 +549,7 @@ def get_bitbucket_steps(self) -> list[BitbucketStep]:
495549

496550

497551
ALL_TOOLS: list[Tool] = [
552+
CodespellTool(),
498553
CoverageTool(),
499554
DeptryTool(),
500555
PreCommitTool(),

tests/usethis/_core/test_ci.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
from usethis._ci import update_bitbucket_pytest_steps
66
from usethis._core.ci import use_ci_bitbucket
7-
from usethis._core.tool import use_deptry, use_pre_commit, use_pyproject_fmt, use_ruff
7+
from usethis._core.tool import (
8+
use_codespell,
9+
use_deptry,
10+
use_pre_commit,
11+
use_pyproject_fmt,
12+
use_ruff,
13+
)
814
from usethis._integrations.bitbucket.steps import get_steps_in_default
915
from usethis._test import change_cwd
1016

@@ -256,6 +262,46 @@ def test_content(
256262
"☐ Run your pipeline via the Bitbucket website.\n"
257263
)
258264

265+
class TestCodespellIntegration:
266+
def test_content(
267+
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
268+
):
269+
with change_cwd(uv_init_dir):
270+
# Arrange
271+
use_codespell()
272+
capfd.readouterr()
273+
274+
# Act
275+
use_ci_bitbucket()
276+
277+
# Assert
278+
contents = (uv_init_dir / "bitbucket-pipelines.yml").read_text()
279+
assert "codespell" in contents
280+
assert (
281+
contents
282+
== """\
283+
image: atlassian/default-image:3
284+
definitions:
285+
caches:
286+
uv: ~/.cache/uv
287+
script_items:
288+
- &install-uv |
289+
curl -LsSf https://astral.sh/uv/install.sh | sh
290+
source $HOME/.local/bin/env
291+
export UV_LINK_MODE=copy
292+
uv --version
293+
pipelines:
294+
default:
295+
- step:
296+
name: Run Codespell
297+
caches:
298+
- uv
299+
script:
300+
- *install-uv
301+
- uv run codespell
302+
"""
303+
)
304+
259305
def test_lots_of_tools_no_precommit(
260306
self, uv_init_dir: Path, capfd: pytest.CaptureFixture[str]
261307
):

0 commit comments

Comments
 (0)