diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 94c0e142e7..96b06f2080 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -6,7 +6,7 @@ from commitizen import cmd, factory, out from commitizen.__version__ import __version__ -from commitizen.config import BaseConfig, JsonConfig, TomlConfig +from commitizen.config import BaseConfig, JsonConfig, TomlConfig, YAMLConfig from commitizen.cz import registry from commitizen.defaults import config_files from commitizen.exceptions import NoAnswersError @@ -28,6 +28,8 @@ def __call__(self): self.config = TomlConfig(data="", path=config_path) elif "json" in config_path: self.config = JsonConfig(data="{}", path=config_path) + elif "yaml" in config_path: + self.config = YAMLConfig(data="", path=config_path) self.config.init_empty_config_content() diff --git a/commitizen/config/__init__.py b/commitizen/config/__init__.py index ed9c090013..2cbd544825 100644 --- a/commitizen/config/__init__.py +++ b/commitizen/config/__init__.py @@ -6,6 +6,7 @@ from .base_config import BaseConfig from .json_config import JsonConfig from .toml_config import TomlConfig +from .yaml_config import YAMLConfig def read_cfg() -> BaseConfig: @@ -25,15 +26,17 @@ def read_cfg() -> BaseConfig: if not filename.exists(): continue + _conf: Union[TomlConfig, JsonConfig, YAMLConfig] + with open(filename, "r") as f: data: str = f.read() - _conf: Union[TomlConfig, JsonConfig] if "toml" in filename.suffix: _conf = TomlConfig(data=data, path=filename) - - if "json" in filename.suffix: + elif "json" in filename.suffix: _conf = JsonConfig(data=data, path=filename) + elif "yaml" in filename.suffix: + _conf = YAMLConfig(data=data, path=filename) if _conf.is_empty_config: continue diff --git a/commitizen/config/yaml_config.py b/commitizen/config/yaml_config.py new file mode 100644 index 0000000000..cc6c5f0338 --- /dev/null +++ b/commitizen/config/yaml_config.py @@ -0,0 +1,47 @@ +from pathlib import Path +from typing import Union + +import yaml + +from .base_config import BaseConfig + + +class YAMLConfig(BaseConfig): + def __init__(self, *, data: str, path: Union[Path, str]): + super(YAMLConfig, self).__init__() + self.is_empty_config = False + self._parse_setting(data) + self.add_path(path) + + def init_empty_config_content(self): + with open(self.path, "a") as json_file: + yaml.dump({"commitizen": {}}, json_file) + + def _parse_setting(self, data: str): + """We expect to have a section in cz.yaml looking like + + ``` + commitizen: + name: cz_conventional_commits + ``` + """ + doc = yaml.safe_load(data) + try: + self.settings.update(doc["commitizen"]) + except (KeyError, TypeError): + self.is_empty_config = True + + def set_key(self, key, value): + """Set or update a key in the conf. + + For now only strings are supported. + We use to update the version number. + """ + with open(self.path, "r") as yaml_file: + parser = yaml.load(yaml_file) + + parser["commitizen"][key] = value + with open(self.path, "w") as yaml_file: + yaml.dump(parser, yaml_file) + + return self diff --git a/commitizen/defaults.py b/commitizen/defaults.py index a0dfbb8668..c5f07992f6 100644 --- a/commitizen/defaults.py +++ b/commitizen/defaults.py @@ -2,7 +2,14 @@ from typing import Any, Dict, List name: str = "cz_conventional_commits" -config_files: List[str] = ["pyproject.toml", ".cz.toml", ".cz.json", "cz.json"] +config_files: List[str] = [ + "pyproject.toml", + ".cz.toml", + ".cz.json", + "cz.json", + ".cz.yaml", + "cz.yaml", +] DEFAULT_SETTINGS: Dict[str, Any] = { "name": "cz_conventional_commits", diff --git a/docs/config.md b/docs/config.md index bff8112eac..204edcf887 100644 --- a/docs/config.md +++ b/docs/config.md @@ -30,7 +30,7 @@ style = [ ## .cz.json or cz.json -JSON may be a more commong configuration format for non-python projects, so Commitizen supports JSON config files, now. +JSON might be a more common configuration format for non-python projects, so Commitizen supports JSON config files, now. ```json { @@ -87,6 +87,39 @@ JSON may be a more commong configuration format for non-python projects, so Comm } ``` +## .cz.yaml or cz.yaml +YAML is another format for **non-python** proyects as well, supported by Commitizen: + +```yaml +commitizen: + name: cz_conventional_commits + version: 0.1.0 + version_files: + - src/__version__.py + - pyproject.toml:version + style: + - - qmark + - fg:#ff9d00 bold + - - question + - bold + - - answer + - fg:#ff9d00 bold + - - pointer + - fg:#ff9d00 bold + - - highlighted + - fg:#ff9d00 bold + - - selected + - fg:#cc5454 + - - separator + - fg:#cc5454 + - - instruction + - '' + - - text + - '' + - - disabled + - fg:#858585 italic +``` + ## Settings | Variable | Type | Default | Description | diff --git a/docs/customization.md b/docs/customization.md index 06e084a758..0d86dc057f 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -98,6 +98,41 @@ The equivalent example for a json config file: } ``` +And the correspondent example for a yaml json file: + +```yaml +commitizen: + name: cz_customize + customize: + message_template: "{{change_type}}:{% if show_message %} {{message}}{% endif %}" + example: 'feature: this feature enable customize through config file' + schema: ": " + schema_pattern: "(feature|bug fix):(\\s.*)" + bump_pattern: "^(break|new|fix|hotfix)" + bump_map: + break: MAJOR + new: MINOR + fix: PATCH + hotfix: PATCH + info_path: cz_customize_info.txt + info: This is customized info + questions: + - type: list + name: change_type + choices: + - value: feature + name: 'feature: A new feature.' + - value: bug fix + name: 'bug fix: A bug fix.' + message: Select the type of change you are committing + - type: input + name: message + message: Body. + - type: confirm + name: show_message + message: Do you want to add body message in commit? +``` + ### Customize configuration | Parameter | Type | Default | Description | diff --git a/tests/commands/test_init_command.py b/tests/commands/test_init_command.py index 432139695c..977e9e6a11 100644 --- a/tests/commands/test_init_command.py +++ b/tests/commands/test_init_command.py @@ -31,7 +31,7 @@ def ask(self): 'tag_format = "$version"\n' ) -EXPECTED_JSON_CONFIG = { +EXPECTED_DICT_CONFIG = { "commitizen": { "name": "cz_conventional_commits", "version": "0.0.1", @@ -94,7 +94,7 @@ def test_init_without_choosing_tag(config, mocker, tmpdir): class TestPreCommitCases: - @pytest.fixture(scope="function", params=["pyproject.toml", ".cz.json"]) + @pytest.fixture(scope="function", params=["pyproject.toml", ".cz.json", ".cz.yaml"]) def default_choice(_, request, mocker): mocker.patch( "questionary.select", @@ -108,13 +108,15 @@ def default_choice(_, request, mocker): mocker.patch("questionary.confirm", return_value=FakeQuestion(True)) return request.param - def test_no_existing_pre_commit_json_conifg(_, default_choice, tmpdir, config): + def test_no_existing_pre_commit_conifg(_, default_choice, tmpdir, config): with tmpdir.as_cwd(): commands.Init(config)() with open(default_choice, "r") as file: if "json" in default_choice: - assert json.load(file) == EXPECTED_JSON_CONFIG + assert json.load(file) == EXPECTED_DICT_CONFIG + elif "yaml" in default_choice: + assert yaml.load(file) == EXPECTED_DICT_CONFIG else: config_data = file.read() assert config_data == expected_config @@ -132,7 +134,9 @@ def test_empty_pre_commit_config(_, default_choice, tmpdir, config): with open(default_choice, "r") as file: if "json" in default_choice: - assert json.load(file) == EXPECTED_JSON_CONFIG + assert json.load(file) == EXPECTED_DICT_CONFIG + elif "yaml" in default_choice: + assert yaml.load(file) == EXPECTED_DICT_CONFIG else: config_data = file.read() assert config_data == expected_config @@ -156,7 +160,9 @@ def test_pre_commit_config_without_cz_hook(_, default_choice, tmpdir, config): with open(default_choice, "r") as file: if "json" in default_choice: - assert json.load(file) == EXPECTED_JSON_CONFIG + assert json.load(file) == EXPECTED_DICT_CONFIG + elif "yaml" in default_choice: + assert yaml.load(file) == EXPECTED_DICT_CONFIG else: config_data = file.read() assert config_data == expected_config @@ -176,7 +182,9 @@ def test_cz_hook_exists_in_pre_commit_config(_, default_choice, tmpdir, config): with open(default_choice, "r") as file: if "json" in default_choice: - assert json.load(file) == EXPECTED_JSON_CONFIG + assert json.load(file) == EXPECTED_DICT_CONFIG + elif "yaml" in default_choice: + assert yaml.load(file) == EXPECTED_DICT_CONFIG else: config_data = file.read() assert config_data == expected_config diff --git a/tests/test_conf.py b/tests/test_conf.py index 5ab66c2ff1..ea7b3a3c98 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest +import yaml from commitizen import config, defaults, git @@ -24,7 +25,7 @@ target-version = ['py36', 'py37', 'py38'] """ -JSON_CONFIG = { +DICT_CONFIG = { "commitizen": { "name": "cz_jira", "version": "1.0.0", @@ -33,6 +34,7 @@ } } + _settings = { "name": "cz_jira", "version": "1.0.0", @@ -75,8 +77,10 @@ def config_files_manager(request, tmpdir): with open(filename, "w") as f: if "toml" in filename: f.write(PYPROJECT) - if "json" in filename: - json.dump(JSON_CONFIG, f) + elif "json" in filename: + json.dump(DICT_CONFIG, f) + elif "yaml" in filename: + yaml.dump(DICT_CONFIG, f) yield diff --git a/tests/test_cz_customize.py b/tests/test_cz_customize.py index d74814087e..479fcef7c4 100644 --- a/tests/test_cz_customize.py +++ b/tests/test_cz_customize.py @@ -1,6 +1,6 @@ import pytest -from commitizen.config import BaseConfig, JsonConfig, TomlConfig +from commitizen.config import BaseConfig, JsonConfig, TomlConfig, YAMLConfig from commitizen.cz.customize import CustomizeCommitsCz from commitizen.exceptions import MissingCzCustomizeConfigError @@ -89,6 +89,43 @@ } """ +YAML_STR = """ +commitizen: + name: cz_jira + version: 1.0.0 + version_files: + - commitizen/__version__.py + - pyproject.toml + customize: + message_template: "{{change_type}}:{% if show_message %} {{message}}{% endif %}" + example: 'feature: this feature enable customize through config file' + schema: ": " + schema_pattern: "(feature|bug fix):(\\s.*)" + bump_pattern: "^(break|new|fix|hotfix)" + bump_map: + break: MAJOR + new: MINOR + fix: PATCH + hotfix: PATCH + info: This is a customized cz. + questions: + - type: list + name: change_type + choices: + - value: feature + name: 'feature: A new feature.' + - value: bug fix + name: 'bug fix: A bug fix.' + message: Select the type of change you are committing + - type: input + name: message + message: Body. + - type: confirm + name: show_message + message: Do you want to add body message in commit? +""" + + TOML_STR_INFO_PATH = """ [tool.commitizen.customize] message_template = "{{change_type}}:{% if show_message %} {{message}}{% endif %}" @@ -119,6 +156,21 @@ } """ +YAML_STR_INFO_PATH = """ +commitizen: + customize: + message_template: "{{change_type}}:{% if show_message %} {{message}}{% endif %}" + example: 'feature: this feature enable customize through config file' + schema: ": " + bump_pattern: "^(break|new|fix|hotfix)" + bump_map: + break: MAJOR + new: MINOR + fix: PATCH + hotfix: PATCH + info_path: info.txt +""" + TOML_STR_WITHOUT_INFO = """ [tool.commitizen.customize] message_template = "{{change_type}}:{% if show_message %} {{message}}{% endif %}" @@ -147,6 +199,20 @@ } """ +YAML_STR_WITHOUT_PATH = """ +commitizen: + customize: + message_template: "{{change_type}}:{% if show_message %} {{message}}{% endif %}" + example: 'feature: this feature enable customize through config file' + schema: ": " + bump_pattern: "^(break|new|fix|hotfix)" + bump_map: + break: MAJOR + new: MINOR + fix: PATCH + hotfix: PATCH +""" + @pytest.fixture( params=[ @@ -167,6 +233,7 @@ def config(request): params=[ TomlConfig(data=TOML_STR_INFO_PATH, path="not_exist.toml"), JsonConfig(data=JSON_STR_INFO_PATH, path="not_exist.json"), + YAMLConfig(data=YAML_STR_INFO_PATH, path="not_exist.yaml"), ] ) def config_info(request): @@ -177,6 +244,7 @@ def config_info(request): params=[ TomlConfig(data=TOML_STR_WITHOUT_INFO, path="not_exist.toml"), JsonConfig(data=JSON_STR_WITHOUT_PATH, path="not_exist.json"), + YAMLConfig(data=YAML_STR_WITHOUT_PATH, path="not_exist.yaml"), ] ) def config_without_info(request):