Skip to content

Commit fffcff3

Browse files
committed
feat(init): add new settings
1 parent ced094b commit fffcff3

File tree

5 files changed

+211
-39
lines changed

5 files changed

+211
-39
lines changed

commitizen/commands/init.py

Lines changed: 189 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,59 +13,148 @@
1313
from commitizen.defaults import config_files
1414
from commitizen.exceptions import InitFailedError, NoAnswersError
1515
from commitizen.git import get_latest_tag_name, get_tag_names, smart_open
16+
from commitizen.version_types import VERSION_TYPES
1617

18+
class ProjectInfo:
19+
"""Discover information about the current folder."""
20+
21+
@property
22+
def has_pyproject(self) -> bool:
23+
return os.path.isfile("pyproject.toml")
24+
25+
@property
26+
def has_setup(self) -> bool:
27+
return os.path.isfile("setup.py")
28+
29+
@property
30+
def has_pre_commit_config(self) -> bool:
31+
return os.path.isfile(".pre-commit-config.yaml")
32+
33+
@property
34+
def is_python_poetry(self) -> bool:
35+
if not self.has_pyproject:
36+
return False
37+
with open("pyproject.toml") as f:
38+
return "tool.poetry.version" in f.read()
39+
40+
@property
41+
def is_python(self) -> bool:
42+
return self.has_pyproject or self.has_setup
43+
44+
@property
45+
def is_rust_cargo(self) -> bool:
46+
return os.path.isfile("Cargo.toml")
47+
48+
@property
49+
def is_npm_package(self) -> bool:
50+
return os.path.isfile("package.json")
51+
52+
@property
53+
def is_php_composer(self) -> bool:
54+
return os.path.isfile("composer.json")
55+
56+
@property
57+
def latest_tag(self) -> Optional[str]:
58+
return get_latest_tag_name()
59+
60+
def tags(self) -> Optional[List]:
61+
"""Not a property, only use if necessary"""
62+
if self.latest_tag is None:
63+
return None
64+
return get_tag_names()
65+
66+
@property
67+
def is_pre_commit_installed(self) -> bool:
68+
return shutil.which("pre-commit") is not None
1769

1870
class Init:
1971
def __init__(self, config: BaseConfig, *args):
2072
self.config: BaseConfig = config
2173
self.cz = factory.commiter_factory(self.config)
74+
self.project_info = ProjectInfo()
2275

2376
def __call__(self):
2477
if self.config.path:
2578
out.line(f"Config file {self.config.path} already exists")
2679
return
2780

28-
# No config for commitizen exist
29-
config_path = self._ask_config_path()
81+
out.info("Welcome to commitizen!\n")
82+
out.line(
83+
"The prompts will ask you different questions " "to configure your project."
84+
)
85+
out.line("For further configuration visit:")
86+
out.line("\n\thttps://commitizen-tools.github.io/commitizen/config/\n")
87+
88+
# Collect information
89+
try:
90+
config_path = self._ask_config_path() # select
91+
cz_name = self._ask_name() # select
92+
version_provider = self._ask_version_provider() # select
93+
tag = self._ask_tag() # confirm & select
94+
version = Version(tag)
95+
tag_format = self._ask_tag_format(tag) # confirm & text
96+
version_type = self._ask_version_type() # select
97+
update_changelog_on_bump = self._ask_update_changelog_on_bump() # confirm
98+
major_version_zero = self._ask_major_version_zero(version) # confirm
99+
except KeyboardInterrupt:
100+
raise InitFailedError("Stopped by user")
101+
102+
# Initialize configuration
30103
if "toml" in config_path:
31104
self.config = TomlConfig(data="", path=config_path)
32105
elif "json" in config_path:
33106
self.config = JsonConfig(data="{}", path=config_path)
34107
elif "yaml" in config_path:
35108
self.config = YAMLConfig(data="", path=config_path)
36-
self.config.init_empty_config_content()
37-
38109
values_to_add = {}
39-
values_to_add["name"] = self._ask_name()
40-
tag = self._ask_tag()
41-
values_to_add["version"] = Version(tag).public
42-
values_to_add["tag_format"] = self._ask_tag_format(tag)
43-
self._update_config_file(values_to_add)
110+
values_to_add["name"] = cz_name
111+
values_to_add["tag_format"] = tag_format
112+
values_to_add["version_type"] = version_type
113+
114+
if version_provider == "commitizen":
115+
values_to_add["version"] = version.public
116+
else:
117+
values_to_add["version_provider"] = version_provider
118+
119+
if update_changelog_on_bump:
120+
values_to_add["update_changelog_on_bump"] = update_changelog_on_bump
121+
122+
if major_version_zero:
123+
values_to_add["major_version_zero"] = major_version_zero
44124

