Skip to main content

i3status_rs/formatting/
config.rs

1use super::{Format, 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<Vec<Format>> {
146        self.with_defaults(default_full, "")
147    }
148
149    pub fn with_defaults(&self, default_full: &str, default_short: &str) -> Result<Vec<Format>> {
150        Ok(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]) -> Vec<Format> {
178        match self.clone() {
179            MaybeMultiConfig::Multiple { configs } => configs,
180            MaybeMultiConfig::Split { config, config_alt } => {
181                vec![config.unwrap_or_default(), config_alt.unwrap_or_default()]
182            }
183        }
184        .into_iter()
185        .zip_longest(default_formats)
186        .filter_map(|pair| match pair {
187            itertools::EitherOrBoth::Both(config, default_format) => {
188                Some(config.with_default_format(default_format))
189            }
190            itertools::EitherOrBoth::Left(config) => Some(config.into()),
191            itertools::EitherOrBoth::Right(_) => None,
192        })
193        .collect()
194    }
195}
196
197impl FromStr for Config {
198    type Err = Error;
199
200    fn from_str(s: &str) -> Result<Self, Self::Err> {
201        Ok(Self {
202            full: Some(s.parse()?),
203            short: None,
204        })
205    }
206}
207
208impl<'de> Deserialize<'de> for Config {
209    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210    where
211        D: Deserializer<'de>,
212    {
213        #[derive(Deserialize)]
214        #[serde(field_identifier, rename_all = "lowercase")]
215        enum Field {
216            Full,
217            Short,
218        }
219
220        struct FormatTemplateVisitor;
221
222        impl<'de> Visitor<'de> for FormatTemplateVisitor {
223            type Value = Config;
224
225            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
226                formatter.write_str("format structure")
227            }
228
229            /// Handle configs like:
230            ///
231            /// ```toml
232            /// format = "{layout}"
233            /// ```
234            fn visit_str<E>(self, full: &str) -> Result<Config, E>
235            where
236                E: de::Error,
237            {
238                full.parse().serde_error()
239            }
240
241            /// Handle configs like:
242            ///
243            /// ```toml
244            /// [block.format]
245            /// full = "{layout}"
246            /// short = "{layout^2}"
247            /// ```
248            fn visit_map<V>(self, mut map: V) -> Result<Config, V::Error>
249            where
250                V: MapAccess<'de>,
251            {
252                let mut full: Option<FormatTemplate> = None;
253                let mut short: Option<FormatTemplate> = None;
254                while let Some(key) = map.next_key()? {
255                    match key {
256                        Field::Full => {
257                            if full.is_some() {
258                                return Err(de::Error::duplicate_field("full"));
259                            }
260                            full = Some(map.next_value::<String>()?.parse().serde_error()?);
261                        }
262                        Field::Short => {
263                            if short.is_some() {
264                                return Err(de::Error::duplicate_field("short"));
265                            }
266                            short = Some(map.next_value::<String>()?.parse().serde_error()?);
267                        }
268                    }
269                }
270                Ok(Config { full, short })
271            }
272        }
273
274        deserializer.deserialize_any(FormatTemplateVisitor)
275    }
276}
277
278impl<'de> Deserialize<'de> for MaybeMultiConfig {
279    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280    where
281        D: Deserializer<'de>,
282    {
283        #[derive(Deserialize)]
284        #[serde(untagged)]
285        enum MaybeVecConfig {
286            Multiple(Vec<Config>),
287            Single(Config),
288        }
289
290        struct MaybeMultiConfigVisitor;
291
292        impl<'de> Visitor<'de> for MaybeMultiConfigVisitor {
293            type Value = MaybeMultiConfig;
294
295            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
296                f.write_str("multiformat structure")
297            }
298
299            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
300            where
301                A: MapAccess<'de>,
302            {
303                let mut format: Option<MaybeVecConfig> = None;
304                let mut format_alt: Option<Config> = None;
305
306                while let Some(key) = map.next_key::<String>()? {
307                    match key.as_str() {
308                        "format" => {
309                            if format.is_some() {
310                                return Err(de::Error::duplicate_field("format"));
311                            }
312
313                            // This ensures Config deserialization errors are surfaced
314                            let maybe_vec_config = match map.next_value()? {
315                                serde_json::Value::Array(arr) => MaybeVecConfig::Multiple(
316                                    serde_json::from_value(serde_json::Value::Array(arr))
317                                        .serde_error()?,
318                                ),
319                                value => MaybeVecConfig::Single(
320                                    serde_json::from_value(value).serde_error()?,
321                                ),
322                            };
323                            format = Some(maybe_vec_config);
324                        }
325                        "format_alt" => {
326                            if format_alt.is_some() {
327                                return Err(de::Error::duplicate_field("format_alt"));
328                            }
329                            format_alt = Some(map.next_value()?);
330                        }
331                        unknown => {
332                            return Err(de::Error::unknown_field(
333                                unknown,
334                                &["format", "format_alt"],
335                            ));
336                        }
337                    }
338                }
339
340                match format {
341                    Some(MaybeVecConfig::Multiple(configs)) => {
342                        if format_alt.is_some() {
343                            return Err(de::Error::custom(
344                                "data did not match any variant of untagged enum MaybeMultiConfig",
345                            ));
346                        }
347                        if configs.is_empty() {
348                            return Err(de::Error::custom(
349                                "An empty list of configs is not allowed",
350                            ));
351                        }
352                        Ok(MaybeMultiConfig::Multiple { configs })
353                    }
354                    Some(MaybeVecConfig::Single(config)) => Ok(MaybeMultiConfig::Split {
355                        config: Some(config),
356                        config_alt: format_alt,
357                    }),
358                    None => Ok(MaybeMultiConfig::Split {
359                        config: None,
360                        config_alt: format_alt,
361                    }),
362                }
363            }
364        }
365
366        deserializer.deserialize_map(MaybeMultiConfigVisitor)
367    }
368}