Skip to content

Customize committing and bumping through toml file #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion commitizen/cz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@

from commitizen.cz.conventional_commits import ConventionalCommitsCz
from commitizen.cz.jira import JiraSmartCz
from commitizen.cz.customize import CustomizeCommitsCz

registry = {"cz_conventional_commits": ConventionalCommitsCz, "cz_jira": JiraSmartCz}
registry = {
"cz_conventional_commits": ConventionalCommitsCz,
"cz_jira": JiraSmartCz,
"cz_customize": CustomizeCommitsCz,
}
plugins = {
name: importlib.import_module(name).discover_this
for finder, name, ispkg in pkgutil.iter_modules()
Expand Down
1 change: 1 addition & 0 deletions commitizen/cz/customize/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .customize import CustomizeCommitsCz # noqa
45 changes: 45 additions & 0 deletions commitizen/cz/customize/customize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from commitizen import defaults
from commitizen.cz.base import BaseCommitizen

__all__ = ["CustomizeCommitsCz"]


class CustomizeCommitsCz(BaseCommitizen):
bump_pattern = defaults.bump_pattern
bump_map = defaults.bump_map

def __init__(self, config: dict):
super(CustomizeCommitsCz, self).__init__(config)
self.custom_config = self.config.get("customize")

custom_bump_pattern = self.custom_config.get("bump_pattern")
if custom_bump_pattern:
self.bump_pattern = custom_bump_pattern

custom_bump_map = self.custom_config.get("bump_map")
if custom_bump_map:
self.bump_map = custom_bump_map

def questions(self) -> list:
return self.custom_config.get("questions")

def message(self, answers: dict) -> str:
message_template = self.custom_config.get("message_template")
return message_template.format(**answers)

def example(self) -> str:
return self.custom_config.get("example")

def schema(self) -> str:
return self.custom_config.get("schema")

def info(self) -> str:
info_path = self.custom_config.get("info_path")
info = self.custom_config.get("info")
if info_path:
with open(info_path, "r") as f:
content = f.read()
return content
elif info:
return info
raise NotImplementedError("Not Implemented yet")
Empty file.
1 change: 1 addition & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ The extra tab before the square brakets (`]`) at the end is required.
| `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) |
| `bump_message` | `str` | `None` | Create custom commit message, useful to skip ci. [See more](https://woile.github.io/commitizen/bump#bump_message) |
| `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) |
| `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/) |
69 changes: 66 additions & 3 deletions docs/customization.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Customizing commitizen is not hard at all.

## Customize through customize class

The basic steps are:

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

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

## Custom commit rules
### Custom commit rules

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

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

## Custom bump rules
### Custom bump rules

You need to define 2 parameters inside `BaseCommitizen`.

Expand Down Expand Up @@ -123,7 +125,7 @@ cz -n cz_strange bump

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

## Raise Customize Exception
### Raise Customize Exception

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

Expand All @@ -133,3 +135,64 @@ from commitizen.cz.exception import CzException
class NoSubjectProvidedException(CzException):
...
```

## Customize in toml

**This is only supported when configuring through `toml` (e.g., `pyproject.toml`, `.cz`, and `.cz.toml`)**

The basic steps are:
1. Define your custom committing or bumpping rules in the configuration file.
2. Declare `name = "cz_customize"` in your configuration file, or add `-n cz_customize` when running commitizen.

Example:

```toml
[tool.commitizen]
name = "cz_customize"

[tool.commitizen.customize]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet!

message_template = "{change_type}: {message}"
example = "feature: this feature eanable customize through config file"
schema = "<type>: <body>"
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
"""

[[tool.commitizen.customize.questions]]
type = "list"
name = "change_type"
choices = ["feature", "bug fix"]
message = "Select the type of change you are committing"

[[tool.commitizen.customize.questions]]
type = "input"
name = "message"
message = "Body."
```

### Customize configuration

| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `question` | `dict` | `None` | Questions regarding the commit message. Detatiled below. |
| `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`. |
| `example` | `str` | `None` | (OPTIONAL) Provide an example to help understand the style. Used by `cz example`. |
| `schema` | `str` | `None` | (OPTIONAL) Show the schema used. Used by `cz schema`. |
| `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. |
| `info` | `str` | `None` | (OPTIONAL) Explanation of the commit rules. Used by `cz info`. |
| `bump_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the extracted information to a `SemVer` increment type (`MAJOR`, `MINOR`, `PATCH`) |
| `bump_pattern` | `str` | `None` | (OPTIONAL) Regex to extract information from commit (subject and body) |

#### Detailed `question` content

| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `type` | `str` | `None` | The type of questions. Valid type: `list`, `input` and etc. [See More](https://github.com/tmbo/questionary#different-question-types) |
| `name` | `str` | `None` | The key for the value answered by user. It's used in `message_template` |
| `message` | `str` | `None` | Detail description for the question. |
| `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."}]`) |
| `default` | `Any` | `None` | (OPTIONAL) The default value for this question. |
| `filter` | `str` | `None` | (Optional) Validator for user's answer. **(Work in Progress)** |
94 changes: 94 additions & 0 deletions tests/test_cz_customize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import pytest
from tomlkit import parse

from commitizen.cz.customize import CustomizeCommitsCz
from commitizen.config import Config


@pytest.fixture(scope="module")
def config():
_conf = Config()
toml_str = """
[tool.commitizen.customize]
# message_template should follow the python string formatting spec
message_template = "{change_type}: {message}"
example = "feature: this feature eanable customize through config file"
schema = "<type>: <body>"
bump_pattern = "^(break|new|fix|hotfix)"
bump_map = {"break" = "MAJOR", "new" = "MINOR", "fix" = "PATCH", "hotfix" = "PATCH"}
info = "This is a customized cz."

[[tool.commitizen.customize.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"

[[tool.commitizen.customize.questions]]
type = "input"
name = "message"
message = "Body."
"""
_conf.update(parse(toml_str)["tool"]["commitizen"])
return _conf.config


def test_bump_pattern(config):
cz = CustomizeCommitsCz(config)
assert cz.bump_pattern == "^(break|new|fix|hotfix)"


def test_bump_map(config):
cz = CustomizeCommitsCz(config)
assert cz.bump_map == {
"break": "MAJOR",
"new": "MINOR",
"fix": "PATCH",
"hotfix": "PATCH",
}


def test_questions(config):
cz = CustomizeCommitsCz(config)
questions = cz.questions()
expected_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."},
]
assert list(questions) == expected_questions


def test_answer(config):
cz = CustomizeCommitsCz(config)
answers = {
"change_type": "feature",
"message": "this feature eanable customize through config file",
}
message = cz.message(answers)
assert message == "feature: this feature eanable customize through config file"


def test_example(config):
cz = CustomizeCommitsCz(config)
assert "feature: this feature eanable customize through config file" in cz.example()


def test_schema(config):
cz = CustomizeCommitsCz(config)
assert "<type>: <body>" in cz.schema()


def test_info(config):
cz = CustomizeCommitsCz(config)
assert "This is a customized cz." in cz.info()