summaryrefslogtreecommitdiffstats
path: root/src/formatter
diff options
context:
space:
mode:
authorZhenhui Xie <xiezh0831@yahoo.co.jp>2020-07-08 06:45:32 +0800
committerGitHub <noreply@github.com>2020-07-07 18:45:32 -0400
commitec76fafff08933f6f31fb99ea974bdb5ae97a0af (patch)
treebb2c822cdf291635f03d27677c419488ecf77f53 /src/formatter
parent0f52b7b12e8c1a2060aa873a68032937dfa2c044 (diff)
feat: refactor modules to use format strings (#1374)
Diffstat (limited to 'src/formatter')
-rw-r--r--src/formatter/mod.rs1
-rw-r--r--src/formatter/model.rs86
-rw-r--r--src/formatter/parser.rs71
-rw-r--r--src/formatter/spec.pest51
-rw-r--r--src/formatter/string_formatter.rs616
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);
-