diff --git a/Cargo.lock b/Cargo.lock index 598565286..303219dab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4006,7 +4006,6 @@ dependencies = [ "assert_fs", "cairo-lang-filesystem", "camino", - "clap", "derive_builder", "semver", "serde", diff --git a/scarb-metadata/Cargo.toml b/scarb-metadata/Cargo.toml index 085db6994..a1deb56ab 100644 --- a/scarb-metadata/Cargo.toml +++ b/scarb-metadata/Cargo.toml @@ -14,7 +14,6 @@ repository.workspace = true [dependencies] camino.workspace = true -clap = { workspace = true, optional = true } derive_builder = { workspace = true, optional = true } semver.workspace = true serde.workspace = true @@ -30,4 +29,3 @@ snapbox.workspace = true default = ["command"] builder = ["dep:derive_builder"] command = ["dep:thiserror"] -packages_filter = ["dep:clap", "dep:thiserror"] diff --git a/scarb-metadata/src/lib.rs b/scarb-metadata/src/lib.rs index 6a194ecd9..6342f3ccd 100644 --- a/scarb-metadata/src/lib.rs +++ b/scarb-metadata/src/lib.rs @@ -28,17 +28,12 @@ use derive_builder::Builder; use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; -#[cfg(feature = "packages_filter")] -#[allow(deprecated)] -use crate::packages_filter::WithManifestPath; #[cfg(feature = "command")] pub use command::*; pub use version_pin::*; #[cfg(feature = "command")] mod command; -#[cfg(feature = "packages_filter")] -pub mod packages_filter; mod version_pin; /// An "opaque" identifier for a package. @@ -486,14 +481,6 @@ impl PackageMetadata { } } -#[cfg(feature = "packages_filter")] -#[allow(deprecated)] -impl WithManifestPath for PackageMetadata { - fn manifest_path(&self) -> &Utf8Path { - &self.manifest_path - } -} - impl TargetMetadata { /// Path to the main source directory of the target. pub fn source_root(&self) -> &Utf8Path { diff --git a/scarb-metadata/src/packages_filter.rs b/scarb-metadata/src/packages_filter.rs deleted file mode 100644 index d50fc3ed9..000000000 --- a/scarb-metadata/src/packages_filter.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! [`clap`] arguments implementing Scarb-compatible package selection (`-p` flag etc.) - -#![deprecated( - since = "1.7.0", - note = "This module has been moved to `scarb-ui` crate hosted in Scarb repository. \ - Removal from `scarb-metadata` is planned in when no usage will be present in open source projects." -)] - -use camino::{Utf8Path, Utf8PathBuf}; - -use crate::{Metadata, PackageMetadata}; - -/// [`clap`] structured arguments that provide package selection. -/// -/// ## Usage -/// -/// ```no_run -/// # use scarb_metadata::packages_filter::PackagesFilter; -/// #[derive(clap::Parser)] -/// struct Args { -/// #[command(flatten)] -/// packages_filter: PackagesFilter, -/// } -/// ``` -#[derive(clap::Parser, Clone, Debug)] -pub struct PackagesFilter { - /// Packages to run this command on, can be a concrete package name (`foobar`) or - /// a prefix glob (`foo*`). - #[arg(short, long, value_name = "SPEC", default_value = "*")] - package: String, - /// Run for all packages in the workspace. - #[arg(short, long, conflicts_with = "package")] - workspace: bool, -} - -/// Error type returned from [`PackagesFilter::match_one`] and [`PackagesFilter::match_many`] -/// functions. -/// -/// Its internal structure is unspecified, but stringified messages convey meaningful information -/// to application users. -#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)] -#[error(transparent)] -pub struct Error(#[from] InnerError); - -#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)] -enum InnerError { - // Matching errors. - #[error("package `{package_name}` not found in workspace")] - OneNotFound { package_name: String }, - #[error("no workspace members match `{spec}`")] - ManyNotFound { spec: String }, - #[error("workspace has no members")] - WorkspaceHasNoMembers, - #[error("could not determine which package to work on. Use the `--package` option to specify the package.")] - CouldNotDeterminePackageToWorkOn, - #[error("workspace has multiple members matching `{spec}`. Use the `--package` option to specify single package.")] - FoundMultiple { spec: String }, - - // Spec parsing errors. - #[error("invalid package spec: * character can only occur once in the pattern")] - MultipleStars, - #[error("invalid package spec: only `prefix*` patterns are allowed")] - NotPrefix, -} - -impl InnerError { - fn not_found(spec: &Spec<'_>) -> Self { - match spec { - Spec::One(package_name) => Self::OneNotFound { - package_name: package_name.to_string(), - }, - spec @ (Spec::All | Spec::Glob(_)) => Self::ManyNotFound { - spec: spec.to_string(), - }, - } - } -} - -impl PackagesFilter { - /// Find *exactly one* package matching the filter. - /// - /// Returns an error if no or more than one packages were found. - pub fn match_one(&self, source: &S) -> Result { - let spec = Spec::parse(&self.package)?; - - // Check for current package. - // If none (in case of virtual workspace), run for all members. - if self.current_selected(&spec) { - if let Some(pkg) = self.current_package(source)? { - return Ok(pkg); - } - } - - let members = source.members(); - if (self.workspace || matches!(spec, Spec::All)) && members.len() > 1 { - return Err(InnerError::CouldNotDeterminePackageToWorkOn.into()); - } - - let found = Self::do_match::(&spec, self.workspace, members.into_iter())?; - - if found.len() > 1 { - return Err(InnerError::FoundMultiple { - spec: spec.to_string(), - } - .into()); - } - - Ok(found.into_iter().next().unwrap()) - } - - /// Find *at least one* package matching the filter. - /// - /// Returns an error if no packages were found. - pub fn match_many(&self, source: &S) -> Result, Error> { - let spec = Spec::parse(&self.package)?; - - // Check for current package. - // If none (in case of virtual workspace), run for all members. - if self.current_selected(&spec) { - if let Some(pkg) = self.current_package(source)? { - return Ok(vec![pkg]); - } - } - - let members = source.members(); - Self::do_match::(&spec, self.workspace, members.into_iter()) - } - - fn current_package(&self, source: &S) -> Result, Error> { - Ok(source - .members() - .iter() - .find(|m| m.manifest_path() == source.runtime_manifest()) - .cloned()) - } - - fn current_selected(&self, spec: &Spec<'_>) -> bool { - !self.workspace && matches!(spec, Spec::All) - } - - fn do_match( - spec: &Spec<'_>, - workspace: bool, - members: impl Iterator, - ) -> Result, Error> { - let mut members = members.peekable(); - - if members.peek().is_none() { - return Err(InnerError::WorkspaceHasNoMembers.into()); - } - - let matches = if workspace { - members.collect::>() - } else { - members - .filter(|pkg| spec.matches(S::package_name_of(pkg))) - .collect::>() - }; - - if matches.is_empty() { - return Err(InnerError::not_found(spec).into()); - } - - Ok(matches) - } -} - -enum Spec<'a> { - All, - One(&'a str), - Glob(&'a str), -} - -impl<'a> Spec<'a> { - fn parse(string: &'a str) -> Result { - let string = string.trim(); - - if !string.contains('*') { - return Ok(Self::One(string)); - } - - if string.chars().filter(|c| *c == '*').count() != 1 { - return Err(InnerError::MultipleStars); - } - - if !string.ends_with('*') { - return Err(InnerError::NotPrefix); - } - - let string = string.trim_end_matches('*'); - - if string.is_empty() { - Ok(Self::All) - } else { - Ok(Self::Glob(string)) - } - } - - fn matches(&self, name: &str) -> bool { - match self { - Spec::All => true, - Spec::One(pat) => name == *pat, - Spec::Glob(pat) => name.starts_with(pat), - } - } -} - -impl<'a> ToString for Spec<'a> { - fn to_string(&self) -> String { - match self { - Spec::All => "*".to_owned(), - Spec::One(name) => name.to_string(), - Spec::Glob(pat) => format!("{pat}*"), - } - } -} - -/// Generic interface used by [`PackagesSource`] to pull information from. -/// -/// This trait is Scarb's internal implementation detail, **do not implement for your own types**. -pub trait WithManifestPath { - #[doc(hidden)] - fn manifest_path(&self) -> &Utf8Path; -} - -/// Generic interface used by [`PackagesFilter`] to pull information from. -/// -/// This trait is Scarb's internal implementation detail, **do not implement for your own types**. -/// Inside Scarb there are implementations for Scarb's core types, which allows Scarb to re-use -/// [`PackagesFilter`] logic. -pub trait PackagesSource { - /// Type which represents a Scarb package in this source. - type Package: Clone + WithManifestPath; - - #[doc(hidden)] - fn package_name_of(package: &Self::Package) -> &str; - - #[doc(hidden)] - fn members(&self) -> Vec; - - #[doc(hidden)] - fn runtime_manifest(&self) -> Utf8PathBuf; -} - -impl PackagesSource for Metadata { - type Package = PackageMetadata; - - #[inline(always)] - fn package_name_of(package: &Self::Package) -> &str { - &package.name - } - - #[inline(always)] - fn members(&self) -> Vec { - self.packages - .iter() - .filter(|pkg| self.workspace.members.contains(&pkg.id)) - .cloned() - .collect() - } - - fn runtime_manifest(&self) -> Utf8PathBuf { - self.runtime_manifest.clone() - } -} diff --git a/scarb/Cargo.toml b/scarb/Cargo.toml index 1f5171fb3..7caa7e4a7 100644 --- a/scarb/Cargo.toml +++ b/scarb/Cargo.toml @@ -46,7 +46,7 @@ once_cell.workspace = true pathdiff.workspace = true petgraph.workspace = true scarb-build-metadata = { path = "../utils/scarb-build-metadata" } -scarb-metadata = { path = "../scarb-metadata", default-features = false, features = ["builder", "packages_filter"] } +scarb-metadata = { path = "../scarb-metadata", default-features = false, features = ["builder"] } scarb-ui = { path = "../utils/scarb-ui" } semver.workspace = true serde-value.workspace = true