Skip to content

Commit 1ca2a45

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 Closes commitizen-tools#640
1 parent 4060cc2 commit 1ca2a45

15 files changed

+615
-12
lines changed

Diff for: commitizen/changelog.py

+20-5
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)
@@ -176,11 +184,18 @@ def order_changelog_tree(tree: Iterable, change_type_order: List[str]) -> Iterab
176184
return sorted_tree
177185

178186

179-
def render_changelog(tree: Iterable) -> str:
180-
loader = PackageLoader("commitizen", "templates")
187+
def render_changelog(
188+
tree: Iterable,
189+
loader: Optional[BaseLoader] = None,
190+
template: Optional[str] = None,
191+
**kwargs,
192+
) -> str:
193+
loader = ChoiceLoader(
194+
[FileSystemLoader("."), loader or PackageLoader("commitizen", "templates")]
195+
)
181196
env = Environment(loader=loader, trim_blocks=True)
182-
jinja_template = env.get_template("keep_a_changelog_template.j2")
183-
changelog: str = jinja_template.render(tree=tree)
197+
jinja_template = env.get_template(template or DEFAULT_TEMPLATE)
198+
changelog: str = jinja_template.render(tree=tree, **kwargs)
184199
return changelog
185200

186201

Diff for: commitizen/cli.py

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import argparse
22
import logging
33
import sys
4-
from pathlib import Path
4+
from copy import deepcopy
55
from functools import partial
6+
from pathlib import Path
67
from types import TracebackType
78
from typing import List
89

@@ -18,6 +19,51 @@
1819
)
1920

2021
logger = logging.getLogger(__name__)
22+
23+
24+
class ParseKwargs(argparse.Action):
25+
"""
26+
Parse arguments in the for `key=value`.
27+
28+
Quoted strings are automatically unquoted.
29+
Can be submitted multiple times:
30+
31+
ex:
32+
-k key=value -k double-quotes="value" -k single-quotes='value'
33+
34+
will result in
35+
36+
namespace["opt"] == {
37+
"key": "value",
38+
"double-quotes": "value",
39+
"single-quotes": "value",
40+
}
41+
"""
42+
43+
def __call__(self, parser, namespace, kwarg, option_string=None):
44+
kwargs = getattr(namespace, self.dest, None) or {}
45+
key, value = kwarg.split("=", 1)
46+
kwargs[key] = value.strip("'\"")
47+
setattr(namespace, self.dest, kwargs)
48+
49+
50+
tpl_arguments = (
51+
{
52+
"name": ["--template", "-t"],
53+
"help": (
54+
"changelog template file name "
55+
"(relative to the current working directory)"
56+
),
57+
},
58+
{
59+
"name": ["--extra", "-e"],
60+
"action": ParseKwargs,
61+
"dest": "extras",
62+
"metavar": "EXTRA",
63+
"help": "a changelog extra variable (in the form 'key=value')",
64+
},
65+
)
66+
2167
data = {
2268
"prog": "cz",
2369
"description": (
@@ -197,6 +243,7 @@
197243
"default": None,
198244
"help": "keep major version at zero, even for breaking changes",
199245
},
246+
*deepcopy(tpl_arguments),
200247
{
201248
"name": ["--prerelease-offset"],
202249
"type": int,
@@ -274,6 +321,7 @@
274321
"If not set, it will include prereleases in the changelog"
275322
),
276323
},
324+
*deepcopy(tpl_arguments),
277325
],
278326
},
279327
{

Diff for: commitizen/commands/bump.py

+5
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."""
@@ -271,6 +274,8 @@ def __call__(self): # noqa: C901
271274
"unreleased_version": new_tag_version,
272275
"incremental": True,
273276
"dry_run": dry_run,
277+
"template": self.template,
278+
"extras": self.extras,
274279
},
275280
)
276281
changelog_cmd()

Diff for: commitizen/commands/changelog.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ def __init__(self, config: BaseConfig, args):
6464

6565
version_type = self.config.settings.get("version_type")
6666
self.version_type = version_type and version_types.VERSION_TYPES[version_type]
67+
self.template = args.get("template") or self.config.settings.get("template")
68+
self.extras = args.get("extras") or {}
6769

6870
def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
6971
"""Try to find the 'start_rev'.
@@ -182,7 +184,13 @@ def __call__(self):
182184
)
183185
if self.change_type_order:
184186
tree = changelog.order_changelog_tree(tree, self.change_type_order)
185-
changelog_out = changelog.render_changelog(tree)
187+
188+
extras = self.cz.template_extras.copy()
189+
extras.update(self.config.settings["extras"])
190+
extras.update(self.extras)
191+
changelog_out = changelog.render_changelog(
192+
tree, loader=self.cz.template_loader, template=self.template, **extras
193+
)
186194
changelog_out = changelog_out.lstrip("\n")
187195

188196
if self.dry_run:

Diff for: commitizen/cz/base.py

+9-1
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

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

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

Diff for: commitizen/defaults.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import pathlib
24
import sys
35
from collections import OrderedDict
@@ -51,6 +53,8 @@ class Settings(TypedDict, total=False):
5153
post_bump_hooks: Optional[List[str]]
5254
prerelease_offset: int
5355
version_type: Optional[str]
56+
template: Optional[str]
57+
extras: dict[str, Any]
5458

