use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DependencySpec {
Simple(String),
Detailed {
version: Option<String>,
features: Option<Vec<String>>,
path: Option<String>,
git: Option<String>,
branch: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WjConfig {
#[serde(default)]
pub project: Option<ProjectConfig>,
#[serde(default)]
pub package: PackageConfig,
#[serde(default)]
pub sources: Option<SourcesConfig>,
#[serde(default)]
pub dependencies: HashMap<String, DependencySpec>,
#[serde(default, alias = "dev-dependencies")]
pub dev_dependencies: HashMap<String, DependencySpec>,
#[serde(default)]
pub backend: Option<BackendConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProjectConfig {
#[serde(default)]
pub name: String,
#[serde(default)]
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PackageConfig {
#[serde(default)]
pub name: String,
#[serde(default)]
pub version: String,
#[serde(default)]
pub authors: Vec<String>,
#[serde(default)]
pub edition: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SourcesConfig {
#[serde(default)]
pub roots: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackendConfig {
pub url: String,
#[serde(default)]
pub api_key: Option<String>,
}
impl WjConfig {
pub fn load_from_file(path: &Path) -> Result<Self, String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
toml::from_str(&content).map_err(|e| format!("Failed to parse {}: {}", path.display(), e))
}
pub fn save_to_file(&self, path: &Path) -> Result<(), String> {
let content = toml::to_string_pretty(self)
.map_err(|e| format!("Failed to serialize config: {}", e))?;
fs::write(path, content).map_err(|e| format!("Failed to write {}: {}", path.display(), e))
}
pub fn add_dependency(&mut self, name: String, spec: DependencySpec) {
self.dependencies.insert(name, spec);
}
pub fn remove_dependency(&mut self, name: &str) -> bool {
self.dependencies.remove(name).is_some()
}
pub fn to_cargo_toml(&self) -> String {
let mut output = String::new();
output.push_str("[package]\n");
output.push_str(&format!("name = \"{}\"\n", self.package.name));
output.push_str(&format!("version = \"{}\"\n", self.package.version));
output.push_str(&format!(
"edition = \"{}\"\n",
if self.package.edition.is_empty() {
"2021"
} else {
&self.package.edition
}
));
if !self.package.authors.is_empty() {
output.push_str(&format!("authors = {:?}\n", self.package.authors));
}
output.push('\n');
if !self.dependencies.is_empty() {
output.push_str("[dependencies]\n");
for (name, spec) in &self.dependencies {
match spec {
DependencySpec::Simple(version) => {
output.push_str(&format!("{} = \"{}\"\n", name, version));
}
DependencySpec::Detailed {
version,
features,
path,
git,
branch,
} => {
output.push_str(&format!("{} = {{ ", name));
let mut parts = Vec::new();
if let Some(v) = version {
parts.push(format!("version = \"{}\"", v));
}
if let Some(f) = features {
parts.push(format!("features = {:?}", f));
}
if let Some(p) = path {
parts.push(format!("path = \"{}\"", p));
}
if let Some(g) = git {
parts.push(format!("git = \"{}\"", g));
}
if let Some(b) = branch {
parts.push(format!("branch = \"{}\"", b));
}
output.push_str(&parts.join(", "));
output.push_str(" }\n");
}
}
}
output.push('\n');
}
if !self.dev_dependencies.is_empty() {
output.push_str("[dev-dependencies]\n");
for (name, spec) in &self.dev_dependencies {
match spec {
DependencySpec::Simple(version) => {
output.push_str(&format!("{} = \"{}\"\n", name, version));
}
DependencySpec::Detailed {
version,
features,
path,
git,
branch,
} => {
output.push_str(&format!("{} = {{ ", name));
let mut parts = Vec::new();
if let Some(v) = version {
parts.push(format!("version = \"{}\"", v));
}
if let Some(f) = features {
parts.push(format!("features = {:?}", f));
}
if let Some(p) = path {
parts.push(format!("path = \"{}\"", p));
}
if let Some(g) = git {
parts.push(format!("git = \"{}\"", g));
}
if let Some(b) = branch {
parts.push(format!("branch = \"{}\"", b));
}
output.push_str(&parts.join(", "));
output.push_str(" }\n");
}
}
}
}
output
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WindjammerConfig {
#[serde(default)]
pub backend: Option<BackendConfig>,
#[serde(flatten)]
pub custom: HashMap<String, toml::Value>,
}
impl WindjammerConfig {
pub fn load(path: &Path) -> Result<Self, String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
toml::from_str(&content).map_err(|e| format!("Failed to parse {}: {}", path.display(), e))
}
pub fn backend_url(&self) -> Option<&str> {
self.backend.as_ref().map(|b| b.url.as_str())
}
}