125+
# Collect hook data
45126
hook_types = questionary.checkbox(
46127
"What types of pre-commit hook you want to install? (Leave blank if you don't want to install)",
47128
choices=[
48-
questionary.Choice("commit-msg", checked=True),
49-
questionary.Choice("pre-push", checked=True),
129+
questionary.Choice("commit-msg", checked=False),
130+
questionary.Choice("pre-push", checked=False),
50131
],
51-
).ask()
132+
).unsafe_ask()
52133
if hook_types:
53134
try:
54135
self._install_pre_commit_hook(hook_types)
55136
except InitFailedError as e:
56137
raise InitFailedError(f"Failed to install pre-commit hook.\n{e}")
57138

58-
out.write("You can bump the version and create changelog running:\n")
59-
out.info("cz bump --changelog")
60-
out.success("The configuration are all set.")
139+
# Create and initialize config
140+
self.config.init_empty_config_content()
141+
self._update_config_file(values_to_add)
142+
143+
out.write("\nYou can bump the version running:\n")
144+
out.info("\tcz bump\n")
145+
out.success("Configuration are all set 🚀")
61146

62147
def _ask_config_path(self) -> str:
148+
default_path = ".cz.toml"
149+
if self.project_info.has_pyproject:
150+
default_path = "pyproject.toml"
151+
63152
name: str = questionary.select(
64-
"Please choose a supported config file: (default: pyproject.toml)",
153+
"Please choose a supported config file: ",
65154
choices=config_files,
66-
default="pyproject.toml",
155+
default=default_path,
67156
style=self.cz.style,
68-
).ask()
157+
).unsafe_ask()
69158
return name
70159

71160
def _ask_name(self) -> str:
@@ -74,29 +163,29 @@ def _ask_name(self) -> str:
74163
choices=list(registry.keys()),
75164
default="cz_conventional_commits",
76165
style=self.cz.style,
77-
).ask()
166+
).unsafe_ask()
78167
return name
79168

80169
def _ask_tag(self) -> str:
81-
latest_tag = get_latest_tag_name()
170+
latest_tag = self.project_info.latest_tag
82171
if not latest_tag:
83172
out.error("No Existing Tag. Set tag to v0.0.1")
84173
return "0.0.1"
85174

86175
is_correct_tag = questionary.confirm(
87176
f"Is {latest_tag} the latest tag?", style=self.cz.style, default=False
88-
).ask()
177+
).unsafe_ask()
89178
if not is_correct_tag:
90-
tags = get_tag_names()
179+
tags = self.project_info.tags()
91180
if not tags:
92181
out.error("No Existing Tag. Set tag to v0.0.1")
93182
return "0.0.1"
94183

95184
latest_tag = questionary.select(
96185
"Please choose the latest tag: ",
97-
choices=get_tag_names(), # type: ignore
186+
choices=tags,
98187
style=self.cz.style,
99-
).ask()
188+
).unsafe_ask()
100189

101190
if not latest_tag:
102191
raise NoAnswersError("Tag is required!")
@@ -108,21 +197,90 @@ def _ask_tag_format(self, latest_tag) -> str:
108197
tag_format = r"v$version"
109198
is_correct_format = questionary.confirm(
110199
f'Is "{tag_format}" the correct tag format?', style=self.cz.style
111-
).ask()
200+
).unsafe_ask()
112201

113202
if not is_correct_format:
114203
tag_format = questionary.text(
115204
'Please enter the correct version format: (default: "$version")',
116205
style=self.cz.style,
117-
).ask()
206+
).unsafe_ask()
118207

119208
if not tag_format:
120209
tag_format = "$version"
121210
return tag_format
122211

