#![no_std]
#![doc(test(
no_crate_inject,
attr(allow(
dead_code,
unused_variables,
clippy::undocumented_unsafe_blocks,
clippy::unused_trait_names,
))
))]
#![forbid(unsafe_code)]
#![warn(
// Lints that may help when writing public library.
missing_debug_implementations,
missing_docs,
clippy::alloc_instead_of_core,
clippy::exhaustive_enums,
clippy::exhaustive_structs,
clippy::impl_trait_in_params,
// clippy::missing_inline_in_public_items,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
)]
extern crate alloc;
extern crate std;
#[cfg(test)]
#[path = "gen/tests/assert_impl.rs"]
mod assert_impl;
#[cfg(test)]
#[path = "gen/tests/track_size.rs"]
mod track_size;
mod error;
use alloc::{borrow::ToOwned as _, string::String};
use core::str::FromStr;
use std::{
env, fs,
path::{Path, PathBuf},
};
use toml::value::{Table, Value};
pub use self::error::{Error, TomlError};
type Result<T, E = Error> = core::result::Result<T, E>;
const MANIFEST_DIR: &str = "CARGO_MANIFEST_DIR";
pub fn find_crate<P>(predicate: P) -> Result<Package>
where
P: FnMut(&str) -> bool,
{
Manifest::new()?.find(predicate).ok_or(Error::NotFound)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum Dependencies {
#[default]
Default,
Release,
Dev,
Build,
All,
}
impl Dependencies {
fn as_slice(self) -> &'static [&'static str] {
match self {
Dependencies::Default => &["dependencies", "dev-dependencies"],
Dependencies::Release => &["dependencies"],
Dependencies::Dev => &["dev-dependencies"],
Dependencies::Build => &["build-dependencies"],
Dependencies::All => &["dependencies", "dev-dependencies", "build-dependencies"],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Package {
key: String,
package: Option<String>,
pub name: String,
pub version: String,
}
impl Package {
#[must_use]
pub fn original_name(&self) -> &str {
self.package.as_ref().unwrap_or(&self.key)
}
#[must_use]
pub fn is_original(&self) -> bool {
self.package.is_none()
}
}
#[derive(Debug, Clone)]
pub struct Manifest {
manifest: Table,
pub dependencies: Dependencies,
}
impl Manifest {
pub fn new() -> Result<Self> {
Self::from_path(&manifest_path()?)
}
pub fn from_path(manifest_path: &Path) -> Result<Self> {
Self::from_str(&fs::read_to_string(manifest_path)?)
}
pub fn find<P>(&self, mut predicate: P) -> Option<Package>
where
P: FnMut(&str) -> bool,
{
self.find2(|s, _| predicate(s))
}
pub fn find2<P>(&self, predicate: P) -> Option<Package>
where
P: FnMut(&str, &str) -> bool,
{
find(&self.manifest, self.dependencies, predicate)
}
pub fn crate_package(&self) -> Result<Package> {
let package_section = self
.manifest
.get("package")
.ok_or_else(|| Error::InvalidManifest("[package] section is missing".to_owned()))?;
let package_key_value = package_section.get("name").ok_or_else(|| {
Error::InvalidManifest("[package] section is missing `name`".to_owned())
})?;
let package_key = package_key_value.as_str().ok_or_else(|| {
Error::InvalidManifest("`name` in [package] section is not a string".to_owned())
})?;
let package_version = match package_section.get("version") {
Some(package_version_value) => package_version_value.as_str().ok_or_else(|| {
Error::InvalidManifest("`version` in [package] section is not a string".to_owned())
})?,
None => "0.0.0",
};
let package = Package {
key: package_key.to_owned(),
package: None,
name: package_key.replace('-', "_"),
version: package_version.to_owned(),
};
Ok(package)
}
}
impl FromStr for Manifest {
type Err = Error;
fn from_str(manifest: &str) -> Result<Self, Self::Err> {
Ok(Self {
manifest: toml::from_str(manifest).map_err(|e| Error::Toml(TomlError { error: e }))?,
dependencies: Dependencies::default(),
})
}
}
fn manifest_path() -> Result<PathBuf> {
let mut path: PathBuf = env::var_os(MANIFEST_DIR).ok_or(Error::NotFoundManifestDir)?.into();
path.push("Cargo.toml");
Ok(path)
}
fn find<P>(manifest: &Table, dependencies: Dependencies, mut predicate: P) -> Option<Package>
where
P: FnMut(&str, &str) -> bool,
{
fn find_inner<P>(table: &Table, dependencies: &str, predicate: &mut P) -> Option<Package>
where
P: FnMut(&str, &str) -> bool,
{
find_from_dependencies(table.get(dependencies)?.as_table()?, predicate)
}
fn find_target<P>(table: &Table, dependencies: &str, predicate: &mut P) -> Option<Package>
where
P: FnMut(&str, &str) -> bool,
{
table.values().find_map(|table| {
let table = table.as_table()?;
find_inner(table, dependencies, predicate)
.or_else(|| find_target(table, dependencies, predicate))
})
}
dependencies
.as_slice()
.iter()
.find_map(|dependencies| find_inner(manifest, dependencies, &mut predicate))
.or_else(|| {
dependencies.as_slice().iter().find_map(|dependencies| {
find_target(manifest.get("target")?.as_table()?, dependencies, &mut predicate)
})
})
}
fn find_from_dependencies<P>(table: &Table, mut predicate: P) -> Option<Package>
where
P: FnMut(&str, &str) -> bool,
{
fn package<P>(value: &Value, version: &str, predicate: P) -> Option<String>
where
P: FnOnce(&str, &str) -> bool,
{
value
.as_table()?
.get("package")?
.as_str()
.and_then(|name| if predicate(name, version) { Some(name.to_owned()) } else { None })
}
fn version(value: &Value) -> Option<&str> {
value.as_str().or_else(|| value.as_table()?.get("version")?.as_str())
}
table.iter().find_map(|(key, value)| {
let version = version(value).unwrap_or("*");
let package = package(value, version, &mut predicate);
if package.is_some() || predicate(key, version) {
Some(Package {
key: key.clone(),
name: key.replace('-', "_"),
version: version.to_owned(),
package,
})
} else {
None
}
})
}