1use std::fmt;
2
3use strum::{AsRefStr, Display, EnumIter, EnumString, VariantNames};
4
5use crate::pkg::Restrict as PkgRestrict;
6use crate::restrict::dep::Restrict as DepRestrict;
7use crate::types::Deque;
8
9pub(crate) mod boolean;
10pub mod dep;
11pub mod depset;
12pub mod ordered;
13pub mod parse;
14pub mod set;
15pub mod str;
16
17boolean::restrict_with_boolean! {Restrict,
18 True,
20 False,
21
22 Dep(DepRestrict),
24 Pkg(PkgRestrict),
25
26 Str(str::Restrict),
28}
29
30#[allow(clippy::derivable_impls)]
31impl Default for Restrict {
32 fn default() -> Self {
33 Self::False
34 }
35}
36
37impl From<&Restrict> for Restrict {
38 fn from(r: &Restrict) -> Self {
39 r.clone()
40 }
41}
42
43pub trait TryIntoRestrict<C> {
44 fn try_into_restrict(self, context: &C) -> crate::Result<Restrict>;
45}
46
47impl<C> TryIntoRestrict<C> for &str {
48 fn try_into_restrict(self, _context: &C) -> crate::Result<Restrict> {
49 parse::dep(self)
50 }
51}
52
53impl<C, T: Into<Restrict>> TryIntoRestrict<C> for T {
54 fn try_into_restrict(self, _context: &C) -> crate::Result<Restrict> {
55 Ok(self.into())
56 }
57}
58
59impl Restrict {
60 pub fn iter_flatten(&self) -> IterFlatten<'_> {
62 IterFlatten([self].into_iter().collect())
63 }
64}
65
66#[derive(Debug)]
67pub struct IterFlatten<'a>(Deque<&'a Restrict>);
68
69impl<'a> Iterator for IterFlatten<'a> {
70 type Item = &'a Restrict;
71
72 fn next(&mut self) -> Option<Self::Item> {
73 while let Some(restrict) = self.0.pop_front() {
74 match restrict {
75 Restrict::And(vals) => self.0.extend_left(vals.iter().map(AsRef::as_ref)),
76 Restrict::Or(vals) => self.0.extend_left(vals.iter().map(AsRef::as_ref)),
77 Restrict::Xor(vals) => self.0.extend_left(vals.iter().map(AsRef::as_ref)),
78 _ => return Some(restrict),
79 }
80 }
81 None
82 }
83}
84
85macro_rules! restrict_match {
86 ($r:expr, $obj:expr, $($matcher:pat $(if $pred:expr)* => $result:expr,)+) => {
87 match $r {
88 $($matcher $(if $pred)* => $result,)+
89
90 Self::True => true,
92 Self::False => false,
93
94 Self::And(vals) => vals.iter().all(|r| r.matches($obj)),
96 Self::Or(vals) => vals.iter().any(|r| r.matches($obj)),
97 Self::Xor(vals) => {
98 let mut curr: Option<bool>;
99 let mut prev: Option<bool> = None;
100 for r in vals {
101 curr = Some(r.matches($obj));
102 if prev.is_some() && curr != prev {
103 return true;
104 }
105 prev = curr
106 }
107 false
108 },
109 Self::Not(r) => !r.matches($obj),
110
111 _ => {
112 tracing::warn!("invalid restriction {:?} for matching {:?}", $r, $obj);
113 false
114 }
115 }
116 }
117}
118pub(crate) use restrict_match;
119
120impl Restrict {
121 boolean::restrict_impl_boolean! {Self}
122}
123
124boolean::restrict_ops_boolean!(Restrict);
125
126pub trait Restriction<T>: fmt::Debug {
127 fn matches(&self, object: T) -> bool;
128}
129
130impl Restriction<&String> for Restrict {
131 fn matches(&self, s: &String) -> bool {
132 restrict_match! {self, s,
133 Self::Dep(r) => r.matches(s.as_str()),
134 Self::Str(r) => r.matches(s.as_str()),
135 }
136 }
137}
138
139impl Restriction<&str> for Restrict {
140 fn matches(&self, s: &str) -> bool {
141 restrict_match! {self, s,
142 Self::Dep(r) => r.matches(s),
143 Self::Str(r) => r.matches(s),
144 }
145 }
146}
147
148#[derive(
150 AsRefStr,
151 Display,
152 EnumIter,
153 EnumString,
154 VariantNames,
155 Debug,
156 PartialEq,
157 Eq,
158 PartialOrd,
159 Ord,
160 Hash,
161 Copy,
162 Clone,
163)]
164#[strum(serialize_all = "kebab-case")]
165pub enum Scope {
166 Version,
168 Package,
170 Category,
172 Repo,
174}
175
176impl From<&Restrict> for Scope {
177 fn from(value: &Restrict) -> Self {
178 use dep::Restrict::{Category, Package, Version};
179 use str::Restrict::Equal;
180
181 let restrict_scope = |restrict: &Restrict| match restrict {
182 Restrict::Dep(Version(Some(_))) => Scope::Version,
183 Restrict::Dep(Package(Equal(_))) => Scope::Package,
184 Restrict::Dep(Package(_)) => Scope::Category,
185 Restrict::Dep(Category(_)) => Scope::Category,
186 _ => Scope::Repo,
187 };
188
189 match value {
190 Restrict::And(_) => value
191 .iter_flatten()
192 .map(restrict_scope)
193 .min()
194 .unwrap_or(Scope::Repo),
195 Restrict::Or(_) => value
196 .iter_flatten()
197 .map(restrict_scope)
198 .max()
199 .unwrap_or(Scope::Repo),
200 _ => restrict_scope(value),
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::dep::Dep;
208
209 use super::*;
210
211 #[test]
212 fn filtering() {
213 let dep_strs = vec!["cat/pkg", ">=cat/pkg-1", "=cat/pkg-1:2/3::repo"];
214 let deps: Vec<_> = dep_strs.iter().map(|s| s.parse().unwrap()).collect();
215
216 let filter = |r: Restrict, deps: Vec<Dep>| -> Vec<String> {
217 deps.into_iter()
218 .filter(|a| r.matches(a))
219 .map(|a| a.to_string())
220 .collect()
221 };
222
223 let r = Restrict::Dep(dep::Restrict::category("cat"));
224 assert_eq!(filter(r, deps.clone()), dep_strs);
225
226 let r = Restrict::Dep(dep::Restrict::Version(None));
227 assert_eq!(filter(r, deps.clone()), ["cat/pkg"]);
228
229 let dep = Dep::try_new("=cat/pkg-1").unwrap();
230 let r = Restrict::from(&dep);
231 assert_eq!(filter(r, deps.clone()), [">=cat/pkg-1", "=cat/pkg-1:2/3::repo"]);
232
233 let r = Restrict::True;
234 assert_eq!(filter(r, deps.clone()), dep_strs);
235
236 let r = Restrict::False;
237 assert!(filter(r, deps).is_empty());
238 }
239}