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 not shutil .which ("pre-commit" )
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+ "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
30107 if "toml" in config_path :
31108 self .config = TomlConfig (data = "" , path = config_path )
32109 elif "json" in config_path :
33110 self .config = JsonConfig (data = "{}" , path = config_path )
34111 elif "yaml" in config_path :
35112 self .config = YAMLConfig (data = "" , path = config_path )
36- self .config .init_empty_config_content ()
37-
38113 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
44117
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
45130 hook_types = questionary .checkbox (
46131 "What types of pre-commit hook you want to install? (Leave blank if you don't want to install)" ,
47132 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 ),
50135 ],
51- ).ask ()
136+ ).unsafe_ask ()
52137 if hook_types :
53138 try :
54139 self ._install_pre_commit_hook (hook_types )
55140 except InitFailedError as e :
56141 raise InitFailedError (f"Failed to install pre-commit hook.\n { e } " )
57142
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 🚀" )
61150
62151 def _ask_config_path (self ) -> str :
152+ default_path = ".cz.toml"
153+ if self .project_info .has_pyproject :
154+ default_path = "pyproject.toml"
155+
63156 name : str = questionary .select (
64- "Please choose a supported config file: (default: pyproject.toml) " ,
157+ "Please choose a supported config file: " ,
65158 choices = config_files ,
66- default = "pyproject.toml" ,
159+ default = default_path ,
67160 style = self .cz .style ,
68- ).ask ()
161+ ).unsafe_ask ()
69162 return name
70163
71164 def _ask_name (self ) -> str :
@@ -74,29 +167,29 @@ def _ask_name(self) -> str:
74167 choices = list (registry .keys ()),
75168 default = "cz_conventional_commits" ,
76169 style = self .cz .style ,
77- ).ask ()
170+ ).unsafe_ask ()
78171 return name
79172
80173 def _ask_tag (self ) -> str :
81- latest_tag = get_latest_tag_name ()
174+ latest_tag = self . project_info . latest_tag
82175 if not latest_tag :
83176 out .error ("No Existing Tag. Set tag to v0.0.1" )
84177 return "0.0.1"
85178
86179 is_correct_tag = questionary .confirm (
87180 f"Is { latest_tag } the latest tag?" , style = self .cz .style , default = False
88- ).ask ()
181+ ).unsafe_ask ()
89182 if not is_correct_tag :
90- tags = get_tag_names ()
183+ tags = self . project_info . tags ()
91184 if not tags :
92185 out .error ("No Existing Tag. Set tag to v0.0.1" )
93186 return "0.0.1"
94187
95188 latest_tag = questionary .select (
96189 "Please choose the latest tag: " ,
97- choices = get_tag_names (), # type: ignore
190+ choices = tags ,
98191 style = self .cz .style ,
99- ).ask ()
192+ ).unsafe_ask ()
100193
101194 if not latest_tag :
102195 raise NoAnswersError ("Tag is required!" )
@@ -108,21 +201,90 @@ def _ask_tag_format(self, latest_tag) -> str:
108201 tag_format = r"v$version"
109202 is_correct_format = questionary .confirm (
110203 f'Is "{ tag_format } " the correct tag format?' , style = self .cz .style
111- ).ask ()
204+ ).unsafe_ask ()
112205
113206 if not is_correct_format :
114207 tag_format = questionary .text (
115208 'Please enter the correct version format: (default: "$version")' ,
116209 style = self .cz .style ,
117- ).ask ()
210+ ).unsafe_ask ()
118211
119212 if not tag_format :
120213 tag_format = "$version"
121214 return tag_format
122215
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
126288
127289 def _exec_install_pre_commit_hook (self , hook_types : List [str ]):
128290 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):
157319 }
158320
159321 config_data = {}
160- if not os . path . isfile ( pre_commit_config_filename ) :
322+ if not self . project_info . has_pre_commit_config :
161323 # .pre-commit-config.yaml does not exist
162324 config_data ["repos" ] = [cz_hook_config ]
163325 else :
@@ -180,7 +342,7 @@ def _install_pre_commit_hook(self, hook_types: Optional[List[str]] = None):
180342 with smart_open (pre_commit_config_filename , "w" ) as config_file :
181343 yaml .safe_dump (config_data , stream = config_file )
182344
183- if not self ._search_pre_commit () :
345+ if not self .project_info . is_pre_commit_installed :
184346 raise InitFailedError ("pre-commit is not installed in current environment." )
185347 if hook_types is None :
186348 hook_types = ["commit-msg" , "pre-push" ]
0 commit comments