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
16
17
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
17
69
18
70
class Init :
19
71
def __init__ (self , config : BaseConfig , * args ):
20
72
self .config : BaseConfig = config
21
73
self .cz = factory .commiter_factory (self .config )
74
+ self .project_info = ProjectInfo ()
22
75
23
76
def __call__ (self ):
24
77
if self .config .path :
25
78
out .line (f"Config file { self .config .path } already exists" )
26
79
return
27
80
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 \t https://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
30
103
if "toml" in config_path :
31
104
self .config = TomlConfig (data = "" , path = config_path )
32
105
elif "json" in config_path :
33
106
self .config = JsonConfig (data = "{}" , path = config_path )
34
107
elif "yaml" in config_path :
35
108
self .config = YAMLConfig (data = "" , path = config_path )
36
- self .config .init_empty_config_content ()
37
-
38
109
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
44
124
125
+ # Collect hook data
45
126
hook_types = questionary .checkbox (
46
127
"What types of pre-commit hook you want to install? (Leave blank if you don't want to install)" ,
47
128
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 ),
50
131
],
51
- ).ask ()
132
+ ).unsafe_ask ()
52
133
if hook_types :
53
134
try :
54
135
self ._install_pre_commit_hook (hook_types )
55
136
except InitFailedError as e :
56
137
raise InitFailedError (f"Failed to install pre-commit hook.\n { e } " )
57
138
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 ("\n You can bump the version running:\n " )
144
+ out .info ("\t cz bump\n " )
145
+ out .success ("Configuration are all set 🚀" )
61
146
62
147
def _ask_config_path (self ) -> str :
148
+ default_path = ".cz.toml"
149
+ if self .project_info .has_pyproject :
150
+ default_path = "pyproject.toml"
151
+
63
152
name : str = questionary .select (
64
- "Please choose a supported config file: (default: pyproject.toml) " ,
153
+ "Please choose a supported config file: " ,
65
154
choices = config_files ,
66
- default = "pyproject.toml" ,
155
+ default = default_path ,
67
156
style = self .cz .style ,
68
- ).ask ()
157
+ ).unsafe_ask ()
69
158
return name
70
159
71
160
def _ask_name (self ) -> str :
@@ -74,29 +163,29 @@ def _ask_name(self) -> str:
74
163
choices = list (registry .keys ()),
75
164
default = "cz_conventional_commits" ,
76
165
style = self .cz .style ,
77
- ).ask ()
166
+ ).unsafe_ask ()
78
167
return name
79
168
80
169
def _ask_tag (self ) -> str :
81
- latest_tag = get_latest_tag_name ()
170
+ latest_tag = self . project_info . latest_tag
82
171
if not latest_tag :
83
172
out .error ("No Existing Tag. Set tag to v0.0.1" )
84
173
return "0.0.1"
85
174
86
175
is_correct_tag = questionary .confirm (
87
176
f"Is { latest_tag } the latest tag?" , style = self .cz .style , default = False
88
- ).ask ()
177
+ ).unsafe_ask ()
89
178
if not is_correct_tag :
90
- tags = get_tag_names ()
179
+ tags = self . project_info . tags ()
91
180
if not tags :
92
181
out .error ("No Existing Tag. Set tag to v0.0.1" )
93
182
return "0.0.1"
94
183
95
184
latest_tag = questionary .select (
96
185
"Please choose the latest tag: " ,
97
- choices = get_tag_names (), # type: ignore
186
+ choices = tags ,
98
187
style = self .cz .style ,
99
- ).ask ()
188
+ ).unsafe_ask ()
100
189
101
190
if not latest_tag :
102
191
raise NoAnswersError ("Tag is required!" )
@@ -108,21 +197,90 @@ def _ask_tag_format(self, latest_tag) -> str:
108
197
tag_format = r"v$version"
109
198
is_correct_format = questionary .confirm (
110
199
f'Is "{ tag_format } " the correct tag format?' , style = self .cz .style
111
- ).ask ()
200
+ ).unsafe_ask ()
112
201
113
202
if not is_correct_format :
114
203
tag_format = questionary .text (
115
204
'Please enter the correct version format: (default: "$version")' ,
116
205
style = self .cz .style ,
117
- ).ask ()
206
+ ).unsafe_ask ()
118
207
119
208
if not tag_format :
120
209
tag_format = "$version"
121
210
return tag_format
122
211
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
126
284
127
285
def _exec_install_pre_commit_hook (self , hook_types : List [str ]):
128
286
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):
157
315
}
158
316
159
317
config_data = {}
160
- if not os . path . isfile ( pre_commit_config_filename ) :
318
+ if not self . project_info . has_pre_commit_config :
161
319
# .pre-commit-config.yaml does not exist
162
320
config_data ["repos" ] = [cz_hook_config ]
163
321
else :
@@ -180,7 +338,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
180
338
with smart_open (pre_commit_config_filename , "w" ) as config_file :
181
339
yaml .safe_dump (config_data , stream = config_file )
182
340
183
- if not self ._search_pre_commit () :
341
+ if not self .project_info . is_pre_commit_installed :
184
342
raise InitFailedError (
185
343
"pre-commit is not installed in current environement."
186
344
)
0 commit comments