13
13
from commitizen .defaults import config_files
14
14
from commitizen .exceptions import InitFailedError , NoAnswersError
15
15
from commitizen .git import get_latest_tag_name , get_tag_names , smart_open
16
+ from commitizen .version_types import VERSION_TYPES
17
+
18
+
19
+ class ProjectInfo :
20
+ """Discover information about the current folder."""
21
+
22
+ @property
23
+ def has_pyproject (self ) -> bool :
24
+ return os .path .isfile ("pyproject.toml" )
25
+
26
+ @property
27
+ def has_setup (self ) -> bool :
28
+ return os .path .isfile ("setup.py" )
29
+
30
+ @property
31
+ def has_pre_commit_config (self ) -> bool :
32
+ return os .path .isfile (".pre-commit-config.yaml" )
33
+
34
+ @property
35
+ def is_python_poetry (self ) -> bool :
36
+ if not self .has_pyproject :
37
+ return False
38
+ with open ("pyproject.toml" ) as f :
39
+ return "tool.poetry.version" in f .read ()
40
+
41
+ @property
42
+ def is_python (self ) -> bool :
43
+ return self .has_pyproject or self .has_setup
44
+
45
+ @property
46
+ def is_rust_cargo (self ) -> bool :
47
+ return os .path .isfile ("Cargo.toml" )
48
+
49
+ @property
50
+ def is_npm_package (self ) -> bool :
51
+ return os .path .isfile ("package.json" )
52
+
53
+ @property
54
+ def is_php_composer (self ) -> bool :
55
+ return os .path .isfile ("composer.json" )
56
+
57
+ @property
58
+ def latest_tag (self ) -> Optional [str ]:
59
+ return get_latest_tag_name ()
60
+
61
+ def tags (self ) -> Optional [List ]:
62
+ """Not a property, only use if necessary"""
63
+ if self .latest_tag is None :
64
+ return None
65
+ return get_tag_names ()
66
+
67
+ @property
68
+ def is_pre_commit_installed (self ) -> bool :
69
+ return shutil .which ("pre-commit" ) is not None
16
70
17
71
18
72
class Init :
19
73
def __init__ (self , config : BaseConfig , * args ):
20
74
self .config : BaseConfig = config
21
75
self .cz = factory .commiter_factory (self .config )
76
+ self .project_info = ProjectInfo ()
22
77
23
78
def __call__ (self ):
24
79
if self .config .path :
25
80
out .line (f"Config file { self .config .path } already exists" )
26
81
return
27
82
28
- # No config for commitizen exist
29
- config_path = self ._ask_config_path ()
83
+ out .info ("Welcome to commitizen!\n " )
84
+ out .line (
85
+ "The prompts will ask you different questions " "to configure your project."
86
+ )
87
+ out .line ("For further configuration visit:" )
88
+ out .line ("\n \t https://commitizen-tools.github.io/commitizen/config/\n " )
89
+
90
+ # Collect information
91
+ try :
92
+ config_path = self ._ask_config_path () # select
93
+ cz_name = self ._ask_name () # select
94
+ version_provider = self ._ask_version_provider () # select
95
+ tag = self ._ask_tag () # confirm & select
96
+ version = Version (tag )
97
+ tag_format = self ._ask_tag_format (tag ) # confirm & text
98
+ version_type = self ._ask_version_type () # select
99
+ update_changelog_on_bump = self ._ask_update_changelog_on_bump () # confirm
100
+ major_version_zero = self ._ask_major_version_zero (version ) # confirm
101
+ except KeyboardInterrupt :
102
+ raise InitFailedError ("Stopped by user" )
103
+
104
+ # Initialize configuration
30
105
if "toml" in config_path :
31
106
self .config = TomlConfig (data = "" , path = config_path )
32
107
elif "json" in config_path :
33
108
self .config = JsonConfig (data = "{}" , path = config_path )
34
109
elif "yaml" in config_path :
35
110
self .config = YAMLConfig (data = "" , path = config_path )
36
- self .config .init_empty_config_content ()
37
-
38
111
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 )
112
+ values_to_add ["name" ] = cz_name
113
+ values_to_add ["tag_format" ] = tag_format
114
+ values_to_add ["version_type" ] = version_type
44
115
116
+ if version_provider == "commitizen" :
117
+ values_to_add ["version" ] = version .public
118
+ else :
119
+ values_to_add ["version_provider" ] = version_provider
120
+
121
+ if update_changelog_on_bump :
122
+ values_to_add ["update_changelog_on_bump" ] = update_changelog_on_bump
123
+
124
+ if major_version_zero :
125
+ values_to_add ["major_version_zero" ] = major_version_zero
126
+
127
+ # Collect hook data
45
128
hook_types = questionary .checkbox (
46
129
"What types of pre-commit hook you want to install? (Leave blank if you don't want to install)" ,
47
130
choices = [
48
- questionary .Choice ("commit-msg" , checked = True ),
49
- questionary .Choice ("pre-push" , checked = True ),
131
+ questionary .Choice ("commit-msg" , checked = False ),
132
+ questionary .Choice ("pre-push" , checked = False ),
50
133
],
51
- ).ask ()
134
+ ).unsafe_ask ()
52
135
if hook_types :
53
136
try :
54
137
self ._install_pre_commit_hook (hook_types )
55
138
except InitFailedError as e :
56
139
raise InitFailedError (f"Failed to install pre-commit hook.\n { e } " )
57
140
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." )
141
+ # Create and initialize config
142
+ self .config .init_empty_config_content ()
143
+ self ._update_config_file (values_to_add )
144
+
145
+ out .write ("\n You can bump the version running:\n " )
146
+ out .info ("\t cz bump\n " )
147
+ out .success ("Configuration complete 🚀" )
61
148
62
149
def _ask_config_path (self ) -> str :
150
+ default_path = ".cz.toml"
151
+ if self .project_info .has_pyproject :
152
+ default_path = "pyproject.toml"
153
+
63
154
name : str = questionary .select (
64
- "Please choose a supported config file: (default: pyproject.toml) " ,
155
+ "Please choose a supported config file: " ,
65
156
choices = config_files ,
66
- default = "pyproject.toml" ,
157
+ default = default_path ,
67
158
style = self .cz .style ,
68
- ).ask ()
159
+ ).unsafe_ask ()
69
160
return name
70
161
71
162
def _ask_name (self ) -> str :
@@ -74,29 +165,29 @@ def _ask_name(self) -> str:
74
165
choices = list (registry .keys ()),
75
166
default = "cz_conventional_commits" ,
76
167
style = self .cz .style ,
77
- ).ask ()
168
+ ).unsafe_ask ()
78
169
return name
79
170
80
171
def _ask_tag (self ) -> str :
81
- latest_tag = get_latest_tag_name ()
172
+ latest_tag = self . project_info . latest_tag
82
173
if not latest_tag :
83
174
out .error ("No Existing Tag. Set tag to v0.0.1" )
84
175
return "0.0.1"
85
176
86
177
is_correct_tag = questionary .confirm (
87
178
f"Is { latest_tag } the latest tag?" , style = self .cz .style , default = False
88
- ).ask ()
179
+ ).unsafe_ask ()
89
180
if not is_correct_tag :
90
- tags = get_tag_names ()
181
+ tags = self . project_info . tags ()
91
182
if not tags :
92
183
out .error ("No Existing Tag. Set tag to v0.0.1" )
93
184
return "0.0.1"
94
185
95
186
latest_tag = questionary .select (
96
187
"Please choose the latest tag: " ,
97
- choices = get_tag_names (), # type: ignore
188
+ choices = tags ,
98
189
style = self .cz .style ,
99
- ).ask ()
190
+ ).unsafe_ask ()
100
191
101
192
if not latest_tag :
102
193
raise NoAnswersError ("Tag is required!" )
@@ -108,21 +199,90 @@ def _ask_tag_format(self, latest_tag) -> str:
108
199
tag_format = r"v$version"
109
200
is_correct_format = questionary .confirm (
110
201
f'Is "{ tag_format } " the correct tag format?' , style = self .cz .style
111
- ).ask ()
202
+ ).unsafe_ask ()
112
203
113
204
if not is_correct_format :
114
205
tag_format = questionary .text (
115
206
'Please enter the correct version format: (default: "$version")' ,
116
207
style = self .cz .style ,
117
- ).ask ()
208
+ ).unsafe_ask ()
118
209
119
210
if not tag_format :
120
211
tag_format = "$version"
121
212
return tag_format
122
213
123
- def _search_pre_commit (self ) -> bool :
124
- """Check whether pre-commit is installed"""
125
- return shutil .which ("pre-commit" ) is not None
214
+ def _ask_version_provider (self ) -> str :
215
+ """Ask for setting: version_provider"""
216
+
217
+ OPTS = {
218
+ "commitizen" : "commitizen: Fetch and set version in commitizen config (default)" ,
219
+ "cargo" : "cargo: Get and set version from Cargo.toml:project.version field" ,
220
+ "composer" : "composer: Get and set version from composer.json:project.version field" ,
221
+ "npm" : "npm: Get and set version from package.json:project.version field" ,
222
+ "pep621" : "pep621: Get and set version from pyproject.toml:project.version field" ,
223
+ "poetry" : "poetry: Get and set version from pyproject.toml:tool.poetry.version field" ,
224
+ "scm" : "scm: Fetch the version from git and does not need to set it back" ,
225
+ }
226
+
227
+ default_val = "commitizen"
228
+ if self .project_info .is_python :
229
+ if self .project_info .is_python_poetry :
230
+ default_val = "poetry"
231
+ else :
232
+ default_val = "pep621"
233
+ elif self .project_info .is_rust_cargo :
234
+ default_val = "cargo"
235
+ elif self .project_info .is_npm_package :
236
+ default_val = "npm"
237
+ elif self .project_info .is_php_composer :
238
+ default_val = "composer"
239
+
240
+ choices = [
241
+ questionary .Choice (title = title , value = value )
242
+ for value , title in OPTS .items ()
243
+ ]
244
+ default = next (filter (lambda x : x .value == default_val , choices ))
245
+ version_provider : str = questionary .select (
246
+ "Choose the source of the version:" ,
247
+ choices = choices ,
248
+ style = self .cz .style ,
249
+ default = default ,
250
+ ).unsafe_ask ()
251
+ return version_provider
252
+
253
+ def _ask_version_type (self ) -> str :
254
+ """Ask for setting: version_type"""
255
+ default = "semver"
256
+ if self .project_info .is_python :
257
+ default = "pep440"
258
+
259
+ version_type : str = questionary .select (
260
+ "Choose version type scheme: " ,
261
+ choices = [* VERSION_TYPES ],
262
+ style = self .cz .style ,
263
+ default = default ,
264
+ ).unsafe_ask ()
265
+ return version_type
266
+
267
+ def _ask_major_version_zero (self , version : Version ) -> bool :
268
+ """Ask for setting: major_version_zero"""
269
+ if version .major > 0 :
270
+ return False
271
+ major_version_zero : bool = questionary .confirm (
272
+ "Keep the major version in zero during breaking changes" ,
273
+ default = True ,
274
+ auto_enter = True ,
275
+ ).unsafe_ask ()
276
+ return major_version_zero
277
+
278
+ def _ask_update_changelog_on_bump (self ) -> bool :
279
+ "Ask for setting: update_changelog_on_bump"
280
+ update_changelog_on_bump : bool = questionary .confirm (
281
+ "Create changelog automatically on bump" ,
282
+ default = True ,
283
+ auto_enter = True ,
284
+ ).unsafe_ask ()
285
+ return update_changelog_on_bump
126
286
127
287
def _exec_install_pre_commit_hook (self , hook_types : List [str ]):
128
288
cmd_str = self ._gen_pre_commit_cmd (hook_types )
@@ -157,7 +317,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
157
317
}
158
318
159
319
config_data = {}
160
- if not os . path . isfile ( pre_commit_config_filename ) :
320
+ if not self . project_info . has_pre_commit_config :
161
321
# .pre-commit-config.yaml does not exist
162
322
config_data ["repos" ] = [cz_hook_config ]
163
323
else :
@@ -180,7 +340,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
180
340
with smart_open (pre_commit_config_filename , "w" ) as config_file :
181
341
yaml .safe_dump (config_data , stream = config_file )
182
342
183
- if not self ._search_pre_commit () :
343
+ if not self .project_info . is_pre_commit_installed :
184
344
raise InitFailedError ("pre-commit is not installed in current environment." )
185
345
if hook_types is None :
186
346
hook_types = ["commit-msg" , "pre-push" ]
0 commit comments