Repository: orhun/git-cliff (rust)

Issue:
Add output file configuration option
### Is there an existing issue or pull request for this?

- [X] I have searched the existing issues and pull requests

### Feature description

Where I work, it is the common case for us to want to generate and regenerate our CHANGELOG.md docs. Currently, to actually generate a document with git-cliff, we always have to pass it the `--output CHANGELOG.md` argument. This gets tedious to have to type out every time. It also means that, if we happen to typo the file name, we have to rename the file and delete the original (if regenerating).

### Desired solution

Create an `output-file` option that would go under the `[changelog]` section in the `cliff.toml` file that behaves the same as the `--output` CLI argument.

### Alternatives considered

I was not able to discern an alternative solution for this issue.

### Additional context

_No response_

Pull request description:
feat(config): allow configuring output file from config
## Description

This PR adds a new configuration option as follows:

```toml
[changelog]
output = "CHANGELOG.md"
```

It does not take precedence over command-line arguments which means you can override it with the `--output` option.

## Motivation and Context

closes #827

## How Has This Been Tested?

Locally.

## Types of Changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation (no code change)
- [ ] Refactor (refactoring production code)
- [ ] Other <!--- (provide information) -->

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My code follows the code style of this project.
- [x] I have updated the documentation accordingly.
- [x] I have formatted the code with [rustfmt](https://github.com/rust-lang/rustfmt).
- [x] I checked the lints with [clippy](https://github.com/rust-lang/rust-clippy).
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.

<!--- Thank you for contributing to git-cliff! ⛰️  -->


Patch:
diff --git a/cliff.toml b/cliff.toml
index bc36075..fca00cd 100644
--- a/cliff.toml
+++ b/cliff.toml
@@ -45,8 +45,9 @@ body = """
     | filter(attribute="scope")
     | sort(attribute="scope") %}
         {{ self::print_commit(commit=commit) }}
-    {%- endfor %}
-    {% for commit in commits %}
+    {%- endfor -%}
+    {% raw %}\n{% endraw %}\
+    {%- for commit in commits %}
         {%- if not commit.scope -%}
             {{ self::print_commit(commit=commit) }}
         {% endif -%}
@@ -63,8 +64,7 @@ body = """
     {%- endif %}
 {%- endfor -%}
 {%- endif %}
-
-
+{% raw %}\n{% endraw -%}
 """
 # template for the changelog footer
 footer = """
diff --git a/config/cliff.toml b/config/cliff.toml
index 4d368fa..fc5def1 100644
--- a/config/cliff.toml
+++ b/config/cliff.toml
@@ -38,6 +38,8 @@ trim = true
 postprocessors = [
   # { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
 ]
+# output file path
+# output = "test.md"
 
 [git]
 # parse the commits based on https://www.conventionalcommits.org
diff --git a/git-cliff-core/src/changelog.rs b/git-cliff-core/src/changelog.rs
index 06b77a2..2c8ea3a 100644
--- a/git-cliff-core/src/changelog.rs
+++ b/git-cliff-core/src/changelog.rs
@@ -652,6 +652,7 @@ mod test {
 					replace:         Some(String::from("exciting")),
 					replace_command: None,
 				}]),
+				output:         None,
 			},
 			git:       GitConfig {
 				conventional_commits:     Some(true),
diff --git a/git-cliff-core/src/config.rs b/git-cliff-core/src/config.rs
index 5949d33..e455197 100644
--- a/git-cliff-core/src/config.rs
+++ b/git-cliff-core/src/config.rs
@@ -79,6 +79,8 @@ pub struct ChangelogConfig {
 	pub trim:           Option<bool>,
 	/// Changelog postprocessors.
 	pub postprocessors: Option<Vec<TextProcessor>>,
+	/// Output file path.
+	pub output:         Option<PathBuf>,
 }
 
 /// Git configuration
diff --git a/git-cliff/src/lib.rs b/git-cliff/src/lib.rs
index ea8dcb2..135d095 100644
--- a/git-cliff/src/lib.rs
+++ b/git-cliff/src/lib.rs
@@ -416,6 +416,7 @@ pub fn run(mut args: Opt) -> Result<()> {
 	}
 
 	// Update the configuration based on command line arguments and vice versa.
+	let output = args.output.clone().or(config.changelog.output.clone());
 	match args.strip {
 		Some(Strip::Header) => {
 			config.changelog.header = None;
@@ -437,9 +438,9 @@ pub fn run(mut args: Opt) -> Result<()> {
 			)));
 		}
 	}
