Skip to content

Commit 8229584

Browse files
bearomorphismLee-W
authored andcommitted
refactor(Init): make project_info a module and remove self.project_info
1 parent 1ec5dc4 commit 8229584

File tree

4 files changed

+145
-87
lines changed

4 files changed

+145
-87
lines changed

commitizen/commands/init.py

Lines changed: 6 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
from __future__ import annotations
22

3-
import os
4-
import shutil
53
from pathlib import Path
64
from typing import Any, NamedTuple
75

86
import questionary
97
import yaml
108

11-
from commitizen import cmd, factory, out
9+
from commitizen import cmd, factory, out, project_info
1210
from commitizen.__version__ import __version__
1311
from commitizen.config import (
1412
BaseConfig,
@@ -69,64 +67,12 @@ def title(self) -> str:
6967
)
7068

7169

72-
class ProjectInfo:
73-
"""Discover information about the current folder."""
74-
75-
@property
76-
def has_pyproject(self) -> bool:
77-
return os.path.isfile("pyproject.toml")
78-
79-
@property
80-
def has_uv_lock(self) -> bool:
81-
return os.path.isfile("uv.lock")
82-
83-
@property
84-
def has_setup(self) -> bool:
85-
return os.path.isfile("setup.py")
86-
87-
@property
88-
def has_pre_commit_config(self) -> bool:
89-
return os.path.isfile(".pre-commit-config.yaml")
90-
91-
@property
92-
def is_python_uv(self) -> bool:
93-
return self.has_pyproject and self.has_uv_lock
94-
95-
@property
96-
def is_python_poetry(self) -> bool:
97-
if not self.has_pyproject:
98-
return False
99-
with open("pyproject.toml") as f:
100-
return "[tool.poetry]" in f.read()
101-
102-
@property
103-
def is_python(self) -> bool:
104-
return self.has_pyproject or self.has_setup
105-
106-
@property
107-
def is_rust_cargo(self) -> bool:
108-
return os.path.isfile("Cargo.toml")
109-
110-
@property
111-
def is_npm_package(self) -> bool:
112-
return os.path.isfile("package.json")
113-
114-
@property
115-
def is_php_composer(self) -> bool:
116-
return os.path.isfile("composer.json")
117-
118-
@property
119-
def is_pre_commit_installed(self) -> bool:
120-
return bool(shutil.which("pre-commit"))
121-
122-
12370
class Init:
12471
_PRE_COMMIT_CONFIG_PATH = ".pre-commit-config.yaml"
12572

12673
def __init__(self, config: BaseConfig, *args: object) -> None:
12774
self.config: BaseConfig = config
12875
self.cz = factory.committer_factory(self.config)
129-
self.project_info = ProjectInfo()
13076

13177
def __call__(self) -> None:
13278
if self.config.path:
@@ -172,7 +118,7 @@ def __call__(self) -> None:
172118
) as config_file:
173119
yaml.safe_dump(config_data, stream=config_file)
174120

175-
if not self.project_info.is_pre_commit_installed:
121+
if not project_info.is_pre_commit_installed():
176122
raise InitFailedError(
177123
"Failed to install pre-commit hook.\n"
178124
"pre-commit is not installed in current environment."
@@ -208,14 +154,10 @@ def __call__(self) -> None:
208154
out.success("Configuration complete 🚀")
209155

210156
def _ask_config_path(self) -> Path:
211-
default_path = (
212-
"pyproject.toml" if self.project_info.has_pyproject else ".cz.toml"
213-
)
214-
215157
filename: str = questionary.select(
216158
"Please choose a supported config file: ",
217159
choices=CONFIG_FILES,
218-
default=default_path,
160+
default=project_info.get_default_config_filename(),
219161
style=self.cz.style,
220162
).unsafe_ask()
221163
return Path(filename)
@@ -280,37 +222,17 @@ def _ask_version_provider(self) -> str:
280222
"Choose the source of the version:",
281223
choices=_VERSION_PROVIDER_CHOICES,
282224
style=self.cz.style,
283-
default=self._default_version_provider,
225+
default=project_info.get_default_version_provider(),
284226
).unsafe_ask()
285227
return version_provider
286228

287-
@property
288-
def _default_version_provider(self) -> str:
289-
if self.project_info.is_python:
290-
if self.project_info.is_python_poetry:
291-
return "poetry"
292-
if self.project_info.is_python_uv:
293-
return "uv"
294-
return "pep621"
295-
296-
if self.project_info.is_rust_cargo:
297-
return "cargo"
298-
if self.project_info.is_npm_package:
299-
return "npm"
300-
if self.project_info.is_php_composer:
301-
return "composer"
302-
303-
return "commitizen"
304-
305229
def _ask_version_scheme(self) -> str:
306230
"""Ask for setting: version_scheme"""
307-
default_scheme = "pep440" if self.project_info.is_python else "semver"
308-
309231
scheme: str = questionary.select(
310232
"Choose version scheme: ",
311233
choices=KNOWN_SCHEMES,
312234
style=self.cz.style,
313-
default=default_scheme,
235+
default=project_info.get_default_version_scheme(),
314236
).unsafe_ask()
315237
return scheme
316238

@@ -344,8 +266,7 @@ def _get_config_data(self) -> dict[str, Any]:
344266
],
345267
}
346268

