1use matchmaker_partial_macros::partial;
5
6use std::{fmt, ops::Deref};
7
8pub use crate::utils::{Percentage, serde::StringOrVec};
9use crate::{
10 MAX_SPLITS,
11 tui::IoStream,
12 utils::serde::{escaped_opt_char, escaped_opt_string, serde_duration_ms},
13};
14pub use cli_boilerplate_automation::bother::types::When;
15use cli_boilerplate_automation::define_transparent_wrapper;
16use cli_boilerplate_automation::serde::{
17 through_string,
19 transform::camelcase_normalized,
20};
21use ratatui::{
22 style::{Color, Modifier, Style},
23 text::Span,
24 widgets::{BorderType, Borders, Padding},
25};
26
27use regex::Regex;
28
29use serde::{
30 Deserialize, Deserializer, Serialize,
31 de::{self, IntoDeserializer, Visitor},
32 ser::SerializeSeq,
33};
34
35#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
39#[partial(recurse, path, derive(Debug, Deserialize))]
40pub struct MatcherConfig {
41 #[serde(flatten)]
42 #[partial(skip)]
43 pub matcher: NucleoMatcherConfig,
44 #[serde(flatten)]
45 pub worker: WorkerConfig,
46 #[serde(flatten)]
48 pub start: StartConfig,
49 #[serde(flatten)]
50 pub exit: ExitConfig,
51}
52
53#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
57#[serde(default)]
58#[partial(path, derive(Debug, Deserialize))]
59pub struct WorkerConfig {
60 #[partial(recurse)]
61 #[partial(alias = "c")]
62 pub columns: ColumnsConfig,
64 pub trim: bool,
66 pub sort_threshold: u32,
68
69 pub raw: bool,
71 pub track: bool,
73 pub reverse: bool,
75}
76
77#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
81#[serde(default)]
82#[partial(path, derive(Debug, Deserialize))]
83pub struct StartConfig {
84 #[serde(deserialize_with = "escaped_opt_char")]
85 #[partial(alias = "is")]
86 pub input_separator: Option<char>,
87 #[serde(deserialize_with = "escaped_opt_string")]
88 #[partial(alias = "os")]
89 pub output_separator: Option<String>,
90
91 pub output_template: Option<String>,
93
94 #[partial(alias = "cmd", alias = "x")]
96 pub command: String,
97 #[partial(alias = "s")]
98 pub sync: bool,
99}
100
101#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
105#[serde(default)]
106#[partial(path, derive(Debug, Deserialize))]
107pub struct ExitConfig {
108 pub select_1: bool,
110 pub allow_empty: bool,
112 pub abort_empty: bool,
114}
115
116#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
118#[serde(default, deny_unknown_fields)]
119#[partial(recurse, path, derive(Debug, Deserialize))]
120pub struct RenderConfig {
121 pub ui: UiConfig,
123 pub input: InputConfig,
125 #[partial(alias = "r")]
127 pub results: ResultsConfig,
128 #[partial(alias = "p")]
130 pub preview: PreviewConfig,
131 pub footer: DisplayConfig,
132 #[partial(alias = "h")]
133 pub header: DisplayConfig,
134}
135
136impl RenderConfig {
137 pub fn tick_rate(&self) -> u8 {
138 self.ui.tick_rate
139 }
140}
141
142#[partial(path, derive(Debug, Deserialize))]
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145#[serde(default, deny_unknown_fields)]
146pub struct TerminalConfig {
147 pub stream: IoStream, pub restore_fullscreen: bool,
149 pub redraw_on_resize: bool,
150 pub extended_keys: bool,
152 #[serde(with = "serde_duration_ms")]
153 pub sleep_ms: std::time::Duration, #[serde(flatten)]
156 #[partial(recurse)]
157 pub layout: Option<TerminalLayoutSettings>, pub clear_on_exit: bool,
159}
160
161impl Default for TerminalConfig {
162 fn default() -> Self {
163 Self {
164 stream: IoStream::default(),
165 restore_fullscreen: true,
166 redraw_on_resize: bool::default(),
167 sleep_ms: std::time::Duration::default(),
168 layout: Option::default(),
169 extended_keys: true,
170 clear_on_exit: true,
171 }
172 }
173}
174#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
175#[serde(default, deny_unknown_fields)]
176pub struct TerminalSettings {}
177
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
180#[serde(default, deny_unknown_fields)]
181#[partial(path, derive(Debug, Deserialize))]
182pub struct UiConfig {
183 #[partial(recurse)]
184 pub border: BorderSetting,
185 pub tick_rate: u8, }
187
188impl Default for UiConfig {
189 fn default() -> Self {
190 Self {
191 border: Default::default(),
192 tick_rate: 60,
193 }
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199#[serde(default, deny_unknown_fields)]
200#[partial(path, derive(Debug, Deserialize))]
201pub struct InputConfig {
202 #[partial(recurse)]
203 pub border: BorderSetting,
204
205 #[serde(deserialize_with = "camelcase_normalized")]
207 pub fg: Color,
208 pub modifier: Modifier,
210
211 #[serde(deserialize_with = "camelcase_normalized")]
212 pub prompt_fg: Color,
213 pub prompt_modifier: Modifier,
215
216 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
217 pub prompt: String,
218 pub cursor: CursorSetting,
219 pub initial: String,
220
221 pub scroll_padding: bool,
223}
224
225impl Default for InputConfig {
226 fn default() -> Self {
227 Self {
228 border: Default::default(),
229 fg: Default::default(),
230 modifier: Default::default(),
231 prompt_fg: Default::default(),
232 prompt_modifier: Default::default(),
233 prompt: "> ".to_string(),
234 cursor: Default::default(),
235 initial: Default::default(),
236
237 scroll_padding: true,
238 }
239 }
240}
241
242#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
243#[serde(default, deny_unknown_fields)]
244#[partial(path, derive(Debug, Deserialize))]
245pub struct OverlayConfig {
246 #[partial(recurse)]
247 pub border: BorderSetting,
248 pub outer_dim: bool,
249 pub layout: OverlayLayoutSettings,
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253#[partial(path, derive(Debug, Deserialize))]
254pub struct OverlayLayoutSettings {
255 #[partial(alias = "p")]
257 pub percentage: [Percentage; 2],
258 pub min: [u16; 2],
260 pub max: [u16; 2],
262}
263
264impl Default for OverlayLayoutSettings {
265 fn default() -> Self {
266 Self {
267 percentage: [Percentage::new(60), Percentage::new(30)],
268 min: [10, 5],
269 max: [200, 30],
270 }
271 }
272}
273
274#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
275pub enum RowConnectionStyle {
276 #[default]
277 Disjoint,
278 Capped,
279 Full,
280}
281
282#[partial(path, derive(Debug, Deserialize))]
285#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286#[serde(default, deny_unknown_fields)]
287pub struct ResultsConfig {
288 #[partial(recurse)]
289 pub border: BorderSetting,
290
291 #[serde(deserialize_with = "deserialize_string_or_char_as_double_width")]
293 pub multi_prefix: String,
294 pub default_prefix: String,
295
296 #[serde(deserialize_with = "camelcase_normalized")]
298 pub fg: Color,
299 pub modifier: Modifier,
301
302 #[serde(deserialize_with = "camelcase_normalized")]
303 pub match_fg: Color,
304 pub match_modifier: Modifier,
306
307 #[serde(deserialize_with = "camelcase_normalized")]
309 pub current_fg: Color,
310 #[serde(deserialize_with = "camelcase_normalized")]
312 pub current_bg: Color,
313 pub current_modifier: Modifier,
316 #[serde(deserialize_with = "camelcase_normalized")]
318 pub row_connection_style: RowConnectionStyle,
319
320 #[serde(deserialize_with = "camelcase_normalized")]
322 pub status_fg: Color,
323 pub status_modifier: Modifier,
325 pub status_show: bool,
326
327 pub scroll_wrap: bool,
333 pub scroll_padding: u16,
334 #[partial(alias = "r")]
335 pub reverse: When,
336
337 #[partial(alias = "w")]
339 pub wrap: bool,
340 pub wrap_scaling_min_width: u16,
341
342 pub column_spacing: Count,
344 pub current_prefix: String,
345 #[partial(alias = "ra")]
346 pub right_align_last: bool,
347}
348define_transparent_wrapper!(
349 #[derive(Copy, Clone)]
350 Count: u16 = 1
351);
352
353impl Default for ResultsConfig {
354 fn default() -> Self {
355 ResultsConfig {
356 border: Default::default(),
357
358 multi_prefix: "▌ ".to_string(),
359 default_prefix: Default::default(),
360
361 fg: Default::default(),
362 modifier: Default::default(),
363 match_fg: Color::Green,
364 match_modifier: Modifier::ITALIC,
365
366 current_fg: Default::default(),
367 current_bg: Color::Black,
368 current_modifier: Modifier::BOLD,
369 row_connection_style: RowConnectionStyle::Disjoint,
370
371 status_fg: Color::Green,
372 status_modifier: Modifier::ITALIC,
373 status_show: true,
374
375 scroll_wrap: true,
376 scroll_padding: 2,
377 reverse: Default::default(),
378
379 wrap: Default::default(),
380 wrap_scaling_min_width: 5,
381
382 column_spacing: Default::default(),
383 current_prefix: Default::default(),
384 right_align_last: false,
385 }
386 }
387}
388
389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
390#[serde(default, deny_unknown_fields)]
391#[partial(path, derive(Debug, Deserialize))]
392pub struct DisplayConfig {
393 #[partial(recurse)]
394 pub border: BorderSetting,
395
396 #[serde(deserialize_with = "camelcase_normalized")]
397 pub fg: Color,
398 pub modifier: Modifier,
400
401 pub match_indent: bool,
402 pub wrap: bool,
403
404 #[serde(deserialize_with = "deserialize_option_auto")]
405 pub content: Option<StringOrVec>,
406
407 #[serde(deserialize_with = "camelcase_normalized")]
416 pub row_connection_style: RowConnectionStyle,
417
418 #[partial(alias = "h")]
423 pub header_lines: usize,
424}
425
426impl Default for DisplayConfig {
427 fn default() -> Self {
428 DisplayConfig {
429 border: Default::default(),
430 match_indent: true,
431 fg: Color::Green,
432 wrap: false,
433 row_connection_style: Default::default(),
434 modifier: Modifier::ITALIC, content: None,
436 header_lines: 0,
437 }
438 }
439}
440
441#[partial(path, derive(Debug, Deserialize))]
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
457#[serde(default)]
458pub struct PreviewConfig {
459 #[partial(recurse)]
460 pub border: BorderSetting,
461 #[partial(recurse)]
462 #[partial(alias = "l")]
463 pub layout: Vec<PreviewSetting>,
464 #[partial(recurse)]
465 #[serde(flatten)]
466 pub scroll: PreviewScrollSetting,
467 #[partial(alias = "c")]
469 pub scroll_wrap: bool,
470 pub wrap: bool,
471 pub show: bool,
472}
473
474impl Default for PreviewConfig {
475 fn default() -> Self {
476 PreviewConfig {
477 border: BorderSetting {
478 padding: Padding::left(2),
479 ..Default::default()
480 },
481 scroll: Default::default(),
482 layout: Default::default(),
483 scroll_wrap: true,
484 wrap: Default::default(),
485 show: Default::default(),
486 }
487 }
488}
489
490#[partial(path, derive(Debug, Deserialize))]
492#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
493#[serde(default, deny_unknown_fields)]
494#[derive(Default)]
495pub struct PreviewScrollSetting {
496 pub index: Option<String>,
499 #[partial(alias = "o")]
501 pub offset: isize,
502 #[partial(alias = "p")]
504 pub percentage: Percentage,
505 #[partial(alias = "h")]
507 pub header_lines: usize,
508}
509
510#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
511#[serde(default, deny_unknown_fields)]
512pub struct PreviewerConfig {
513 pub try_lossy: bool,
514
515 pub cache: u8,
517
518 pub help_colors: TomlColorConfig,
519}
520
521#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
523pub struct TomlColorConfig {
524 #[serde(deserialize_with = "camelcase_normalized")]
525 pub section: Color,
526 #[serde(deserialize_with = "camelcase_normalized")]
527 pub key: Color,
528 #[serde(deserialize_with = "camelcase_normalized")]
529 pub string: Color,
530 #[serde(deserialize_with = "camelcase_normalized")]
531 pub number: Color,
532 pub section_bold: bool,
533}
534
535impl Default for TomlColorConfig {
536 fn default() -> Self {
537 Self {
538 section: Color::Blue,
539 key: Color::Yellow,
540 string: Color::Green,
541 number: Color::Cyan,
542 section_bold: true,
543 }
544 }
545}
546
547#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
551#[serde(transparent)]
552pub struct FormatString(String);
553
554impl Deref for FormatString {
555 type Target = str;
556
557 fn deref(&self) -> &Self::Target {
558 &self.0
559 }
560}
561
562#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
563#[serde(default, deny_unknown_fields)]
564#[partial(path, derive(Debug, Deserialize))]
565pub struct BorderSetting {
566 #[serde(with = "through_string")]
567 pub r#type: BorderType,
568 #[serde(deserialize_with = "camelcase_normalized")]
569 pub color: Color,
570 pub sides: Option<Borders>,
578 #[serde(with = "padding")]
586 pub padding: Padding,
587 pub title: String,
588 pub title_modifier: Modifier,
590 #[serde(deserialize_with = "camelcase_normalized")]
591 pub bg: Color,
592}
593
594impl BorderSetting {
595 pub fn as_block(&self) -> ratatui::widgets::Block<'_> {
596 let mut ret = ratatui::widgets::Block::default()
597 .padding(self.padding)
598 .style(Style::default().bg(self.bg));
599
600 if !self.title.is_empty() {
601 let title = Span::styled(
602 &self.title,
603 Style::default().add_modifier(self.title_modifier),
604 );
605
606 ret = ret.title(title)
607 };
608
609 if !self.is_empty() {
610 ret = ret
611 .borders(self.sides())
612 .border_type(self.r#type)
613 .border_style(ratatui::style::Style::default().fg(self.color))
614 }
615
616 ret
617 }
618
619 pub fn sides(&self) -> Borders {
620 if let Some(s) = self.sides {
621 s
622 } else if self.color != Default::default() || self.r#type != Default::default() {
623 Borders::ALL
624 } else {
625 Borders::NONE
626 }
627 }
628
629 pub fn as_static_block(&self) -> ratatui::widgets::Block<'static> {
630 let mut ret = ratatui::widgets::Block::default()
631 .padding(self.padding)
632 .style(Style::default().bg(self.bg));
633
634 if !self.title.is_empty() {
635 let title: Span<'static> = Span::styled(
636 self.title.clone(),
637 Style::default().add_modifier(self.title_modifier),
638 );
639
640 ret = ret.title(title)
641 };
642
643 if !self.is_empty() {
644 ret = ret
645 .borders(self.sides())
646 .border_type(self.r#type)
647 .border_style(ratatui::style::Style::default().fg(self.color))
648 }
649
650 ret
651 }
652
653 pub fn is_empty(&self) -> bool {
654 self.sides() == Borders::NONE
655 }
656
657 pub fn height(&self) -> u16 {
658 let mut height = 0;
659 height += 2 * !self.is_empty() as u16;
660 height += self.padding.top + self.padding.bottom;
661 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
662
663 height
664 }
665
666 pub fn width(&self) -> u16 {
667 let mut width = 0;
668 width += 2 * !self.is_empty() as u16;
669 width += self.padding.left + self.padding.right;
670
671 width
672 }
673
674 pub fn left(&self) -> u16 {
675 let mut width = 0;
676 width += !self.is_empty() as u16;
677 width += self.padding.left;
678
679 width
680 }
681
682 pub fn top(&self) -> u16 {
683 let mut height = 0;
684 height += !self.is_empty() as u16;
685 height += self.padding.top;
686 height += (!self.title.is_empty() as u16).saturating_sub(!self.is_empty() as u16);
687
688 height
689 }
690}
691
692#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
694#[partial(path, derive(Debug, Deserialize))]
695pub struct TerminalLayoutSettings {
696 #[partial(alias = "p")]
698 pub percentage: Percentage,
699 pub min: u16,
700 pub max: u16, }
702
703impl Default for TerminalLayoutSettings {
704 fn default() -> Self {
705 Self {
706 percentage: Percentage::new(50),
707 min: 10,
708 max: 120,
709 }
710 }
711}
712
713#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
714#[serde(rename_all = "lowercase")]
715pub enum Side {
716 Top,
717 Bottom,
718 Left,
719 #[default]
720 Right,
721}
722
723#[partial(path, derive(Debug, Deserialize))]
724#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
725pub struct PreviewSetting {
726 #[serde(flatten)]
727 #[partial(recurse)]
728 pub layout: PreviewLayout,
729 #[serde(default, alias = "cmd", alias = "x")]
730 pub command: String,
731}
732
733#[partial(path, derive(Debug, Deserialize))]
734#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
735pub struct PreviewLayout {
736 pub side: Side,
737 #[serde(alias = "p")]
740 pub percentage: Percentage,
742 pub min: i16,
743 pub max: i16,
744}
745
746impl Default for PreviewLayout {
747 fn default() -> Self {
748 Self {
749 side: Side::Right,
750 percentage: Percentage::new(60),
751 min: 30,
752 max: 120,
753 }
754 }
755}
756
757#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
758#[serde(rename_all = "lowercase")]
759pub enum CursorSetting {
760 None,
761 #[default]
762 Default,
763}
764
765use crate::utils::serde::bounded_usize;
766#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
768#[serde(default, deny_unknown_fields)]
769#[partial(path, derive(Debug, Deserialize))]
770pub struct ColumnsConfig {
771 pub split: Split,
773 #[partial(alias = "n")]
775 pub names: Vec<ColumnSetting>,
776 #[serde(deserialize_with = "bounded_usize::<{crate::MAX_SPLITS},_>")]
778 max_columns: usize,
779}
780
781impl ColumnsConfig {
782 pub fn max_cols(&self) -> usize {
783 self.max_columns.min(MAX_SPLITS)
784 }
785}
786
787#[derive(Default, Debug, Clone, PartialEq)]
788pub struct ColumnSetting {
789 pub filter: bool,
790 pub hidden: bool,
791 pub name: String,
792}
793
794#[derive(Default, Debug, Clone)]
795pub enum Split {
796 Delimiter(Regex),
798 Regexes(Vec<Regex>),
800 #[default]
802 None,
803}
804
805impl PartialEq for Split {
806 fn eq(&self, other: &Self) -> bool {
807 match (self, other) {
808 (Split::Delimiter(r1), Split::Delimiter(r2)) => r1.as_str() == r2.as_str(),
809 (Split::Regexes(v1), Split::Regexes(v2)) => {
810 if v1.len() != v2.len() {
811 return false;
812 }
813 v1.iter()
814 .zip(v2.iter())
815 .all(|(r1, r2)| r1.as_str() == r2.as_str())
816 }
817 (Split::None, Split::None) => true,
818 _ => false,
819 }
820 }
821}
822
823pub fn serialize_borders<S>(borders: &Borders, serializer: S) -> Result<S::Ok, S::Error>
825where
826 S: serde::Serializer,
827{
828 use serde::ser::SerializeSeq;
829 let mut seq = serializer.serialize_seq(None)?;
830 if borders.contains(Borders::TOP) {
831 seq.serialize_element("top")?;
832 }
833 if borders.contains(Borders::BOTTOM) {
834 seq.serialize_element("bottom")?;
835 }
836 if borders.contains(Borders::LEFT) {
837 seq.serialize_element("left")?;
838 }
839 if borders.contains(Borders::RIGHT) {
840 seq.serialize_element("right")?;
841 }
842 seq.end()
843}
844
845pub fn deserialize_string_or_char_as_double_width<'de, D, T>(deserializer: D) -> Result<T, D::Error>
846where
847 D: Deserializer<'de>,
848 T: From<String>,
849{
850 struct GenericVisitor<T> {
851 _marker: std::marker::PhantomData<T>,
852 }
853
854 impl<'de, T> Visitor<'de> for GenericVisitor<T>
855 where
856 T: From<String>,
857 {
858 type Value = T;
859
860 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
861 formatter.write_str("a string or single character")
862 }
863
864 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
865 where
866 E: de::Error,
867 {
868 let s = if v.chars().count() == 1 {
869 let mut s = String::with_capacity(2);
870 s.push(v.chars().next().unwrap());
871 s.push(' ');
872 s
873 } else {
874 v.to_string()
875 };
876 Ok(T::from(s))
877 }
878
879 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
880 where
881 E: de::Error,
882 {
883 self.visit_str(&v)
884 }
885 }
886
887 deserializer.deserialize_string(GenericVisitor {
888 _marker: std::marker::PhantomData,
889 })
890}
891
892#[derive(Debug, Clone, PartialEq)]
894pub struct NucleoMatcherConfig(pub nucleo::Config);
895
896impl Default for NucleoMatcherConfig {
897 fn default() -> Self {
898 Self(nucleo::Config::DEFAULT)
899 }
900}
901
902#[derive(Debug, Clone, Serialize, Deserialize)]
903#[serde(default)]
904#[derive(Default)]
905struct MatcherConfigHelper {
906 pub normalize: Option<bool>,
907 pub ignore_case: Option<bool>,
908 pub prefer_prefix: Option<bool>,
909}
910
911impl serde::Serialize for NucleoMatcherConfig {
912 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
913 where
914 S: serde::Serializer,
915 {
916 let helper = MatcherConfigHelper {
917 normalize: Some(self.0.normalize),
918 ignore_case: Some(self.0.ignore_case),
919 prefer_prefix: Some(self.0.prefer_prefix),
920 };
921 helper.serialize(serializer)
922 }
923}
924
925impl<'de> Deserialize<'de> for NucleoMatcherConfig {
926 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
927 where
928 D: serde::Deserializer<'de>,
929 {
930 let helper = MatcherConfigHelper::deserialize(deserializer)?;
931 let mut config = nucleo::Config::DEFAULT;
932
933 if let Some(norm) = helper.normalize {
934 config.normalize = norm;
935 }
936 if let Some(ic) = helper.ignore_case {
937 config.ignore_case = ic;
938 }
939 if let Some(pp) = helper.prefer_prefix {
940 config.prefer_prefix = pp;
941 }
942
943 Ok(NucleoMatcherConfig(config))
944 }
945}
946
947impl serde::Serialize for Split {
948 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
949 where
950 S: serde::Serializer,
951 {
952 match self {
953 Split::Delimiter(r) => serializer.serialize_str(r.as_str()),
954 Split::Regexes(rs) => {
955 let mut seq = serializer.serialize_seq(Some(rs.len()))?;
956 for r in rs {
957 seq.serialize_element(r.as_str())?;
958 }
959 seq.end()
960 }
961 Split::None => serializer.serialize_none(),
962 }
963 }
964}
965
966impl<'de> Deserialize<'de> for Split {
967 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
968 where
969 D: Deserializer<'de>,
970 {
971 struct SplitVisitor;
972
973 impl<'de> Visitor<'de> for SplitVisitor {
974 type Value = Split;
975
976 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
977 formatter.write_str("string for delimiter or array of strings for regexes")
978 }
979
980 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
981 where
982 E: de::Error,
983 {
984 Regex::new(value)
986 .map(Split::Delimiter)
987 .map_err(|e| E::custom(format!("Invalid regex: {}", e)))
988 }
989
990 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
991 where
992 A: serde::de::SeqAccess<'de>,
993 {
994 let mut regexes = Vec::new();
995 while let Some(s) = seq.next_element::<String>()? {
996 let r = Regex::new(&s)
997 .map_err(|e| de::Error::custom(format!("Invalid regex: {}", e)))?;
998 regexes.push(r);
999 }
1000 Ok(Split::Regexes(regexes))
1001 }
1002 }
1003
1004 deserializer.deserialize_any(SplitVisitor)
1005 }
1006}
1007
1008impl serde::Serialize for ColumnSetting {
1009 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1010 where
1011 S: serde::Serializer,
1012 {
1013 use serde::ser::SerializeStruct;
1014 let mut state = serializer.serialize_struct("ColumnSetting", 3)?;
1015 state.serialize_field("filter", &self.filter)?;
1016 state.serialize_field("hidden", &self.hidden)?;
1017 state.serialize_field("name", &self.name)?;
1018 state.end()
1019 }
1020}
1021
1022impl<'de> Deserialize<'de> for ColumnSetting {
1023 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1024 where
1025 D: Deserializer<'de>,
1026 {
1027 #[derive(Deserialize)]
1028 #[serde(deny_unknown_fields)]
1029 struct ColumnStruct {
1030 #[serde(default = "default_true")]
1031 filter: bool,
1032 #[serde(default)]
1033 hidden: bool,
1034 name: String,
1035 }
1036
1037 fn default_true() -> bool {
1038 true
1039 }
1040
1041 #[derive(Deserialize)]
1042 #[serde(untagged)]
1043 enum Input {
1044 Str(String),
1045 Obj(ColumnStruct),
1046 }
1047
1048 match Input::deserialize(deserializer)? {
1049 Input::Str(name) => Ok(ColumnSetting {
1050 filter: true,
1051 hidden: false,
1052 name,
1053 }),
1054 Input::Obj(obj) => Ok(ColumnSetting {
1055 filter: obj.filter,
1056 hidden: obj.hidden,
1057 name: obj.name,
1058 }),
1059 }
1060 }
1061}
1062
1063mod padding {
1064 use super::*;
1065
1066 pub fn serialize<S>(padding: &Padding, serializer: S) -> Result<S::Ok, S::Error>
1067 where
1068 S: serde::Serializer,
1069 {
1070 use serde::ser::SerializeSeq;
1071 if padding.top == padding.bottom
1072 && padding.left == padding.right
1073 && padding.top == padding.left
1074 {
1075 serializer.serialize_u16(padding.top)
1076 } else if padding.top == padding.bottom && padding.left == padding.right {
1077 let mut seq = serializer.serialize_seq(Some(2))?;
1078 seq.serialize_element(&padding.left)?;
1079 seq.serialize_element(&padding.top)?;
1080 seq.end()
1081 } else {
1082 let mut seq = serializer.serialize_seq(Some(4))?;
1083 seq.serialize_element(&padding.top)?;
1084 seq.serialize_element(&padding.right)?;
1085 seq.serialize_element(&padding.bottom)?;
1086 seq.serialize_element(&padding.left)?;
1087 seq.end()
1088 }
1089 }
1090
1091 pub fn deserialize<'de, D>(deserializer: D) -> Result<Padding, D::Error>
1092 where
1093 D: Deserializer<'de>,
1094 {
1095 struct PaddingVisitor;
1096
1097 impl<'de> Visitor<'de> for PaddingVisitor {
1098 type Value = Padding;
1099
1100 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1101 formatter.write_str("a number or an array of 1, 2, or 4 numbers")
1102 }
1103
1104 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
1105 where
1106 E: de::Error,
1107 {
1108 let v = u16::try_from(value).map_err(|_| {
1109 E::custom(format!("padding value {} is out of range for u16", value))
1110 })?;
1111
1112 Ok(Padding {
1113 top: v,
1114 right: v,
1115 bottom: v,
1116 left: v,
1117 })
1118 }
1119
1120 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
1121 where
1122 E: de::Error,
1123 {
1124 let v = u16::try_from(value).map_err(|_| {
1125 E::custom(format!("padding value {} is out of range for u16", value))
1126 })?;
1127
1128 Ok(Padding {
1129 top: v,
1130 right: v,
1131 bottom: v,
1132 left: v,
1133 })
1134 }
1135
1136 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1138 where
1139 A: de::SeqAccess<'de>,
1140 {
1141 let first: u16 = seq
1142 .next_element()?
1143 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
1144
1145 let second: Option<u16> = seq.next_element()?;
1146 let third: Option<u16> = seq.next_element()?;
1147 let fourth: Option<u16> = seq.next_element()?;
1148
1149 match (second, third, fourth) {
1150 (None, None, None) => Ok(Padding {
1151 top: first,
1152 right: first,
1153 bottom: first,
1154 left: first,
1155 }),
1156 (Some(v2), None, None) => Ok(Padding {
1157 top: first,
1158 bottom: first,
1159 left: v2,
1160 right: v2,
1161 }),
1162 (Some(v2), Some(v3), Some(v4)) => Ok(Padding {
1163 top: first,
1164 right: v2,
1165 bottom: v3,
1166 left: v4,
1167 }),
1168 _ => Err(de::Error::invalid_length(3, &self)),
1169 }
1170 }
1171 }
1172
1173 deserializer.deserialize_any(PaddingVisitor)
1174 }
1175}
1176
1177pub fn deserialize_option_auto<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
1178where
1179 D: serde::Deserializer<'de>,
1180 T: Deserialize<'de>,
1181{
1182 let opt = Option::<String>::deserialize(deserializer)?;
1183 match opt.as_deref() {
1184 Some("auto") => Ok(None),
1185 Some(s) => Ok(Some(T::deserialize(s.into_deserializer())?)),
1186 None => Ok(None),
1187 }
1188}