use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use super::version::SemanticVersion;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceMetadata {
pub root: PathBuf,
pub members: Vec<String>,
pub exclude: Vec<String>,
pub version: Option<SemanticVersion>,
pub resolver: Option<String>,
pub metadata: WorkspaceMetadataExtra,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WorkspaceMetadataExtra {
pub governor: Option<GovernorMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GovernorMetadata {
pub owners: Option<OwnersConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OwnersConfig {
pub users: Vec<String>,
pub groups: Vec<String>,
}
impl WorkspaceMetadata {
#[must_use]
pub fn new(root: PathBuf) -> Self {
Self {
root,
members: Vec::new(),
exclude: Vec::new(),
version: None,
resolver: None,
metadata: WorkspaceMetadataExtra::default(),
}
}
#[must_use]
pub const fn is_shared_version(&self) -> bool {
self.version.is_some()
}
#[must_use]
pub fn member_paths(&self) -> Vec<PathBuf> {
self.members
.iter()
.map(|m| {
if m.starts_with("./") || m.starts_with('/') {
PathBuf::from(m)
} else {
self.root.join(m)
}
})
.collect()
}
#[must_use]
pub fn is_excluded(&self, path: &std::path::Path) -> bool {
self.exclude.iter().any(|e| {
let exclude_path = if e.starts_with("./") || e.starts_with('/') {
PathBuf::from(e)
} else {
self.root.join(e)
};
path.starts_with(exclude_path)
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkingTreeStatus {
pub has_changes: bool,
pub modified: Vec<String>,
pub added: Vec<String>,
pub deleted: Vec<String>,
pub untracked: Vec<String>,
}
impl WorkingTreeStatus {
#[must_use]
pub const fn clean() -> Self {
Self {
has_changes: false,
modified: Vec::new(),
added: Vec::new(),
deleted: Vec::new(),
untracked: Vec::new(),
}
}
#[must_use]
pub const fn is_clean(&self) -> bool {
!self.has_changes
}
#[must_use]
pub fn all_changes(&self) -> Vec<&String> {
let mut changes = Vec::new();
changes.extend(self.modified.iter());
changes.extend(self.added.iter());
changes.extend(self.deleted.iter());
changes
}
#[must_use]
pub const fn total_changes(&self) -> usize {
self.modified.len() + self.added.len() + self.deleted.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workspace_metadata() {
let mut meta = WorkspaceMetadata::new(PathBuf::from("/test"));
meta.members.push("crate1".to_string());
meta.members.push("crate2".to_string());
let paths = meta.member_paths();
assert_eq!(paths.len(), 2);
assert!(paths[0].ends_with("crate1"));
}
#[test]
fn test_working_tree_status() {
let status = WorkingTreeStatus::clean();
assert!(status.is_clean());
assert_eq!(status.total_changes(), 0);
}
#[test]
fn test_excluded_paths() {
let mut meta = WorkspaceMetadata::new(PathBuf::from("/test"));
meta.exclude.push("target".to_string());
assert!(meta.is_excluded(&PathBuf::from("/test/target")));
assert!(!meta.is_excluded(&PathBuf::from("/test/src")));
}
}