#[derive(Debug, Clone, PartialEq)]
pub enum ReleaseStatus {
Unreleased,
Latest,
Pending,
Inconsistent,
Unknown,
}
impl std::fmt::Display for ReleaseStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unreleased => write!(f, "未发布"),
Self::Latest => write!(f, "已是最新"),
Self::Pending => write!(f, "待发布"),
Self::Inconsistent => write!(f, "版本冲突"),
Self::Unknown => write!(f, "状态未知"),
}
}
}
#[derive(Debug)]
pub struct ReleaseState {
pub status: ReleaseStatus,
pub scope: String,
pub scope_path: String,
pub current_version: Option<String>,
pub pending_commits: usize,
pub changelog: String,
pub version_consistent: Option<bool>,
}
impl ReleaseState {
pub fn builder() -> ReleaseStateBuilder {
ReleaseStateBuilder::default()
}
}
#[derive(Debug)]
pub struct ReleaseStateBuilder {
status: Option<ReleaseStatus>,
scope: Option<String>,
scope_path: Option<String>,
current_version: Option<String>,
pending_commits: usize,
version_consistent: Option<bool>,
changelog: String,
}
impl Default for ReleaseStateBuilder {
fn default() -> Self {
Self {
status: None,
scope: None,
scope_path: None,
current_version: None,
pending_commits: 0,
version_consistent: None,
changelog: "CHANGELOG.md".into(),
}
}
}
impl ReleaseStateBuilder {
pub fn status(mut self, value: ReleaseStatus) -> Self {
self.status = Some(value);
self
}
pub fn scope(mut self, value: impl Into<String>) -> Self {
self.scope = Some(value.into());
self
}
pub fn scope_path(mut self, value: impl Into<String>) -> Self {
self.scope_path = Some(value.into());
self
}
pub fn current_version(mut self, value: impl Into<String>) -> Self {
self.current_version = Some(value.into());
self
}
pub fn pending_commits(mut self, value: usize) -> Self {
self.pending_commits = value;
self
}
pub fn version_consistent(mut self, value: bool) -> Self {
self.version_consistent = Some(value);
self
}
pub fn changelog(mut self, value: impl Into<String>) -> Self {
self.changelog = value.into();
self
}
pub fn build(self) -> ReleaseState {
ReleaseState {
status: self.status.expect("ReleaseStateBuilder: status 是必需的"),
scope: self.scope.expect("ReleaseStateBuilder: scope 是必需的"),
scope_path: self
.scope_path
.expect("ReleaseStateBuilder: scope_path 是必需的"),
current_version: self.current_version,
pending_commits: self.pending_commits,
changelog: self.changelog,
version_consistent: self.version_consistent,
}
}
}
impl std::fmt::Display for ReleaseState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, " [{}]", self.scope)?;
writeln!(f, " 状态: {}", self.status)?;
writeln!(f, " 路径: {}", self.scope_path)?;
match &self.current_version {
Some(v) => writeln!(f, " 最新版本: {}", v)?,
None => writeln!(f, " 最新版本: (无)")?,
}
writeln!(f, " 未发布提交: {}", self.pending_commits)?;
writeln!(f, " 变更日志: {}", self.changelog)?;
match self.version_consistent {
Some(true) => writeln!(f, " 版本一致: 是"),
Some(false) => writeln!(f, " 版本一致: 否"),
None => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_release_status_variants() {
assert_ne!(ReleaseStatus::Unreleased, ReleaseStatus::Latest);
assert_ne!(ReleaseStatus::Latest, ReleaseStatus::Pending);
assert_ne!(ReleaseStatus::Pending, ReleaseStatus::Inconsistent);
assert_ne!(ReleaseStatus::Inconsistent, ReleaseStatus::Unknown);
}
#[test]
fn test_release_status_debug() {
let s = format!("{:?}", ReleaseStatus::Unreleased);
assert_eq!(s, "Unreleased");
}
#[test]
fn test_release_status_display() {
assert_eq!(format!("{}", ReleaseStatus::Unreleased), "未发布");
assert_eq!(format!("{}", ReleaseStatus::Latest), "已是最新");
assert_eq!(format!("{}", ReleaseStatus::Pending), "待发布");
assert_eq!(format!("{}", ReleaseStatus::Inconsistent), "版本冲突");
assert_eq!(format!("{}", ReleaseStatus::Unknown), "状态未知");
}
#[test]
fn test_release_state_unreleased() {
let state = ReleaseState {
status: ReleaseStatus::Unreleased,
scope: "cli".into(),
scope_path: "src/cli".into(),
current_version: None,
pending_commits: 0,
changelog: "CHANGELOG.md".into(),
version_consistent: None,
};
assert_eq!(state.status, ReleaseStatus::Unreleased);
assert!(state.current_version.is_none());
assert_eq!(state.pending_commits, 0);
}
#[test]
fn test_release_state_pending() {
let state = ReleaseState {
status: ReleaseStatus::Pending,
scope: "core".into(),
scope_path: "packages/core".into(),
current_version: Some("v1.2.3".into()),
pending_commits: 5,
changelog: "CHANGELOG.md".into(),
version_consistent: Some(true),
};
assert_eq!(state.status, ReleaseStatus::Pending);
assert_eq!(state.current_version.as_deref(), Some("v1.2.3"));
assert_eq!(state.pending_commits, 5);
assert_eq!(state.version_consistent, Some(true));
}
#[test]
fn test_release_state_latest() {
let state = ReleaseState {
status: ReleaseStatus::Latest,
scope: "(root)".into(),
scope_path: ".".into(),
current_version: Some("v5.0.0".into()),
pending_commits: 0,
changelog: "CHANGELOG.md".into(),
version_consistent: Some(true),
};
assert_eq!(state.status, ReleaseStatus::Latest);
assert_eq!(state.pending_commits, 0);
}
#[test]
fn test_release_state_inconsistent() {
let state = ReleaseState {
status: ReleaseStatus::Inconsistent,
scope: "web".into(),
scope_path: "src/web".into(),
current_version: Some("v2.0.0".into()),
pending_commits: 3,
changelog: "CHANGELOG.md".into(),
version_consistent: Some(false),
};
assert_eq!(state.status, ReleaseStatus::Inconsistent);
assert_eq!(state.version_consistent, Some(false));
}
#[test]
fn test_release_state_unknown() {
let state = ReleaseState {
status: ReleaseStatus::Unknown,
scope: "service".into(),
scope_path: "apps/service".into(),
current_version: None,
pending_commits: 0,
changelog: "CHANGELOG.md".into(),
version_consistent: None,
};
assert_eq!(state.status, ReleaseStatus::Unknown);
}
#[test]
fn test_release_state_builder_minimal() {
let s = ReleaseState::builder()
.status(ReleaseStatus::Unreleased)
.scope("cli")
.scope_path("src/cli")
.build();
assert_eq!(s.scope, "cli");
assert_eq!(s.scope_path, "src/cli");
assert_eq!(s.status, ReleaseStatus::Unreleased);
assert!(s.current_version.is_none());
assert_eq!(s.pending_commits, 0);
assert_eq!(s.changelog, "CHANGELOG.md");
assert!(s.version_consistent.is_none());
}
#[test]
fn test_release_state_builder_full() {
let s = ReleaseState::builder()
.status(ReleaseStatus::Pending)
.scope("(root)")
.scope_path(".")
.current_version("v2.0.0")
.pending_commits(5)
.version_consistent(false)
.build();
assert_eq!(s.scope, "(root)");
assert_eq!(s.current_version.as_deref(), Some("v2.0.0"));
assert_eq!(s.pending_commits, 5);
assert_eq!(s.version_consistent, Some(false));
}
#[test]
fn test_release_state_display() {
let state = ReleaseState {
status: ReleaseStatus::Latest,
scope: "qtcloud-core".into(),
scope_path: "apps/qtcloud-core".into(),
current_version: Some("v2.1.0".into()),
pending_commits: 0,
changelog: "CHANGELOG.md".into(),
version_consistent: Some(true),
};
assert_eq!(
format!("{}", state),
" [qtcloud-core]\n 状态: 已是最新\n 路径: apps/qtcloud-core\n 最新版本: v2.1.0\n 未发布提交: 0\n 变更日志: CHANGELOG.md\n 版本一致: 是\n"
);
let state = ReleaseState {
status: ReleaseStatus::Unreleased,
scope: "(root)".into(),
scope_path: ".".into(),
current_version: None,
pending_commits: 0,
changelog: "CHANGELOG.md".into(),
version_consistent: None,
};
assert_eq!(
format!("{}", state),
" [(root)]\n 状态: 未发布\n 路径: .\n 最新版本: (无)\n 未发布提交: 0\n 变更日志: CHANGELOG.md\n"
);
let state = ReleaseState {
status: ReleaseStatus::Inconsistent,
scope: "web".into(),
scope_path: "src/web".into(),
current_version: Some("v2.0.0".into()),
pending_commits: 3,
changelog: "CHANGELOG.md".into(),
version_consistent: Some(false),
};
assert_eq!(
format!("{}", state),
" [web]\n 状态: 版本冲突\n 路径: src/web\n 最新版本: v2.0.0\n 未发布提交: 3\n 变更日志: CHANGELOG.md\n 版本一致: 否\n"
);
}
}