Skip to content

Commit

Permalink
feat: add link_parsing
Browse files Browse the repository at this point in the history
Closes #41
  • Loading branch information
bachp committed Dec 17, 2021
1 parent d23cf22 commit 432c691
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 41 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ This minimal example creates artifacts that can be used on another job.
- CHANGELOG.md
```
Please note that the stage is `doc` and has to be changed accordingly to your need.
Please note that the stage is `doc` and has to be changed accordingly to your need.

## Configuration File

Expand Down Expand Up @@ -406,6 +406,10 @@ skip_tags = "v0.1.0-beta.1"
ignore_tags = ""
topo_order = false
sort_commits = "oldest"
link_parsers = [
{ pattern = "#(\\d+)", href = "https://github.com/orhun/git-cliff/issues/$1"},
{ pattern = "RFC(\\d+)", replace = "https://datatracker.ietf.org/doc/html/rfc$1"},
]
```

#### conventional_commits
Expand Down Expand Up @@ -513,6 +517,17 @@ Possible values:

This can also be achieved by specifying the `--sort` command line argument.

#### link_parsers

An array of link parsers for extracting external references, and turning them into URLs, using regex.

Examples:

- `{ pattern = "#(\\d+)", href = "https://github.com/orhun/git-cliff/issues/$1"}`
- Extract all gitlab issues and PRs and generate URLs linking to them.
- `{ pattern = "RFC(\\d+)", replace = "https://datatracker.ietf.org/doc/html/rfc$1"}`
- Extract mentiones of IETF RFCs and generate URLs linking to them.

## Templating

A template is a text where variables and expressions get replaced with values when it is rendered.
Expand Down Expand Up @@ -550,7 +565,8 @@ following context is generated to use for templating:
"footers": ["[footer]", "[footer]"],
"breaking_description": "<description>",
"breaking": false,
"conventional": true
"conventional": true,
"links": [{"text": "[text]", "href": "[href]"}]
}
],
"commit_id": "a440c6eb26404be4877b7e3ad592bfaa5d4eb210 (release commit)",
Expand Down Expand Up @@ -595,6 +611,7 @@ If [conventional_commits](#conventional_commits) is set to `false`, then some of
"scope": "(overrided by commit_parsers)",
"message": "(full commit message including description, footers, etc.)",
"conventional": false,
"links": [{"text": "[text]", "href": "[href]"}]
}
],
"commit_id": "a440c6eb26404be4877b7e3ad592bfaa5d4eb210 (release commit)",
Expand Down
96 changes: 96 additions & 0 deletions git-cliff-core/src/commit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::config::{
CommitParser,
GitConfig,
LinkParser,
};
use crate::error::{
Error as AppError,
Expand Down Expand Up @@ -29,6 +30,20 @@ pub struct Commit<'a> {
pub group: Option<String>,
/// Commit scope based on conventional type or a commit parser.
pub scope: Option<String>,
/// A list of links found in the commit
pub links: Vec<Link>,
}

/// Object representing a link
#[derive(
Debug, Clone, PartialEq, serde_derive::Deserialize, serde_derive::Serialize,
)]
#[serde(rename_all = "camelCase")]
pub struct Link {
/// Text of the link.
pub text: String,
/// URL of the link
pub href: String,
}

impl<'a> From<&GitCommit<'a>> for Commit<'a> {
Expand All @@ -49,13 +64,15 @@ impl Commit<'_> {
conv: None,
group: None,
scope: None,
links: vec![],
}
}

/// Processes the commit.
///
/// * converts commit to a conventional commit
/// * sets the group for the commit
/// * extacts links and generates URLs
pub fn process(&self, config: &GitConfig) -> Result<Self> {
let mut commit = self.clone();
if config.conventional_commits {
Expand All @@ -69,6 +86,9 @@ impl Commit<'_> {
commit =
commit.parse(parsers, config.filter_commits.unwrap_or(false))?;
}
if let Some(parsers) = &config.link_parsers {
commit = commit.parse_links(parsers)?;
}
Ok(commit)
}

Expand Down Expand Up @@ -118,6 +138,27 @@ impl Commit<'_> {
)))
}
}

/// Parses the commit using [`LinkParser`]s.
///
/// Sets the [`links`] of the commit.
///
/// [`links`]: Commit::links
pub fn parse_links(mut self, parsers: &[LinkParser]) -> Result<Self> {
for parser in parsers {
let regex = &parser.pattern;
let replace = &parser.href;
for mat in regex.find_iter(&self.message) {
let text = mat.as_str();
let href = regex.replace(text, replace);
self.links.push(Link {
text: text.to_string(),
href: href.to_string(),
});
}
}
Ok(self)
}
}

impl Serialize for Commit<'_> {
Expand Down Expand Up @@ -163,6 +204,7 @@ impl Serialize for Commit<'_> {
commit.serialize_field("scope", &self.scope)?;
}
}
commit.serialize_field("links", &self.links)?;
commit.serialize_field("conventional", &self.conv.is_some())?;
commit.end()
}
Expand Down Expand Up @@ -207,4 +249,58 @@ mod test {
assert_eq!(Some(String::from("test_group")), commit.group);
assert_eq!(Some(String::from("test_scope")), commit.scope);
}

