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