diff --git a/src/formatter/parser.rs b/src/formatter/parser.rs index 95c267a3a..3097cbabe 100644 --- a/src/formatter/parser.rs +++ b/src/formatter/parser.rs @@ -22,55 +22,45 @@ fn _parse_variable(variable: Pair) -> &str { } fn _parse_text(text: Pair) -> 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) -> Vec { - let mut result: Vec = 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))), + 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!(), - } - } - - result + }) + .collect() } fn _parse_style(style: Pair) -> Vec { - let mut result: Vec = 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, Error> { - let pairs = IdentParser::parse(Rule::expression, format)?; - let mut result: Vec = 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(|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() + }) } diff --git a/src/formatter/spec.pest b/src/formatter/spec.pest index 36be53be5..94d0b110b 100644 --- a/src/formatter/spec.pest +++ b/src/formatter/spec.pest @@ -1,16 +1,46 @@ +// 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 } -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)* } diff --git a/src/formatter/string_formatter.rs b/src/formatter/string_formatter.rs index 8f33ce9ef..62843f156 100644 --- a/src/formatter/string_formatter.rs +++ b/src/formatter/string_formatter.rs @@ -86,15 +86,13 @@ impl<'a> StringFormatter<'a> { } fn _parse_format<'a>( - mut format: Vec>, + format: Vec>, style: Option