Skip to main content

i3status_rs/formatting/
template.rs

1use super::formatter::{Formatter, new_formatter};
2use super::{FormatError, Fragment, Values, parse};
3use crate::config::SharedConfig;
4use crate::errors::*;
5
6use std::str::FromStr;
7use std::sync::Arc;
8
9/// The alternative token lists `render` tries in order. Crate-visible so a
10/// prepared block plan can inspect a template without rendering it.
11#[derive(Debug, Clone)]
12pub struct FormatTemplate(pub(crate) Arc<[TokenList]>);
13
14impl Default for FormatTemplate {
15    fn default() -> Self {
16        Self(Arc::new([]))
17    }
18}
19
20#[derive(Debug)]
21pub struct TokenList(pub Vec<Token>);
22
23#[derive(Debug)]
24pub enum Token {
25    Text(String),
26    Recursive(FormatTemplate),
27    Placeholder {
28        name: String,
29        formatter: Option<Box<dyn Formatter>>,
30    },
31    Icon {
32        name: String,
33    },
34}
35
36impl FormatTemplate {
37    pub fn contains_key(&self, key: &str) -> bool {
38        self.0.iter().any(|token_list| {
39            token_list.0.iter().any(|token| match token {
40                Token::Placeholder { name, .. } => name == key,
41                Token::Recursive(rec) => rec.contains_key(key),
42                _ => false,
43            })
44        })
45    }
46
47    pub fn render(
48        &self,
49        values: &Values,
50        config: &SharedConfig,
51    ) -> Result<Vec<Fragment>, FormatError> {
52        for (i, token_list) in self.0.iter().enumerate() {
53            match token_list.render(values, config) {
54                Ok(res) => return Ok(res),
55                Err(
56                    FormatError::PlaceholderNotFound(_)
57                    | FormatError::IncompatibleFormatter { .. }
58                    | FormatError::NumberOutOfRange(_),
59                ) if i != self.0.len() - 1 => (),
60                Err(e) => return Err(e),
61            }
62        }
63        Ok(Vec::new())
64    }
65
66    pub fn init_intervals(&self, intervals: &mut Vec<u64>) {
67        for tl in self.0.iter() {
68            for t in &tl.0 {
69                match t {
70                    Token::Recursive(r) => r.init_intervals(intervals),
71                    Token::Placeholder {
72                        formatter: Some(f), ..
73                    } => {
74                        if let Some(i) = f.interval() {
75                            intervals.push(i.as_millis() as u64);
76                        }
77                    }
78                    _ => (),
79                }
80            }
81        }
82    }
83}
84
85impl TokenList {
86    pub fn render(
87        &self,
88        values: &Values,
89        config: &SharedConfig,
90    ) -> Result<Vec<Fragment>, FormatError> {
91        let mut retval = Vec::new();
92        let mut cur = Fragment::default();
93        for token in &self.0 {
94            match token {
95                Token::Text(text) => {
96                    if cur.metadata.is_default() {
97                        cur.text.push_str(text);
98                    } else {
99                        if !cur.text.is_empty() {
100                            retval.push(cur);
101                        }
102                        cur = text.clone().into();
103                    }
104                }
105                Token::Recursive(rec) => {
106                    if !cur.text.is_empty() {
107                        retval.push(cur);
108                    }
109                    retval.extend(rec.render(values, config)?);
110                    cur = retval.pop().unwrap_or_default();
111                }
112                Token::Placeholder { name, formatter } => {
113                    let value = values
114                        .get(name.as_str())
115                        .ok_or_else(|| FormatError::PlaceholderNotFound(name.into()))?;
116                    let formatter = formatter
117                        .as_ref()
118                        .map(Box::as_ref)
119                        .unwrap_or_else(|| value.default_formatter());
120                    let formatted = formatter.format(&value.inner, config)?;
121                    if value.metadata == cur.metadata {
122                        cur.text.push_str(&formatted);
123                    } else {
124                        if !cur.text.is_empty() {
125                            retval.push(cur);
126                        }
127                        cur = Fragment {
128                            text: formatted,
129                            metadata: value.metadata,
130                        };
131                    }
132                }
133                Token::Icon { name } => {
134                    let icon = config.get_icon(name, None)?;
135                    if cur.metadata.is_default() {
136                        cur.text.push_str(&icon);
137                    } else {
138                        if !cur.text.is_empty() {
139                            retval.push(cur);
140                        }
141                        cur = icon.into();
142                    }
143                }
144            }
145        }
146
147        if !cur.text.is_empty() {
148            retval.push(cur);
149        }
150
151        Ok(retval)
152    }
153}
154
155impl FromStr for FormatTemplate {
156    type Err = Error;
157
158    fn from_str(s: &str) -> Result<Self> {
159        parse::parse_full(s)
160            .and_then(TryInto::try_into)
161            .error("Incorrect format template")
162    }
163}
164
165impl TryFrom<parse::FormatTemplate<'_>> for FormatTemplate {
166    type Error = Error;
167
168    fn try_from(value: parse::FormatTemplate) -> Result<Self, Self::Error> {
169        value
170            .0
171            .into_iter()
172            .map(TryInto::try_into)
173            .collect::<Result<Arc<[_]>>>()
174            .map(Self)
175    }
176}
177
178impl TryFrom<parse::TokenList<'_>> for TokenList {
179    type Error = Error;
180
181    fn try_from(value: parse::TokenList) -> Result<Self, Self::Error> {
182        value
183            .0
184            .into_iter()
185            .map(TryInto::try_into)
186            .collect::<Result<Vec<_>>>()
187            .map(Self)
188    }
189}
190
191impl TryFrom<parse::Token<'_>> for Token {
192    type Error = Error;
193
194    fn try_from(value: parse::Token) -> Result<Self, Self::Error> {
195        Ok(match value {
196            parse::Token::Text(text) => Self::Text(text),
197            parse::Token::Placeholder(placeholder) => Self::Placeholder {
198                name: placeholder.name.to_owned(),
199                formatter: placeholder
200                    .formatter
201                    .map(|fmt| new_formatter(fmt.name, &fmt.args))
202                    .transpose()?,
203            },
204            parse::Token::Icon(icon) => Self::Icon {
205                name: icon.to_owned(),
206            },
207            parse::Token::Recursive(rec) => Self::Recursive(rec.try_into()?),
208        })
209    }
210}