1
1
mirror of https://github.com/orhun/git-cliff.git synced 2024-10-26 07:49:41 +03:00

feat(changelog)!: support templating in the footer (#369)

* feat(changelog)!: support templating in the footer (#95)

* test(fixture): run test-footer-template fixture
This commit is contained in:
Orhun Parmaksız 2023-12-05 22:23:09 +01:00 committed by GitHub
parent 1e571c2f32
commit 0945fa806c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 328 additions and 147 deletions

View File

@ -19,9 +19,9 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true

View File

@ -19,9 +19,9 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true

View File

@ -22,9 +22,9 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true

View File

@ -19,12 +19,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespaces from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# regex for preprocessing the commit messages

View File

@ -22,12 +22,12 @@ body = """
{% endfor %}\
{% endfor %}\n
"""
# remove the leading and trailing whitespaces from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# regex for parsing and grouping commits

View File

@ -19,9 +19,9 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true

View File

@ -0,0 +1,35 @@
[changelog]
# changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}]
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
{% for release in releases %}\
{% if release.version %}\
{% if release.previous.version %}\
<!--{{ release.previous.version }}..{{ release.version }}-->
{% endif %}\
{% else %}\
<!--{{ release.previous.version }}..HEAD-->
{% endif %}\
{% endfor %}\
"""
# remove the leading and trailing whitespace from the templates
trim = true

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -e
GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 1"
GIT_COMMITTER_DATE="2021-01-23 01:23:45" git commit --allow-empty -m "feat: add feature 2"
git tag v0.1.0
GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "fix: fix feature 1"
GIT_COMMITTER_DATE="2021-01-23 01:23:46" git commit --allow-empty -m "fix: fix feature 2"
git tag v0.2.0
GIT_COMMITTER_DATE="2021-01-23 01:23:47" git commit --allow-empty -m "feat: add footer"
git tag v3.0.0
GIT_COMMITTER_DATE="2021-01-23 01:23:47" git commit --allow-empty -m "test: footer"
GIT_COMMITTER_DATE="2021-01-23 01:23:47" git commit --allow-empty -m "perf: footer"

View File

@ -0,0 +1,38 @@
# Changelog
All notable changes to this project will be documented in this file.
## [unreleased]
### Perf
- Footer
### Test
- Footer
## [3.0.0]
### Feat
- Add footer
## [0.2.0]
### Fix
- Fix feature 1
- Fix feature 2
## [0.1.0]
### Feat
- Add feature 1
- Add feature 2
<!--v3.0.0..HEAD-->
<!--v0.2.0..v3.0.0-->
<!--v0.1.0..v0.2.0-->

View File

@ -19,12 +19,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# regex for skipping tags

View File

@ -38,12 +38,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# parse the commits based on https://www.conventionalcommits.org

View File

@ -19,9 +19,9 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespaces from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true

View File

@ -19,12 +19,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
limit_commits = 2

View File

@ -22,12 +22,12 @@ body = """
{% endfor %}\
{% endfor %}\n
"""
# remove the leading and trailing whitespaces from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# regex for parsing and grouping commits

View File

@ -19,12 +19,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespaces from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# process each line of a commit as an individual commit

View File

@ -19,9 +19,9 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true

View File

@ -19,12 +19,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
topo_order = true

View File

@ -36,6 +36,7 @@ jobs:
command: --bump
- fixtures-name: test-bumped-version
command: --bumped-version
- fixtures-name: test-footer-template
steps:
- name: Checkout
uses: actions/checkout@v4

View File

@ -44,12 +44,12 @@ body = """
{% endfor -%}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL

View File

@ -26,12 +26,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing s
trim = true
# postprocessors
postprocessors = [
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL

View File

@ -38,12 +38,12 @@ body = """
{% endfor -%}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
{ pattern = '\$REPO', replace = "https://github.com/cocogitto/cocogitto" }, # replace repository URL

View File

@ -31,12 +31,12 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# parse the commits based on https://www.conventionalcommits.org

View File

@ -25,12 +25,26 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
{% for release in releases %}\
{% if release.version %}\
{% if release.previous.version %}\
[{{ release.version | trim_start_matches(pat="v") }}]: <REPO>/compare/{{ release.previous.version }}..{{ release.version }}
{% endif %}\
{% else %}\
[unreleased]: <REPO>/compare/{{ release.previous.version }}..HEAD
{% endif %}\
{% endfor %}
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
# replace repository URL
{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
]
[git]
# parse the commits based on https://www.conventionalcommits.org
@ -41,14 +55,14 @@ filter_unconventional = true
split_commits = false
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^.*: add", group = "Added" },
{ message = "^.*: support", group = "Added" },
{ message = "^.*: remove", group = "Removed" },
{ message = "^.*: delete", group = "Removed" },
{ message = "^test", group = "Fixed" },
{ message = "^fix", group = "Fixed" },
{ message = "^.*: fix", group = "Fixed" },
{ message = "^.*", group = "Changed" },
{ message = "^.*: add", group = "Added" },
{ message = "^.*: support", group = "Added" },
{ message = "^.*: remove", group = "Removed" },
{ message = "^.*: delete", group = "Removed" },
{ message = "^test", group = "Fixed" },
{ message = "^fix", group = "Fixed" },
{ message = "^.*: fix", group = "Fixed" },
{ message = "^.*", group = "Changed" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false

View File

@ -25,12 +25,12 @@ body = """
{% endfor %}\
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# parse the commits based on https://www.conventionalcommits.org

View File

@ -38,12 +38,13 @@ body = """
{% raw %}\n{% endraw %}\
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# parse the commits based on https://www.conventionalcommits.org

View File

@ -22,12 +22,13 @@ body = """
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
[git]
# parse the commits based on https://www.conventionalcommits.org

View File

@ -11,30 +11,31 @@ use std::io::Write;
/// Changelog generator.
#[derive(Debug)]
pub struct Changelog<'a> {
releases: Vec<Release<'a>>,
template: Template,
config: &'a Config,
releases: Vec<Release<'a>>,
body_template: Template,
footer_template: Option<Template>,
config: &'a Config,
}
impl<'a> Changelog<'a> {
/// Constructs a new instance.
pub fn new(releases: Vec<Release<'a>>, config: &'a Config) -> Result<Self> {
let mut template = config
.changelog
.body
.as_deref()
.unwrap_or_default()
.to_string();
if config.changelog.trim.unwrap_or(true) {
template = template
.lines()
.map(|v| v.trim())
.collect::<Vec<&str>>()
.join("\n")
}
let trim = config.changelog.trim.unwrap_or(true);
let mut changelog = Self {
releases,
template: Template::new(template)?,
body_template: Template::new(
config
.changelog
.body
.as_deref()
.unwrap_or_default()
.to_string(),
trim,
)?,
footer_template: match &config.changelog.footer {
Some(footer) => Some(Template::new(footer.to_string(), trim)?),
None => None,
},
config,
};
changelog.process_commits();
@ -141,19 +142,30 @@ impl<'a> Changelog<'a> {
if let Some(header) = &self.config.changelog.header {
write!(out, "{header}")?;
}
let postprocessors = self
.config
.changelog
.postprocessors
.clone()
.unwrap_or_default();
for release in &self.releases {
let mut rendered = self.template.render(release)?;
if let Some(postprocessors) =
self.config.changelog.postprocessors.as_ref()
{
for postprocessor in postprocessors {
postprocessor.replace(&mut rendered, vec![])?;
}
}
write!(out, "{}", rendered)?;
write!(
out,
"{}",
self.body_template.render(release, &postprocessors)?
)?;
}
if let Some(footer) = &self.config.changelog.footer {
write!(out, "{footer}")?;
if let Some(footer_template) = &self.footer_template {
writeln!(
out,
"{}",
footer_template.render(
&Releases {
releases: &self.releases,
},
&postprocessors,
)?
)?;
}
Ok(())
}
@ -175,7 +187,10 @@ impl<'a> Changelog<'a> {
/// Prints the changelog context to the given output.
pub fn write_context<W: Write>(&self, out: &mut W) -> Result<()> {
let output = Releases(&self.releases).as_json()?;
let output = Releases {
releases: &self.releases,
}
.as_json()?;
writeln!(out, "{output}")?;
Ok(())
}
@ -209,7 +224,9 @@ mod test {
- {{ commit.message }}{% endfor %}
{% endfor %}{% endfor %}"#,
)),
footer: Some(String::from("------------")),
footer: Some(String::from(
r#"-- total releases: {{ releases | length }} --"#,
)),
trim: Some(true),
postprocessors: Some(vec![TextProcessor {
pattern: Regex::new("boring")
@ -468,7 +485,8 @@ mod test {
#### ui
- make good stuff
------------"#
-- total releases: 2 --
"#
)
.replace(" ", ""),
str::from_utf8(&out).unwrap_or_default()
@ -583,7 +601,8 @@ chore(deps): fix broken deps
#### ui
- make good stuff
------------"#
-- total releases: 2 --
"#
)
.replace(" ", ""),
str::from_utf8(&out).unwrap_or_default()

View File

@ -48,12 +48,16 @@ impl<'a> Release<'a> {
}
/// Representation of a list of releases.
pub struct Releases<'a>(pub &'a Vec<Release<'a>>);
#[derive(Serialize)]
pub struct Releases<'a> {
/// Releases.
pub releases: &'a Vec<Release<'a>>,
}
impl<'a> Releases<'a> {
/// Returns the list of releases as JSON.
pub fn as_json(&self) -> Result<String> {
Ok(serde_json::to_string(self.0)?)
Ok(serde_json::to_string(self.releases)?)
}
}

View File

@ -1,8 +1,11 @@
use crate::error::{
Error,
Result,
use crate::{
config::TextProcessor,
error::{
Error,
Result,
},
};
use crate::release::Release;
use serde::Serialize;
use std::collections::HashMap;
use std::error::Error as ErrorImpl;
use tera::{
@ -20,7 +23,14 @@ pub struct Template {
impl Template {
/// Constructs a new instance.
pub fn new(template: String) -> Result<Self> {
pub fn new(mut template: String, trim: bool) -> Result<Self> {
if trim {
template = template
.lines()
.map(|v| v.trim())
.collect::<Vec<&str>>()
.join("\n")
}
let mut tera = Tera::default();
if let Err(e) = tera.add_raw_template("template", &template) {
return if let Some(error_source) = e.source() {
@ -49,10 +59,19 @@ impl Template {
}
/// Renders the template.
pub fn render(&self, release: &Release) -> Result<String> {
let context = TeraContext::from_serialize(release)?;
pub fn render<T: Serialize>(
&self,
context: &T,
postprocessors: &[TextProcessor],
) -> Result<String> {
let context = TeraContext::from_serialize(context)?;
match self.tera.render("template", &context) {
Ok(v) => Ok(v),
Ok(mut v) => {
for postprocessor in postprocessors {
postprocessor.replace(&mut v, vec![])?;
}
Ok(v)
}
Err(e) => {
return if let Some(error_source) = e.source() {
Err(Error::TemplateRenderError(error_source.to_string()))
@ -68,19 +87,21 @@ impl Template {
mod test {
use super::*;
use crate::commit::Commit;
use crate::release::Release;
use regex::Regex;
#[test]
fn render_template() -> Result<()> {
let template = r#"
## {{ version }}
## {{ version }} - <DATE>
{% for commit in commits %}
### {{ commit.group }}
- {{ commit.message | upper_first }}
{% endfor %}"#;
let template = Template::new(template.to_string())?;
let template = Template::new(template.to_string(), false)?;
assert_eq!(
r#"
## 1.0
## 1.0 - 2023
### feat
- Add xyz
@ -88,25 +109,33 @@ mod test {
### fix
- Fix abc
"#,
template.render(&Release {
version: Some(String::from("1.0")),
commits: vec![
Commit::new(
String::from("123123"),
String::from("feat(xyz): add xyz"),
),
Commit::new(
String::from("124124"),
String::from("fix(abc): fix abc"),
)
]
.into_iter()
.filter_map(|c| c.into_conventional().ok())
.collect(),
commit_id: None,
timestamp: 0,
previous: None,
})?
template.render(
&Release {
version: Some(String::from("1.0")),
commits: vec![
Commit::new(
String::from("123123"),
String::from("feat(xyz): add xyz"),
),
Commit::new(
String::from("124124"),
String::from("fix(abc): fix abc"),
)
]
.into_iter()
.filter_map(|c| c.into_conventional().ok())
.collect(),
commit_id: None,
timestamp: 0,
previous: None,
},
&[TextProcessor {
pattern: Regex::new("<DATE>")
.expect("failed to compile regex"),
replace: Some(String::from("2023")),
replace_command: None,
}]
)?
);
Ok(())
}

View File

@ -22,7 +22,7 @@ fn generate_changelog() -> Result<()> {
header: Some(String::from("this is a changelog")),
body: Some(String::from(
r#"
## Release {{ version }}
## Release {{ version }} - <DATE>
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group }}
{% for commit in commits %}
@ -199,18 +199,27 @@ fn generate_changelog() -> Result<()> {
];
let out = &mut String::new();
let template = Template::new(changelog_config.body.unwrap())?;
let template = Template::new(changelog_config.body.unwrap(), false)?;
writeln!(out, "{}", changelog_config.header.unwrap()).unwrap();
for release in releases {
write!(out, "{}", template.render(&release)?).unwrap();
write!(
out,
"{}",
template.render(&release, &[TextProcessor {
pattern: Regex::new("<DATE>").unwrap(),
replace: Some(String::from("2023")),
replace_command: None,
}])?
)
.unwrap();
}
writeln!(out, "{}", changelog_config.footer.unwrap()).unwrap();
assert_eq!(
r#"this is a changelog
## Release v2.0.0
## Release v2.0.0 - 2023
### docs
- *(cool)* testing author filtering
@ -229,7 +238,7 @@ fn generate_changelog() -> Result<()> {
### test
- *(tests)* test some stuff
## Release v1.0.0
## Release v1.0.0 - 2023
### chore
- do nothing

View File

@ -47,16 +47,22 @@ Body template that represents a single release in the changelog.
See [templating](/docs/category/templating) for more detail.
### footer
Footer template that will be rendered and added to the end of the changelog.
The template context is the same as [`body`](#body) and contains all the releases instead of a single release.
For example, to get the list of releases, use the `{{ releases }}` variable in the template. To get information about a single release, iterate over this array and access the fields similar to [`body`](#body).
See [Keep a Changelog configuration](/docs/templating/examples#keep-a-changelog) for seeing the example of adding links to the end of the changelog.
### trim
If set to `true`, leading and trailing whitespace are removed from the [`body`](#body).
It is useful for adding indentation to the template for readability, as shown [in the example](#changelog).
### footer
Footer text that will be added to the end of the changelog.
### postprocessors
An array of commit postprocessors for manipulating the changelog before outputting.

View File

@ -1,6 +1,7 @@
---
sidebar_position: 1
---
# Context
Context is the model that holds the required data for a template rendering. The [JSON](https://en.wikipedia.org/wiki/JSON) format is used in the following examples for the representation of a context.

View File

@ -662,6 +662,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Rename help argument due to conflict
[unreleased]: https://github.com/orhun/git-cliff/compare/v1.0.1..HEAD
[1.0.1]: https://github.com/orhun/git-cliff/compare/v1.0.0..v1.0.1
<!-- generated by git-cliff -->
```
@ -709,6 +712,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Rename help argument due to conflict
[unreleased]: https://github.com/orhun/git-cliff/compare/v1.0.1..HEAD
[1.0.1]: https://github.com/orhun/git-cliff/compare/v1.0.0..v1.0.1
<!-- generated by git-cliff -->
</details>

View File

@ -1,6 +1,7 @@
{
"name": "git-cliff-website",
"version": "0.0.0",
"license": "MIT OR Apache-2.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",