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}