347-
if not self.project_info.has_pre_commit_config:
348-
# .pre-commit-config.yaml does not exist
269+
if not Path(".pre-commit-config.yaml").is_file():
349270
return {"repos": [CZ_HOOK_CONFIG]}
350271

351272
with open(

commitizen/project_info.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Resolves project information about the current working directory."""
2+
3+
import shutil
4+
from pathlib import Path
5+
from typing import Literal
6+
7+
8+
def is_pre_commit_installed() -> bool:
9+
return bool(shutil.which("pre-commit"))
10+
11+
12+
def get_default_version_provider() -> Literal[
13+
"commitizen", "cargo", "composer", "npm", "pep621", "poetry", "uv"
14+
]:
15+
pyproject_path = Path("pyproject.toml")
16+
if pyproject_path.is_file():
17+
if "[tool.poetry]" in pyproject_path.read_text():
18+
return "poetry"
19+
if Path("uv.lock").is_file():
20+
return "uv"
21+
return "pep621"
22+
23+
if Path("setup.py").is_file():
24+
return "pep621"
25+
26+
if Path("Cargo.toml").is_file():
27+
return "cargo"
28+
29+
if Path("package.json").is_file():
30+
return "npm"
31+
32+
if Path("composer.json").is_file():
33+
return "composer"
34+
35+
return "commitizen"
36+
37+
38+
def get_default_config_filename() -> Literal["pyproject.toml", ".cz.toml"]:
39+
return "pyproject.toml" if Path("pyproject.toml").is_file() else ".cz.toml"
40+
41+
42+
def get_default_version_scheme() -> Literal["pep440", "semver"]:
43+
return (
44+
"pep440"
45+
if Path("pyproject.toml").is_file() or Path("setup.py").is_file()
46+
else "semver"
47+
)

tests/commands/test_init_command.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def test_init_without_choosing_tag(config: BaseConfig, mocker: MockFixture, tmpd
122122
def pre_commit_installed(mocker: MockFixture):
123123
# Assume the `pre-commit` is installed
124124
mocker.patch(
125-
"commitizen.commands.init.ProjectInfo.is_pre_commit_installed",
125+
"commitizen.project_info.is_pre_commit_installed",
126126
return_value=True,
127127
)
128128
# And installation success (i.e. no exception raised)
@@ -232,7 +232,7 @@ def test_pre_commit_not_installed(
232232
):
233233
# Assume `pre-commit` is not installed
234234
mocker.patch(
235-
"commitizen.commands.init.ProjectInfo.is_pre_commit_installed",
235+
"commitizen.project_info.is_pre_commit_installed",
236236
return_value=False,
237237
)
238238
with tmpdir.as_cwd():

tests/test_project_info.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""Tests for project_info module."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
from commitizen import project_info
10+
11+
12+
def _create_project_files(files: dict[str, str | None]) -> None:
13+
for file_path, content in files.items():
14+
path = Path(file_path)
15+
if content is None:
16+
path.touch()
17+
else:
18+
path.write_text(content)
19+
20+
21+
@pytest.mark.parametrize(
22+
"which_return, expected",
23+
[
24+
("/usr/local/bin/pre-commit", True),
25+
(None, False),
26+
("", False),
27+
],
28+
)
29+
def test_is_pre_commit_installed(mocker, which_return, expected):
30+
mocker.patch("shutil.which", return_value=which_return)
31+
assert project_info.is_pre_commit_installed() is expected
32+
33+
34+
@pytest.mark.parametrize(
35+
"files, expected",
36+
[
37+
(
38+
{"pyproject.toml": '[tool.poetry]\nname = "test"\nversion = "0.1.0"'},
39+
"poetry",
40+
),
41+
({"pyproject.toml": "", "uv.lock": ""}, "uv"),
42+
(
43+
{"pyproject.toml": '[tool.commitizen]\nversion = "0.1.0"'},
44+
"pep621",
45+
),
46+
({"setup.py": ""}, "pep621"),
47+
({"Cargo.toml": ""}, "cargo"),
48+
({"package.json": ""}, "npm"),
49+
({"composer.json": ""}, "composer"),
50+
({}, "commitizen"),
51+
(
52+
{
53+
"pyproject.toml": "",
54+
"Cargo.toml": "",
55+
"package.json": "",
56+
"composer.json": "",
57+
},
58+
"pep621",
59+
),
60+
],
61+
)
62+
def test_get_default_version_provider(chdir, files, expected):
63+
_create_project_files(files)
64+
assert project_info.get_default_version_provider() == expected
65+
66+
67+
@pytest.mark.parametrize(
68+
"files, expected",
69+
[
70+
({"pyproject.toml": ""}, "pyproject.toml"),
71+
({}, ".cz.toml"),
72+
],
73+
)
74+
def test_get_default_config_filename(chdir, files, expected):
75+
_create_project_files(files)
76+
assert project_info.get_default_config_filename() == expected
77+
78+
79+
@pytest.mark.parametrize(
80+
"files, expected",
81+
[
82+
({"pyproject.toml": ""}, "pep440"),
83+
({"setup.py": ""}, "pep440"),
84+
({"package.json": ""}, "semver"),
85+
({}, "semver"),
86+
],
87+
)
88+
def test_get_default_version_scheme(chdir, files, expected):
89+
_create_project_files(files)
90+
assert project_info.get_default_version_scheme() == expected

0 commit comments

Comments
 (0)