use std::{
collections::HashMap,
env,
fs::File,
io::Read,
path::{Path, PathBuf},
fmt::Display,
};
use toml::{self, value::Table};
type CargoToml = HashMap<String, toml::Value>;
pub fn crate_name(orig_name: &'static str) -> Result<String, String> {
let manifest_dir = env::var("CARGO_MANIFEST_DIR")
.map_err(|_| "Could not find `CARGO_MANIFEST_DIR` env variable.")?;
let cargo_toml_path = PathBuf::from(manifest_dir).join("Cargo.toml");
if !cargo_toml_path.exists() {
return Err(format!("`{}` does not exist.", cargo_toml_path.display()));
}
let cargo_toml = open_cargo_toml(&cargo_toml_path)?;
extract_crate_name(orig_name, cargo_toml, &cargo_toml_path)
}
fn open_cargo_toml(path: &Path) -> Result<CargoToml, String> {
let mut content = String::new();
File::open(path)
.map_err(|e| format!("Could not open `{}`: {:?}", path.display(), e))?
.read_to_string(&mut content)
.map_err(|e| format!("Could not read `{}` to string: {:?}", path.display(), e))?;
toml::from_str(&content).map_err(|e| format!("{:?}", e))
}
fn create_not_found_err(orig_name: &'static str, path: &Display) -> Result<String, String> {
Err(format!(
"Could not find `{}` in `dependencies` or `dev-dependencies` in `{}`!",
orig_name,
path
))
}
fn extract_crate_name(
orig_name: &'static str,
mut cargo_toml: CargoToml,
cargo_toml_path: &Path,
) -> Result<String, String> {
if let Some(name) = cargo_toml
.remove("dependencies")
.and_then(|v| v.try_into::<Table>().ok())
.and_then(|t| extract_crate_name_from_deps(orig_name, t))
{
return Ok(name);
}
if let Some(name) = cargo_toml
.remove("dev-dependencies")
.and_then(|v| v.try_into::<Table>().ok())
.and_then(|t| extract_crate_name_from_deps(orig_name, t))
{
return Ok(name);
}
create_not_found_err(orig_name, &cargo_toml_path.display())
}
fn extract_crate_name_from_deps(orig_name: &'static str, deps: Table) -> Option<String> {
for (key, value) in deps.into_iter() {
let renamed = value
.try_into::<Table>()
.ok()
.and_then(|t| t.get("package").cloned())
.map(|t| t.as_str() == Some(orig_name))
.unwrap_or(false);
if key == orig_name || renamed {
return Some(key.clone());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! create_test {
(
$name:ident,
$cargo_toml:expr,
$result:expr,
) => {
#[test]
fn $name() {
let cargo_toml = toml::from_str($cargo_toml).expect("Parses `Cargo.toml`");
let path = PathBuf::from("test-path");
assert_eq!($result, extract_crate_name("my_crate", cargo_toml, &path));
}
};
}
create_test! {
deps_with_crate,
r#"
[dependencies]
my_crate = "0.1"
"#,
Ok("my_crate".into()),
}
create_test! {
dev_deps_with_crate,
r#"
[dev-dependencies]
my_crate = "0.1"
"#,
Ok("my_crate".into()),
}
create_test! {
deps_with_crate_renamed,
r#"
[dependencies]
cool = { package = "my_crate", version = "0.1" }
"#,
Ok("cool".into()),
}
create_test! {
deps_with_crate_renamed_second,
r#"
[dependencies.cool]
package = "my_crate"
version = "0.1"
"#,
Ok("cool".into()),
}
create_test! {
deps_empty,
r#"
[dependencies]
"#,
create_not_found_err("my_crate", &"test-path"),
}
create_test! {
crate_not_found,
r#"
[dependencies]
serde = "1.0"
"#,
create_not_found_err("my_crate", &"test-path"),
}
}