use std::{collections::BTreeMap, fmt};
use cargo_metadata::TargetKind;
use globset::{Glob, GlobMatcher};
use rustc_hash::FxHashSet;
use serde::{Deserialize, Deserializer};
use toml::Spanned;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FeatureRef {
Implicit,
Explicit { feature: Spanned<String>, value: Spanned<String> },
DepFeature { feature: Spanned<String>, value: Spanned<String> },
WeakDepFeature { feature: Spanned<String>, value: Spanned<String> },
}
impl FeatureRef {
pub fn parse(feature: &Spanned<String>, value: &Spanned<String>) -> (String, Self) {
if let Some(dep) = value.as_ref().strip_prefix("dep:") {
let import = dep.replace('-', "_");
return (import, Self::Explicit { feature: feature.clone(), value: value.clone() });
}
if let Some((dep, _)) = value.as_ref().split_once('/') {
let is_weak = dep.ends_with('?');
let dep = dep.trim_end_matches('?');
let import = dep.replace('-', "_");
let feature = if is_weak {
Self::WeakDepFeature { feature: feature.clone(), value: value.clone() }
} else {
Self::DepFeature { feature: feature.clone(), value: value.clone() }
};
return (import, feature);
}
let import = value.as_ref().replace('-', "_");
(import, Self::Explicit { feature: feature.clone(), value: value.clone() })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DepTable {
Normal,
Dev,
Build,
}
impl From<&TargetKind> for DepTable {
fn from(kind: &TargetKind) -> Self {
match kind {
TargetKind::CustomBuild => Self::Build,
TargetKind::Test | TargetKind::Bench | TargetKind::Example => Self::Dev,
TargetKind::Bin
| TargetKind::CDyLib
| TargetKind::DyLib
| TargetKind::Lib
| TargetKind::ProcMacro
| TargetKind::RLib
| TargetKind::StaticLib
| TargetKind::Unknown(_)
| _ => Self::Normal,
}
}
}
impl fmt::Display for DepTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Normal => f.write_str("dependencies"),
Self::Dev => f.write_str("dev-dependencies"),
Self::Build => f.write_str("build-dependencies"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DepLocation {
Root(DepTable),
Target { cfg: String, table: DepTable },
}
impl DepLocation {
#[must_use]
pub const fn is_normal(&self) -> bool {
matches!(self, Self::Root(DepTable::Normal) | Self::Target { table: DepTable::Normal, .. })
}
#[must_use]
pub fn as_table(&self, new: DepTable) -> Self {
match self {
Self::Root(_) => Self::Root(new),
Self::Target { cfg, .. } => Self::Target { cfg: cfg.clone(), table: new },
}
}
}
impl fmt::Display for DepLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Root(table) => write!(f, "[{table}]"),
Self::Target { cfg, table } => write!(f, "[target.'{cfg}'.{table}]"),
}
}
}
#[derive(Debug, Deserialize, Clone)]
#[serde(untagged)]
pub enum Dependency {
#[expect(unused, reason = "Needed for deserialization")]
Simple(String),
Detailed(DependencyDetail),
}
#[derive(Debug, Deserialize, Clone, Default)]
pub struct DependencyDetail {
pub package: Option<String>,
#[serde(default)]
pub optional: bool,
}
impl Dependency {
#[must_use]
pub fn package(&self) -> Option<&str> {
match self {
Self::Simple(_) => None,
Self::Detailed(detail) => detail.package.as_deref(),
}
}
#[must_use]
pub const fn optional(&self) -> bool {
match self {
Self::Simple(_) => false,
Self::Detailed(detail) => detail.optional,
}
}
}
pub type DepsSet = BTreeMap<Spanned<String>, Spanned<Dependency>>;
#[derive(Debug, Deserialize)]
pub struct Target {
#[serde(default)]
pub dependencies: DepsSet,
#[serde(default, rename = "dev-dependencies")]
pub dev_dependencies: DepsSet,
#[serde(default, rename = "build-dependencies")]
pub build_dependencies: DepsSet,
}
#[derive(Deserialize, Default)]
pub struct ShearConfig {
#[serde(default)]
pub ignored: FxHashSet<Spanned<String>>,
#[serde(default, rename = "ignored-paths", deserialize_with = "deserialize_glob_matchers")]
pub ignored_paths: Vec<GlobMatcher>,
}
fn deserialize_glob_matchers<'de, D>(deserializer: D) -> Result<Vec<GlobMatcher>, D::Error>
where
D: Deserializer<'de>,
{
let globs: Vec<Glob> = Vec::deserialize(deserializer)?;
Ok(globs.into_iter().map(|glob| glob.compile_matcher()).collect())
}
#[derive(Deserialize, Default)]
pub struct Metadata {
#[serde(default, rename = "cargo-shear")]
pub cargo_shear: ShearConfig,
}
#[derive(Deserialize, Default)]
pub struct Package {
#[serde(default)]
pub metadata: Metadata,
}
#[derive(Deserialize, Default)]
pub struct Workspace {
#[serde(default)]
pub metadata: Metadata,
#[serde(default)]
pub dependencies: DepsSet,
}
#[derive(Deserialize)]
pub struct Manifest {
#[serde(default)]
pub workspace: Workspace,
#[serde(default)]
pub package: Package,
#[serde(default)]
pub dependencies: DepsSet,
#[serde(default, rename = "dev-dependencies")]
pub dev_dependencies: DepsSet,
#[serde(default, rename = "build-dependencies")]
pub build_dependencies: DepsSet,
#[serde(default)]
pub target: BTreeMap<String, Target>,
#[serde(default)]
pub features: BTreeMap<Spanned<String>, Vec<Spanned<String>>>,
}
impl Manifest {
pub fn all_dependencies(
&self,
) -> impl Iterator<Item = (&Spanned<String>, &Spanned<Dependency>, DepLocation)> {
let dependencies = self
.dependencies
.iter()
.map(|(key, value)| (key, value, DepLocation::Root(DepTable::Normal)));
let dev_dependencies = self
.dev_dependencies
.iter()
.map(|(key, value)| (key, value, DepLocation::Root(DepTable::Dev)));
let build_dependencies = self
.build_dependencies
.iter()
.map(|(key, value)| (key, value, DepLocation::Root(DepTable::Build)));
let target_dependencies = self.target.iter().flat_map(|(cfg, target)| {
let location = |table| DepLocation::Target { cfg: cfg.clone(), table };
let target_dependencies = target
.dependencies
.iter()
.map(move |(key, value)| (key, value, location(DepTable::Normal)));
let target_dev_dependencies = target
.dev_dependencies
.iter()
.map(move |(key, value)| (key, value, location(DepTable::Dev)));
let target_build_dependencies = target
.build_dependencies
.iter()
.map(move |(key, value)| (key, value, location(DepTable::Build)));
target_dependencies.chain(target_dev_dependencies).chain(target_build_dependencies)
});
dependencies.chain(dev_dependencies).chain(build_dependencies).chain(target_dependencies)
}
}