pkgcraft/
restrict.rs

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    // constants
19    True,
20    False,
21
22    // object attributes
23    Dep(DepRestrict),
24    Pkg(PkgRestrict),
25
26    // strings
27    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    /// Flatten a restriction, returning an iterator of its component restrictions.
61    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            // boolean
91            Self::True => true,
92            Self::False => false,
93
94            // boolean combinations
95            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/// Defines the scope for restriction matches.
149#[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    /// Single package version target.
167    Version,
168    /// Single unversioned package target.
169    Package,
170    /// Multiple unversioned package or category targets.
171    Category,
172    /// Full repo target.
173    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}