Skip to content

Commit

Permalink
feat(template): allow to override the template from cli, configuratio…
Browse files Browse the repository at this point in the history
…n and plugins

Fixes #132
Fixes #384
Fixes #433
Closes #376
  • Loading branch information
noirbizarre committed Dec 26, 2022
1 parent 61c3024 commit 45d3ceb
Show file tree
Hide file tree
Showing 15 changed files with 500 additions and 9 deletions.
25 changes: 20 additions & 5 deletions commitizen/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@
from datetime import date
from typing import Callable, Dict, Iterable, List, Optional, Tuple

from jinja2 import Environment, PackageLoader
from jinja2 import (
BaseLoader,
ChoiceLoader,
Environment,
FileSystemLoader,
PackageLoader,
)

from commitizen import defaults
from commitizen.bump import normalize_tag
from commitizen.exceptions import InvalidConfigurationError, NoCommitsFoundError
from commitizen.git import GitCommit, GitTag

DEFAULT_TEMPLATE = "keep_a_changelog_template.j2"


def get_commit_tag(commit: GitCommit, tags: List[GitTag]) -> Optional[GitTag]:
return next((tag for tag in tags if tag.rev == commit.rev), None)
Expand Down Expand Up @@ -139,11 +147,18 @@ def order_changelog_tree(tree: Iterable, change_type_order: List[str]) -> Iterab
return sorted_tree


def render_changelog(tree: Iterable) -> str:
loader = PackageLoader("commitizen", "templates")
def render_changelog(
tree: Iterable,
loader: BaseLoader | None = None,
template: str | None = None,
**kwargs,
) -> str:
loader = ChoiceLoader(
[FileSystemLoader("."), loader or PackageLoader("commitizen", "templates")]
)
env = Environment(loader=loader, trim_blocks=True)
jinja_template = env.get_template("keep_a_changelog_template.j2")
changelog: str = jinja_template.render(tree=tree)
jinja_template = env.get_template(template or DEFAULT_TEMPLATE)
changelog: str = jinja_template.render(tree=tree, **kwargs)
return changelog


Expand Down
28 changes: 28 additions & 0 deletions commitizen/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@
"default": None,
"help": "keep major version at zero, even for breaking changes",
},
{
"name": ["--template", "-t"],
"help": (
"changelog template file name "
"(relative to the current working directory)"
),
},
{
"name": ["--extra", "-e"],
"action": "append",
"dest": "extras",
"metavar": "EXTRA",
"help": "a changelog extra variable (in the form 'key=value')",
},
{
"name": "manual_version",
"type": str,
Expand Down Expand Up @@ -246,6 +260,20 @@
"If not set, it will generate changelog from the start"
),
},
{
"name": ["--template", "-t"],
"help": (
"changelog template file name "
"(relative to the current working directory)"
),
},
{
"name": ["--extra", "-e"],
"action": "append",
"dest": "extras",
"metavar": "EXTRA",
"help": "a changelog extra variable (in the form 'key=value')",
},
],
},
{
Expand Down
5 changes: 5 additions & 0 deletions commitizen/commands/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self, config: BaseConfig, arguments: dict):
"gpg_sign",
"annotated_tag",
"major_version_zero",
"template",
]
if arguments[key] is not None
},
Expand All @@ -58,6 +59,8 @@ def __init__(self, config: BaseConfig, arguments: dict):
self.no_verify = arguments["no_verify"]
self.check_consistency = arguments["check_consistency"]
self.retry = arguments["retry"]
self.template = arguments["template"] or self.config.settings.get("template")
self.extras = arguments["extras"]

def is_initial_tag(self, current_tag_version: str, is_yes: bool = False) -> bool:
"""Check if reading the whole git tree up to HEAD is needed."""
Expand Down Expand Up @@ -244,6 +247,8 @@ def __call__(self): # noqa: C901
"unreleased_version": new_tag_version,
"incremental": True,
"dry_run": dry_run,
"template": self.template,
"extras": self.extras,
},
)
changelog_cmd()
Expand Down
10 changes: 9 additions & 1 deletion commitizen/commands/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def __init__(self, config: BaseConfig, args):
self.tag_format = args.get("tag_format") or self.config.settings.get(
"tag_format"
)
self.template = args.get("template") or self.config.settings.get("template")
self.extras = dict(
extra.split("=") for extra in args.get("extras") or [] if "=" in extra
)

