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 => "─", Self::Normal => "─",
52 Self::Heavy => "━", Self::Dashed => "╌", }
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
101impl 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#[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 Delimiter(Regex),
248 Regexes(Vec<Regex>),
250 #[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
273impl 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 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
377pub 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
425define_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 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 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}