use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use sublime_standard_tools::config::{ConfigResult, Configurable};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct ChangelogConfig {
pub enabled: bool,
pub format: ChangelogFormat,
pub filename: String,
pub include_commit_links: bool,
pub include_issue_links: bool,
pub include_authors: bool,
pub repository_url: Option<String>,
pub monorepo_mode: MonorepoMode,
pub version_tag_format: String,
pub root_tag_format: String,
pub conventional: ConventionalConfig,
pub exclude: ExcludeConfig,
pub template: TemplateConfig,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum ChangelogFormat {
KeepAChangelog,
Conventional,
Custom,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum MonorepoMode {
PerPackage,
Root,
Both,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct ConventionalConfig {
pub enabled: bool,
pub types: HashMap<String, String>,
pub breaking_section: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ExcludeConfig {
pub patterns: Vec<String>,
pub authors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct TemplateConfig {
pub header: String,
pub version_header: String,
pub section_header: String,
pub entry_format: String,
}
impl Default for ChangelogConfig {
fn default() -> Self {
Self {
enabled: true,
format: ChangelogFormat::KeepAChangelog,
filename: "CHANGELOG.md".to_string(),
include_commit_links: true,
include_issue_links: true,
include_authors: false,
repository_url: None,
monorepo_mode: MonorepoMode::PerPackage,
version_tag_format: "{name}@{version}".to_string(),
root_tag_format: "v{version}".to_string(),
conventional: ConventionalConfig::default(),
exclude: ExcludeConfig::default(),
template: TemplateConfig::default(),
}
}
}
impl Default for ConventionalConfig {
fn default() -> Self {
let mut types = HashMap::new();
types.insert("feat".to_string(), "Features".to_string());
types.insert("fix".to_string(), "Bug Fixes".to_string());
types.insert("perf".to_string(), "Performance Improvements".to_string());
types.insert("refactor".to_string(), "Code Refactoring".to_string());
types.insert("docs".to_string(), "Documentation".to_string());
types.insert("build".to_string(), "Build System".to_string());
types.insert("ci".to_string(), "Continuous Integration".to_string());
types.insert("test".to_string(), "Tests".to_string());
types.insert("chore".to_string(), "Chores".to_string());
Self { enabled: true, types, breaking_section: "Breaking Changes".to_string() }
}
}
impl Default for TemplateConfig {
fn default() -> Self {
Self {
header: "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n".to_string(),
version_header: "## [{version}] - {date}".to_string(),
section_header: "### {section}".to_string(),
entry_format: "- {description} ({hash})".to_string(),
}
}
}
impl Configurable for ChangelogConfig {
fn validate(&self) -> ConfigResult<()> {
if self.filename.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message: "changelog.filename: Filename cannot be empty".to_string(),
});
}
if self.version_tag_format.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message: "changelog.version_tag_format: Version tag format cannot be empty"
.to_string(),
});
}
if self.root_tag_format.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message: "changelog.root_tag_format: Root tag format cannot be empty".to_string(),
});
}
self.conventional.validate()?;
self.exclude.validate()?;
self.template.validate()?;
Ok(())
}
fn merge_with(&mut self, other: Self) -> ConfigResult<()> {
self.enabled = other.enabled;
self.format = other.format;
self.filename = other.filename;
self.include_commit_links = other.include_commit_links;
self.include_issue_links = other.include_issue_links;
self.include_authors = other.include_authors;
self.repository_url = other.repository_url;
self.monorepo_mode = other.monorepo_mode;
self.version_tag_format = other.version_tag_format;
self.root_tag_format = other.root_tag_format;
self.conventional.merge_with(other.conventional)?;
self.exclude.merge_with(other.exclude)?;
self.template.merge_with(other.template)?;
Ok(())
}
}
impl Configurable for ConventionalConfig {
fn validate(&self) -> ConfigResult<()> {
if self.breaking_section.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message: "changelog.conventional.breaking_section: Breaking section title cannot be empty".to_string(),
});
}
Ok(())
}
fn merge_with(&mut self, other: Self) -> ConfigResult<()> {
self.enabled = other.enabled;
self.types = other.types;
self.breaking_section = other.breaking_section;
Ok(())
}
}
impl Configurable for ExcludeConfig {
fn validate(&self) -> ConfigResult<()> {
Ok(())
}
fn merge_with(&mut self, other: Self) -> ConfigResult<()> {
self.patterns = other.patterns;
self.authors = other.authors;
Ok(())
}
}
impl Configurable for TemplateConfig {
fn validate(&self) -> ConfigResult<()> {
if self.header.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message: "changelog.template.header: Header template cannot be empty".to_string(),
});
}
if self.version_header.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message:
"changelog.template.version_header: Version header template cannot be empty"
.to_string(),
});
}
if self.section_header.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message:
"changelog.template.section_header: Section header template cannot be empty"
.to_string(),
});
}
if self.entry_format.is_empty() {
return Err(sublime_standard_tools::config::ConfigError::ValidationError {
message: "changelog.template.entry_format: Entry format template cannot be empty"
.to_string(),
});
}
Ok(())
}
fn merge_with(&mut self, other: Self) -> ConfigResult<()> {
self.header = other.header;
self.version_header = other.version_header;
self.section_header = other.section_header;
self.entry_format = other.entry_format;
Ok(())
}
}