Skip to main content

matchmaker/
config_types.rs

1use std::fmt;
2
3use cba::{bird::one_or_many, define_transparent_wrapper};
4use ratatui::{
5    style::{Color, Modifier, Style},
6    widgets::Borders,
7};
8
9use regex::Regex;
10
11use serde::{
12    Deserialize, Deserializer, Serialize, Serializer,
13    de::{self, Visitor},
14    ser::SerializeSeq,
15};
16
17#[derive(Debug, Default, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
18#[serde(default, deny_unknown_fields)]
19pub struct StyleSetting {
20    pub fg: Color,
21    pub bg: Color,
22    pub modifier: Modifier,
23}
24
25impl From<StyleSetting> for Style {
26    fn from(s: StyleSetting) -> Style {
27        Style::default().fg(s.fg).bg(s.bg).add_modifier(s.modifier)
28    }
29}
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
32pub enum HorizontalSeparator {
33    #[default]
34    None,
35    Empty,
36    Light,
37    Normal,
38    Heavy,
39    Dashed,
40}
41
42impl HorizontalSeparator {
43    pub fn as_str(self) -> &'static str {
44        match self {
45            Self::None => unreachable!(),
46            Self::Empty => " ",
47            Self::Light => "─", // U+2500
48            Self::Normal => "─",
49            Self::Heavy => "━",  // U+2501
50            Self::Dashed => "╌", // U+254C (box drawings light double dash)
51        }
52    }
53}
54
55#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
56pub enum RowConnectionStyle {
57    #[default]
58    Disjoint,
59    Capped,
60    Full,
61}
62
63define_transparent_wrapper!(
64    #[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
65    #[serde(transparent)]
66    Count: u16 = 1
67);
68use ratatui::widgets::Padding as rPadding;
69
70define_transparent_wrapper!(
71    #[derive(Copy, Clone, Default)]
72    Padding: rPadding
73);
74
75#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
76#[serde(untagged)]
77pub enum ShowCondition {
78    Bool(bool),
79    Free(u16),
80}
81impl Default for ShowCondition {
82    fn default() -> Self {
83        Self::Bool(false)
84    }
85}
86impl From<bool> for ShowCondition {
87    fn from(value: bool) -> Self {
88        ShowCondition::Bool(value)
89    }
90}
91
92impl From<u16> for ShowCondition {
93    fn from(value: u16) -> Self {
94        ShowCondition::Free(value)
95    }
96}
97
98// -----------------------------------------------------------------------------------------
99
100impl Serialize for Padding {
101    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102    where
103        S: Serializer,
104    {
105        use serde::ser::SerializeSeq;
106        let padding = self;
107        if padding.top == padding.bottom
108            && padding.left == padding.right
109            && padding.top == padding.left
110        {
111            serializer.serialize_u16(padding.top)
112        } else if padding.top == padding.bottom && padding.left == padding.right {
113            let mut seq = serializer.serialize_seq(Some(2))?;
114            seq.serialize_element(&padding.left)?;
115            seq.serialize_element(&padding.top)?;
116            seq.end()
117        } else {
118            let mut seq = serializer.serialize_seq(Some(4))?;
119            seq.serialize_element(&padding.top)?;
120            seq.serialize_element(&padding.right)?;
121            seq.serialize_element(&padding.bottom)?;
122            seq.serialize_element(&padding.left)?;
123            seq.end()
124        }
125    }
126}
127
128impl<'de> Deserialize<'de> for Padding {
129    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
130    where
131        D: Deserializer<'de>,
132    {
133        use serde::de::Error;
134
135        let repr: Vec<u16> = one_or_many::deserialize(deserializer)?;
136
137        let inner = match repr.len() {
138            1 => {
139                let v = repr[0];
140                rPadding {
141                    top: v,
142                    right: v,
143                    bottom: v,
144                    left: v,
145                }
146            }
147            2 => {
148                let lr = repr[0];
149                let tb = repr[1];
150                rPadding {
151                    top: tb,
152                    right: lr,
153                    bottom: tb,
154                    left: lr,
155                }
156            }
157            4 => rPadding {
158                top: repr[0],
159                right: repr[1],
160                bottom: repr[2],
161                left: repr[3],
162            },
163            _ => {
164                return Err(D::Error::custom(
165                    "a number or an array of 1, 2, or 4 numbers",
166                ));
167            }
168        };
169
170        Ok(inner.into())
171    }
172}
173
174// ---------------------------------------------------------------------------------
175
176// define_restricted_wrapper!(
177//     #[derive(Clone, serde::Serialize, serde::Deserialize)]
178//     #[serde(transparent)]
179//     FormatString: String
180// );
181
182#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
183#[serde(rename_all = "lowercase")]
184pub enum Side {
185    Top,
186    Bottom,
187    Left,
188    #[default]
189    Right,
190}
191
192impl Side {
193    pub fn opposite(&self) -> Borders {
194        match self {
195            Side::Top => Borders::BOTTOM,
196            Side::Bottom => Borders::TOP,
197            Side::Left => Borders::RIGHT,
198            Side::Right => Borders::LEFT,
199        }
200    }
201}
202
203#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
204#[serde(rename_all = "lowercase")]
205pub enum CursorSetting {
206    None,
207    #[default]
208    Default,
209}
210
211define_transparent_wrapper!(
212    #[derive(Clone, Serialize, Default)]
213    #[serde(transparent)]
214    ColumnName: String
215);
216
217impl<'de> Deserialize<'de> for ColumnName {
218    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219    where
220        D: Deserializer<'de>,
221    {
222        let s = String::deserialize(deserializer)?;
223        if s.chars().all(|c| c.is_alphanumeric()) {
224            Ok(ColumnName(s))
225        } else {
226            Err(serde::de::Error::custom(format!(
227                "Invalid column name '{}': name must be alphanumeric",
228                s
229            )))
230        }
231    }
232}
233
234#[derive(Default, Debug, Clone, PartialEq, serde::Serialize)]
235pub struct ColumnSetting {
236    pub filter: bool,
237    pub hidden: bool,
238    pub name: ColumnName,
239}
240
241#[derive(Default, Debug, Clone)]
242pub enum Split {
243    /// Split by delimiter. Supports regex.
244    Delimiter(Regex),
245    /// A sequence of regexes.
246    Regexes(Vec<Regex>),
247    /// No splitting.
248    #[default]
249    None,
250}
251
252impl PartialEq for Split {
253    fn eq(&self, other: &Self) -> bool {
254        match (self, other) {
255            (Split::Delimiter(r1), Split::Delimiter(r2)) => r1.as_str() == r2.as_str(),
256            (Split::Regexes(v1), Split::Regexes(v2)) => {
257                if v1.len() != v2.len() {
258                    return false;
259                }
260                v1.iter()
261                    .zip(v2.iter())
262                    .all(|(r1, r2)| r1.as_str() == r2.as_str())
263            }
264            (Split::None, Split::None) => true,
265            _ => false,
266        }
267    }
268}
269
270// ---------------------------------------------------------------------------------
271
272impl serde::Serialize for Split {
273    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
274    where
275        S: serde::Serializer,
276    {
277        match self {
278            Split::Delimiter(r) => serializer.serialize_str(r.as_str()),
279            Split::Regexes(rs) => {
280                let mut seq = serializer.serialize_seq(Some(rs.len()))?;
281                for r in rs {
282                    seq.serialize_element(r.as_str())?;
283                }
284                seq.end()
285            }
286            Split::None => serializer.serialize_none(),
287        }
288    }
289}
290
291impl<'de> Deserialize<'de> for Split {
292    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293    where
294        D: Deserializer<'de>,
295    {
296        struct SplitVisitor;
297
298        impl<'de> Visitor<'de> for SplitVisitor {
299            type Value = Split;
300
301            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
302                formatter.write_str("string for delimiter or array of strings for regexes")
303            }
304
305            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
306            where
307                E: de::Error,
308            {
309                // Try to compile single regex
310                Regex::new(value)
311                    .map(Split::Delimiter)
312                    .map_err(|e| E::custom(format!("Invalid regex: {}", e)))
313            }
314
315            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
316            where
317                A: serde::de::SeqAccess<'de>,
318            {
319                let mut regexes = Vec::new();
320                while let Some(s) = seq.next_element::<String>()? {
321                    let r = Regex::new(&s)
322                        .map_err(|e| de::Error::custom(format!("Invalid regex: {}", e)))?;
323                    regexes.push(r);
324                }
325                Ok(Split::Regexes(regexes))
326            }
327        }
328
329        deserializer.deserialize_any(SplitVisitor)
330    }
331}
332
333impl<'de> Deserialize<'de> for ColumnSetting {
334    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
335    where
336        D: Deserializer<'de>,
337    {
338        #[derive(Deserialize)]
339        #[serde(deny_unknown_fields)]
340        struct ColumnStruct {
341            #[serde(default = "default_true")]
342            filter: bool,
343            #[serde(default)]
344            hidden: bool,
345            name: ColumnName,
346        }
347
348        fn default_true() -> bool {
349            true
350        }
351
352        #[derive(Deserialize)]
353        #[serde(untagged)]
354        enum Input {
355            Str(ColumnName),
356            Obj(ColumnStruct),
357        }
358
359        match Input::deserialize(deserializer)? {
360            Input::Str(name) => Ok(ColumnSetting {
361                filter: true,
362                hidden: false,
363                name,
364            }),
365            Input::Obj(obj) => Ok(ColumnSetting {
366                filter: obj.filter,
367                hidden: obj.hidden,
368                name: obj.name,
369            }),
370        }
371    }
372}
373
374// ----------------------------------------------------------------------
375pub fn deserialize_string_or_char_as_double_width<'de, D, T>(deserializer: D) -> Result<T, D::Error>
376where
377    D: Deserializer<'de>,
378    T: From<String>,
379{
380    struct GenericVisitor<T> {
381        _marker: std::marker::PhantomData<T>,
382    }
383
384    impl<'de, T> Visitor<'de> for GenericVisitor<T>
385    where
386        T: From<String>,
387    {
388        type Value = T;
389
390        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
391            formatter.write_str("a string or single character")
392        }
393
394        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
395        where
396            E: de::Error,
397        {
398            let s = if v.chars().count() == 1 {
399                let mut s = String::with_capacity(2);
400                s.push(v.chars().next().unwrap());
401                s.push(' ');
402                s
403            } else {
404                v.to_string()
405            };
406            Ok(T::from(s))
407        }
408
409        fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
410        where
411            E: de::Error,
412        {
413            self.visit_str(&v)
414        }
415    }
416
417    deserializer.deserialize_string(GenericVisitor {
418        _marker: std::marker::PhantomData,
419    })
420}
421
422// ----------------------------------------------------------------------------
423define_transparent_wrapper!(
424    #[derive(Clone, Eq, Serialize)]
425    StringValue: String
426
427);
428
429impl<'de> Deserialize<'de> for StringValue {
430    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
431    where
432        D: Deserializer<'de>,
433    {
434        struct Visitor;
435
436        impl<'de> de::Visitor<'de> for Visitor {
437            type Value = StringValue;
438
439            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
440                formatter.write_str("a string, number, or bool")
441            }
442
443            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
444                Ok(StringValue(v.to_owned()))
445            }
446
447            fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
448                Ok(StringValue(v))
449            }
450
451            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> {
452                Ok(StringValue(v.to_string()))
453            }
454
455            fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E> {
456                Ok(StringValue(v.to_string()))
457            }
458
459            fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E> {
460                Ok(StringValue(v.to_string()))
461            }
462
463            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> {
464                Ok(StringValue(v.to_string()))
465            }
466        }
467
468        deserializer.deserialize_any(Visitor)
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475
476    #[derive(Deserialize)]
477    struct TestName {
478        name: ColumnName,
479    }
480
481    #[test]
482    fn test_column_name_validation() {
483        // Valid
484        let name: TestName = toml::from_str("name = \"col1\"").expect("Valid name");
485        assert_eq!(name.name.as_str(), "col1");
486
487        let name: TestName = toml::from_str("name = \"Column123\"").expect("Valid name");
488        assert_eq!(name.name.as_str(), "Column123");
489
490        // Invalid
491        let res: Result<TestName, _> = toml::from_str("name = \"col-1\"");
492        assert!(res.is_err());
493
494        let res: Result<TestName, _> = toml::from_str("name = \"col 1\"");
495        assert!(res.is_err());
496
497        let res: Result<TestName, _> = toml::from_str("name = \"col_1\"");
498        assert!(res.is_err());
499    }
500}