use crate::{Result, ResultContext};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ToolsManifest {
pub tools: Vec<String>,
}
impl ToolsManifest {
pub fn new() -> Self {
Self { tools: Vec::new() }
}
pub fn with_tools(tools: Vec<String>) -> Self {
Self { tools }
}
pub fn add_tool(&mut self, tool_id: String) -> bool {
if !self.tools.contains(&tool_id) {
self.tools.push(tool_id);
true
} else {
false
}
}
pub fn remove_tool(&mut self, tool_id: &str) -> bool {
if let Some(pos) = self.tools.iter().position(|t| t == tool_id) {
self.tools.remove(pos);
true
} else {
false
}
}
pub fn contains(&self, tool_id: &str) -> bool {
self.tools.contains(&tool_id.to_string())
}
pub fn count(&self) -> usize {
self.tools.len()
}
pub fn read(path: impl AsRef<Path>) -> Result<Self> {
let content = std::fs::read_to_string(path.as_ref()).with_context(|| {
format!("Failed to read tools manifest: {}", path.as_ref().display())
})?;
let manifest: Self = yaml_serde::from_str(&content)
.with_context(|| "Failed to parse tools manifest YAML")?;
Ok(manifest)
}
pub fn write(&self, path: impl AsRef<Path>) -> Result<()> {
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)?;
}
let yaml = yaml_serde::to_string(self)
.with_context(|| "Failed to serialize tools manifest to YAML")?;
std::fs::write(path.as_ref(), yaml).with_context(|| {
format!(
"Failed to write tools manifest: {}",
path.as_ref().display()
)
})?;
Ok(())
}
}
impl Default for ToolsManifest {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_new_manifest() {
let manifest = ToolsManifest::new();
assert_eq!(manifest.count(), 0);
}
#[test]
fn test_with_tools() {
let manifest = ToolsManifest::with_tools(vec!["tool1".to_string(), "tool2".to_string()]);
assert_eq!(manifest.count(), 2);
assert!(manifest.contains("tool1"));
assert!(manifest.contains("tool2"));
}
#[test]
fn test_add_tool() {
let mut manifest = ToolsManifest::new();
assert!(manifest.add_tool("test-id".to_string()));
assert_eq!(manifest.count(), 1);
assert!(manifest.contains("test-id"));
assert!(!manifest.add_tool("test-id".to_string()));
assert_eq!(manifest.count(), 1);
}
#[test]
fn test_remove_tool() {
let mut manifest =
ToolsManifest::with_tools(vec!["tool1".to_string(), "tool2".to_string()]);
assert!(manifest.remove_tool("tool1"));
assert_eq!(manifest.count(), 1);
assert!(!manifest.contains("tool1"));
assert!(!manifest.remove_tool("nonexistent"));
}
#[test]
fn test_read_write() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("manifest").join("tools.yml");
let original = ToolsManifest::with_tools(vec![
"tool1".to_string(),
"tool2".to_string(),
"tool3".to_string(),
]);
original.write(&manifest_path).unwrap();
assert!(manifest_path.exists());
let loaded = ToolsManifest::read(&manifest_path).unwrap();
assert_eq!(loaded, original);
assert_eq!(loaded.count(), 3);
}
#[test]
fn test_yaml_format() {
let manifest = ToolsManifest::with_tools(vec!["tool1".to_string(), "tool2".to_string()]);
let yaml = yaml_serde::to_string(&manifest).unwrap();
assert!(yaml.contains("tools:"));
assert!(yaml.contains("tool1"));
assert!(yaml.contains("tool2"));
}
}