#[cfg(test)]
mod tests;
mod types;
use crate::Error;
use cargo_toml::Manifest;
use std::path::{Path, PathBuf};
use toml_edit::{Array, DocumentMut, InlineTable, Item, Table, Value};
pub use types::{ManifestDependencyConfig, ManifestDependencyOrigin};
pub fn find_innermost_manifest<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
fn do_find_innermost_manifest(path: &Path) -> Option<PathBuf> {
let mut path = path;
let cargo_toml_path = path.join("Cargo.toml");
match Manifest::from_path(&cargo_toml_path) {
Ok(manifest) if manifest.package.is_some() || manifest.workspace.is_some() =>
return Some(cargo_toml_path),
_ => (),
}
while let Some(parent) = path.parent() {
let cargo_toml_path = parent.join("Cargo.toml");
match Manifest::from_path(&cargo_toml_path) {
Ok(manifest) if manifest.package.is_some() || manifest.workspace.is_some() =>
return Some(cargo_toml_path),
_ => path = parent,
}
}
None
}
do_find_innermost_manifest(&crate::paths::prefix_with_current_dir(path))
}
pub fn find_workspace_manifest<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
fn do_find_workspace_manifest(path: &Path) -> Option<PathBuf> {
let mut path = path;
let cargo_toml_path = path.join("Cargo.toml");
match Manifest::from_path(&cargo_toml_path) {
Ok(manifest) if manifest.workspace.is_some() => return Some(cargo_toml_path),
_ => (),
}
while let Some(parent) = path.parent() {
let cargo_toml_path = parent.join("Cargo.toml");
match Manifest::from_path(&cargo_toml_path) {
Ok(manifest) if manifest.workspace.is_some() => return Some(cargo_toml_path),
_ => path = parent,
}
}
None
}
do_find_workspace_manifest(&crate::paths::prefix_with_current_dir(path))
}
pub fn find_crate_name<P: AsRef<Path>>(manifest_path: P) -> Option<String> {
Manifest::from_path(manifest_path.as_ref())
.ok()?
.package
.map(|package| package.name)
}
pub fn add_crate_to_dependencies<P: AsRef<Path>>(
manifest_path: P,
dependency_name: &str,
dependency_config: ManifestDependencyConfig,
) -> Result<(), Error> {
let mut doc = std::fs::read_to_string(manifest_path.as_ref())?.parse::<DocumentMut>()?;
if let Some(Item::Table(dependencies)) = doc.get_mut("dependencies") {
add_dependency_to_dependencies_table(dependencies, dependency_name, dependency_config);
} else if let Some(Item::Table(workspace)) = doc.get_mut("workspace") {
if let Some(Item::Table(dependencies)) = workspace.get_mut("dependencies") {
add_dependency_to_dependencies_table(dependencies, dependency_name, dependency_config);
} else {
let mut dependencies = Table::new();
add_dependency_to_dependencies_table(
&mut dependencies,
dependency_name,
dependency_config,
);
workspace.insert("dependencies", Item::Table(dependencies));
}
} else {
let mut dependencies = Table::new();
add_dependency_to_dependencies_table(&mut dependencies, dependency_name, dependency_config);
doc.insert("dependencies", Item::Table(dependencies));
}
std::fs::write(manifest_path, doc.to_string())?;
Ok(())
}
fn add_dependency_to_dependencies_table(
dependencies: &mut Table,
dependency_name: &str,
dependency_config: ManifestDependencyConfig,
) {
let mut dependency_declaration = InlineTable::new();
match &dependency_config.origin {
ManifestDependencyOrigin::Workspace => {
dependency_declaration.insert(
"workspace",
toml_edit::value(true)
.into_value()
.expect("true is bool, so value(true) is Value::Boolean;qed;"),
);
},
ManifestDependencyOrigin::Git { url, branch } => {
dependency_declaration.insert(
"git",
toml_edit::value(url.to_owned())
.into_value()
.expect("url is String, so value(url) is Value::String; qed;"),
);
dependency_declaration.insert(
"branch",
toml_edit::value(branch.to_owned())
.into_value()
.expect("branch is String, so value(branch) is Value::String; qed;"),
);
},
ManifestDependencyOrigin::CratesIO { version } => {
dependency_declaration.insert(
"version",
toml_edit::value(version.to_owned())
.into_value()
.expect("version is String, so value(version) is Value::String; qed;"),
);
},
ManifestDependencyOrigin::Local { relative_path } => {
dependency_declaration.insert(
"path",
toml_edit::value(relative_path.to_string_lossy().into_owned())
.into_value()
.expect(
"relative_path is String, so value(relative_path) is Value::String; qed;",
),
);
},
}
if !dependency_config.default_features {
dependency_declaration.insert(
"default-features",
toml_edit::value(false)
.into_value()
.expect("false is bool so value(false) is Value::Boolean; qed;"),
);
}
if !dependency_config.features.is_empty() {
let mut features = Array::new();
dependency_config
.features
.iter()
.for_each(|feature| features.push(feature.to_owned()));
dependency_declaration.insert(
"features",
toml_edit::value(features)
.into_value()
.expect("features is Array, so value(features) is Value::Array; qed;"),
);
}
if dependency_config.optional {
dependency_declaration.insert(
"optional",
toml_edit::value(true)
.into_value()
.expect("true is bool so value(true) is Value::Boolean; qed;"),
);
}
dependencies.insert(dependency_name, toml_edit::value(dependency_declaration));
}
pub fn add_crate_to_workspace<P: AsRef<Path>, Q: AsRef<Path>>(
workspace_toml: P,
crate_path: Q,
) -> Result<(), Error> {
fn do_add_crate_to_workspace(workspace_toml: &Path, crate_path: &Path) -> Result<(), Error> {
let mut doc = std::fs::read_to_string(workspace_toml)?.parse::<DocumentMut>()?;
let workspace_dir = workspace_toml.parent().expect("A file always lives inside a dir; qed");
let crate_relative_path = crate_path.strip_prefix(workspace_dir)?;
if let Some(Item::Table(workspace_table)) = doc.get_mut("workspace") {
if let Some(Item::Value(members_array)) = workspace_table.get_mut("members") {
if let Value::Array(array) = members_array {
let crate_relative_path =
crate_relative_path.to_str().expect("target's always a valid string; qed");
let already_in_array = array.iter().any(
|member| matches!(member.as_str(), Some(s) if s == crate_relative_path),
);
if !already_in_array {
array.push(crate_relative_path);
}
} else {
return Err(Error::Descriptive(
"The provided manifest path members field is corrupted".to_owned(),
));
}
} else {
let mut toml_array = Array::new();
toml_array.push(
crate_relative_path
.to_str()
.expect("Path::to_str() is always a valid string; qed"),
);
workspace_table["members"] = toml_edit::value(toml_array);
}
} else {
return Err(Error::Descriptive(
"The provided manifest path isn't a workspace manifest".to_owned(),
));
}
std::fs::write(workspace_toml, doc.to_string())?;
Ok(())
}
do_add_crate_to_workspace(workspace_toml.as_ref(), crate_path.as_ref())
}