use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct Manifest {
pub name: String,
pub schema_version: String,
pub items: HashMap<String, Item>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Item {
#[serde(rename = "type")]
pub item_type: String,
pub description: String,
pub dependencies: Vec<String>,
pub internal_dependencies: Vec<String>,
pub files: Vec<FileMapping>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct FileMapping {
pub source: String,
pub target: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct UserConfig {
pub aliases: HashMap<String, String>,
}
pub fn load_manifest(path: &Path) -> Result<Manifest, Box<dyn std::error::Error>> {
let file_content = fs::read_to_string(path)?;
let manifest = serde_json::from_str::<Manifest>(&file_content)?;
Ok(manifest)
}
pub fn load_user_config(path: &Path) -> Result<UserConfig, Box<dyn std::error::Error>> {
let file_content = fs::read_to_string(path)?;
let config: UserConfig = serde_json::from_str(&file_content)?;
Ok(config)
}
pub fn resolve_target_path(mapping: &FileMapping, config: &UserConfig) -> PathBuf {
let target = mapping.target.clone();
let target_parts: Vec<&str> = target.splitn(2, '/').collect();
if target_parts.len() < 2 {
return Path::new(mapping.source.as_str()).to_path_buf();
}
for (alias, path) in config.aliases.iter() {
if alias == target_parts[0] {
return Path::new(path).join(target_parts[1]);
}
}
Path::new(mapping.source.as_str()).to_path_buf()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_target_path_success() {
let mut aliases = HashMap::new();
aliases.insert("components".to_string(), "app/components".to_string());
let config = UserConfig { aliases };
let mapping = FileMapping {
source: "src/app/components/button/Button.vue".to_string(),
target: "components/ui/button/Button.vue".to_string(),
};
let result = resolve_target_path(&mapping, &config);
assert_eq!(result, PathBuf::from("app/components/ui/button/Button.vue"));
}
#[test]
fn test_resolve_target_path_unknown_alias() {
let config = UserConfig {
aliases: HashMap::new(),
};
let mapping = FileMapping {
source: "local/file".to_string(),
target: "unknown/file".to_string(),
};
let result = resolve_target_path(&mapping, &config);
assert_eq!(result, PathBuf::from("local/file"));
}
#[test]
fn test_resolve_target_path_no_slash() {
let config = UserConfig {
aliases: HashMap::new(),
};
let mapping = FileMapping {
source: "local/file".to_string(),
target: "simple_name".to_string(),
};
let result = resolve_target_path(&mapping, &config);
assert_eq!(result, PathBuf::from("local/file"));
}
#[test]
fn test_load_manifest_file_not_found() {
let res = load_manifest(Path::new("non_existent_file.json"));
assert!(res.is_err());
}
#[test]
fn load_manifest_valid_file() {
let file_path = Path::new("test_manifest.json");
let json_content = r#"{
"name": "ui-vue",
"schema_version": "1.0.0",
"items": {
"button": {
"type": "component",
"description": "Standard button component with variants.",
"files": [
{
"source": "app/components/button/Button.vue",
"target": "components/button/Button.vue"
}
],
"dependencies": ["vue", "class-variance-authority"],
"internal_dependencies": []
}
}
}"#;
fs::write(file_path, json_content).expect("could not write test manifest file");
let res = load_manifest(&file_path);
assert!(res.is_ok());
let manifest = res.unwrap();
assert_eq!(manifest.name, "ui-vue");
assert_eq!(manifest.schema_version, "1.0.0");
assert!(manifest.items.contains_key("button"));
assert_eq!(manifest.items["button"].item_type, "component");
let _ = fs::remove_file(file_path);
}
}