5559

5660
name: str = "cz_conventional_commits"
@@ -82,6 +86,8 @@ class Settings(TypedDict, total=False):
8286
"post_bump_hooks": [],
8387
"prerelease_offset": 0,
8488
"version_type": None,
89+
"template": None,
90+
"extras": {},
8591
}
8692

8793
MAJOR = "MAJOR"

Diff for: docs/bump.md

+20
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog]
5858
[--devrelease DEVRELEASE] [--increment {MAJOR,MINOR,PATCH}]
5959
[--check-consistency] [--annotated-tag] [--gpg-sign]
6060
[--changelog-to-stdout] [--retry] [--major-version-zero]
61+
[--template TEMPLATE] [--extra EXTRA]
6162
[MANUAL_VERSION]
6263

6364
positional arguments:
@@ -97,6 +98,10 @@ options:
9798
--version-type {pep440,semver}
9899
choose version type
99100

101+
--template TEMPLATE, -t TEMPLATE
102+
changelog template file name (relative to the current working directory)
103+
--extra EXTRA, -e EXTRA
104+
a changelog extra variable (in the form 'key=value')
100105
```
101106
102107
### `--files-only`
@@ -241,6 +246,21 @@ Can I transition from one to the other?
241246
242247
Yes, you shouldn't have any issues.
243248
249+
### `--template`
250+
251+
Provides your own changelog jinja template.
252+
See [the template customization section](customization.md#customizing-the-changelog-template)
253+
254+
### `--extra`
255+
256+
Provides your own changelog extra variables by using the `extras` settings or the `--extra/-e` parameter.
257+
258+
```bash
259+
cz bump --changelog --extra key=value -e short="quoted value"
260+
```
261+
262+
See [the template customization section](customization.md#customizing-the-changelog-template).
263+
244264
## Avoid raising errors
245265
246266
Some situations from commitizen raise an exit code different than 0.

Diff for: docs/changelog.md

+26
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ update_changelog_on_bump = true
1414
```bash
1515
$ cz changelog --help
1616
usage: cz changelog [-h] [--dry-run] [--file-name FILE_NAME] [--unreleased-version UNRELEASED_VERSION] [--incremental] [--start-rev START_REV]
17+
[--template TEMPLATE] [--extra EXTRA]
1718
[rev_range]
1819

1920
positional arguments:
@@ -28,9 +29,19 @@ optional arguments:
2829
set the value for the new version (use the tag value), instead of using unreleased
2930
--incremental generates changelog from last created version, useful if the changelog has been manually modified
3031
--start-rev START_REV
32+
<<<<<<< HEAD
3133
start rev of the changelog. If not set, it will generate changelog from the start
3234
--merge-prerelease
3335
collect all changes from prereleases into next non-prerelease. If not set, it will include prereleases in the changelog
36+
||||||| parent of a3c1026 (feat(template): allow to override the template from cli, configuration and plugins)
37+
start rev of the changelog.If not set, it will generate changelog from the start
38+
=======
39+
start rev of the changelog.If not set, it will generate changelog from the start
40+
--template TEMPLATE, -t TEMPLATE
41+
changelog template file name (relative to the current working directory)
42+
--extra EXTRA, -e EXTRA
43+
a changelog extra variable (in the form 'key=value')
44+
>>>>>>> a3c1026 (feat(template): allow to override the template from cli, configuration and plugins)
3445
```
3546
3647
### Examples
@@ -186,6 +197,21 @@ cz changelog --merge-prerelease
186197
changelog_merge_prerelease = true
187198
```
188199
200+
### `template`
201+
202+
Provides your own changelog jinja template by using the `template` settings or the `--template` parameter.
203+
See [the template customization section](customization.md#customizing-the-changelog-template)
204+
205+
### `extras`
206+
207+
Provides your own changelog extra variables by using the `extras` settings or the `--extra/-e` parameter.
208+
209+
```bash
210+
cz changelog --extra key=value -e short="quoted value"
211+
```
212+
213+
See [the template customization section](customization.md#customizing-the-changelog-template)
214+
189215
## Hooks
190216
191217
Supported hook methods:

Diff for: docs/config.md

+17
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,22 @@ Default: `[]`
178178

179179
Calls the hook scripts **after** bumping the version. [Read more][post_bump_hooks]
180180

181+
### `template`
182+
183+
Type: `str`
184+
185+
Default: `None` (provided by plugin)
186+
187+
Provide custom changelog jinja template path relative to the current working directory. [Read more][template-customization]
188+
189+
### `extras`
190+
191+
Type: `dict[str, Any]`
192+
193+
Default: `{}`
194+
195+
Provide extra variables to the changelog template. [Read more][template-customization] |
196+
181197
## Configuration file
182198

183199
### pyproject.toml or .cz.toml
@@ -349,4 +365,5 @@ setup(
349365
[additional-features]: https://github.com/tmbo/questionary#additional-features
350366
[customization]: customization.md
351367
[shortcuts]: customization.md#shortcut-keys
368+
[template-customization]: customization.md#customizing-the-changelog-template
352369
[annotated-tags-vs-lightweight]: https://stackoverflow.com/a/11514139/2047185

0 commit comments

Comments
 (0)