i3status-rs 0.36.0

A feature-rich and resource-friendly replacement for i3status, written in Rust.
Documentation
use super::formatter::{Formatter, new_formatter};
use super::{FormatError, Fragment, Values, parse};
use crate::config::SharedConfig;
use crate::errors::*;

use std::str::FromStr;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct FormatTemplate(Arc<[TokenList]>);

impl Default for FormatTemplate {
    fn default() -> Self {
        Self(Arc::new([]))
    }
}

#[derive(Debug)]
pub struct TokenList(pub Vec<Token>);

#[derive(Debug)]
pub enum Token {
    Text(String),
    Recursive(FormatTemplate),
    Placeholder {
        name: String,
        formatter: Option<Box<dyn Formatter>>,
    },
    Icon {
        name: String,
    },
}

impl FormatTemplate {
    pub fn contains_key(&self, key: &str) -> bool {
        self.0.iter().any(|token_list| {
            token_list.0.iter().any(|token| match token {
                Token::Placeholder { name, .. } => name == key,
                Token::Recursive(rec) => rec.contains_key(key),
                _ => false,
            })
        })
    }

    pub fn render(
        &self,
        values: &Values,
        config: &SharedConfig,
    ) -> Result<Vec<Fragment>, FormatError> {
        for (i, token_list) in self.0.iter().enumerate() {
            match token_list.render(values, config) {
                Ok(res) => return Ok(res),
                Err(
                    FormatError::PlaceholderNotFound(_)
                    | FormatError::IncompatibleFormatter { .. }
                    | FormatError::NumberOutOfRange(_),
                ) if i != self.0.len() - 1 => (),
                Err(e) => return Err(e),
            }
        }
        Ok(Vec::new())
    }

    pub fn init_intervals(&self, intervals: &mut Vec<u64>) {
        for tl in self.0.iter() {
            for t in &tl.0 {
                match t {
                    Token::Recursive(r) => r.init_intervals(intervals),
                    Token::Placeholder {
                        formatter: Some(f), ..
                    } => {
                        if let Some(i) = f.interval() {
                            intervals.push(i.as_millis() as u64);
                        }
                    }
                    _ => (),
                }
            }
        }
    }
}

impl TokenList {
    pub fn render(
        &self,
        values: &Values,
        config: &SharedConfig,
    ) -> Result<Vec<Fragment>, FormatError> {
        let mut retval = Vec::new();
        let mut cur = Fragment::default();
        for token in &self.0 {
            match token {
                Token::Text(text) => {
                    if cur.metadata.is_default() {
                        cur.text.push_str(text);
                    } else {
                        if !cur.text.is_empty() {
                            retval.push(cur);
                        }
                        cur = text.clone().into();
                    }
                }
                Token::Recursive(rec) => {
                    if !cur.text.is_empty() {
                        retval.push(cur);
                    }
                    retval.extend(rec.render(values, config)?);
                    cur = retval.pop().unwrap_or_default();
                }
                Token::Placeholder { name, formatter } => {
                    let value = values
                        .get(name.as_str())
                        .ok_or_else(|| FormatError::PlaceholderNotFound(name.into()))?;
                    let formatter = formatter
                        .as_ref()
                        .map(Box::as_ref)
                        .unwrap_or_else(|| value.default_formatter());
                    let formatted = formatter.format(&value.inner, config)?;
                    if value.metadata == cur.metadata {
                        cur.text.push_str(&formatted);
                    } else {
                        if !cur.text.is_empty() {
                            retval.push(cur);
                        }
                        cur = Fragment {
                            text: formatted,
                            metadata: value.metadata,
                        };
                    }
                }
                Token::Icon { name } => {
                    let icon = config.get_icon(name, None)?;
                    if cur.metadata.is_default() {
                        cur.text.push_str(&icon);
                    } else {
                        if !cur.text.is_empty() {
                            retval.push(cur);
                        }
                        cur = icon.into();
                    }
                }
            }
        }

        if !cur.text.is_empty() {
            retval.push(cur);
        }

        Ok(retval)
    }
}

impl FromStr for FormatTemplate {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        parse::parse_full(s)
            .and_then(TryInto::try_into)
            .error("Incorrect format template")
    }
}

impl TryFrom<parse::FormatTemplate<'_>> for FormatTemplate {
    type Error = Error;

    fn try_from(value: parse::FormatTemplate) -> Result<Self, Self::Error> {
        value
            .0
            .into_iter()
            .map(TryInto::try_into)
            .collect::<Result<Arc<[_]>>>()
            .map(Self)
    }
}

impl TryFrom<parse::TokenList<'_>> for TokenList {
    type Error = Error;

    fn try_from(value: parse::TokenList) -> Result<Self, Self::Error> {
        value
            .0
            .into_iter()
            .map(TryInto::try_into)
            .collect::<Result<Vec<_>>>()
            .map(Self)
    }
}

impl TryFrom<parse::Token<'_>> for Token {
    type Error = Error;

    fn try_from(value: parse::Token) -> Result<Self, Self::Error> {
        Ok(match value {
            parse::Token::Text(text) => Self::Text(text),
            parse::Token::Placeholder(placeholder) => Self::Placeholder {
                name: placeholder.name.to_owned(),
                formatter: placeholder
                    .formatter
                    .map(|fmt| new_formatter(fmt.name, &fmt.args))
                    .transpose()?,
            },
            parse::Token::Icon(icon) => Self::Icon {
                name: icon.to_owned(),
            },
            parse::Token::Recursive(rec) => Self::Recursive(rec.try_into()?),
        })
    }
}