Skip to content

Commit 0844f8e

Browse files
committed
feat(template): allow to override the template from cli, configuration and plugins
Fixes commitizen-tools#132 Fixes commitizen-tools#384 Fixes commitizen-tools#433 Closes commitizen-tools#376
1 parent a829488 commit 0844f8e

15 files changed

+601
-11
lines changed

commitizen/changelog.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@
3333
from datetime import date
3434
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Type
3535

36-
from jinja2 import Environment, PackageLoader
36+
from jinja2 import (
37+
BaseLoader,
38+
ChoiceLoader,
39+
Environment,
40+
FileSystemLoader,
41+
PackageLoader,
42+
)
3743
from packaging.version import InvalidVersion, Version
3844

3945
from commitizen import defaults
@@ -47,6 +53,8 @@
4753
# workaround mypy issue for 3.7 python
4854
VersionProtocol = typing.Any
4955

56+
DEFAULT_TEMPLATE = "keep_a_changelog_template.j2"
57+
5058

5159
def get_commit_tag(commit: GitCommit, tags: List[GitTag]) -> Optional[GitTag]:
5260
return next((tag for tag in tags if tag.rev == commit.rev), None)
@@ -174,11 +182,18 @@ def order_changelog_tree(tree: Iterable, change_type_order: List[str]) -> Iterab
174182
return sorted_tree
175183

176184

177-
def render_changelog(tree: Iterable) -> str:
178-
loader = PackageLoader("commitizen", "templates")
185+
def render_changelog(
186+
tree: Iterable,
187+
loader: Optional[BaseLoader] = None,
188+
template: Optional[str] = None,
189+
**kwargs,
190+
) -> str:
191+
loader = ChoiceLoader(
192+
[FileSystemLoader("."), loader or PackageLoader("commitizen", "templates")]
193+
)
179194
env = Environment(loader=loader, trim_blocks=True)
180-
jinja_template = env.get_template("keep_a_changelog_template.j2")
181-
changelog: str = jinja_template.render(tree=tree)
195+
jinja_template = env.get_template(template or DEFAULT_TEMPLATE)
196+
changelog: str = jinja_template.render(tree=tree, **kwargs)
182197
return changelog
183198

184199

commitizen/cli.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import argparse
22
import logging
33
import sys
4+
from copy import deepcopy
45
from functools import partial
56
from types import TracebackType
67
from typing import List
@@ -17,6 +18,51 @@
1718
)
1819

1920
logger = logging.getLogger(__name__)
21+
22+
23+
class ParseKwargs(argparse.Action):
24+
"""
25+
Parse arguments in the for `key=value`.
26+
27+
Quoted strings are automatically unquoted.
28+
Can be submitted multiple times:
29+
30+
ex:
31+
-k key=value -k double-quotes="value" -k single-quotes='value'
32+
33+
will result in
34+
35+
namespace["opt"] == {
36+
"key": "value",
37+
"double-quotes": "value",
38+
"single-quotes": "value",
39+
}
40+
"""
41+
42+
def __call__(self, parser, namespace, kwarg, option_string=None):
43+
kwargs = getattr(namespace, self.dest, None) or {}
44+
key, value = kwarg.split("=", 1)
45+
kwargs[key] = value.strip("'\"")
46+
setattr(namespace, self.dest, kwargs)
47+
48+
49+
tpl_arguments = (
50+
{
51+
"name": ["--template", "-t"],
52+
"help": (
53+
"changelog template file name "
54+
"(relative to the current working directory)"
55+
),
56+
},
57+
{
58+
"name": ["--extra", "-e"],
59+
"action": ParseKwargs,
60+
"dest": "extras",
61+
"metavar": "EXTRA",
62+
"help": "a changelog extra variable (in the form 'key=value')",
63+
},
64+
)
65+
2066
data = {
2167
"prog": "cz",
2268
"description": (
@@ -190,6 +236,7 @@
190236
"default": None,
191237
"help": "keep major version at zero, even for breaking changes",
192238
},
239+
*deepcopy(tpl_arguments),
193240
{
194241
"name": ["--prerelease-offset"],
195242
"type": int,
@@ -267,6 +314,7 @@
267314
"If not set, it will include prereleases in the changelog"
268315
),
269316
},
317+
*deepcopy(tpl_arguments),
270318
],
271319
},
272320
{

commitizen/commands/bump.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
4848
"annotated_tag",
4949
"major_version_zero",
5050
"prerelease_offset",
51+
"template",
5152
]
5253
if arguments[key] is not None
5354
},
@@ -66,6 +67,8 @@ def __init__(self, config: BaseConfig, arguments: dict):
6667
"version_type"
6768
)
6869
self.version_type = version_type and version_types.VERSION_TYPES[version_type]
70+
self.template = arguments["template"] or self.config.settings.get("template")
71+
self.extras = arguments["extras"]
6972

