anathema_value_resolver/
attributes.rs

1use std::borrow::Borrow;
2
3use anathema_store::slab::{Gen, SecondaryMap};
4use anathema_store::smallmap::SmallIndex;
5
6use crate::ValueKind;
7use crate::expression::ValueExpr;
8use crate::value::{Value, Values};
9
10type WidgetId = anathema_store::slab::Key;
11
12#[derive(Debug, Copy, Clone, PartialEq, Default)]
13pub enum ValueKey<'bp> {
14    #[default]
15    Value,
16    Attribute(&'bp str),
17}
18
19impl ValueKey<'_> {
20    pub fn as_str(&self) -> &str {
21        match self {
22            ValueKey::Value => "[value]",
23            ValueKey::Attribute(name) => name,
24        }
25    }
26}
27
28impl Borrow<str> for ValueKey<'_> {
29    fn borrow(&self) -> &str {
30        self.as_str()
31    }
32}
33
34// -----------------------------------------------------------------------------
35//   - Attribute storage -
36// -----------------------------------------------------------------------------
37#[derive(Debug)]
38pub struct AttributeStorage<'bp>(SecondaryMap<WidgetId, (Gen, Attributes<'bp>)>);
39
40impl<'bp> AttributeStorage<'bp> {
41    pub fn empty() -> Self {
42        Self(SecondaryMap::empty())
43    }
44
45    /// Get a reference to attributes by widget id
46    pub fn get(&self, id: WidgetId) -> &Attributes<'bp> {
47        self.0.get(id).map(|(_, a)| a).expect("every element has attributes")
48    }
49
50    /// Get a reference to attributes by widget id
51    pub fn try_get(&self, id: WidgetId) -> Option<&Attributes<'bp>> {
52        self.0.get(id).map(|(_, a)| a)
53    }
54
55    /// Get a mutable reference to attributes by widget id
56    pub fn get_mut(&mut self, id: WidgetId) -> &mut Attributes<'bp> {
57        self.0
58            .get_mut(id)
59            .map(|(_, a)| a)
60            .expect("every element has attributes")
61    }
62
63    pub fn with_mut<F, O>(&mut self, widget_id: WidgetId, f: F) -> Option<O>
64    where
65        F: FnOnce(&mut Attributes<'bp>, &mut Self) -> O,
66    {
67        let mut value = self.try_remove(widget_id)?;
68        let output = f(&mut value, self);
69        self.insert(widget_id, value);
70        Some(output)
71    }
72
73    /// Insert attributes for a given widget.
74    ///
75    /// This will overwrite any existing attributes at that location
76    pub fn insert(&mut self, widget_id: WidgetId, attribs: Attributes<'bp>) {
77        self.0.insert(widget_id, (widget_id.generation(), attribs))
78    }
79
80    /// Try to remove attributes for a specific widget
81    pub fn try_remove(&mut self, id: WidgetId) -> Option<Attributes<'bp>> {
82        self.0
83            .remove_if(id, |(current_gen, _)| *current_gen == id.generation())
84            .map(|(_, value)| value)
85    }
86}
87
88// -----------------------------------------------------------------------------
89//   - Attributes -
90// -----------------------------------------------------------------------------
91
92#[derive(Debug)]
93pub struct Attributes<'bp> {
94    pub(crate) attribs: Values<'bp>,
95    pub value: Option<SmallIndex>,
96}
97
98// TODO
99// Only get, set and remove should be part of the interface
100// that is exposed to the end user.
101//
102// The rest is for widget creation and should be moved to its own type.
103
104impl<'bp> Attributes<'bp> {
105    /// Create an empty set of attributes
106    pub fn empty() -> Self {
107        Self {
108            attribs: Values::empty(),
109            value: None,
110        }
111    }
112
113    /// Set an attribute value.
114    /// ```
115    /// # use anathema_value_resolver::{Attributes, ValueKind};
116    ///
117    /// let mut attributes = Attributes::empty();
118    /// attributes.set("name", "Nonsense");
119    /// attributes.get_as::<&str>("name").unwrap();
120    /// ```
121    pub fn set(&mut self, key: &'bp str, value: impl Into<ValueKind<'bp>>) {
122        let key = ValueKey::Attribute(key);
123        let value = value.into();
124        let value = Value {
125            expr: ValueExpr::Null,
126            kind: value,
127            sub: anathema_state::Subscriber::MAX,
128            sub_to: anathema_state::SubTo::Zero,
129        };
130
131        self.attribs.set(key, value);
132    }
133
134    #[doc(hidden)]
135    pub fn insert_with<F>(&mut self, key: ValueKey<'bp>, f: F) -> SmallIndex
136    where
137        F: FnMut(SmallIndex) -> Value<'bp>,
138    {
139        self.attribs.insert_with(key, f)
140    }
141
142    /// Remove a value from attributes
143    pub fn remove(&mut self, key: &'bp str) -> Option<Value<'bp>> {
144        let key = ValueKey::Attribute(key);
145        self.attribs.remove(&key)
146    }
147
148    /// Get the `Value` out of attributes.
149    /// This is always the first item
150    pub fn value(&self) -> Option<&ValueKind<'bp>> {
151        let idx = self.value?;
152        self.attribs.get_with_index(idx).map(|val| &val.kind)
153    }
154
155    pub fn get(&self, key: &str) -> Option<&ValueKind<'bp>> {
156        self.attribs.get(key).map(|val| &val.kind)
157    }
158
159    /// Get a value as a given type.
160    /// If the value doesn't exist or can not be cast to the
161    /// expected type `None` is returned.
162    /// ```
163    /// # use anathema_value_resolver::{Attributes, ValueKind};
164    ///
165    /// let mut attributes = Attributes::empty();
166    /// attributes.set("num", 123);
167    ///
168    /// assert_eq!(attributes.get_as::<u32>("num").unwrap(), 123);
169    /// assert_eq!(attributes.get_as::<i16>("num").unwrap(), 123);
170    /// ```
171    pub fn get_as<'a, T>(&'a self, key: &str) -> Option<T>
172    where
173        T: TryFrom<&'a ValueKind<'bp>>,
174    {
175        self.attribs.get(key).and_then(|val| (&val.kind).try_into().ok())
176    }
177
178    /// Iterate over values of a given type
179    /// ```
180    /// # use anathema_value_resolver::{Attributes, ValueKind};
181    ///
182    /// let mut attributes = Attributes::empty();
183    /// let values =
184    ///     ValueKind::List([ValueKind::Int(1), ValueKind::Bool(true), ValueKind::Int(2)].into());
185    /// attributes.set("mixed_list", values);
186    ///
187    /// let iter = attributes.iter_as::<u32>("mixed_list");
188    /// assert_eq!(vec![1u32, 2], iter.collect::<Vec<_>>());
189    /// ```
190    pub fn iter_as<'a, T>(&'a self, key: &str) -> impl Iterator<Item = T>
191    where
192        T: TryFrom<&'a ValueKind<'bp>>,
193    {
194        self.attribs
195            .get(key)
196            .and_then(|val| match &val.kind {
197                ValueKind::List(value_kinds) => {
198                    let list = value_kinds.iter().filter_map(|v| T::try_from(v).ok());
199                    Some(list)
200                }
201
202                _ => None,
203            })
204            .into_iter()
205            .flatten()
206    }
207
208    #[doc(hidden)]
209    /// This should only be used internally by the widgets
210    /// when updating a value.
211    pub fn get_mut_with_index(&mut self, index: SmallIndex) -> Option<&mut Value<'bp>> {
212        self.attribs.get_mut_with_index(index)
213    }
214
215    /// Iterate over attributes.
216    /// This will skip the value
217    pub fn iter(&self) -> impl Iterator<Item = (&ValueKey<'_>, &ValueKind<'bp>)> {
218        self.attribs.iter().filter_map(|(key, val)| match key {
219            ValueKey::Value => None,
220            ValueKey::Attribute(_) => Some((key, &val.kind)),
221        })
222    }
223
224    pub(super) fn get_value_expr(&self, key: &str) -> Option<ValueExpr<'bp>> {
225        let value = self.attribs.get(key)?;
226        Some(value.expr.clone())
227    }
228}
229
230#[cfg(test)]
231mod test {
232    use anathema_state::{Color, Hex};
233
234    use super::*;
235
236    fn attribs() -> Attributes<'static> {
237        let mut attributes = Attributes::empty();
238
239        let values = ValueKind::List([ValueKind::Int(1), ValueKind::Bool(true), ValueKind::Int(2)].into());
240        attributes.set("mixed_list", values);
241        attributes.set("num", 123);
242        attributes.set("static_str", "static");
243        attributes.set("string", String::from("string"));
244        attributes.set("hex", Hex::from((1, 2, 3)));
245        attributes.set("red", Color::Red);
246        attributes.set("float", 1.23);
247        attributes.set("bool", true);
248        attributes.set("char", 'a');
249
250        attributes
251    }
252
253    #[test]
254    fn iter_as_type() {
255        let attributes = attribs();
256
257        let values = attributes.iter_as::<u8>("mixed_list").collect::<Vec<_>>();
258        assert_eq!(vec![1, 2], values);
259
260        let values = attributes.iter_as::<bool>("mixed_list").collect::<Vec<_>>();
261        assert_eq!(vec![true], values);
262    }
263
264    #[test]
265    fn get_as_int() {
266        assert_eq!(123, attribs().get_as::<u8>("num").unwrap());
267        assert_eq!(123, attribs().get_as::<i8>("num").unwrap());
268        assert_eq!(123, attribs().get_as::<u16>("num").unwrap());
269        assert_eq!(123, attribs().get_as::<i16>("num").unwrap());
270        assert_eq!(123, attribs().get_as::<u32>("num").unwrap());
271        assert_eq!(123, attribs().get_as::<i32>("num").unwrap());
272        assert_eq!(123, attribs().get_as::<u64>("num").unwrap());
273        assert_eq!(123, attribs().get_as::<i64>("num").unwrap());
274        assert_eq!(123, attribs().get_as::<usize>("num").unwrap());
275        assert_eq!(123, attribs().get_as::<isize>("num").unwrap());
276    }
277
278    #[test]
279    fn get_as_strings() {
280        let attributes = attribs();
281        assert_eq!("static", attributes.get_as::<&str>("static_str").unwrap());
282        assert_eq!("string", attributes.get_as::<&str>("string").unwrap());
283    }
284
285    #[test]
286    fn get_as_hex() {
287        let attributes = attribs();
288        assert_eq!(Hex::from((1, 2, 3)), attributes.get_as::<Hex>("hex").unwrap());
289    }
290
291    #[test]
292    fn get_as_color() {
293        let attributes = attribs();
294        assert_eq!(Color::Red, attributes.get_as::<Color>("red").unwrap());
295    }
296
297    #[test]
298    fn get_as_float() {
299        let attributes = attribs();
300        assert_eq!(1.23, attributes.get_as::<f32>("float").unwrap());
301        assert_eq!(1.23, attributes.get_as::<f64>("float").unwrap());
302    }
303
304    #[test]
305    fn get_as_bool() {
306        let attributes = attribs();
307        assert!(attributes.get_as::<bool>("bool").unwrap());
308    }
309
310    #[test]
311    fn get_as_char() {
312        let attributes = attribs();
313        assert_eq!('a', attributes.get_as::<char>("char").unwrap());
314    }
315}