use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspaceDescriptor {
pub workspace: WorkspaceMeta,
#[serde(default)]
pub repos: BTreeMap<String, RepoSpec>,
#[serde(skip)]
pub descriptor_dir: PathBuf,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspaceMeta {
pub name: String,
#[serde(default)]
pub deep_scan: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RepoSpec {
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub git: Option<String>,
#[serde(default)]
pub branch: Option<String>,
}
#[derive(Debug, Clone)]
pub enum RepoSource {
Path(PathBuf),
Git { url: String, branch: Option<String> },
}
impl RepoSpec {
pub fn source(&self, descriptor_dir: &Path) -> Result<RepoSource> {
match (&self.path, &self.git) {
(Some(p), None) => {
let raw = PathBuf::from(p);
let resolved = if raw.is_absolute() { raw } else { descriptor_dir.join(raw) };
Ok(RepoSource::Path(resolved))
}
(None, Some(url)) => Ok(RepoSource::Git {
url: url.clone(),
branch: self.branch.clone(),
}),
(Some(_), Some(_)) => Err(anyhow!("repo spec has both `path` and `git`; choose one")),
(None, None) => Err(anyhow!("repo spec has neither `path` nor `git`")),
}
}
}
impl WorkspaceDescriptor {
pub fn load(path: &Path) -> Result<Self> {
let text = std::fs::read_to_string(path)
.with_context(|| format!("read workspace descriptor {}", path.display()))?;
let mut d: WorkspaceDescriptor = toml::from_str(&text)
.with_context(|| format!("parse workspace descriptor {}", path.display()))?;
d.descriptor_dir = path
.parent()
.ok_or_else(|| anyhow!("descriptor path has no parent"))?
.canonicalize()
.with_context(|| format!("canonicalize descriptor parent of {}", path.display()))?;
Ok(d)
}
pub fn sources(&self) -> Result<Vec<(String, RepoSource)>> {
self.repos
.iter()
.map(|(name, spec)| spec.source(&self.descriptor_dir).map(|s| (name.clone(), s)))
.collect()
}
}