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