uv_configuration/
prerelease.rs1use std::ops::{Deref, DerefMut};
2use std::str::FromStr;
3
4use rustc_hash::FxHashMap;
5use uv_normalize::PackageName;
6use uv_pep440::{Operator, VersionSpecifier};
7
8pub fn specifier_opts_into_prereleases(specifier: &VersionSpecifier) -> bool {
12 match specifier.operator() {
13 Operator::NotEqual | Operator::NotEqualStar => false,
14 Operator::Equal
15 | Operator::EqualStar
16 | Operator::ExactEqual
17 | Operator::TildeEqual
18 | Operator::LessThan
19 | Operator::LessThanEqual
20 | Operator::GreaterThan
21 | Operator::GreaterThanEqual => specifier.any_prerelease(),
22 }
23}
24
25#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
26#[serde(deny_unknown_fields, rename_all = "kebab-case")]
27#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
28#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
29pub enum PrereleaseMode {
30 Disallow,
32
33 Allow,
35
36 #[default]
38 IfNecessary,
39
40 Explicit,
44
45 #[deprecated(note = "use `if-necessary` instead")]
47 IfNecessaryOrExplicit,
48}
49
50#[allow(deprecated)]
51impl std::fmt::Display for PrereleaseMode {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 Self::Disallow => write!(f, "disallow"),
55 Self::Allow => write!(f, "allow"),
56 Self::IfNecessary => write!(f, "if-necessary"),
57 Self::Explicit => write!(f, "explicit"),
58 Self::IfNecessaryOrExplicit => write!(f, "if-necessary-or-explicit"),
59 }
60 }
61}
62
63impl FromStr for PrereleaseMode {
64 type Err = String;
65
66 fn from_str(value: &str) -> Result<Self, Self::Err> {
67 match value {
68 "disallow" => Ok(Self::Disallow),
69 "allow" => Ok(Self::Allow),
70 "if-necessary" => Ok(Self::IfNecessary),
71 "explicit" => Ok(Self::Explicit),
72 #[allow(deprecated)]
73 "if-necessary-or-explicit" => Ok(Self::IfNecessaryOrExplicit),
74 _ => Err(format!(
75 "expected one of `disallow`, `allow`, `if-necessary`, `explicit`, or `if-necessary-or-explicit`, found `{value}`"
76 )),
77 }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
84pub struct PrereleasePackageEntry {
85 package: PackageName,
86 mode: PrereleaseMode,
87}
88
89impl FromStr for PrereleasePackageEntry {
90 type Err = String;
91
92 fn from_str(value: &str) -> Result<Self, Self::Err> {
93 let Some((package, mode)) = value.split_once('=') else {
94 return Err(format!(
95 "Invalid `prerelease-package` value `{value}`: expected format `PACKAGE=MODE`"
96 ));
97 };
98
99 let package = PackageName::from_str(package).map_err(|err| {
100 format!("Invalid `prerelease-package` package name `{package}`: {err}")
101 })?;
102 let mode = PrereleaseMode::from_str(mode)
103 .map_err(|err| format!("Invalid `prerelease-package` mode: {err}"))?;
104
105 Ok(Self { package, mode })
106 }
107}
108
109#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
111#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
112pub struct PrereleasePackage(FxHashMap<PackageName, PrereleaseMode>);
113
114impl Deref for PrereleasePackage {
115 type Target = FxHashMap<PackageName, PrereleaseMode>;
116
117 fn deref(&self) -> &Self::Target {
118 &self.0
119 }
120}
121
122impl DerefMut for PrereleasePackage {
123 fn deref_mut(&mut self) -> &mut Self::Target {
124 &mut self.0
125 }
126}
127
128impl FromIterator<PrereleasePackageEntry> for PrereleasePackage {
129 fn from_iter<T: IntoIterator<Item = PrereleasePackageEntry>>(iter: T) -> Self {
130 Self(
131 iter.into_iter()
132 .map(|entry| (entry.package, entry.mode))
133 .collect(),
134 )
135 }
136}
137
138impl IntoIterator for PrereleasePackage {
139 type Item = (PackageName, PrereleaseMode);
140 type IntoIter = std::collections::hash_map::IntoIter<PackageName, PrereleaseMode>;
141
142 fn into_iter(self) -> Self::IntoIter {
143 self.0.into_iter()
144 }
145}
146
147impl<'a> IntoIterator for &'a PrereleasePackage {
148 type Item = (&'a PackageName, &'a PrereleaseMode);
149 type IntoIter = std::collections::hash_map::Iter<'a, PackageName, PrereleaseMode>;
150
151 fn into_iter(self) -> Self::IntoIter {
152 self.0.iter()
153 }
154}
155
156impl PrereleasePackage {
157 pub fn is_empty(&self) -> bool {
159 self.0.is_empty()
160 }
161}
162
163#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
165#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
166pub struct Prerelease {
167 pub global: PrereleaseMode,
169 pub package: PrereleasePackage,
171}
172
173impl Prerelease {
174 pub fn mode(&self, package: &PackageName) -> PrereleaseMode {
176 self.package.get(package).copied().unwrap_or(self.global)
177 }
178}