diff options
author | Zhenhui Xie <xiezh0831@yahoo.co.jp> | 2020-07-08 06:45:32 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-07 18:45:32 -0400 |
commit | ec76fafff08933f6f31fb99ea974bdb5ae97a0af (patch) | |
tree | bb2c822cdf291635f03d27677c419488ecf77f53 /src/formatter | |
parent | 0f52b7b12e8c1a2060aa873a68032937dfa2c044 (diff) |
feat: refactor modules to use format strings (#1374)
Diffstat (limited to 'src/formatter')
-rw-r--r-- | src/formatter/mod.rs | 1 | ||||
-rw-r--r-- | src/formatter/model.rs | 86 | ||||
-rw-r--r-- | src/formatter/parser.rs | 71 | ||||
-rw-r--r-- | src/formatter/spec.pest | 51 | ||||
-rw-r--r-- | src/formatter/string_formatter.rs | 616 |
5 files changed, 650 insertions, 175 deletions
diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index b0607df0a..fa378c4a6 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -2,4 +2,5 @@ pub mod model; mod parser; pub mod string_formatter; +pub use model::{StyleVariableHolder, VariableHolder}; pub use string_formatter::StringFormatter; diff --git a/src/formatter/model.rs b/src/formatter/model.rs index 8e4bbea39..291bf80be 100644 --- a/src/formatter/model.rs +++ b/src/formatter/model.rs @@ -1,17 +1,103 @@ use std::borrow::Cow; +use std::collections::BTreeSet; +/// Type that holds a number of variables of type `T` +pub trait VariableHolder<T> { + fn get_variables(&self) -> BTreeSet<T>; +} + +/// Type that holds a number of style variables of type `T` +pub trait StyleVariableHolder<T> { + fn get_style_variables(&self) -> BTreeSet<T>; +} + +#[derive(Clone)] pub struct TextGroup<'a> { pub format: Vec<FormatElement<'a>>, pub style: Vec<StyleElement<'a>>, } +#[derive(Clone)] pub enum FormatElement<'a> { Text(Cow<'a, str>), Variable(Cow<'a, str>), TextGroup(TextGroup<'a>), + Conditional(Vec<FormatElement<'a>>), } +#[derive(Clone)] pub enum StyleElement<'a> { Text(Cow<'a, str>), Variable(Cow<'a, str>), } + +impl<'a> VariableHolder<Cow<'a, str>> for FormatElement<'a> { + fn get_variables(&self) -> BTreeSet<Cow<'a, str>> { + match self { + FormatElement::Variable(var) => { + let mut variables = BTreeSet::new(); + variables.insert(var.clone()); + variables + } + FormatElement::TextGroup(textgroup) => textgroup.format.get_variables(), + FormatElement::Conditional(format) => format.get_variables(), + _ => Default::default(), + } + } +} + +impl<'a> VariableHolder<Cow<'a, str>> for Vec<FormatElement<'a>> { + fn get_variables(&self) -> BTreeSet<Cow<'a, str>> { + self.iter().fold(BTreeSet::new(), |mut acc, el| { + acc.extend(el.get_variables()); + acc + }) + } +} + +impl<'a> VariableHolder<Cow<'a, str>> for &[FormatElement<'a>] { + fn get_variables(&self) -> BTreeSet<Cow<'a, str>> { + self.iter().fold(BTreeSet::new(), |mut acc, el| { + acc.extend(el.get_variables()); + acc + }) + } +} + +impl<'a> StyleVariableHolder<Cow<'a, str>> for StyleElement<'a> { + fn get_style_variables(&self) -> BTreeSet<Cow<'a, str>> { + match self { + StyleElement::Variable(var) => { + let mut variables = BTreeSet::new(); + variables.insert(var.clone()); + variables + } + _ => Default::default(), + } + } +} + +impl<'a> StyleVariableHolder<Cow<'a, str>> for Vec<StyleElement<'a>> { + fn get_style_variables(&self) -> BTreeSet<Cow<'a, str>> { + self.iter().fold(BTreeSet::new(), |mut acc, el| { + acc.extend(el.get_style_variables()); + acc + }) + } +} + +impl<'a> StyleVariableHolder<Cow<'a, str>> for Vec<FormatElement<'a>> { + fn get_style_variables(&self) -> BTreeSet<Cow<'a, str>> { + self.iter().fold(BTreeSet::new(), |mut acc, el| match el { + FormatElement::TextGroup(textgroup) => { + acc.extend(textgroup.style.get_style_variables()); + acc + } + FormatElement::Conditional(format) => { + acc.extend(format.get_style_variables()); + acc + } + _ => acc, + }) + } +} diff --git a/src/formatter/parser.rs b/src/formatter/parser.rs index 95c267a3a..6fc2595f7 100644 --- a/src/formatter/parser.rs +++ b/src/formatter/parser.rs @@ -6,6 +6,18 @@ use super::model::*; #[grammar = "formatter/spec.pest"] struct IdentParser; +fn _parse_value(value: Pair<Rule>) -> FormatElement { + match value.as_rule() { + Rule::text => FormatElement::Text(_parse_text(value).into()), + Rule::variable => FormatElement::Variable(_parse_variable(value).into()), + Rule::textgroup => FormatElement::TextGroup(_parse_textgroup(value)), + Rule::conditional => { + FormatElement::Conditional(_parse_format(value.into_inner().next().unwrap())) + } + _ => unreachable!(), + } +} + fn _parse_textgroup(textgroup: Pair<Rule>) -> TextGroup { let mut inner_rules = textgroup.into_inner(); let format = inner_rules.next().unwrap(); @@ -22,55 +34,32 @@ fn _parse_variable(variable: Pair<Rule>) -> &str { } fn _parse_text(text: Pair<Rule>) -> String { - let mut result = String::new(); - for pair in text.into_inner() { - result.push_str(pair.as_str()); - } - result + text.into_inner() + .map(|pair| pair.as_str().chars()) + .flatten() + .collect() } fn _parse_format(format: Pair<Rule>) -> Vec<FormatElement> { - let mut result: Vec<FormatElement> = Vec::new(); - - for pair in format.into_inner() { - match pair.as_rule() { - Rule::text => result.push(FormatElement::Text(_parse_text(pair).into())), - Rule::variable => result.push(FormatElement::Variable(_parse_variable(pair).into())), - Rule::textgroup => result.push(FormatElement::TextGroup(_parse_textgroup(pair))), - _ => unreachable!(), - } - } - - result + format.into_inner().map(_parse_value).collect() } fn _parse_style(style: Pair<Rule>) -> Vec<StyleElement> { - let mut result: Vec<StyleElement> = Vec::new(); - - for pair in style.into_inner() { - match pair.as_rule() { - Rule::text => result.push(StyleElement::Text(_parse_text(pair).into())), - Rule::variable => result.push(StyleElement::Variable(_parse_variable(pair).into())), + style + .into_inner() + .map(|pair| match pair.as_rule() { + Rule::string => StyleElement::Text(pair.as_str().into()), + Rule::variable => StyleElement::Variable(_parse_variable(pair).into()), _ => unreachable!(), - } - } - - result + }) + .collect() } pub fn parse(format: &str) -> Result<Vec<FormatElement>, Error<Rule>> { - let pairs = IdentParser::parse(Rule::expression, format)?; - let mut result: Vec<FormatElement> = Vec::new(); - - // Lifetime of Segment is the same as result - for pair in pairs.take_while(|pair| pair.as_rule() != Rule::EOI) { - match pair.as_rule() { - Rule::text => result.push(FormatElement::Text(_parse_text(pair).into())), - Rule::variable => result.push(FormatElement::Variable(_parse_variable(pair).into())), - Rule::textgroup => result.push(FormatElement::TextGroup(_parse_textgroup(pair))), - _ => unreachable!(), - } - } - - Ok(result) + IdentParser::parse(Rule::expression, format).map(|pairs| { + pairs + .take_while(|pair| pair.as_rule() != Rule::EOI) + .map(_parse_value) + .collect() + }) } diff --git a/src/formatter/spec.pest b/src/formatter/spec.pest index 36be53be5..400c58ef7 100644 --- a/src/formatter/spec.pest +++ b/src/formatter/spec.pest @@ -1,16 +1,51 @@ +// Expression +// +// The expression of the format string. +// +// Should be started with SOI and ended with EOI, with a format string in it. expression = _{ SOI ~ value* ~ EOI } -value = _{ text | variable | textgroup } +value = _{ text | variable | textgroup | conditional } -variable = { "$" ~ variable_name } -variable_name = @{ char+ } +// Variable +// +// A variable is defined as one of the following: +// +// - A valid variable name followed by a `$` character (`$[a-zA-Z_][a-zA-Z0-9_]*`), +// e.g. `$variable`. +// +// - Some texts wrapped in a curly bracket (`${[^\(\)\[\]\\\${}]+}`), +// e.g. `${env:HOST}`. +variable = { "$" ~ (variable_name | variable_scope) } +variable_name = @{ ('a'..'z' | 'A'..'Z' | "_") ~ char* } char = _{ 'a'..'z' | 'A'..'Z' | '0'..'9' | "_" } -text = { text_inner+ } -text_inner = _{ text_inner_char | escape } -text_inner_char = { !("[" | "]" | "(" | ")" | "$" | "\\") ~ ANY } +variable_scope = _{ "{" ~ variable_scoped_name ~ "}" } +variable_scoped_name = { scoped_char+ } +scoped_char = _{ !(escaped_char | "{" | "}") ~ ANY } + +// Text +// +// Texts can be one of `string` or `escaped_char`, where string is one or more of +// unescapable chars. +// +// This is implemented so as to ensure all functional characters are escaped. +text = { (string | escape)+ } +string = @{ text_inner_char+ } +text_inner_char = { !escaped_char ~ ANY } escape = _{ "\\" ~ escaped_char } escaped_char = { "[" | "]" | "(" | ")" | "\\" | "$" } +// TextGroup +// +// A textgroup is a pair of `format` and `style` (`[format](style)`) +// +// - `format`: A format string, can contain any number of variables, texts or textgroups. +// - `style`: A style string, can contain any number of variables or texts. textgroup = { "[" ~ format ~ "]" ~ "(" ~ style ~ ")" } -format = { (variable | text | textgroup)* } -style = { (variable | text)* } +format = { value* } +style = { (variable | string)* } + +// Conditional +// +// A conditional format string that won't render if all the containing variables are empty. +conditional = { "(" ~ format ~ ")" } diff --git a/src/formatter/string_formatter.rs b/src/formatter/string_formatter.rs index 8f33ce9ef..2cc9fdb3f 100644 --- a/src/formatter/string_formatter.rs +++ b/src/formatter/string_formatter.rs @@ -1,7 +1,11 @@ use ansi_term::Style; -use pest::error::Error; +use pest::error::Error as PestError; use rayon::prelude::*; -use std::collections::BTreeMap; +use std::borrow::Cow; +use std::collections::{BTreeMap, BTreeSet}; +use std::error::Error; +use std::fmt; +use std::iter::FromIterator; use crate::config::parse_style_string; use crate::segment::Segment; @@ -10,109 +14,263 @@ use super::model::*; use super::parser::{parse, Rule}; #[derive(Clone)] -enum VariableValue { - Plain(String), +enum VariableValue<'a> { + Plain(Cow<'a, str>), Styled(Vec<Segment>), + Meta(Vec<FormatElement<'a>>), } -impl Default for VariableValue { +impl<'a> Default for VariableValue<'a> { fn default() -> Self { - VariableValue::Plain(String::new()) + VariableValue::Plain(Cow::Borrowed("")) } } -type VariableMapType = BTreeMap<String, Option<VariableValue>>; +type VariableMapType<'a> = + BTreeMap<String, Option<Result<VariableValue<'a>, StringFormatterError>>>; +type StyleVariableMapType<'a> = + BTreeMap<String, Option<Result<Cow<'a, str>, StringFormatterError>>>; + +#[derive(Debug, Clone)] +pub enum StringFormatterError { + Custom(String), + Parse(PestError<Rule>), +} + +impl fmt::Display for StringFormatterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Custom(error) => write!(f, "{}", error), + Self::Parse(error) => write!(f, "{}", error), + } + } +} + +impl Error for StringFormatterError {} + +impl From<String> for StringFormatterError { + fn from(error: String) -> Self { + StringFormatterError::Custom(error) + } +} pub struct StringFormatter<'a> { format: Vec<FormatElement<'a>>, - variables: VariableMapType, + variables: VariableMapType<'a>, + style_variables: StyleVariableMapType<'a>, } impl<'a> StringFormatter<'a> { /// Creates an instance of StringFormatter from a format string - pub fn new(format: &'a str) -> Result<Self, Error<Rule>> { + /// + /// This method will throw an Error when the given format string fails to parse. + pub fn new(format: &'a str) -> Result<Self, StringFormatterError> { parse(format) .map(|format| { - let variables = _get_variables(&format); - (format, variables) + // Cache all variables + let variables = VariableMapType::from_iter( + format + .get_variables() + .into_iter() + .map(|key| (key.to_string(), None)) + .collect::<Vec<(String, Option<_>)>>(), + ); + let style_variables = StyleVariableMapType::from_iter( + format + .get_style_variables() + .into_iter() + .map(|key| (key.to_string(), None)) + .collect::<Vec<(String, Option<_>)>>(), + ); + (format, variables, style_variables) }) - .map(|(format, variables)| Self { format, variables }) + .map(|(format, variables, style_variables)| Self { + format, + variables, + style_variables, + }) + .map_err(StringFormatterError::Parse) } /// Maps variable name to its value - pub fn map(mut self, mapper: impl Fn(&str) -> Option<String> + Sync) -> Self { - self.variables.par_iter_mut().for_each(|(key, value)| { - *value = mapper(key).map(VariableValue::Plain); - }); + /// + /// You should provide a function or closure that accepts the variable name `name: &str` as a + /// parameter and returns the one of the following values: + /// + /// - `None`: This variable will be reserved for further mappers. If it is `None` when + /// `self.parse()` is called, it will be dropped. + /// + /// - `Some(Err(StringFormatterError))`: This variable will throws `StringFormatterError` when + /// `self.parse()` is called. Return this if some fatal error occurred and the format string + /// should not be rendered. + /// + /// - `Some(Ok(_))`: The value of this variable will be displayed in the format string. + /// + pub fn map<T, M>(mut self, mapper: M) -> Self + where + T: Into<Cow<'a, str>>, + M: Fn(&str) -> Option<Result<T, StringFormatterError>> + Sync, + { + self.variables + .par_iter_mut() + .filter(|(_, value)| value.is_none()) + .for_each(|(key, value)| { + *value = mapper(key).map(|var| var.map(|var| VariableValue::Plain(var.into()))); + }); + self + } + + /// Maps a meta-variable to a format string containing other variables. + /// + /// This function should be called **before** other map methods so that variables found in + /// the format strings of meta-variables can be cached properly. + /// + /// See `StringFormatter::map` for description on the parameters. + pub fn map_meta<M>(mut self, mapper: M) -> Self + where + M: Fn(&str, &BTreeSet<String>) -> Option<&'a str> + Sync, + { + let variables = self.get_variables(); + let (variables, style_variables) = self + .variables + .iter_mut() + .filter(|(_, value)| value.is_none()) + .fold( + (VariableMapType::new(), StyleVariableMapType::new()), + |(mut v, mut sv), (key, value)| { + *value = mapper(key, &variables).map(|format| { + StringFormatter::new(format).map(|formatter| { + let StringFormatter { + format, + mut variables, + mut style_variables, + } = formatter; + + // Add variables in meta variables to self + v.append(&mut variables); + sv.append(&mut style_variables); + + VariableValue::Meta(format) + }) + }); + + (v, sv) + }, + ); + + self.variables.extend(variables); + self.style_variables.extend(style_variables); + self } /// Maps variable name to an array of segments - pub fn map_variables_to_segments( - mut self, - mapper: impl Fn(&str) -> Option<Vec<Segment>> + Sync, - ) -> Self { - self.variables.par_iter_mut().for_each(|(key, value)| { - *value = mapper(key).map(VariableValue::Styled); - }); + /// + /// See `StringFormatter::map` for description on the parameters. + pub fn map_variables_to_segments<M>(mut self, mapper: M) -> Self + where + M: Fn(&str) -> Option<Result<Vec<Segment>, StringFormatterError>> + Sync, + { + self.variables + .par_iter_mut() + .filter(|(_, value)| value.is_none()) + .for_each(|(key, value)| { + *value = mapper(key).map(|var| var.map(VariableValue::Styled)); + }); + self + } + + /// Maps variable name in a style string to its value + /// + /// See `StringFormatter::map` for description on the parameters. + pub fn map_style<T, M>(mut self, mapper: M) -> Self + where + T: Into<Cow<'a, str>>, + M: Fn(&str) -> Option<Result<T, StringFormatterError>> + Sync, + { + self.style_variables + .par_iter_mut() + .filter(|(_, value)| value.is_none()) + .for_each(|(key, value)| { + *value = mapper(key).map(|var| var.map(|var| var.into())); + }); self } /// Parse the format string and consume self. - pub fn parse(self, default_style: Option<Style>) -> Vec<Segment> { + /// + /// This method will throw an Error in the following conditions: + /// + /// - Format string in meta variables fails to parse + /// - Variable mapper returns an error. + pub fn parse(self, default_style: Option<Style>) -> Result<Vec<Segment>, StringFormatterError> { fn _parse_textgroup<'a>( textgroup: TextGroup<'a>, - variables: &'a VariableMapType, - ) -> Vec<Segment> { - let style = _parse_style(textgroup.style); - _parse_format(textgroup.format, style, &variables) + variables: &'a VariableMapType<'a>, + style_variables: &'a StyleVariableMapType<'a>, + ) -> Result<Vec<Segment>, StringFormatterError> { + let style = _parse_style(textgroup.style, style_variables); + _parse_format( + textgroup.format, + style.transpose()?, + &variables, + &style_variables, + ) } - fn _parse_style(style: Vec<StyleElement>) -> Option<Style> { - let style_string = style - .iter() - .flat_map(|style| match style { - StyleElement::Text(text) => text.as_ref().chars(), - StyleElement::Variable(variable) => { - log::warn!( - "Variable `{}` monitored in style string, which is not allowed", - &variable - ); - "".chars() + fn _parse_style<'a>( + style: Vec<StyleElement>, + variables: &'a StyleVariableMapType<'a>, + ) -> Option<Result<Style, StringFormatterError>> { + let style_strings = style + .into_iter() + .map(|style| match style { + StyleElement::Text(text) => Ok(text), + StyleElement::Variable(name) => { + let variable = variables.get(name.as_ref()).unwrap_or(&None); + match variable { + Some(style_string) => style_string.clone().map(|string| string), + None => Ok("".into()), + } } }) - .collect::<String>(); - parse_style_string(&style_string) + .collect::<Result<Vec<Cow<str>>, StringFormatterError>>(); + style_strings + .map(|style_strings| { + let style_string: String = + style_strings.iter().flat_map(|s| s.chars()).collect(); + parse_style_string(&style_string) + }) + .transpose() } fn _parse_format<'a>( - mut format: Vec<FormatElement<'a>>, + format: Vec<FormatElement<'a>>, style: Option<Style>, - variables: &'a VariableMapType, - ) -> Vec<Segment> { - let mut result: Vec<Segment> = Vec::new(); - - format.reverse(); - while let Some(el) = format.pop() { - let mut segments = match el { - FormatElement::Text(text) => { - vec![_new_segment("_text".into(), text.into_owned(), style)] - } - FormatElement::TextGroup(textgroup) => { - let textgroup = TextGroup { - format: textgroup.format, - style: textgroup.style, - }; - _parse_textgroup(textgroup, &variables) - } - FormatElement::Variable(name) => variables - .get(name.as_ref()) - .map(|segments| { - let value = segments.clone().unwrap_or_default(); - match value { - VariableValue::Styled(segments) => segments + variables: &'a VariableMapType<'a>, + style_variables: &'a StyleVariableMapType<'a>, + ) -> Result<Vec<Segment>, StringFormatterError> { + let results: Result<Vec<Vec<Segment>>, StringFormatterError> = format + .into_iter() + .map(|el| { + match el { + FormatElement::Text(text) => Ok(vec![_new_segment("_text", text, style)]), + FormatElement::TextGroup(textgroup) => { + let textgroup = TextGroup { + format: textgroup.format, + style: textgroup.style, + }; + _parse_textgroup(textgroup, &variables, &style_variables) + } + FormatElement::Variable(name) => variables + .get(name.as_ref()) + .expect("Uncached variable found") + .as_ref() + .map(|segments| match segments.clone()? { + VariableValue::Styled(segments) => Ok(segments .into_iter() .map(|mut segment| { + // Derive upper style if the style of segments are none. if !segment.has_style() { if let Some(style) = style { segment.set_style(style); @@ -120,80 +278,124 @@ impl<'a> StringFormatter<'a> { } segment }) - .collect(), + .collect()), VariableValue::Plain(text) => { - vec![_new_segment(name.to_string(), text, style)] + Ok(vec![_new_segment(name, text, style)]) + } + VariableValue::Meta(format) => { + let formatter = StringFormatter { + format, + variables: _clone_without_meta(variables), + style_variables: style_variables.clone(), + }; + formatter.parse(style) } + }) + .unwrap_or_else(|| Ok(Vec::new())), + FormatElement::Conditional(format) => { + // Show the conditional format string if all the variables inside are not + // none. + fn _should_show_elements<'a>( + format_elements: &[FormatElement], + variables: &'a VariableMapType<'a>, + ) -> bool { + format_elements.get_variables().iter().any(|var| { + variables + .get(var.as_ref()) + .map(|map_result| { + let map_result = map_result.as_ref(); + map_result + .and_then(|result| result.as_ref().ok()) + .map(|result| match result { + // If the variable is a meta variable, also + // check the format string inside it. + VariableValue::Meta(meta_elements) => { + let meta_variables = + _clone_without_meta(variables); + _should_show_elements( + &meta_elements, + &meta_variables, + ) + } + _ => true, + }) + // The variable is None or Err, or a meta variable + // that shouldn't show + .unwrap_or(false) + }) + // Can't find the variable in format string + .unwrap_or(false) + }) } - }) - .unwrap_or_default(), - }; - result.append(&mut segments); - } - result + let should_show: bool = _should_show_elements(&format, variables); + + if should_show { + _parse_format(format, style, variables, style_variables) + } else { + Ok(Vec::new()) + } + } + } + }) + .collect(); + Ok(results?.into_iter().flatten().collect()) } - _parse_format(self.format, default_style, &self.variables) + _parse_format( + self.format, + default_style, + &self.variables, + &self.style_variables, + ) } } -/// Extract variable names from an array of `FormatElement` into a `BTreeMap` -fn _get_variables<'a>(format: &[FormatElement<'a>]) -> VariableMapType { - let mut variables: VariableMapType = Default::default(); - - fn _push_variables_from_textgroup<'a>( - variables: &mut VariableMapType, - textgroup: &'a TextGroup<'a>, - ) { - for el in &textgroup.format { - match el { - FormatElement::Variable(name) => _push_variable(variables, name.as_ref()), - FormatElement::TextGroup(textgroup) => { - _push_variables_from_textgroup(variables, &textgroup) - } - _ => {} - } - } - for el in &textgroup.style { - if let StyleElement::Variable(name) = el { - _push_variable(variables, name.as_ref()) - } - } - } - - fn _push_variable<'a>(variables: &mut VariableMapType, name: &'a str) { - variables.insert(name.to_owned(), None); +impl<'a> VariableHolder<String> for StringFormatter<'a> { + fn get_variables(&self) -> BTreeSet<String> { + BTreeSet::from_iter(self.variables.keys().cloned()) } +} - for el in format { - match el { - FormatElement::Variable(name) => _push_variable(&mut variables, name.as_ref()), - FormatElement::TextGroup(textgroup) => { - _push_variables_from_textgroup(&mut variables, &textgroup) - } - _ => {} - } +impl<'a> StyleVariableHolder<String> for StringFormatter<'a> { + fn get_style_variables(&self) -> BTreeSet<String> { + BTreeSet::from_iter(self.style_variables.keys().cloned()) } - - variables } /// Helper function to create a new segment -fn _new_segment(name: String, value: String, style: Option<Style>) -> Segment { +fn _new_segment( + name: impl Into<String>, + value: impl Into<String>, + style: Option<Style>, +) -> Segment { Segment { - _name: name, - value, + _name: name.into(), + value: value.into(), style, } } +fn _clone_without_meta<'a>(variables: &VariableMapType<'a>) -> VariableMapType<'a> { + VariableMapType::from_iter(variables.iter().map(|(key, value)| { + let value = match value { + Some(Ok(value)) => match value { + VariableValue::Meta(_) => None, + other => Some(Ok(other.clone())), + }, + Some(Err(e)) => Some(Err(e.clone())), + None => None, + }; + (key.clone(), value) + })) +} + #[cfg(test)] mod tests { use super::*; use ansi_term::Color; - // match_next(result: Iter<Segment>, value, style) + // match_next(result: IterMut<Segment>, value, style) macro_rules! match_next { ($iter:ident, $value:literal, $($style:tt)+) => { let _next = $iter.next().unwrap(); @@ -202,7 +404,7 @@ mod tests { } } - fn empty_mapper(_: &str) -> Option<String> { + fn empty_mapper(_: &str) -> Option<Result<String, StringFormatterError>> { None } @@ -212,7 +414,7 @@ mod tests { let style = Some(Color::Red.bold()); let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); - let result = formatter.parse(style); + let result = formatter.parse(style).unwrap(); let mut result_iter = result.iter(); match_next!(result_iter, "text", style); } @@ -221,7 +423,7 @@ mod tests { fn test_textgroup_text_only() { const FORMAT_STR: &str = "[text](red bold)"; let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); - let result = formatter.parse(None); + let result = formatter.parse(None).unwrap(); let mut result_iter = result.iter(); match_next!(result_iter, "text", Some(Color::Red.bold())); } @@ -233,20 +435,48 @@ mod tests { let formatter = StringFormatter::new(FORMAT_STR) .unwrap() .map(|variable| match variable { - "var1" => Some("text1".to_owned()), + "var1" => Some(Ok("text1".to_owned())), _ => None, }); - let result = formatter.parse(None); + let result = formatter.parse(None).unwrap(); let mut result_iter = result.iter(); match_next!(result_iter, "text1", None); } #[test] + fn test_variable_in_style() { + const FORMAT_STR: &str = "[root]($style)"; + let root_style = Some(Color::Red.bold()); + + let formatter = StringFormatter::new(FORMAT_STR) + .unwrap() + .map_style(|variable| match variable { + "style" => Some(Ok("red bold".to_owned())), + _ => None, + }); + let result = formatter.parse(None).unwrap(); + let mut result_iter = result.iter(); + match_next!(result_iter, "root", root_style); + } + + #[test] + fn test_scoped_variable() { + const FORMAT_STR: &str = "${env:PWD}"; + + let formatter = StringFormatter::new(FORMAT_STR) + .unwrap() + .map(|variable| Some(Ok(format!("${{{}}}", variable)))); + let result = formatter.parse(None).unwrap(); + let mut result_iter = result.iter(); + match_next!(result_iter, "${env:PWD}", None); + } + + #[test] fn test_escaped_chars() { const FORMAT_STR: &str = r#"\\\[\$text\]\(red bold\)"#; let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); - let result = formatter.parse(None); + let result = formatter.parse(None).unwrap(); let mut result_iter = result.iter(); match_next!(result_iter, r#"\[$text](red bold)"#, None); } @@ -259,7 +489,7 @@ mod tests { let inner_style = Some(Color::Blue.normal()); let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); - |