123-
def _search_pre_commit(self) -> bool:
124-
"""Check whether pre-commit is installed"""
125-
return shutil.which("pre-commit") is not None
212+
def _ask_version_provider(self) -> str:
213+
"""Ask for setting: version_provider"""
214+
215+
OPTS = {
216+
"commitizen": "commitizen: Fetch and set version in commitizen config (default)",
217+
"cargo": "cargo: Get and set version from Cargo.toml:project.version field",
218+
"composer": "composer: Get and set version from composer.json:project.version field",
219+
"npm": "npm: Get and set version from package.json:project.version field",
220+
"pep621": "pep621: Get and set version from pyproject.toml:project.version field",
221+
"poetry": "poetry: Get and set version from pyproject.toml:tool.poetry.version field",
222+
"scm": "scm: Fetch the version from git and does not need to set it back",
223+
}
224+
225+
default_val = "commitizen"
226+
if self.project_info.is_python:
227+
if self.project_info.is_python_poetry:
228+
default_val = "poetry"
229+
else:
230+
default_val = "pep621"
231+
elif self.project_info.is_rust_cargo:
232+
default_val = "cargo"
233+
elif self.project_info.is_npm_package:
234+
default_val = "npm"
235+
elif self.project_info.is_php_composer:
236+
default_val = "composer"
237+
238+
choices = [
239+
questionary.Choice(title=title, value=value)
240+
for value, title in OPTS.items()
241+
]
242+
default = next(filter(lambda x: x.value == default_val, choices))
243+
version_provider: str = questionary.select(
244+
"Choose the source of the version:",
245+
choices=choices,
246+
style=self.cz.style,
247+
default=default,
248+
).unsafe_ask()
249+
return version_provider
250+
251+
def _ask_version_type(self) -> str:
252+
"""Ask for setting: version_type"""
253+
default = "semver"
254+
if self.project_info.is_python:
255+
default = "pep440"
256+
257+
version_type: str = questionary.select(
258+
"Choose version type scheme: ",
259+
choices=[*VERSION_TYPES],
260+
style=self.cz.style,
261+
default=default,
262+
).unsafe_ask()
263+
return version_type
264+
265+
def _ask_major_version_zero(self, version: Version) -> bool:
266+
"""Ask for setting: major_version_zero"""
267+
if version.major > 0:
268+
return False
269+
major_version_zero: bool = questionary.confirm(
270+
"Keep the major version in zero during breaking changes",
271+
default=True,
272+
auto_enter=True,
273+
).unsafe_ask()
274+
return major_version_zero
275+
276+
def _ask_update_changelog_on_bump(self) -> bool:
277+
"Ask for setting: update_changelog_on_bump"
278+
update_changelog_on_bump: bool = questionary.confirm(
279+
"Create changelog automatically on bump",
280+
default=True,
281+
auto_enter=True,
282+
).unsafe_ask()
283+
return update_changelog_on_bump
126284

127285
def _exec_install_pre_commit_hook(self, hook_types: List[str]):
128286
cmd_str = self._gen_pre_commit_cmd(hook_types)
@@ -157,7 +315,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
157315
}
158316

159317
config_data = {}
160-
if not os.path.isfile(pre_commit_config_filename):
318+
if not self.project_info.has_pre_commit_config:
161319
# .pre-commit-config.yaml does not exist
162320
config_data["repos"] = [cz_hook_config]
163321
else:
@@ -180,7 +338,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
180338
with smart_open(pre_commit_config_filename, "w") as config_file:
181339
yaml.safe_dump(config_data, stream=config_file)
182340

183-
if not self._search_pre_commit():
341+
if not self.project_info.is_pre_commit_installed:
184342
raise InitFailedError(
185343
"pre-commit is not installed in current environement."
186344
)

commitizen/defaults.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ class Settings(TypedDict, total=False):
5050

5151
name: str = "cz_conventional_commits"
5252
config_files: List[str] = [
53-
"pyproject.toml",
5453
".cz.toml",
54+
"pyproject.toml",
5555
".cz.json",
5656
"cz.json",
5757
".cz.yaml",

commitizen/version_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,6 @@ def __str__(self) -> str:
9494

9595

9696
VERSION_TYPES = {
97-
"pep440": Version,
9897
"semver": SemVerVersion,
98+
"pep440": Version,
9999
}

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ exclude =
3636
build,
3737
dist
3838
max-line-length = 88
39-
max-complexity = 12
39+
max-complexity = 13

0 commit comments

Comments
 (0)