diff --git a/src/formatter/model.rs b/src/formatter/model.rs index 8e4bbea39..fc45104ae 100644 --- a/src/formatter/model.rs +++ b/src/formatter/model.rs @@ -1,4 +1,9 @@ use std::borrow::Cow; +use std::collections::BTreeSet; + +pub trait VariableHolder { + fn get_variables(&self) -> BTreeSet; +} pub struct TextGroup<'a> { pub format: Vec>, @@ -9,9 +14,34 @@ pub enum FormatElement<'a> { Text(Cow<'a, str>), Variable(Cow<'a, str>), TextGroup(TextGroup<'a>), + Positional(Vec>), } pub enum StyleElement<'a> { Text(Cow<'a, str>), Variable(Cow<'a, str>), } + +impl<'a> VariableHolder> for FormatElement<'a> { + fn get_variables(&self) -> BTreeSet> { + match self { + FormatElement::Variable(var) => { + let mut variables = BTreeSet::new(); + variables.insert(var.clone()); + variables + } + FormatElement::TextGroup(textgroup) => textgroup.format.get_variables(), + FormatElement::Positional(format) => format.get_variables(), + _ => Default::default(), + } + } +} + +impl<'a> VariableHolder> for Vec> { + fn get_variables(&self) -> BTreeSet> { + self.iter().fold(BTreeSet::new(), |mut acc, el| { + acc.extend(el.get_variables()); + acc + }) + } +} diff --git a/src/formatter/parser.rs b/src/formatter/parser.rs index 3097cbabe..daba5e6d2 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) -> 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::positional => { + FormatElement::Positional(_parse_format(value.into_inner().next().unwrap())) + } + _ => unreachable!(), + } +} + fn _parse_textgroup(textgroup: Pair) -> TextGroup { let mut inner_rules = textgroup.into_inner(); let format = inner_rules.next().unwrap(); @@ -29,15 +41,7 @@ fn _parse_text(text: Pair) -> String { } fn _parse_format(format: Pair) -> Vec { - format - .into_inner() - .map(|pair| match pair.as_rule() { - Rule::text => FormatElement::Text(_parse_text(pair).into()), - Rule::variable => FormatElement::Variable(_parse_variable(pair).into()), - Rule::textgroup => FormatElement::TextGroup(_parse_textgroup(pair)), - _ => unreachable!(), - }) - .collect() + format.into_inner().map(_parse_value).collect() } fn _parse_style(style: Pair) -> Vec { @@ -55,12 +59,7 @@ pub fn parse(format: &str) -> Result, Error> { IdentParser::parse(Rule::expression, format).map(|pairs| { pairs .take_while(|pair| pair.as_rule() != Rule::EOI) - .map(|pair| match pair.as_rule() { - Rule::text => FormatElement::Text(_parse_text(pair).into()), - Rule::variable => FormatElement::Variable(_parse_variable(pair).into()), - Rule::textgroup => FormatElement::TextGroup(_parse_textgroup(pair)), - _ => unreachable!(), - }) + .map(_parse_value) .collect() }) } diff --git a/src/formatter/spec.pest b/src/formatter/spec.pest index 94d0b110b..5ce59fc85 100644 --- a/src/formatter/spec.pest +++ b/src/formatter/spec.pest @@ -4,7 +4,7 @@ // // 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 | positional } // Variable // @@ -44,3 +44,8 @@ escaped_char = { "[" | "]" | "(" | ")" | "\\" | "$" } textgroup = { "[" ~ format ~ "]" ~ "(" ~ style ~ ")" } format = { value* } style = { (variable | string)* } + +// Positional +// +// A positional format string that won't render if all the containing variables are empty. +positional = { "(" ~ format ~ ")" } diff --git a/src/formatter/string_formatter.rs b/src/formatter/string_formatter.rs index 62843f156..a0e198d25 100644 --- a/src/formatter/string_formatter.rs +++ b/src/formatter/string_formatter.rs @@ -2,6 +2,7 @@ use ansi_term::Style; use pest::error::Error; use rayon::prelude::*; use std::collections::BTreeMap; +use std::iter::FromIterator; use crate::config::parse_style_string; use crate::segment::Segment; @@ -33,16 +34,22 @@ impl<'a> StringFormatter<'a> { pub fn new(format: &'a str) -> Result> { parse(format) .map(|format| { - let variables = _get_variables(&format); + let variables = VariableMapType::from_iter( + format + .get_variables() + .into_iter() + .map(|key| (key.to_string(), None)) + .collect::)>>(), + ); (format, variables) }) .map(|(format, variables)| Self { format, variables }) } /// Maps variable name to its value - pub fn map(mut self, mapper: impl Fn(&str) -> Option + Sync) -> Self { + pub fn map>(mut self, mapper: impl Fn(&str) -> Option + Sync) -> Self { self.variables.par_iter_mut().for_each(|(key, value)| { - *value = mapper(key).map(VariableValue::Plain); + *value = mapper(key).map(|var| var.into()).map(VariableValue::Plain); }); self } @@ -105,9 +112,9 @@ impl<'a> StringFormatter<'a> { } FormatElement::Variable(name) => variables .get(name.as_ref()) - .map(|segments| { - let value = segments.clone().unwrap_or_default(); - match value { + .and_then(|segments| { + let value = segments.clone()?; + Some(match value { VariableValue::Styled(segments) => segments .into_iter() .map(|mut segment| { @@ -122,9 +129,23 @@ impl<'a> StringFormatter<'a> { VariableValue::Plain(text) => { vec![_new_segment(name.to_string(), text, style)] } - } + }) }) .unwrap_or_default(), + FormatElement::Positional(format) => { + let should_show: bool = format.get_variables().iter().any(|var| { + variables + .get(var.as_ref()) + .map(|segments| segments.is_some()) + .unwrap_or(false) + }); + + if should_show { + _parse_format(format, style, variables) + } else { + Vec::new() + } + } }) .collect() } @@ -133,47 +154,6 @@ impl<'a> StringFormatter<'a> { } } -/// 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); - } - - 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) - } - _ => {} - } - } - - variables -} - /// Helper function to create a new segment fn _new_segment(name: String, value: String, style: Option