Skip to main content

i3status_rs/formatting/
config.rs

1use super::{Format, MultiFormat, template::FormatTemplate};
2use crate::errors::*;
3use itertools::Itertools as _;
4use serde::de::{MapAccess, Visitor};
5use serde::{Deserialize, Deserializer, de};
6use smart_default::SmartDefault;
7use std::fmt;
8use std::str::FromStr;
9
10#[derive(Debug, Default, Clone)]
11pub struct Config {
12    pub full: Option<FormatTemplate>,
13    pub short: Option<FormatTemplate>,
14}
15
16impl Config {
17    pub fn with_default(&self, default_full: &str) -> Result<Format> {
18        self.with_defaults(default_full, "")
19    }
20
21    pub fn with_defaults(&self, default_full: &str, default_short: &str) -> Result<Format> {
22        let full = match self.full.clone() {
23            Some(full) => full,
24            None => default_full.parse()?,
25        };
26
27        let short = match self.short.clone() {
28            Some(short) => short,
29            None => default_short.parse()?,
30        };
31
32        Ok(Format::new(full, short))
33    }
34
35    pub fn with_default_config(&self, default_config: &Self) -> Format {
36        let full = self
37            .full
38            .clone()
39            .or_else(|| default_config.full.clone())
40            .unwrap_or_default();
41        let short = self
42            .short
43            .clone()
44            .or_else(|| default_config.short.clone())
45            .unwrap_or_default();
46
47        Format::new(full, short)
48    }
49
50    pub fn with_default_format(&self, default_format: &Format) -> Format {
51        let full = self
52            .full
53            .clone()
54            .unwrap_or_else(|| default_format.full.clone());
55        let short = self
56            .short
57            .clone()
58            .unwrap_or_else(|| default_format.short.clone());
59
60        Format::new(full, short)
61    }
62}
63
64impl From<Config> for Format {
65    fn from(config: Config) -> Self {
66        let full = config.full.unwrap_or_default();
67        let short = config.short.unwrap_or_default();
68
69        Format::new(full, short)
70    }
71}
72
73/// The [`MaybeMultiConfig`] type is used to represent a configuration that can either be a single [`Config`] or multiple [`Config`]s.
74///
75/// <div class="warning">
76/// Note: An empty list of configs is not allowed
77/// </div>
78///
79/// It is used to support different ways of specifying formats in the configuration file:
80///
81/// * A single format:
82/// ```toml
83/// [[block]]
84/// block = "xxx"
85/// format = "{layout}"
86/// ```
87///
88/// * A format and an alternative format:
89/// ```toml
90/// [[block]]
91/// block = "xxx"
92/// format = "{layout}"
93/// format_alt = "{layout^2}"
94/// ```
95///
96/// * A list of formats:
97/// ```toml
98/// [[block]]
99/// block = "xxx"
100/// format = ["{layout}"]
101/// ```
102///
103/// * A single format with full and short variants:
104/// ```toml
105/// [[block]]
106/// block = "xxx"
107/// [block.format]
108/// full = "{layout}"
109/// short = "{layout^2}"
110/// ```
111///
112/// * A format and an alternative format with full and short variants:
113/// ```toml
114/// [[block]]
115/// block = "xxx"
116/// [block.format]
117/// full = "{layout}"
118/// short = "{layout^2}"
119/// [block.format_alt]
120/// full = "{layout^3}"
121/// short = "{layout^4}"
122/// ```
123///
124/// * A list of formats with full and short variants:
125/// ```toml
126/// [[block]]
127/// block = "xxx"
128/// [[block.format]]
129/// full = "{layout}"
130/// short = "{layout^2}"
131/// ```
132#[derive(Debug, Clone, SmartDefault)]
133pub enum MaybeMultiConfig {
134    #[default]
135    Split {
136        config: Option<Config>,
137        config_alt: Option<Config>,
138    },
139    Multiple {
140        configs: Vec<Config>,
141    },
142}
143
144impl MaybeMultiConfig {
145    pub fn with_default(&self, default_full: &str) -> Result<MultiFormat> {
146        self.with_defaults(default_full, "")
147    }
148
149    pub fn with_defaults(&self, default_full: &str, default_short: &str) -> Result<MultiFormat> {
150        Ok(MultiFormat::new(match self.clone() {
151            MaybeMultiConfig::Multiple { configs } => configs
152                .into_iter()
153                .enumerate()
154                .map(|(i, config)| {
155                    if i == 0 {
156                        config.with_defaults(default_full, default_short)
157                    } else {
158                        Ok(config.into())
159                    }
160                })
161                .collect::<Result<Vec<_>>>()?,
162            MaybeMultiConfig::Split { config, config_alt } => {
163                let mut formats = vec![
164                    config
165                        .unwrap_or_default()
166                        .with_defaults(default_full, default_short)?,
167                ];
168
169                if let Some(config_alt) = config_alt {
170                    formats.push(config_alt.into());
171                }
172                formats
173            }
174        }))
175    }
176
177    pub fn with_default_formats(&self, default_formats: &[Format]) -> MultiFormat {
178        MultiFormat::new(
179            match self.clone() {
180                MaybeMultiConfig::Multiple { configs } => configs,
181                MaybeMultiConfig::Split { config, config_alt } => {
182                    vec![config.unwrap_or_default(), config_alt.unwrap_or_default()]
183                }
184            }
185            .into_iter()
186            .zip_longest(default_formats)
187            .filter_map(|pair| match pair {
188                itertools::EitherOrBoth::Both(config, default_format) => {
189                    Some(config.with_default_format(default_format))
190                }
191                itertools::EitherOrBoth::Left(config) => Some(config.into()),
192                itertools::EitherOrBoth::Right(_) => None,
193            })
194            .collect(),
195        )
196    }
197}
198
199impl FromStr for Config {
200    type Err = Error;
201
202    fn from_str(s: &str) -> Result<Self, Self::Err> {
203        Ok(Self {
204            full: Some(s.parse()?),
205            short: None,
206        })
207    }
208}
209
210impl<'de> Deserialize<'de> for Config {
211    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
212    where
213        D: Deserializer<'de>,
214    {
215        #[derive(Deserialize)]
216        #[serde(field_identifier, rename_all = "lowercase")]
217        enum Field {
218            Full,
219            Short,
220        }
221
222        struct FormatTemplateVisitor;
223
224        impl<'de> Visitor<'de> for FormatTemplateVisitor {
225            type Value = Config;
226
227            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
228                formatter.write_str("format structure")
229            }
230
231            /// Handle configs like:
232            ///
233            /// ```toml
234            /// format = "{layout}"
235            /// ```
236            fn visit_str<E>(self, full: &str) -> Result<Config, E>
237            where
238                E: de::Error,
239            {
240                full.parse().serde_error()
241            }
242
243            /// Handle configs like:
244            ///
245            /// ```toml
246            /// [block.format]
247            /// full = "{layout}"
248            /// short = "{layout^2}"
249            /// ```
250            fn visit_map<V>(self, mut map: V) -> Result<Config, V::Error>
251            where
252                V: MapAccess<'de>,
253            {
254                let mut full: Option<FormatTemplate> = None;
255                let mut short: Option<FormatTemplate> = None;
256                while let Some(key) = map.next_key()? {
257                    match key {
258                        Field::Full => {
259                            if full.is_some() {
260                                return Err(de::Error::duplicate_field("full"));
261                            }
262                            full = Some(map.next_value::<String>()?.parse().serde_error()?);
263                        }
264                        Field::Short => {
265                            if short.is_some() {
266                                return Err(de::Error::duplicate_field("short"));
267                            }
268                            short = Some(map.next_value::<String>()?.parse().serde_error()?);
269                        }
270                    }
271                }
272                Ok(Config { full, short })
273            }
274        }
275
276        deserializer.deserialize_any(FormatTemplateVisitor)
277    }
278}
279
280impl<'de> Deserialize<'de> for MaybeMultiConfig {
281    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
282    where
283        D: Deserializer<'de>,
284    {
285        #[derive(Deserialize)]
286        #[serde(untagged)]
287        enum MaybeVecConfig {
288            Multiple(Vec<Config>),
289            Single(Config),
290        }
291
292        struct MaybeMultiConfigVisitor;
293
294        impl<'de> Visitor<'de> for MaybeMultiConfigVisitor {
295            type Value = MaybeMultiConfig;
296
297            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
298                f.write_str("multiformat structure")
299            }
300
301            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
302            where
303                A: MapAccess<'de>,
304            {
305                let mut format: Option<MaybeVecConfig> = None;
306                let mut format_alt: Option<Config> = None;
307
308                while let Some(key) = map.next_key::<String>()? {
309                    match key.as_str() {
310                        "format" => {
311                            if format.is_some() {
312                                return Err(de::Error::duplicate_field("format"));
313                            }
314
315                            // This ensures Config deserialization errors are surfaced
316                            let maybe_vec_config = match map.next_value()? {
317                                serde_json::Value::Array(arr) => MaybeVecConfig::Multiple(
318                                    serde_json::from_value(serde_json::Value::Array(arr))
319                                        .serde_error()?,
320                                ),
321                                value => MaybeVecConfig::Single(
322                                    serde_json::from_value(value).serde_error()?,
323                                ),
324                            };
325                            format = Some(maybe_vec_config);
326                        }
327                        "format_alt" => {
328                            if format_alt.is_some() {
329                                return Err(de::Error::duplicate_field("format_alt"));
330                            }
331                            format_alt = Some(map.next_value()?);
332                        }
333                        unknown => {
334                            return Err(de::Error::unknown_field(
335                                unknown,
336                                &["format", "format_alt"],
337                            ));
338                        }
339                    }
340                }
341
342                match format {
343                    Some(MaybeVecConfig::Multiple(configs)) => {
344                        if format_alt.is_some() {
345                            return Err(de::Error::custom(
346                                "data did not match any variant of untagged enum MaybeMultiConfig",
347                            ));
348                        }
349                        if configs.is_empty() {
350                            return Err(de::Error::custom(
351                                "An empty list of configs is not allowed",
352                            ));
353                        }
354                        Ok(MaybeMultiConfig::Multiple { configs })
355                    }
356                    Some(MaybeVecConfig::Single(config)) => Ok(MaybeMultiConfig::Split {
357                        config: Some(config),
358                        config_alt: format_alt,
359                    }),
360                    None => Ok(MaybeMultiConfig::Split {
361                        config: None,
362                        config_alt: format_alt,
363                    }),
364                }
365            }
366        }
367
368        deserializer.deserialize_map(MaybeMultiConfigVisitor)
369    }
370}