diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs index c1702a2f54..d66ba42b98 100644 --- a/git-cliff-core/src/changelog.rs +++ b/git-cliff-core/src/changelog.rs @@ -142,7 +142,15 @@ impl<'a> Changelog<'a> { write!(out, "{header}")?; } for release in &self.releases { - write!(out, "{}", self.template.render(release)?)?; + 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)?; } if let Some(footer) = &self.config.changelog.footer { write!(out, "{footer}")?; @@ -179,8 +187,8 @@ mod test { use crate::config::{ ChangelogConfig, CommitParser, - CommitPreprocessor, GitConfig, + TextProcessor, }; use pretty_assertions::assert_eq; use regex::Regex; @@ -189,8 +197,8 @@ mod test { fn get_test_data() -> (Config, Vec>) { let config = Config { changelog: ChangelogConfig { - header: Some(String::from("# Changelog")), - body: Some(String::from( + header: Some(String::from("# Changelog")), + body: Some(String::from( r#"{% if version %} ## Release [{{ version }}] - {{ timestamp | date(format="%Y-%m-%d") }} ({{ commit_id }}){% else %} @@ -201,14 +209,20 @@ mod test { - {{ commit.message }}{% endfor %} {% endfor %}{% endfor %}"#, )), - footer: Some(String::from("------------")), - trim: Some(true), + footer: Some(String::from("------------")), + trim: Some(true), + postprocessors: Some(vec![TextProcessor { + pattern: Regex::new("boring") + .expect("failed to compile regex"), + replace: Some(String::from("exciting")), + replace_command: None, + }]), }, git: GitConfig { conventional_commits: Some(true), filter_unconventional: Some(false), split_commits: Some(false), - commit_preprocessors: Some(vec![CommitPreprocessor { + commit_preprocessors: Some(vec![TextProcessor { pattern: Regex::new("") .expect("failed to compile regex"), replace: Some(String::from( @@ -392,7 +406,7 @@ mod test { - document zyx #### ui - - do boring stuff + - do exciting stuff ## Release [v1.0.0] - 1971-08-02 (0bc123) @@ -494,7 +508,7 @@ chore(deps): fix broken deps - fix broken deps #### ui - - do boring stuff + - do exciting stuff ### feat #### app diff --git a/git-cliff-core/src/commit.rs b/git-cliff-core/src/commit.rs index 14374c3939..0697edc9e2 100644 --- a/git-cliff-core/src/commit.rs +++ b/git-cliff-core/src/commit.rs @@ -1,9 +1,8 @@ -use crate::command; use crate::config::{ CommitParser, - CommitPreprocessor, GitConfig, LinkParser, + TextProcessor, }; use crate::error::{ Error as AppError, @@ -215,25 +214,10 @@ impl Commit<'_> { /// Modifies the commit [`message`] using regex or custom OS command. /// /// [`message`]: Commit::message - pub fn preprocess( - mut self, - preprocessors: &[CommitPreprocessor], - ) -> Result { + pub fn preprocess(mut self, preprocessors: &[TextProcessor]) -> Result { preprocessors.iter().try_for_each(|preprocessor| { - if let Some(text) = &preprocessor.replace { - self.message = preprocessor - .pattern - .replace_all(&self.message, text) - .to_string(); - } else if let Some(command) = &preprocessor.replace_command { - if preprocessor.pattern.is_match(&self.message) { - self.message = command::run( - command, - Some(self.message.to_string()), - vec![("COMMIT_SHA", &self.id)], - )?; - } - } + preprocessor + .replace(&mut self.message, vec![("COMMIT_SHA", &self.id)])?; Ok::<(), AppError>(()) })?; Ok(self) diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs index 82e95f59a0..1f6a9b21e1 100644 --- a/git-cliff-core/src/config.rs +++ b/git-cliff-core/src/config.rs @@ -1,3 +1,4 @@ +use crate::command; use crate::error::Result; use regex::{ Regex, @@ -29,13 +30,15 @@ pub struct Config { #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct ChangelogConfig { /// Changelog header. - pub header: Option, + pub header: Option, /// Changelog body, template. - pub body: Option, + pub body: Option, /// Changelog footer. - pub footer: Option, + pub footer: Option, /// Trim the template. - pub trim: Option, + pub trim: Option, + /// Changelog postprocessors. + pub postprocessors: Option>, } /// Git configuration @@ -50,7 +53,7 @@ pub struct GitConfig { pub split_commits: Option, /// Git commit preprocessors. - pub commit_preprocessors: Option>, + pub commit_preprocessors: Option>, /// Git commit parsers. pub commit_parsers: Option>, /// Whether to protect all breaking changes from being skipped by a commit @@ -95,9 +98,9 @@ pub struct CommitParser { pub skip: Option, } -/// Preprocessor for modifying commit messages. +/// TextProcessor, e.g. for modifying commit messages. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct CommitPreprocessor { +pub struct TextProcessor { /// Regex for matching a text to replace. #[serde(with = "serde_regex")] pub pattern: Regex, @@ -107,6 +110,25 @@ pub struct CommitPreprocessor { pub replace_command: Option, } +impl TextProcessor { + /// Replaces the text with using the given pattern or the command output. + pub fn replace( + &self, + rendered: &mut String, + command_envs: Vec<(&str, &str)>, + ) -> Result<()> { + if let Some(text) = &self.replace { + *rendered = self.pattern.replace_all(rendered, text).to_string(); + } else if let Some(command) = &self.replace_command { + if self.pattern.is_match(rendered) { + *rendered = + command::run(command, Some(rendered.to_string()), command_envs)?; + } + } + Ok(()) + } +} + /// Parser for extracting links in commits. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct LinkParser { diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs index 6bf38ccd85..906bae8ebd 100644 --- a/git-cliff-core/tests/integration_test.rs +++ b/git-cliff-core/tests/integration_test.rs @@ -2,9 +2,9 @@ use git_cliff_core::commit::Commit; use git_cliff_core::config::{ ChangelogConfig, CommitParser, - CommitPreprocessor, GitConfig, LinkParser, + TextProcessor, }; use git_cliff_core::error::Result; use git_cliff_core::release::*; @@ -16,8 +16,8 @@ use std::fmt::Write; #[test] fn generate_changelog() -> Result<()> { let changelog_config = ChangelogConfig { - header: Some(String::from("this is a changelog")), - body: Some(String::from( + header: Some(String::from("this is a changelog")), + body: Some(String::from( r#" ## Release {{ version }} {% for group, commits in commits | group_by(attribute="group") %} @@ -34,14 +34,15 @@ fn generate_changelog() -> Result<()> { {% endfor -%} {% endfor %}"#, )), - footer: Some(String::from("eoc - end of changelog")), - trim: None, + footer: Some(String::from("eoc - end of changelog")), + trim: None, + postprocessors: None, }; let git_config = GitConfig { conventional_commits: Some(true), filter_unconventional: Some(true), split_commits: Some(false), - commit_preprocessors: Some(vec![CommitPreprocessor { + commit_preprocessors: Some(vec![TextProcessor { pattern: Regex::new(r#"\(fixes (#[1-9]+)\)"#).unwrap(), replace: Some(String::from("[closes Issue${1}]")), replace_command: None, diff --git a/website/docs/configuration.md b/website/docs/configuration.md index b5c606aba9..a1d180b501 100644 --- a/website/docs/configuration.md +++ b/website/docs/configuration.md @@ -32,6 +32,7 @@ body = """ """ trim = true footer = "" +postprocessors = [{ pattern = "foo", replace = "bar"}] ``` @@ -56,6 +57,12 @@ It is useful for adding indentation to the template for readability, as shown [i Footer text that will be added to the end of the changelog. +### postprocessors + +An array of commit postprocessors for manipulating the changelog before outputting. +Can e.g. be used for replacing commit author with GitHub usernames. +Internally postprocessors and preprocessors are the same. See [commit_preprocessors](#commit_preprocessors) for more detail and examples, it uses the same syntax. + ## `git` This section contains the parsing and git related configuration options.