7073
def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool:
7174
"""Check if reading the whole git tree up to HEAD is needed."""
@@ -266,6 +269,8 @@ def __call__(self): # noqa: C901
266269
"unreleased_version": new_tag_version,
267270
"incremental": True,
268271
"dry_run": dry_run,
272+
"template": self.template,
273+
"extras": self.extras,
269274
},
270275
)
271276
changelog_cmd()

commitizen/commands/changelog.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def __init__(self, config: BaseConfig, args):
5555

5656
version_type = self.config.settings.get("version_type")
5757
self.version_type = version_type and version_types.VERSION_TYPES[version_type]
58+
self.template = args.get("template") or self.config.settings.get("template")
59+
self.extras = args.get("extras") or {}
5860

5961
def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
6062
"""Try to find the 'start_rev'.
@@ -170,7 +172,13 @@ def __call__(self):
170172
)
171173
if self.change_type_order:
172174
tree = changelog.order_changelog_tree(tree, self.change_type_order)
173-
changelog_out = changelog.render_changelog(tree)
175+
176+
extras = self.cz.template_extras.copy()
177+
extras.update(self.config.settings["extras"])
178+
extras.update(self.extras)
179+
changelog_out = changelog.render_changelog(
180+
tree, loader=self.cz.template_loader, template=self.template, **extras
181+
)
174182
changelog_out = changelog_out.lstrip("\n")
175183

176184
if self.dry_run:

commitizen/cz/base.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from __future__ import annotations
2+
13
from abc import ABCMeta, abstractmethod
2-
from typing import Callable, Dict, List, Optional, Tuple
4+
from typing import Any, Callable, Dict, List, Optional, Tuple
35

6+
from jinja2 import BaseLoader
47
from prompt_toolkit.styles import Style, merge_styles
58

69
from commitizen import git
10+
from commitizen.changelog import DEFAULT_TEMPLATE
711
from commitizen.config.base_config import BaseConfig
812
from commitizen.defaults import Questions
913

@@ -40,6 +44,10 @@ class BaseCommitizen(metaclass=ABCMeta):
4044
# Executed only at the end of the changelog generation
4145
changelog_hook: Optional[Callable[[str, Optional[str]], str]] = None
4246

47+
template: str = DEFAULT_TEMPLATE
48+
template_loader: Optional[BaseLoader] = None
49+
template_extras: dict[str, Any] = {}
50+
4351
def __init__(self, config: BaseConfig):
4452
self.config = config
4553
if not self.config.settings.get("style"):

commitizen/defaults.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import pathlib
24
from collections import OrderedDict
35
from typing import Any, Dict, Iterable, List, MutableMapping, Optional, Tuple, Union
@@ -46,6 +48,8 @@ class Settings(TypedDict, total=False):
4648
post_bump_hooks: Optional[List[str]]
4749
prerelease_offset: int
4850
version_type: Optional[str]
51+
template: Optional[str]
52+
extras: dict[str, Any]
4953

5054

5155
name: str = "cz_conventional_commits"
@@ -77,6 +81,8 @@ class Settings(TypedDict, total=False):
7781
"post_bump_hooks": [],
7882
"prerelease_offset": 0,
7983
"version_type": None,
84+
"template": None,
85+
"extras": {},
8086
}
8187

8288
MAJOR = "MAJOR"

docs/bump.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog]
6060
[--devrelease DEVRELEASE] [--increment {MAJOR,MINOR,PATCH}]
6161
[--check-consistency] [--annotated-tag] [--gpg-sign]
6262
[--changelog-to-stdout] [--retry] [--major-version-zero]
63+
[--template TEMPLATE] [--extra EXTRA]
6364
[MANUAL_VERSION]
6465

6566
positional arguments:
@@ -99,6 +100,10 @@ options:
99100
--version-type {pep440,semver}
100101
choose version type
101102

103+
--template TEMPLATE, -t TEMPLATE
104+
changelog template file name (relative to the current working directory)
105+
--extra EXTRA, -e EXTRA
106+
a changelog extra variable (in the form 'key=value')
102107
```
103108
104109
### `--files-only`
@@ -217,6 +222,21 @@ We recommend setting `major_version_zero = true` in your configuration file whil
217222
is in its initial development. Remove that configuration using a breaking-change commit to bump
218223
your project’s major version to `v1.0.0` once your project has reached maturity.
219224
225+
### `--template`
226+
227+
Provides your own changelog jinja template.
228+
See [the template customization section](customization.md#customizing-the-changelog-template)
229+
230+
### `--extra`
231+
232+
Provides your own changelog extra variables by using the `extras` settings or the `--extra/-e` parameter.
233+
234+
```bash
235+
cz bump --changelog --extra key=value -e short="quoted value"
236+
```
237+
238+
See [the template customization section](customization.md#customizing-the-changelog-template).
239+
220240
## Avoid raising errors
221241
222242
Some situations from commitizen rise an exit code different than 0.

docs/changelog.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This command will generate a changelog following the committing rules establishe
77
```bash
88
$ cz changelog --help
99
usage: cz changelog [-h] [--dry-run] [--file-name FILE_NAME] [--unreleased-version UNRELEASED_VERSION] [--incremental] [--start-rev START_REV]
10+
[--template TEMPLATE] [--extra EXTRA]
1011
[rev_range]
1112

1213
positional arguments:
@@ -21,9 +22,19 @@ optional arguments:
2122
set the value for the new version (use the tag value), instead of using unreleased
2223
--incremental generates changelog from last created version, useful if the changelog has been manually modified
2324
--start-rev START_REV
25+
<<<<<<< HEAD
2426
start rev of the changelog. If not set, it will generate changelog from the start
2527
--merge-prerelease
2628
collect all changes from prereleases into next non-prerelease. If not set, it will include prereleases in the changelog
29+
||||||| parent of a3c1026 (feat(template): allow to override the template from cli, configuration and plugins)
30+
start rev of the changelog.If not set, it will generate changelog from the start
31+
=======
32+
start rev of the changelog.If not set, it will generate changelog from the start
33+
--template TEMPLATE, -t TEMPLATE
34+
changelog template file name (relative to the current working directory)
35+
--extra EXTRA, -e EXTRA
36+
a changelog extra variable (in the form 'key=value')
37+
>>>>>>> a3c1026 (feat(template): allow to override the template from cli, configuration and plugins)
2738
```
2839
2940
### Examples
@@ -173,12 +184,27 @@ Collects changes from prereleases into the next non-prerelease. This means that
173184
cz changelog --merge-prerelease
174185
```
175186
176-
```toml
187+
```toml
177188
[tools.commitizen]
178189
# ...
179190
changelog_merge_prerelease = true
180191
```
181192
193+
### `template`
194+
195+
Provides your own changelog jinja template by using the `template` settings or the `--template` parameter.
196+
See [the template customization section](customization.md#customizing-the-changelog-template)
197+
198+
### `extras`
199+
200+
Provides your own changelog extra variables by using the `extras` settings or the `--extra/-e` parameter.
201+
202+
```bash
203+
cz changelog --extra key=value -e short="quoted value"
204+
```
205+
206+
See [the template customization section](customization.md#customizing-the-changelog-template)
207+
182208
## Hooks
183209
184210
Supported hook methods:

docs/config.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
| `major_version_zero` | `bool` | `false` | When true, breaking changes on a `0.x` will remain as a `0.x` version. On `false`, a breaking change will bump a `0.x` version to `1.0`. [major-version-zero] |
2525
| `prerelease_offset` | `int` | `0` | In special cases it may be necessary that a prerelease cannot start with a 0, e.g. in an embedded project the individual characters are encoded in bytes. This can be done by specifying an offset from which to start counting. [prerelease-offset] |
2626
| `version_type` | `str` | `pep440` | Select a version type from the following options [`pep440`, `semver`]. Useful for non-python projects. [See more][version_type] |
27+
| <a name="cfg-template"></a>`template` | `str` | `None` | Provide custom changelog jinja template path relative to the current working directory. [See more (template customization)][template-customization] |
28+
| <a name="cfg-extras"></a>`extras` | `dict` | `{}` | Provide extra variables to the changelog template. [See more (template customization)][template-customization] |
2729

2830
## pyproject.toml or .cz.toml
2931

@@ -184,4 +186,5 @@ setup(
184186
[additional-features]: https://github.com/tmbo/questionary#additional-features
185187
[customization]: customization.md
186188
[shortcuts]: customization.md#shortcut-keys
189+
[template-customization]: customization.md#customizing-the-changelog-template
187190
[annotated-tags-vs-lightweight]: https://stackoverflow.com/a/11514139/2047185

0 commit comments

Comments
 (0)