snix_eval/value/string/
context.rs

1use rustc_hash::FxHashSet;
2use serde::Serialize;
3
4use super::NixString;
5
6#[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)]
7pub enum NixContextElement {
8    /// A plain store path (e.g. source files copied to the store)
9    Plain(String),
10
11    /// Single output of a derivation, represented by its name and its derivation path.
12    Single { name: String, derivation: String },
13
14    /// A reference to a complete derivation
15    /// including its source and its binary closure.
16    /// It is used for the `drvPath` attribute context.
17    /// The referred string is the store path to
18    /// the derivation path.
19    Derivation(String),
20}
21
22/// Nix context strings representation. This tracks a set of different kinds of string
23/// dependencies that we can come across during manipulation of our language primitives, mostly
24/// strings. There's some simple algebra of context strings and how they propagate w.r.t. primitive
25/// operations, e.g. concatenation, interpolation and other string operations.
26#[repr(transparent)]
27#[derive(Clone, Debug, Serialize, Default)]
28pub struct NixContext(FxHashSet<NixContextElement>);
29
30impl From<NixContextElement> for NixContext {
31    fn from(value: NixContextElement) -> Self {
32        let mut set = FxHashSet::default();
33        set.insert(value);
34        Self(set)
35    }
36}
37
38impl From<FxHashSet<NixContextElement>> for NixContext {
39    fn from(value: FxHashSet<NixContextElement>) -> Self {
40        Self(value)
41    }
42}
43
44impl<const N: usize> From<[NixContextElement; N]> for NixContext {
45    fn from(value: [NixContextElement; N]) -> Self {
46        let mut set = FxHashSet::default();
47        for elt in value {
48            set.insert(elt);
49        }
50        Self(set)
51    }
52}
53
54impl NixContext {
55    /// Creates an empty context that can be populated
56    /// and passed to form a contextful [NixString], albeit
57    /// if the context is concretly empty, the resulting [NixString]
58    /// will be contextless.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// For internal consumers, we let people observe
64    /// if the [NixContext] is actually empty or not
65    /// to decide whether they want to skip the allocation
66    /// of a full blown [std::collections::HashSet].
67    pub(crate) fn is_empty(&self) -> bool {
68        self.0.is_empty()
69    }
70
71    /// Consumes a new [NixContextElement] and add it if not already
72    /// present in this context.
73    pub fn append(mut self, other: NixContextElement) -> Self {
74        self.0.insert(other);
75        self
76    }
77
78    /// Extends the existing context with more context elements.
79    pub fn extend<T>(&mut self, iter: T)
80    where
81        T: IntoIterator<Item = NixContextElement>,
82    {
83        self.0.extend(iter)
84    }
85
86    /// Copies from another [NixString] its context strings
87    /// in this context.
88    pub fn mimic(&mut self, other: &NixString) {
89        if let Some(context) = other.context() {
90            self.extend(context.iter().cloned());
91        }
92    }
93
94    /// Iterates over "plain" context elements, e.g. sources imported
95    /// in the store without more information, i.e. `toFile` or coerced imported paths.
96    /// It yields paths to the store.
97    pub fn iter_plain(&self) -> impl Iterator<Item = &str> {
98        self.iter().filter_map(|elt| {
99            if let NixContextElement::Plain(s) = elt {
100                Some(s.as_str())
101            } else {
102                None
103            }
104        })
105    }
106
107    /// Iterates over "full derivations" context elements, e.g. something
108    /// referring to their `drvPath`, i.e. their full sources and binary closure.
109    /// It yields derivation paths.
110    pub fn iter_derivation(&self) -> impl Iterator<Item = &str> {
111        self.iter().filter_map(|elt| {
112            if let NixContextElement::Derivation(s) = elt {
113                Some(s.as_str())
114            } else {
115                None
116            }
117        })
118    }
119
120    /// Iterates over "single" context elements, e.g. single derived paths,
121    /// or also known as the single output of a given derivation.
122    /// The first element of the tuple is the output name
123    /// and the second element is the derivation path.
124    pub fn iter_single_outputs(&self) -> impl Iterator<Item = (&str, &str)> {
125        self.iter().filter_map(|elt| {
126            if let NixContextElement::Single { name, derivation } = elt {
127                Some((name.as_str(), derivation.as_str()))
128            } else {
129                None
130            }
131        })
132    }
133
134    /// Iterates over any element of the context.
135    pub fn iter(&self) -> impl Iterator<Item = &NixContextElement> {
136        self.0.iter()
137    }
138
139    /// Produces a list of owned references to this current context,
140    /// no matter its type.
141    pub fn to_owned_references(self) -> Vec<String> {
142        self.0
143            .into_iter()
144            .map(|ctx| match ctx {
145                NixContextElement::Derivation(drv_path) => drv_path,
146                NixContextElement::Plain(store_path) => store_path,
147                NixContextElement::Single { derivation, .. } => derivation,
148            })
149            .collect()
150    }
151}
152
153impl IntoIterator for NixContext {
154    type Item = NixContextElement;
155
156    type IntoIter = std::collections::hash_set::IntoIter<NixContextElement>;
157
158    fn into_iter(self) -> Self::IntoIter {
159        self.0.into_iter()
160    }
161}