use crate::cargo::manifest_analyzer::DepKind;
use crate::cargo::manifest_ops;
use crate::cargo::unify_types::{TransitivePin, UnifiedDep};
use crate::error::{RailResult, ResultExt};
use crate::toml::format::TomlFormatter;
use std::path::Path;
pub struct ManifestWriter {
formatter: TomlFormatter,
}
impl Default for ManifestWriter {
fn default() -> Self {
Self::new()
}
}
impl ManifestWriter {
pub fn new() -> Self {
Self {
formatter: TomlFormatter::new(),
}
}
pub fn with_dependency_sort(mut self, sort: bool) -> Self {
self.formatter.sort_dependencies = sort;
self
}
pub fn write_workspace_deps(&self, workspace_toml_path: &Path, deps: &[UnifiedDep]) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(workspace_toml_path)?;
manifest_ops::ensure_section(&mut doc, "workspace").context("Failed to create [workspace] section")?;
let deps_table = manifest_ops::get_or_create_table(&mut doc, "workspace.dependencies")
.context("Failed to create [workspace.dependencies]")?;
for dep in deps {
let entry = manifest_ops::build_dep_entry(dep);
manifest_ops::insert_dependency(deps_table, &dep.name, entry).context("Failed to insert dependency")?;
}
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(workspace_toml_path, &doc)?;
Ok(())
}
pub fn update_member<S: AsRef<str>>(
&self,
member_toml_path: &Path,
dep_name: &str,
dep_kind: DepKind,
target: Option<&str>,
local_features: Option<&[S]>,
is_optional: bool,
) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(member_toml_path)?;
let kind_section = self.dep_kind_to_section(dep_kind);
let entry = manifest_ops::build_workspace_dep_entry(local_features, is_optional);
if let Some(target_cfg) = target {
manifest_ops::insert_target_dependency(&mut doc, target_cfg, kind_section, dep_name, entry)
.context("Failed to insert target-specific workspace dependency")?;
} else {
let deps =
manifest_ops::get_or_create_table(&mut doc, kind_section).context("Failed to get dependencies section")?;
manifest_ops::insert_dependency(deps, dep_name, entry).context("Failed to insert workspace dependency")?;
}
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(member_toml_path, &doc)?;
Ok(())
}
pub fn add_transitive_pins(&self, host_toml_path: &Path, transitives: &[TransitivePin]) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(host_toml_path)?;
let dev_deps =
manifest_ops::get_or_create_table(&mut doc, "dev-dependencies").context("Failed to create [dev-dependencies]")?;
for pin in transitives {
let entry = manifest_ops::build_transitive_entry(&pin.features);
manifest_ops::insert_dependency(dev_deps, &pin.name, entry).context("Failed to insert transitive dependency")?;
}
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(host_toml_path, &doc)?;
Ok(())
}
pub fn write_transitive_workspace_deps(
&self,
workspace_toml_path: &Path,
transitives: &[TransitivePin],
) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(workspace_toml_path)?;
manifest_ops::ensure_section(&mut doc, "workspace").context("Failed to create [workspace] section")?;
let deps_table = manifest_ops::get_or_create_table(&mut doc, "workspace.dependencies")
.context("Failed to create [workspace.dependencies]")?;
for pin in transitives {
let entry = manifest_ops::build_versioned_dep_entry(&pin.version, &pin.features);
manifest_ops::insert_dependency(deps_table, &pin.name, entry)
.context("Failed to insert transitive to workspace.dependencies")?;
}
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(workspace_toml_path, &doc)?;
Ok(())
}
fn dep_kind_to_section(&self, dep_kind: DepKind) -> &'static str {
manifest_ops::dep_kind_to_section(dep_kind)
}
pub fn write_workspace_msrv(&self, workspace_toml_path: &Path, msrv: &semver::Version) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(workspace_toml_path)?;
manifest_ops::ensure_section(&mut doc, "workspace").context("Failed to create [workspace] section")?;
let ws_package = manifest_ops::get_or_create_table(&mut doc, "workspace.package")
.context("Failed to create [workspace.package]")?;
let msrv_str = format!("{}.{}.{}", msrv.major, msrv.minor, msrv.patch);
ws_package.insert("rust-version", toml_edit::value(&msrv_str));
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(workspace_toml_path, &doc)?;
Ok(())
}
pub fn enforce_member_msrv_inheritance(&self, member_toml_path: &Path) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(member_toml_path)?;
let Some(pkg) = doc.get_mut("package").and_then(|p| p.as_table_like_mut()) else {
return Ok(());
};
let mut tbl = toml_edit::InlineTable::new();
tbl.insert("workspace", true.into());
pkg.insert("rust-version", toml_edit::value(toml_edit::Value::InlineTable(tbl)));
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(member_toml_path, &doc)?;
Ok(())
}
pub fn remove_dep(
&self,
member_toml_path: &Path,
dep_name: &str,
dep_kind: DepKind,
target: Option<&str>,
) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(member_toml_path)?;
let kind_section = self.dep_kind_to_section(dep_kind);
if let Some(target_cfg) = target {
manifest_ops::remove_target_dependency(&mut doc, target_cfg, kind_section, dep_name)
.context("Failed to remove target-specific dependency")?;
} else {
if let Some(deps) = doc.get_mut(kind_section).and_then(|d| d.as_table_like_mut()) {
deps.remove(dep_name);
}
}
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(member_toml_path, &doc)?;
Ok(())
}
pub fn remove_feature(&self, member_toml_path: &Path, feature_name: &str) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(member_toml_path)?;
if let Some(features) = doc.get_mut("features").and_then(|f| f.as_table_like_mut()) {
features.remove(feature_name);
}
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(member_toml_path, &doc)?;
Ok(())
}
pub fn add_features<S: AsRef<str>>(
&self,
member_toml_path: &Path,
dep_name: &str,
dep_kind: DepKind,
target: Option<&str>,
features_to_add: &[S],
) -> RailResult<()> {
let mut doc = manifest_ops::read_toml_file(member_toml_path)?;
let kind_section = self.dep_kind_to_section(dep_kind);
let dep_item = if let Some(target_cfg) = target {
let path = format!("target.{}.{}", target_cfg, kind_section);
let table = manifest_ops::get_or_create_table(&mut doc, &path)?;
table.get_mut(dep_name)
} else {
doc
.get_mut(kind_section)
.and_then(|t| t.as_table_mut())
.and_then(|t| t.get_mut(dep_name))
};
let Some(dep_item) = dep_item else {
return Ok(());
};
let mut existing_features: Vec<String> = manifest_ops::extract_features(dep_item).unwrap_or_default();
for feature in features_to_add {
let feat_str = feature.as_ref();
if !existing_features.iter().any(|f| f == feat_str) {
existing_features.push(feat_str.to_string());
}
}
existing_features.sort();
if let Some(version) = dep_item.as_str() {
let mut inline_table = toml_edit::InlineTable::new();
inline_table.insert("version", toml_edit::Value::from(version));
inline_table.insert("features", manifest_ops::build_feature_array(&existing_features));
*dep_item = toml_edit::Item::Value(toml_edit::Value::InlineTable(inline_table));
} else {
manifest_ops::set_features(dep_item, &existing_features)?;
}
self.formatter.format_manifest(&mut doc)?;
manifest_ops::write_toml_file(member_toml_path, &doc)?;
Ok(())
}
}