-	if args.output.is_some() &&
+	if output.is_some() &&
 		args.prepend.is_some() &&
-		args.output.as_ref() == args.prepend.as_ref()
+		output.as_ref() == args.prepend.as_ref()
 	{
 		return Err(Error::ArgumentError(String::from(
 			"'-o' and '-p' can only be used together if they point to different \
@@ -580,7 +581,7 @@ pub fn run(mut args: Opt) -> Result<()> {
 	};
 
 	// Print the result.
-	let mut out: Box<dyn io::Write> = if let Some(path) = &args.output {
+	let mut out: Box<dyn io::Write> = if let Some(path) = &output {
 		if path == Path::new("-") {
 			Box::new(io::stdout())
 		} else {
@@ -616,7 +617,7 @@ pub fn run(mut args: Opt) -> Result<()> {
 		let mut out = io::BufWriter::new(File::create(path)?);
 		changelog.prepend(changelog_before, &mut out)?;
 	}
-	if args.output.is_some() || args.prepend.is_none() {
+	if output.is_some() || args.prepend.is_none() {
 		changelog.generate(&mut out)?;
 	}
 
diff --git a/website/docs/configuration/changelog.md b/website/docs/configuration/changelog.md
index 90387eb..8636eaf 100644
--- a/website/docs/configuration/changelog.md
+++ b/website/docs/configuration/changelog.md
@@ -55,3 +55,7 @@ It is useful for adding indentation to the template for readability, as shown [i
 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](/docs/configuration/git#commit_preprocessors) for more detail and examples, it uses the same syntax.
+
+### output
+
+Output file path for the changelog. You can also use the `--output` argument to override this value.
diff --git a/website/package-lock.json b/website/package-lock.json
index 686c79b..6806b5a 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -14,7 +14,7 @@
         "@easyops-cn/docusaurus-search-local": "^0.44.5",
         "@mdx-js/react": "^3.0.1",
         "clsx": "^2.1.1",
-        "prism-react-renderer": "^2.4.0",
+        "prism-react-renderer": "^2.3.0",
         "react": "^18.3.1",
         "react-dom": "^18.3.1"
       },
@@ -12080,9 +12080,9 @@
       }
     },
     "node_modules/prism-react-renderer": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz",
-      "integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==",
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz",
+      "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==",
       "dependencies": {
         "@types/prismjs": "^1.26.0",
         "clsx": "^2.0.0"
diff --git a/website/package.json b/website/package.json
index 08f55cd..94c9ebe 100644
--- a/website/package.json
+++ b/website/package.json
@@ -21,7 +21,7 @@
     "@easyops-cn/docusaurus-search-local": "^0.44.5",
     "@mdx-js/react": "^3.0.1",
     "clsx": "^2.1.1",
-    "prism-react-renderer": "^2.4.0",
+    "prism-react-renderer": "^2.3.0",
     "react": "^18.3.1",
     "react-dom": "^18.3.1"
   },
diff --git a/website/yarn.lock b/website/yarn.lock
index 820c131..431ca3b 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -6985,10 +6985,10 @@ pretty-time@^1.1.0:
   resolved "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz"
   integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==
 
-prism-react-renderer@^2.3.0, prism-react-renderer@^2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz#c5ea692029c2f8b3fd04f63662d04ffd4eaf10a0"
-  integrity sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==
+prism-react-renderer@^2.3.0:
+  version "2.3.1"
+  resolved "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz"
+  integrity sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==
   dependencies:
     "@types/prismjs" "^1.26.0"
     clsx "^2.0.0"


Test patch:
diff --git a/git-cliff-core/tests/integration_test.rs b/git-cliff-core/tests/integration_test.rs
index 58141a9..4e1ced4 100644
--- a/git-cliff-core/tests/integration_test.rs
+++ b/git-cliff-core/tests/integration_test.rs
@@ -41,6 +41,7 @@ fn generate_changelog() -> Result<()> {
 		footer:         Some(String::from("eoc - end of changelog")),
 		trim:           None,
 		postprocessors: None,
+		output:         None,
 	};
 	let git_config = GitConfig {
 		conventional_commits:     Some(true),