From c09f37777a9f0919f37b8e9a613790c8d056d202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Omar=20Vergara=20P=C3=A9rez?= Date: Tue, 8 Dec 2020 00:53:14 -0600 Subject: [PATCH] feat: add yaml as a config option 314 feat(config): add support for the new class YAMLConfig at the root of the confi internal package 314 test(test_config): add test support for the class YAMLConfig 314 fix(YAMLConfig): add a TypeError exception to handle in _parse_settings method feat(init): add support for yaml config file at init 314 test(tests): add test functions to test YAMLConfig class at init and customization 314 docs(docs): add instructions on how to define yaml config files 314 --- commitizen/commands/init.py | 4 +- commitizen/config/__init__.py | 9 ++-- commitizen/config/yaml_config.py | 47 +++++++++++++++++++ commitizen/defaults.py | 9 +++- docs/config.md | 35 ++++++++++++++- docs/customization.md | 35 +++++++++++++++ tests/commands/test_init_command.py | 22 ++++++--- tests/test_conf.py | 10 +++-- tests/test_cz_customize.py | 70 ++++++++++++++++++++++++++++- 9 files changed, 224 insertions(+), 17 deletions(-) create mode 100644 commitizen/config/yaml_config.py diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 94c0e142e..96b06f208 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 ed9c09001..2cbd54482 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 000000000..cc6c5f033 --- /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 a0dfbb866..c5f07992f 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 bff8112ea..204edcf88 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 06e084a75..0d86dc057 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 432139695..977e9e6a1 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 5ab66c2ff..ea7b3a3c9 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 d74814087..479fcef7c 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):