uv_configuration/
name_specifiers.rs

1#[cfg(feature = "schemars")]
2use std::borrow::Cow;
3use std::str::FromStr;
4
5use uv_normalize::PackageName;
6
7/// A specifier used for (e.g.) pip's `--no-binary` flag.
8///
9/// This is a superset of the package name format, allowing for special values `:all:` and `:none:`.
10#[derive(Debug, Clone)]
11pub enum PackageNameSpecifier {
12    All,
13    None,
14    Package(PackageName),
15}
16
17impl FromStr for PackageNameSpecifier {
18    type Err = uv_normalize::InvalidNameError;
19
20    fn from_str(name: &str) -> Result<Self, Self::Err> {
21        match name {
22            ":all:" => Ok(Self::All),
23            ":none:" => Ok(Self::None),
24            _ => Ok(Self::Package(PackageName::from_str(name)?)),
25        }
26    }
27}
28
29impl<'de> serde::Deserialize<'de> for PackageNameSpecifier {
30    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
31    where
32        D: serde::Deserializer<'de>,
33    {
34        struct Visitor;
35
36        impl serde::de::Visitor<'_> for Visitor {
37            type Value = PackageNameSpecifier;
38
39            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
40                formatter.write_str("a package name or `:all:` or `:none:`")
41            }
42
43            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
44            where
45                E: serde::de::Error,
46            {
47                // Accept the special values `:all:` and `:none:`.
48                match value {
49                    ":all:" => Ok(PackageNameSpecifier::All),
50                    ":none:" => Ok(PackageNameSpecifier::None),
51                    _ => {
52                        // Otherwise, parse the value as a package name.
53                        match PackageName::from_str(value) {
54                            Ok(name) => Ok(PackageNameSpecifier::Package(name)),
55                            Err(err) => Err(E::custom(err)),
56                        }
57                    }
58                }
59            }
60        }
61
62        deserializer.deserialize_str(Visitor)
63    }
64}
65
66#[cfg(feature = "schemars")]
67impl schemars::JsonSchema for PackageNameSpecifier {
68    fn schema_name() -> Cow<'static, str> {
69        Cow::Borrowed("PackageNameSpecifier")
70    }
71
72    fn json_schema(_gen: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
73        schemars::json_schema!({
74            "type": "string",
75            "pattern": r"^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$",
76            "description": "The name of a package, or `:all:` or `:none:` to select or omit all packages, respectively.",
77        })
78    }
79}
80
81/// A repeated specifier used for (e.g.) pip's `--no-binary` flag.
82///
83/// This is a superset of the package name format, allowing for special values `:all:` and `:none:`.
84#[derive(Debug, Clone)]
85pub enum PackageNameSpecifiers {
86    All,
87    None,
88    Packages(Vec<PackageName>),
89}
90
91impl PackageNameSpecifiers {
92    pub(crate) fn from_iter(specifiers: impl Iterator<Item = PackageNameSpecifier>) -> Self {
93        let mut packages = Vec::new();
94        let mut all: bool = false;
95
96        for specifier in specifiers {
97            match specifier {
98                PackageNameSpecifier::None => {
99                    packages.clear();
100                    all = false;
101                }
102                PackageNameSpecifier::All => {
103                    all = true;
104                }
105                PackageNameSpecifier::Package(name) => {
106                    packages.push(name);
107                }
108            }
109        }
110
111        if all {
112            Self::All
113        } else if packages.is_empty() {
114            Self::None
115        } else {
116            Self::Packages(packages)
117        }
118    }
119}