def _find_incremental_rev(self, latest_version: str, tags: List[GitTag]) -> str:
"""Try to find the 'start_rev'.
Expand Down Expand Up @@ -161,7 +165,11 @@ def __call__(self):
)
if self.change_type_order:
tree = changelog.order_changelog_tree(tree, self.change_type_order)
changelog_out = changelog.render_changelog(tree)
extras = self.extras | self.config.settings["extras"] | self.cz.template_extras
extras = self.cz.template_extras | self.config.settings["extras"] | self.extras
changelog_out = changelog.render_changelog(
tree, loader=self.cz.template_loader, template=self.template, **extras
)
changelog_out = changelog_out.lstrip("\n")

if self.dry_run:
Expand Down
8 changes: 7 additions & 1 deletion commitizen/cz/base.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from abc import ABCMeta, abstractmethod
from typing import Callable, Dict, List, Optional, Tuple
from typing import Any, Callable, Dict, List, Optional, Tuple

from jinja2 import BaseLoader
from prompt_toolkit.styles import Style, merge_styles

from commitizen import git
from commitizen.changelog import DEFAULT_TEMPLATE
from commitizen.config.base_config import BaseConfig
from commitizen.defaults import Questions

Expand Down Expand Up @@ -40,6 +42,10 @@ class BaseCommitizen(metaclass=ABCMeta):
# Executed only at the end of the changelog generation
changelog_hook: Optional[Callable[[str, Optional[str]], str]] = None

template: str = DEFAULT_TEMPLATE
template_loader: Optional[BaseLoader] = None
template_extras: dict[str, Any] = {}

def __init__(self, config: BaseConfig):
self.config = config
if not self.config.settings.get("style"):
Expand Down
4 changes: 4 additions & 0 deletions commitizen/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class Settings(TypedDict, total=False):
style: Optional[List[Tuple[str, str]]]
customize: CzSettings
major_version_zero: bool
template: Optional[str]
extras: dict[str, Any]


name: str = "cz_conventional_commits"
Expand All @@ -65,6 +67,8 @@ class Settings(TypedDict, total=False):
"update_changelog_on_bump": False,
"use_shortcuts": False,
"major_version_zero": False,
"template": None,
"extras": {},
}

MAJOR = "MAJOR"
Expand Down
16 changes: 16 additions & 0 deletions docs/bump.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ usage: cz bump [-h] [--dry-run] [--files-only] [--local-version] [--changelog]
[--devrelease DEVRELEASE] [--increment {MAJOR,MINOR,PATCH}]
[--check-consistency] [--annotated-tag] [--gpg-sign]
[--changelog-to-stdout] [--retry] [--major-version-zero]
[--template TEMPLATE] [--extra EXTRA]
[MANUAL_VERSION]

positional arguments:
Expand Down Expand Up @@ -95,6 +96,10 @@ options:
Output changelog to the stdout
--retry retry commit if it fails the 1st time
--major-version-zero keep major version at zero, even for breaking changes
--template TEMPLATE, -t TEMPLATE
changelog template file name (relative to the current working directory)
--extra EXTRA, -e EXTRA
a changelog extra variable (in the form 'key=value')
```
### `--files-only`
Expand Down Expand Up @@ -213,6 +218,17 @@ We recommend setting `major_version_zero = true` in your configuration file whil
is in its initial development. Remove that configuration using a breaking-change commit to bump
your project’s major version to `v1.0.0` once your project has reached maturity.
### `--template`
Provides your own changelog jinja template.
See [the template customization section](customization.md#customizing-the-changelog-template)
### `--extra`
Provides your own changelog extra variables by using the `extras` settings or the `--extra` parameter.
See [the template customization section](customization.md#customizing-the-changelog-template)
## Avoid raising errors
Some situations from commitizen rise an exit code different than 0.
Expand Down
15 changes: 15 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This command will generate a changelog following the committing rules establishe
```bash
$ cz changelog --help
usage: cz changelog [-h] [--dry-run] [--file-name FILE_NAME] [--unreleased-version UNRELEASED_VERSION] [--incremental] [--start-rev START_REV]
[--template TEMPLATE] [--extra EXTRA]
[rev_range]

positional arguments:
Expand All @@ -22,6 +23,10 @@ optional arguments:
--incremental generates changelog from last created version, useful if the changelog has been manually modified
--start-rev START_REV
start rev of the changelog.If not set, it will generate changelog from the start
--template TEMPLATE, -t TEMPLATE
changelog template file name (relative to the current working directory)
--extra EXTRA, -e EXTRA
a changelog extra variable (in the form 'key=value')
```
### Examples
Expand Down Expand Up @@ -161,6 +166,16 @@ cz changelog --start-rev="v0.2.0"
changelog_start_rev = "v0.2.0"
```
### `template`
Provides your own changelog jinja template by using the `template` settings or the `--template` parameter.
See [the template customization section](customization.md#customizing-the-changelog-template)
### `extras`
Provides your own changelog extra variables by using the `extras` settings or the `--extra` parameter.
See [the template customization section](customization.md#customizing-the-changelog-template)
## Hooks
Supported hook methods:
Expand Down
3 changes: 3 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
| `customize` | `dict` | `None` | **This is only supported when config through `toml`.** Custom rules for committing and bumping. [See more][customization] |
| `use_shortcuts` | `bool` | `false` | If enabled, commitizen will show keyboard shortcuts when selecting from a list. Define a `key` for each of your choices to set the key. [See more][shortcuts] |
| `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] |
| <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] |
| <a name="cfg-extras"></a>`extras` | `dict` | `{}` | Provide extra variables to the changelog template. [See more (template customization)][template-customization] |

## pyproject.toml or .cz.toml

Expand Down Expand Up @@ -119,4 +121,5 @@ commitizen:
[additional-features]: https://github.com/tmbo/questionary#additional-features
[customization]: customization.md
[shortcuts]: customization.md#shortcut-keys
[template-customization]: customization.md#customizing-the-changelog-template
[annotated-tags-vs-lightweight]: https://stackoverflow.com/a/11514139/2047185
82 changes: 82 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,85 @@ from commitizen.cz.exception import CzException
class NoSubjectProvidedException(CzException):
...
```

## Customizing the changelog template

Commitizen gives you the possibility to provide your own changelog template, by:

- either by providing one with you customization class
- either by providing one from the current working directory and setting it:
- as [configuration][template-config]
- as `--template` parameter to both `bump` and `changelog` commands
- either by providing a template with the same name as the default template

By default, the template used is the `keep_a_changelog_template.j2` file from the commitizen repository.

### Providing a template with your customization class

There is 3 parameters available to hcange the template rendering from your custom `BaseCommitizen`.

| Parameter | Type | Default | Description |
| ----------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------- |
| `template` | `str` | `None` | Provide your own template name (default to `keep_a_changelog_template.j2`) |
| `template_loader` | `str` | `None` | Override the default template loader (so you can provide template from you customization class) |
| `template_extras` | `dict` | `None` | Provide some extra template parameters |

Let's see an example.

```python
from commitizen.cz.base import BaseCommitizen
from jinja2 import PackageLoader


class MyPlugin(BaseCommitizen):
template = "CHANGELOG.md.jinja"
template_loader = PackageLoader("my_plugin", "templates")
template_extras = {"key": "value"}
```

This snippet will:

- use `CHANGELOG.md.jinja` as template name
- search for it in the `templates` directory for `my_plugin` package
- add the `key=value` variable in the template

### Providing a template from the current working directory

Users can provides their own template from their current working directory (your project root) by:

- providing a template with the same name (`keep_a_changelog_template.j2` unless overriden by your custom class)
- setting your template path as `template` configuration
- giving your template path as `--template` parameter to `bump` and `changelog` commands

!!! Note
The path is relative to the current working directory, aka. your project root most of the time,

### Template variables

The default template use a single `tree` variable which is a list of entries (a release) with the following format:

| Name | Type | Description |
| ---- | ---- | ----------- |
| version | `str` | The release version |
| date | `datetime` | The release date |
| changes | `list[tuple[str, list[Change]]]` | The release sorted changes list in the form `(type,changes)` |

Each `Change` has the following fields:

| Name | Type | Description |
| ---- | ---- | ----------- |
| scope | `Optional[str]` | An optionnal scope |
| message | `str` | The commit message body |

!!! Note
The field values depend on the customization class and/or the settings you provide

When using another template (either provided by a plugin or by yourself), you can also pass extra template variables
by:

- defining them in your configuration with the [`extras` settings][extras-config]
- providing them on the commandline with the `--extra/-e` parameter to `bump` and `changelog` commands


[template-config]: config.md#cfg-template
[extras-config]: config.md#cfg-extras
Loading

0 comments on commit 45d3ceb

Please sign in to comment.