#[test]
fn parse_link() {
let test_cases = vec![
(
Commit::new(
String::from("123123"),
String::from("test(commit): add test\n\nBody with issue #123"),
),
true,
),
(
Commit::new(
String::from("123123"),
String::from(
"test(commit): add test\n\nImlement RFC456\n\nFixes: #456",
),
),
true,
),
];
for (commit, is_conventional) in &test_cases {
assert_eq!(is_conventional, &commit.clone().into_conventional().is_ok())
}
let commit = Commit::new(
String::from("123123"),
String::from("test(commit): add test\n\nImlement RFC456\n\nFixes: #455"),
);
let commit = commit
.parse_links(&[
LinkParser {
pattern: Regex::new("RFC(\\d+)").unwrap(),
href: String::from("rfc://$1"),
},
LinkParser {
pattern: Regex::new("#(\\d+)").unwrap(),
href: String::from("https://github.com/$1"),
},
])
.unwrap();
assert_eq!(
vec![
Link {
text: String::from("RFC456"),
href: String::from("rfc://456"),
},
Link {
text: String::from("#455"),
href: String::from("https://github.com/455"),
}
],
commit.links
);
}
}
12 changes: 12 additions & 0 deletions git-cliff-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub struct GitConfig {
pub filter_unconventional: Option<bool>,
/// Git commit parsers.
pub commit_parsers: Option<Vec<CommitParser>>,
/// Link parsers.
pub link_parsers: Option<Vec<LinkParser>>,
/// Whether to filter out commits.
pub filter_commits: Option<bool>,
/// Blob pattern for git tags.
Expand Down Expand Up @@ -65,6 +67,16 @@ pub struct CommitParser {
pub skip: Option<bool>,
}

/// Parser for extracting links in commits.
#[derive(Debug, Clone, serde_derive::Serialize, serde_derive::Deserialize)]
pub struct LinkParser {
/// Regex for finding links in the commit message.
#[serde(with = "serde_regex")]
pub pattern: Regex,
/// The string used to generate the link URL.
pub href: String,
}

impl Config {
/// Parses the config file and returns the values.
pub fn parse(file_name: String) -> Result<Config> {
Expand Down
86 changes: 47 additions & 39 deletions git-cliff-core/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use git_cliff_core::config::{
ChangelogConfig,
CommitParser,
GitConfig,
LinkParser,
};
use git_cliff_core::error::Result;
use git_cliff_core::release::*;
Expand All @@ -17,19 +18,19 @@ fn generate_changelog() -> Result<()> {
header: Some(String::from("this is a changelog")),
body: String::from(
r#"
## Release {{ version }}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group }}
{% for commit in commits %}
{%- if commit.scope -%}
- *({{commit.scope}})* {{ commit.message }}
{% else -%}
- {{ commit.message }}
{% endif -%}
{% if commit.breaking -%}
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
{% endif -%}
{% endfor -%}
## Release {{ version }}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group }}
{% for commit in commits %}
{%- if commit.scope -%}
- *({{commit.scope}})* {{ commit.message }}{%- if commit.links %} ({% for link in commit.links %}[{{link.text}}]({{link.href}}){% endfor -%}){% endif %}
{% else -%}
- {{ commit.message }}
{% endif -%}
{% if commit.breaking -%}
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
{% endif -%}
{% endfor -%}
{% endfor %}"#,
),
footer: Some(String::from("eoc - end of changelog")),
Expand Down Expand Up @@ -60,6 +61,10 @@ fn generate_changelog() -> Result<()> {
ignore_tags: None,
topo_order: None,
sort_commits: None,
link_parsers: Some(vec![LinkParser {
pattern: Regex::new("#(\\d+)").unwrap(),
href: String::from("https://github.com/$1"),
}]),
};

let releases = vec![
Expand All @@ -74,7 +79,9 @@ fn generate_changelog() -> Result<()> {
Commit::new(String::from("abc124"), String::from("feat: add zyx")),
Commit::new(
String::from("abc124"),
String::from("feat(random-scope): add random feature"),
String::from(
"feat(random-scope): add random feature\n\nCloses #123",
),
),
Commit::new(String::from("def789"), String::from("invalid commit")),
Commit::new(
Expand Down Expand Up @@ -134,32 +141,33 @@ fn generate_changelog() -> Result<()> {
writeln!(out, "{}", changelog_config.footer.unwrap()).unwrap();

assert_eq!(
"this is a changelog
r#"this is a changelog
## Release v2.0.0
### fix bugs
- fix abc
### shiny features
- add xyz
- add zyx
- *(random-scope)* add random feature
- *(big-feature)* this is a breaking change
- **BREAKING**: this is a breaking change
## Release v1.0.0
### chore
- do nothing
### feat
- add cool features
### fix
- fix stuff
- fix more stuff
eoc - end of changelog\n",
## Release v2.0.0
### fix bugs
- fix abc
### shiny features
- add xyz
- add zyx
- *(random-scope)* add random feature ([#123](https://github.com/123))
- *(big-feature)* this is a breaking change
- **BREAKING**: this is a breaking change
## Release v1.0.0
### chore
- do nothing
### feat
- add cool features
### fix
- fix stuff
- fix more stuff
eoc - end of changelog
"#,
out
);

Expand Down
1 change: 1 addition & 0 deletions git-cliff/src/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ mod test {
ignore_tags: None,
topo_order: Some(false),
sort_commits: Some(String::from("oldest")),
link_parsers: None,
},
};
let test_release = Release {
Expand Down

0 comments on commit 432c691

Please sign in to comment.