1313from commitizen .defaults import config_files
1414from commitizen .exceptions import InitFailedError , NoAnswersError
1515from 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
1670
1771
1872class Init :
1973 def __init__ (self , config : BaseConfig , * args ):
2074 self .config : BaseConfig = config
2175 self .cz = factory .commiter_factory (self .config )
76+ self .project_info = ProjectInfo ()
2277
2378 def __call__ (self ):
2479 if self .config .path :
2580 out .line (f"Config file { self .config .path } already exists" )
2681 return
2782
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
30105 if "toml" in config_path :
31106 self .config = TomlConfig (data = "" , path = config_path )
32107 elif "json" in config_path :
33108 self .config = JsonConfig (data = "{}" , path = config_path )
34109 elif "yaml" in config_path :
35110 self .config = YAMLConfig (data = "" , path = config_path )
36- self .config .init_empty_config_content ()
37-
38111 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
44115
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
45128 hook_types = questionary .checkbox (
46129 "What types of pre-commit hook you want to install? (Leave blank if you don't want to install)" ,
47130 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 ),
50133 ],
51- ).ask ()
134+ ).unsafe_ask ()
52135 if hook_types :
53136 try :
54137 self ._install_pre_commit_hook (hook_types )
55138 except InitFailedError as e :
56139 raise InitFailedError (f"Failed to install pre-commit hook.\n { e } " )
57140
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 🚀" )
61148
62149 def _ask_config_path (self ) -> str :
150+ default_path = ".cz.toml"
151+ if self .project_info .has_pyproject :
152+ default_path = "pyproject.toml"
153+
63154 name : str = questionary .select (
64- "Please choose a supported config file: (default: pyproject.toml) " ,
155+ "Please choose a supported config file: " ,
65156 choices = config_files ,
66- default = "pyproject.toml" ,
157+ default = default_path ,
67158 style = self .cz .style ,
68- ).ask ()
159+ ).unsafe_ask ()
69160 return name
70161
71162 def _ask_name (self ) -> str :
@@ -74,29 +165,29 @@ def _ask_name(self) -> str:
74165 choices = list (registry .keys ()),
75166 default = "cz_conventional_commits" ,
76167 style = self .cz .style ,
77- ).ask ()
168+ ).unsafe_ask ()
78169 return name
79170
80171 def _ask_tag (self ) -> str :
81- latest_tag = get_latest_tag_name ()
172+ latest_tag = self . project_info . latest_tag
82173 if not latest_tag :
83174 out .error ("No Existing Tag. Set tag to v0.0.1" )
84175 return "0.0.1"
85176
86177 is_correct_tag = questionary .confirm (
87178 f"Is { latest_tag } the latest tag?" , style = self .cz .style , default = False
88- ).ask ()
179+ ).unsafe_ask ()
89180 if not is_correct_tag :
90- tags = get_tag_names ()
181+ tags = self . project_info . tags ()
91182 if not tags :
92183 out .error ("No Existing Tag. Set tag to v0.0.1" )
93184 return "0.0.1"
94185
95186 latest_tag = questionary .select (
96187 "Please choose the latest tag: " ,
97- choices = get_tag_names (), # type: ignore
188+ choices = tags ,
98189 style = self .cz .style ,
99- ).ask ()
190+ ).unsafe_ask ()
100191
101192 if not latest_tag :
102193 raise NoAnswersError ("Tag is required!" )
@@ -108,21 +199,90 @@ def _ask_tag_format(self, latest_tag) -> str:
108199 tag_format = r"v$version"
109200 is_correct_format = questionary .confirm (
110201 f'Is "{ tag_format } " the correct tag format?' , style = self .cz .style
111- ).ask ()
202+ ).unsafe_ask ()
112203
113204 if not is_correct_format :
114205 tag_format = questionary .text (
115206 'Please enter the correct version format: (default: "$version")' ,
116207 style = self .cz .style ,
117- ).ask ()
208+ ).unsafe_ask ()
118209
119210 if not tag_format :
120211 tag_format = "$version"
121212 return tag_format
122213
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
126286
127287 def _exec_install_pre_commit_hook (self , hook_types : List [str ]):
128288 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):
157317 }
158318
159319 config_data = {}
160- if not os . path . isfile ( pre_commit_config_filename ) :
320+ if not self . project_info . has_pre_commit_config :
161321 # .pre-commit-config.yaml does not exist
162322 config_data ["repos" ] = [cz_hook_config ]
163323 else :
@@ -180,7 +340,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
180340 with smart_open (pre_commit_config_filename , "w" ) as config_file :
181341 yaml .safe_dump (config_data , stream = config_file )
182342
183- if not self ._search_pre_commit () :
343+ if not self .project_info . is_pre_commit_installed :
184344 raise InitFailedError ("pre-commit is not installed in current environment." )
185345 if hook_types is None :
186346 hook_types = ["commit-msg" , "pre-push" ]
0 commit comments