use std::fs;
use std::path::Path;
use anyhow::{anyhow, bail, Context, Result};
use cargo_metadata::camino::Utf8PathBuf;
use cargo_util_schemas::manifest::TomlManifest;
use glob::glob;
use log::warn;
pub struct Workspace {
manifest: TomlManifest,
cargo_toml_path: Utf8PathBuf,
}
impl Workspace {
pub fn try_new(workspace_root: Utf8PathBuf) -> Result<Self> {
let cargo_toml_path = workspace_root.join("Cargo.toml");
let content = fs::read_to_string(cargo_toml_path.as_std_path())?;
let manifest: TomlManifest = toml::from_str(&content)?;
if manifest.workspace.is_none() {
bail!("{} is not a workspace project", workspace_root);
}
Ok(Self { manifest, cargo_toml_path })
}
pub fn add_member(&mut self, member: WorkspaceMember) -> Result<()> {
let Some(workspace) = self.manifest.workspace.as_mut() else {
bail!("There is no workspace project at {}", self.cargo_toml_path);
};
let Some(members) = workspace.members.as_mut() else {
bail!("There are no workspace members yet defined.");
};
if members.contains(&member.name) {
warn!("Project `{}` is already a member of the workspace", member.name);
return Ok(());
}
members.push(member.name.clone());
members.sort();
Ok(())
}
pub fn save(&self) -> Result<()> {
let new_manifest = toml::to_string_pretty(&self.manifest)?;
let cargo_toml_path = &self.cargo_toml_path;
fs::write(cargo_toml_path, new_manifest)?;
Ok(())
}
}
pub struct WorkspaceMember {
name: String,
}
impl WorkspaceMember {
#[allow(unused)]
pub fn try_new(member_path: &Path) -> Result<Self> {
let cargo_toml_path = member_path.join("Cargo.toml");
let content = fs::read_to_string(&cargo_toml_path)
.with_context(|| format!("Failed to read {}", cargo_toml_path.display()))?;
let manifest: TomlManifest =
toml::from_str(&content).with_context(|| format!("Failed to parse {}", cargo_toml_path.display()))?;
let pkg = manifest
.package()
.ok_or_else(|| anyhow!("No [package] section found in Cargo.toml at {}", cargo_toml_path.display()))?;
let name = pkg
.name
.as_ref()
.ok_or_else(|| anyhow!("No `package.name` found in Cargo.toml at {}", cargo_toml_path.display()))?
.to_string();
Ok(Self { name })
}
pub fn try_new_from_glob(pattern: &str) -> Result<Self> {
for entry in glob(pattern)? {
let path = entry?;
if !path.join("Cargo.toml").exists() {
bail!("{} is not a member of the workspace", path.display());
}
}
Ok(Self { name: pattern.to_string() })
}
}