change default config file to include comments
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@ pub fn cl_init(opts: ClInit) -> anyhow::Result<()> {
|
||||
"Creating clpack config file: {}",
|
||||
opts.config_path.display()
|
||||
);
|
||||
file.write_all(toml::to_string_pretty(&default_config)?.as_bytes())?;
|
||||
file.write_all(crate::config::CONFIG_FILE_TEMPLATE.as_bytes())?;
|
||||
} else {
|
||||
println!(
|
||||
"Loading existing config file: {}",
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Configuration for clpack - changelog keeping utility
|
||||
# https://github.com/MightyPork/clpack
|
||||
#
|
||||
# To add a changelog entry manually, place it in a .md file in changelog/entries/
|
||||
|
||||
# Folder for data files - clpack will manage contents of this folder.
|
||||
data_folder = "changelog"
|
||||
|
||||
# ID of the default channel - this only matters inside this config file
|
||||
default_channel = "default"
|
||||
|
||||
# Path or file name of the default changelog file, relative to the root of the project.
|
||||
#
|
||||
# The name is used as-is.
|
||||
changelog_file_default = "CHANGELOG.md"
|
||||
|
||||
# Path or file of a channel-specific changelog file, relative to the root of the project.
|
||||
#
|
||||
# Placeholders supported are:
|
||||
# - `{channel}`, `{Channel}`, `{CHANNEL}` - Channel ID in the respective capitalization
|
||||
changelog_file_channel = "CHANGELOG-{CHANNEL}.md"
|
||||
|
||||
# Title of the changelog file, stripped and put back in front when packing changelog entries
|
||||
changelog_header = '''
|
||||
# Changelog
|
||||
|
||||
'''
|
||||
|
||||
# Pattern for release header
|
||||
release_header = "[{VERSION}] - {DATE}"
|
||||
|
||||
# Date format (strftime-based)
|
||||
#
|
||||
# For supported patterns, see https://docs.rs/chrono/latest/chrono/format/strftime/index.html
|
||||
date_format = "%Y-%m-%d"
|
||||
|
||||
# Changelog sections suggested when creating a new entry.
|
||||
#
|
||||
# Users may also specify custom section names when writing the changelog file.
|
||||
#
|
||||
# Changelog entries under each section will be grouped in the packed changelog.
|
||||
sections = [
|
||||
"Fixes",
|
||||
"Improvements",
|
||||
"New features",
|
||||
"Internal",
|
||||
]
|
||||
|
||||
# Regex pattern to extract issue number from a branch name.
|
||||
# There should be one capture group that is the number.
|
||||
#
|
||||
# If empty, no branch identification will be attempted.
|
||||
#
|
||||
# The default pattern matches 1234-gitlab-style and SW-1234-youtrack-style
|
||||
branch_issue_pattern = '/^((?:SW-)?\d+)-.*/'
|
||||
|
||||
# Regex pattern to extract release number from a branch name.
|
||||
# There should be exactly one capture group that is the version.
|
||||
#
|
||||
# If empty, no branch identification will be attempted.
|
||||
#
|
||||
# The default pattern matches e.g. rel/1.2
|
||||
branch_version_pattern = '/^rel\/([\d.]+)$/'
|
||||
|
||||
# Changelog channels & how to identify them from git branch names.
|
||||
# To add a new release channel, just add it here.
|
||||
# At least one channel must be defined - see the config option `default_channel`
|
||||
#
|
||||
# Format: key=value
|
||||
#
|
||||
# - key - changelog ID; this will be used in the channel file name. Examples: default, eap, beta
|
||||
# - value - git branch name to recognize the channel. This is a regex pattern.
|
||||
#
|
||||
# For simple branch names, e.g. `main`, `master`, `test`, write the name simply as string.
|
||||
#
|
||||
# To specify a regex pattern (wildcard name), enclose it in slashes, e.g. '/^release\//'
|
||||
[channels]
|
||||
default = '/^(?:main|master)$/'
|
||||
+20
-16
@@ -11,30 +11,35 @@ pub type VersionName = String;
|
||||
/// e.g. SW-1234-stuff-is-broken (without .md)
|
||||
pub type EntryName = String;
|
||||
|
||||
pub const CONFIG_FILE_TEMPLATE: &str = include_str!("assets/config_file_template.toml");
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_template_file() {
|
||||
// Check 1. that the example config is valid, and 2. that it matches the defaults in the struct
|
||||
let parsed: Config = toml::from_str(CONFIG_FILE_TEMPLATE).unwrap();
|
||||
let def = Config::default();
|
||||
assert_eq!(parsed, def);
|
||||
}
|
||||
|
||||
/// Main app configuration file
|
||||
#[derive(Debug, Serialize, Deserialize, SmartDefault)]
|
||||
#[derive(Debug, Serialize, Deserialize, SmartDefault, PartialEq, Clone)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
pub struct Config {
|
||||
/// Folder for data files - the tool will manage contents of this folder.
|
||||
/// Changelog entries are simple text files that may be edited manually
|
||||
/// if corrections need to be made.
|
||||
/// Name / path of the folder managed by clpack
|
||||
#[default = "changelog"]
|
||||
pub data_folder: String,
|
||||
|
||||
/// ID of the default channel - this only matters inside this config file
|
||||
/// ID of the default channel
|
||||
#[default = "default"]
|
||||
pub default_channel: String,
|
||||
|
||||
/// Path or file name of the default changelog file, relative to the root of the project.
|
||||
///
|
||||
/// The name is used as-is.
|
||||
/// Path or file name of the default changelog file, relative to project root (CWD)
|
||||
#[default = "CHANGELOG.md"]
|
||||
pub changelog_file_default: String,
|
||||
|
||||
/// Path or file of a channel-specific changelog file, relative to the root of the project.
|
||||
///
|
||||
/// Placeholders supported are:
|
||||
/// - `{channel}`, `{Channel}`, `{CHANNEL}` - Channel ID in the respective capitalization
|
||||
/// Path or file of a channel-specific changelog file, relative to project root (CWD).
|
||||
/// Supports placeholder `{channel}`, `{Channel}`, `{CHANNEL}`
|
||||
#[default = "CHANGELOG-{CHANNEL}.md"]
|
||||
pub changelog_file_channel: String,
|
||||
|
||||
@@ -51,10 +56,9 @@ pub struct Config {
|
||||
pub date_format: String,
|
||||
|
||||
/// Changelog sections suggested when creating a new entry.
|
||||
/// The order is maintained.
|
||||
///
|
||||
/// Users may also specify a custom section name.
|
||||
///
|
||||
/// Changelog entries under each section will be grouped in the packed changelog.
|
||||
/// Users may also specify custom section names when writing the changelog file.
|
||||
#[default(vec![
|
||||
"Fixes".to_string(),
|
||||
"Improvements".to_string(),
|
||||
@@ -97,6 +101,6 @@ pub struct Config {
|
||||
/// If None, no branch identification will be attempted.
|
||||
///
|
||||
/// TODO attempt to parse version from package.json, composer.json, Cargo.toml and others
|
||||
#[default(Some(r"/^rel\/(\d+\.\d+)$/".to_string()))]
|
||||
#[default(Some(r"/^rel\/([\d.]+)$/".to_string()))]
|
||||
pub branch_version_pattern: Option<String>,
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
use crate::AppContext;
|
||||
use crate::utils::empty_to_none::EmptyToNone;
|
||||
use anyhow::bail;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
@@ -79,7 +80,7 @@ impl BranchName {
|
||||
///
|
||||
/// Aborts if the configured regex pattern is invalid.
|
||||
pub fn parse_version(&self, ctx: &AppContext) -> anyhow::Result<Option<String>> {
|
||||
let Some(pat) = ctx.config.branch_version_pattern.as_ref() else {
|
||||
let Some(pat) = ctx.config.branch_version_pattern.as_ref().empty_to_none() else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.parse_using_regex(pat, "branch_version_pattern")
|
||||
@@ -89,7 +90,7 @@ impl BranchName {
|
||||
///
|
||||
/// Aborts if the configured regex pattern is invalid.
|
||||
pub fn parse_issue(&self, ctx: &AppContext) -> anyhow::Result<Option<String>> {
|
||||
let Some(pat) = ctx.config.branch_issue_pattern.as_ref() else {
|
||||
let Some(pat) = ctx.config.branch_issue_pattern.as_ref().empty_to_none() else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.parse_using_regex(pat, "branch_issue_pattern")
|
||||
|
||||
+3
-1
@@ -1,4 +1,4 @@
|
||||
use crate::action_init::{ClInit, cl_init};
|
||||
use crate::action_init::{cl_init, ClInit};
|
||||
use crate::action_log::cl_log;
|
||||
use crate::action_pack::cl_pack;
|
||||
use crate::config::{ChannelName, Config};
|
||||
@@ -19,6 +19,8 @@ mod action_init;
|
||||
|
||||
mod store;
|
||||
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppContext {
|
||||
/// Name of the cl binary
|
||||
|
||||
+8
-8
@@ -243,7 +243,7 @@ impl Release {
|
||||
pub fn render(&self, entries_dir: impl AsRef<Path>, config: &Config) -> anyhow::Result<String> {
|
||||
let mut entries_per_section = IndexMap::<String, String>::new();
|
||||
let entries_dir = entries_dir.as_ref();
|
||||
let unnamed = "".to_string();
|
||||
let unnamed_section = "".to_string();
|
||||
|
||||
for entry in &self.entries {
|
||||
let entry_file = entries_dir.join(&format!("{entry}.md"));
|
||||
@@ -258,17 +258,17 @@ impl Release {
|
||||
let file = OpenOptions::new().read(true).open(&entry_file)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let mut current_section = unnamed.clone();
|
||||
let mut current_section = unnamed_section.clone();
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
let line = line.trim_end();
|
||||
if line.trim().is_empty() {
|
||||
let line_trimmed = line.trim();
|
||||
if line_trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if line.trim().starts_with('#') {
|
||||
if line_trimmed.starts_with('#') {
|
||||
// It is a section name
|
||||
let section = line.trim_matches(|c| c == '#' || c == ' ');
|
||||
current_section = section.to_string();
|
||||
current_section = line.trim_start_matches(|c| c == '#' || c == ' ').to_string();
|
||||
} else {
|
||||
if let Some(buffer) = entries_per_section.get_mut(¤t_section) {
|
||||
buffer.push('\n');
|
||||
@@ -287,7 +287,7 @@ impl Release {
|
||||
reordered_sections.push(("".to_string(), unlabelled));
|
||||
}
|
||||
|
||||
for section_name in [unnamed].iter().chain(config.sections.iter()) {
|
||||
for section_name in [unnamed_section].iter().chain(config.sections.iter()) {
|
||||
if let Some(content) = entries_per_section.swap_remove(section_name) {
|
||||
reordered_sections.push((section_name.clone(), content));
|
||||
}
|
||||
@@ -311,7 +311,7 @@ impl Release {
|
||||
buffer.push_str(&format!("\n### {}\n\n", section_name));
|
||||
}
|
||||
buffer.push_str(content.trim_end());
|
||||
buffer.push_str("\n\n");
|
||||
buffer.push_str("\n");
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
/// Convert Option::Some() to None if the contained value is empty
|
||||
pub trait EmptyToNone<T> {
|
||||
fn empty_to_none(self) -> Option<T>;
|
||||
}
|
||||
|
||||
macro_rules! empty_to_none_impl {
|
||||
($ty:ty) => {
|
||||
fn empty_to_none(self) -> Option<$ty> {
|
||||
match self {
|
||||
None => None,
|
||||
Some(s) if s.is_empty() => None,
|
||||
Some(s) => Some(s),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a> EmptyToNone<&'a str> for Option<&'a str> {
|
||||
empty_to_none_impl!(&'a str);
|
||||
}
|
||||
|
||||
impl<'a> EmptyToNone<&'a String> for Option<&'a String> {
|
||||
empty_to_none_impl!(&'a String);
|
||||
}
|
||||
|
||||
impl EmptyToNone<String> for Option<String> {
|
||||
empty_to_none_impl!(String);
|
||||
}
|
||||
|
||||
impl<X> EmptyToNone<Vec<X>> for Option<Vec<X>> {
|
||||
empty_to_none_impl!(Vec<X>);
|
||||
}
|
||||
|
||||
impl<'a, X> EmptyToNone<&'a Vec<X>> for Option<&'a Vec<X>> {
|
||||
empty_to_none_impl!(&'a Vec<X>);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod empty_to_none;
|
||||
Reference in New Issue
Block a user