Skip to content

Commit 5ab7fe8

Browse files
authored
Merge pull request #72 from Lee-W/easier-customization
Customize committing and bumping through toml file
2 parents d4c40d8 + e4c93d0 commit 5ab7fe8

File tree

7 files changed

+213
-4
lines changed

7 files changed

+213
-4
lines changed

commitizen/cz/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33

44
from commitizen.cz.conventional_commits import ConventionalCommitsCz
55
from commitizen.cz.jira import JiraSmartCz
6+
from commitizen.cz.customize import CustomizeCommitsCz
67

7-
registry = {"cz_conventional_commits": ConventionalCommitsCz, "cz_jira": JiraSmartCz}
8+
registry = {
9+
"cz_conventional_commits": ConventionalCommitsCz,
10+
"cz_jira": JiraSmartCz,
11+
"cz_customize": CustomizeCommitsCz,
12+
}
813
plugins = {
914
name: importlib.import_module(name).discover_this
1015
for finder, name, ispkg in pkgutil.iter_modules()

commitizen/cz/customize/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .customize import CustomizeCommitsCz # noqa

commitizen/cz/customize/customize.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from commitizen import defaults
2+
from commitizen.cz.base import BaseCommitizen
3+
4+
__all__ = ["CustomizeCommitsCz"]
5+
6+
7+
class CustomizeCommitsCz(BaseCommitizen):
8+
bump_pattern = defaults.bump_pattern
9+
bump_map = defaults.bump_map
10+
11+
def __init__(self, config: dict):
12+
super(CustomizeCommitsCz, self).__init__(config)
13+
self.custom_config = self.config.get("customize")
14+
15+
custom_bump_pattern = self.custom_config.get("bump_pattern")
16+
if custom_bump_pattern:
17+
self.bump_pattern = custom_bump_pattern
18+
19+
custom_bump_map = self.custom_config.get("bump_map")
20+
if custom_bump_map:
21+
self.bump_map = custom_bump_map
22+
23+
def questions(self) -> list:
24+
return self.custom_config.get("questions")
25+
26+
def message(self, answers: dict) -> str:
27+
message_template = self.custom_config.get("message_template")
28+
return message_template.format(**answers)
29+
30+
def example(self) -> str:
31+
return self.custom_config.get("example")
32+
33+
def schema(self) -> str:
34+
return self.custom_config.get("schema")
35+
36+
def info(self) -> str:
37+
info_path = self.custom_config.get("info_path")
38+
info = self.custom_config.get("info")
39+
if info_path:
40+
with open(info_path, "r") as f:
41+
content = f.read()
42+
return content
43+
elif info:
44+
return info
45+
raise NotImplementedError("Not Implemented yet")

commitizen/cz/customize/customize_info.txt

Whitespace-only changes.

docs/config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ The extra tab before the square brakets (`]`) at the end is required.
6565
| `tag_format` | `str` | `None` | Format for the git tag, useful for old projects, that use a convention like `"v1.2.1"`. [See more](https://woile.github.io/commitizen/bump#tag_format) |
6666
| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more](https://woile.github.io/commitizen/bump#bump_message) |
6767
| `style` | `list` | see above | Style for the prompts (It will merge this value with default style.) [See More (Styling your prompts with your favorite colors)](https://github.com/tmbo/questionary#additional-features) |
68+
| `customize` | `dict` | `None` | **This is only supported when config through `toml`.** Custom rules for committing and bumping. [See more](https://woile.github.io/commitizen/customization/) |

docs/customization.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
Customizing commitizen is not hard at all.
22

3+
## Customize through customize class
4+
35
The basic steps are:
46

57
1. Inheriting from `BaseCommitizen`
@@ -9,7 +11,7 @@ The basic steps are:
911

1012
Check an [example](convcomms) on how to configure `BaseCommitizen`.
1113

12-
## Custom commit rules
14+
### Custom commit rules
1315

1416
Create a file starting with `cz_` for example `cz_jira.py`. This prefix
1517
is used to detect the plugin. Same method [flask uses]
@@ -95,7 +97,7 @@ If you feel like it should be part of this repo, create a PR.
9597

9698
[flask uses]: http://flask.pocoo.org/docs/0.12/extensiondev/
9799

98-
## Custom bump rules
100+
### Custom bump rules
99101

100102
You need to define 2 parameters inside `BaseCommitizen`.
101103

@@ -123,7 +125,7 @@ cz -n cz_strange bump
123125

124126
[convcomms]: https://github.com/Woile/commitizen/blob/master/commitizen/cz/conventional_commits/conventional_commits.py
125127

126-
## Raise Customize Exception
128+
### Raise Customize Exception
127129

128130
If you wannt `commitizen` to catch your exception and print the message, you'll have to inherit `CzException`.
129131

@@ -133,3 +135,64 @@ from commitizen.cz.exception import CzException
133135
class NoSubjectProvidedException(CzException):
134136
...
135137
```
138+
139+
## Customize in toml
140+
141+
**This is only supported when configuring through `toml` (e.g., `pyproject.toml`, `.cz`, and `.cz.toml`)**
142+
143+
The basic steps are:
144+
1. Define your custom committing or bumpping rules in the configuration file.
145+
2. Declare `name = "cz_customize"` in your configuration file, or add `-n cz_customize` when running commitizen.
146+
147+
Example:
148+
149+
```toml
150+
[tool.commitizen]
151+
name = "cz_customize"
152+
153+
[tool.commitizen.customize]
154+
message_template = "{change_type}: {message}"
155+
example = "feature: this feature eanable customize through config file"
156+
schema = "<type>: <body>"
157+
bump_pattern = "^(break|new|fix|hotfix)"
158+
bump_map = {"break" = "MAJOR", "new" = "MINOR", "fix" = "PATCH", "hotfix" = "PATCH"}
159+
info_path = "cz_customize_info.txt"
160+
info = """
161+
This is customized info
162+
"""
163+
164+
[[tool.commitizen.customize.questions]]
165+
type = "list"
166+
name = "change_type"
167+
choices = ["feature", "bug fix"]
168+
message = "Select the type of change you are committing"
169+
170+
[[tool.commitizen.customize.questions]]
171+
type = "input"
172+
name = "message"
173+
message = "Body."
174+
```
175+
176+
### Customize configuration
177+
178+
| Parameter | Type | Default | Description |
179+
| --------- | ---- | ------- | ----------- |
180+
| `question` | `dict` | `None` | Questions regarding the commit message. Detatiled below. |
181+
| `message_template` | `str` | `None` | The template for generating message from the given answers. `message_template` should follow the python string formatting specification, and all the variables in this template should be defined in `name` in `questions`. |
182+
| `example` | `str` | `None` | (OPTIONAL) Provide an example to help understand the style. Used by `cz example`. |
183+
| `schema` | `str` | `None` | (OPTIONAL) Show the schema used. Used by `cz schema`. |
184+
| `info_path` | `str` | `None` | (OPTIONAL) The path to the file that contains explanation of the commit rules. Used by `cz info`. If not provided `cz info`, will load `info` instead. |
185+
| `info` | `str` | `None` | (OPTIONAL) Explanation of the commit rules. Used by `cz info`. |
186+
| `bump_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`) |
187+
| `bump_pattern` | `str` | `None` | (OPTIONAL) Regex to extract information from commit (subject and body) |
188+
189+
#### Detailed `question` content
190+
191+
| Parameter | Type | Default | Description |
192+
| --------- | ---- | ------- | ----------- |
193+
| `type` | `str` | `None` | The type of questions. Valid type: `list`, `input` and etc. [See More](https://github.com/tmbo/questionary#different-question-types) |
194+
| `name` | `str` | `None` | The key for the value answered by user. It's used in `message_template` |
195+
| `message` | `str` | `None` | Detail description for the question. |
196+
| `choices` | `list` | `None` | (OPTIONAL) The choices when `type = choice`. It should be list of dictionaries with `name` and `value`. (e.g., `[{value = "feature", name = "feature: A new feature."}, {value = "bug fix", name = "bug fix: A bug fix."}]`) |
197+
| `default` | `Any` | `None` | (OPTIONAL) The default value for this question. |
198+
| `filter` | `str` | `None` | (Optional) Validator for user's answer. **(Work in Progress)** |

tests/test_cz_customize.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import pytest
2+
from tomlkit import parse
3+
4+
from commitizen.cz.customize import CustomizeCommitsCz
5+
from commitizen.config import Config
6+
7+
8+
@pytest.fixture(scope="module")
9+
def config():
10+
_conf = Config()
11+
toml_str = """
12+
[tool.commitizen.customize]
13+
# message_template should follow the python string formatting spec
14+
message_template = "{change_type}: {message}"
15+
example = "feature: this feature eanable customize through config file"
16+
schema = "<type>: <body>"
17+
bump_pattern = "^(break|new|fix|hotfix)"
18+
bump_map = {"break" = "MAJOR", "new" = "MINOR", "fix" = "PATCH", "hotfix" = "PATCH"}
19+
info = "This is a customized cz."
20+
21+
[[tool.commitizen.customize.questions]]
22+
type = "list"
23+
name = "change_type"
24+
choices = [
25+
{value = "feature", name = "feature: A new feature."},
26+
{value = "bug fix", name = "bug fix: A bug fix."}
27+
]
28+
message = "Select the type of change you are committing"
29+
30+
[[tool.commitizen.customize.questions]]
31+
type = "input"
32+
name = "message"
33+
message = "Body."
34+
"""
35+
_conf.update(parse(toml_str)["tool"]["commitizen"])
36+
return _conf.config
37+
38+
39+
def test_bump_pattern(config):
40+
cz = CustomizeCommitsCz(config)
41+
assert cz.bump_pattern == "^(break|new|fix|hotfix)"
42+
43+
44+
def test_bump_map(config):
45+
cz = CustomizeCommitsCz(config)
46+
assert cz.bump_map == {
47+
"break": "MAJOR",
48+
"new": "MINOR",
49+
"fix": "PATCH",
50+
"hotfix": "PATCH",
51+
}
52+
53+
54+
def test_questions(config):
55+
cz = CustomizeCommitsCz(config)
56+
questions = cz.questions()
57+
expected_questions = [
58+
{
59+
"type": "list",
60+
"name": "change_type",
61+
"choices": [
62+
{"value": "feature", "name": "feature: A new feature."},
63+
{"value": "bug fix", "name": "bug fix: A bug fix."},
64+
],
65+
"message": "Select the type of change you are committing",
66+
},
67+
{"type": "input", "name": "message", "message": "Body."},
68+
]
69+
assert list(questions) == expected_questions
70+
71+
72+
def test_answer(config):
73+
cz = CustomizeCommitsCz(config)
74+
answers = {
75+
"change_type": "feature",
76+
"message": "this feature eanable customize through config file",
77+
}
78+
message = cz.message(answers)
79+
assert message == "feature: this feature eanable customize through config file"
80+
81+
82+
def test_example(config):
83+
cz = CustomizeCommitsCz(config)
84+
assert "feature: this feature eanable customize through config file" in cz.example()
85+
86+
87+
def test_schema(config):
88+
cz = CustomizeCommitsCz(config)
89+
assert "<type>: <body>" in cz.schema()
90+
91+
92+
def test_info(config):
93+
cz = CustomizeCommitsCz(config)
94+
assert "This is a customized cz." in cz.info()

0 commit comments

Comments
 (0)