stanza/
style.rs

1pub mod blink;
2pub mod bold;
3pub mod border_bg;
4pub mod border_fg;
5pub mod fill_bg;
6pub mod fill_invert;
7pub mod halign;
8pub mod header;
9pub mod italic;
10pub mod max_width;
11pub mod min_width;
12pub mod palette_16;
13pub mod separator;
14pub mod strikethrough;
15pub mod text_bg;
16pub mod text_fg;
17pub mod text_invert;
18pub mod underline;
19
20use alloc::borrow::Cow;
21use alloc::boxed::Box;
22use alloc::collections::btree_map::Iter;
23use alloc::collections::BTreeMap;
24use alloc::string::String;
25pub use blink::Blink;
26pub use bold::Bold;
27pub use border_bg::BorderBg;
28pub use border_fg::BorderFg;
29use core::any;
30use core::any::Any;
31pub use fill_bg::FillBg;
32pub use fill_invert::FillInvert;
33pub use halign::HAlign;
34pub use header::Header;
35pub use italic::Italic;
36pub use max_width::MaxWidth;
37pub use min_width::MinWidth;
38pub use palette_16::Palette16;
39pub use separator::Separator;
40pub use strikethrough::Strikethrough;
41pub use text_bg::TextBg;
42pub use text_fg::TextFg;
43pub use text_invert::TextInvert;
44pub use underline::Underline;
45
46mod private {
47    use alloc::boxed::Box;
48    use super::Style;
49    use core::any::Any;
50
51    /// An object-safe variant of [`Clone`].
52    pub trait Replica {
53        fn replicate(&self) -> Box<dyn Style>;
54    }
55
56    /// Blanket implementation of [`Replica`] for every [`Style`] that is also [`Clone`].
57    impl<C: Clone + Style> Replica for C {
58        fn replicate(&self) -> Box<dyn Style> {
59            Box::new(self.clone())
60        }
61    }
62
63    /// Conversion from an arbitrary trait to a [`&dyn Any`] for subsequent downcasting
64    /// (or other uses of the [`Any`] type).
65    pub trait Upcast {
66        /// Casts the current object to a [`&dyn Any`].
67        fn as_any_ref(&self) -> &dyn Any;
68    }
69
70    /// Upcasts any [`Style`] implementation to a [`&dyn Any`].
71    impl<U: Style> Upcast for U {
72        fn as_any_ref(&self) -> &dyn Any {
73            self
74        }
75    }
76}
77
78pub trait Style: Any + private::Replica + private::Upcast {
79    fn assignability(&self) -> Assignability;
80
81    fn id() -> Cow<'static, str>
82    where
83        Self: Sized,
84    {
85        Cow::Borrowed(any::type_name::<Self>())
86    }
87
88    fn resolve(styles: &Styles) -> Option<&Self>
89    where
90        Self: Sized,
91    {
92        let style_for_id = styles.get(&Self::id());
93        match style_for_id {
94            None => None,
95            Some(style) => style.as_any_ref().downcast_ref(),
96        }
97    }
98
99    fn resolve_or_default(styles: &Styles) -> Cow<Self>
100    where
101        Self: Default + Sized + Clone,
102    {
103        let style = Self::resolve(styles);
104        match style {
105            None => Cow::Owned(Self::default()),
106            Some(style) => Cow::Borrowed(style),
107        }
108    }
109}
110
111#[derive(Default)]
112pub struct Styles(BTreeMap<String, Box<dyn Style>>);
113
114impl Styles {
115    #[must_use]
116    pub fn with(mut self, style: impl Style) -> Self {
117        self.insert(style);
118        self
119    }
120
121    pub fn insert<S: Style>(&mut self, style: S) -> Option<Box<dyn Style>> {
122        self.0.insert(S::id().into(), Box::new(style))
123    }
124
125    #[must_use]
126    pub fn with_all(mut self, styles: &Styles) -> Self {
127        self.insert_all(styles);
128        self
129    }
130
131    pub fn insert_all(&mut self, styles: &Styles) {
132        for (key, style) in &styles.0 {
133            self.0.insert(key.into(), style.replicate());
134        }
135    }
136
137    pub fn get(&self, key: &str) -> Option<&dyn Style> {
138        self.0.get(key).map(|style| &**style)
139    }
140
141    pub fn take(&mut self, key: &str) -> Option<Box<dyn Style>> {
142        self.0.remove(key)
143    }
144
145    /// Verifies the assignability of all styles by evaluating the given `check` predicate, panicking
146    /// if the predicate evaluates to `false` for any style. The type `S` is used purely for generating the
147    /// panic message.
148    ///
149    /// # Panics
150    /// If one of the styles is assignment-incompatible according to the predicate.
151    pub fn assert_assignability<S>(&self, mut check: impl FnMut(Assignability) -> bool) {
152        for entry in self {
153            assert!(
154                check(entry.1.assignability()),
155                "cannot assign style {} to a {}",
156                entry.0,
157                any::type_name::<S>()
158            );
159        }
160    }
161}
162
163impl Clone for Styles {
164    fn clone(&self) -> Self {
165        let mut clone = BTreeMap::new();
166        for (key, style) in &self.0 {
167            clone.insert(key.clone(), style.replicate());
168        }
169
170        Self(clone)
171    }
172}
173
174impl<'a> IntoIterator for &'a Styles {
175    type Item = (&'a String, &'a Box<dyn Style>);
176    type IntoIter = Iter<'a, String, Box<dyn Style>>;
177
178    fn into_iter(self) -> Self::IntoIter {
179        self.0.iter()
180    }
181}
182
183pub trait Styled {
184    fn styles(&self) -> &Styles;
185}
186
187/// Indicates the element types to which a particular [`Style`] may be assigned.
188pub enum Assignability {
189    /// At the table level only.
190    TableOnly,
191
192    /// At the column level and at the table level.
193    ColTable,
194
195    /// At the row level and at the table level.
196    RowTable,
197
198    /// At the row, column and table level.
199    RowColTable,
200
201    /// At the cell, row, column and table level.
202    CellRowColTable,
203}
204
205impl Assignability {
206    pub fn at_col(&self) -> bool {
207        matches!(
208            self,
209            Assignability::ColTable | Assignability::RowColTable | Assignability::CellRowColTable
210        )
211    }
212
213    pub fn at_row(&self) -> bool {
214        matches!(
215            self,
216            Assignability::RowTable | Assignability::RowColTable | Assignability::CellRowColTable
217        )
218    }
219
220    pub fn at_cell(&self) -> bool {
221        matches!(self, Assignability::CellRowColTable)
222    }
223}
224
225#[cfg(test)]
226mod tests;