mirror of
https://github.com/starship/starship.git
synced 2024-12-24 16:18:53 +01:00
feat(formatter): Allow scoped variables (#1094)
* feat: Allow scoped variables , with the following improvements to the format string parser. - Add documentation to spec - Simplify some syntax in the spec - Rewrite for loop with iterators
This commit is contained in:
parent
1ab2ba9e4d
commit
610e63ce4f
@ -22,55 +22,45 @@ 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))),
|
||||
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<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(|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()
|
||||
})
|
||||
}
|
||||
|
@ -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)* }
|
||||
|
@ -86,15 +86,13 @@ impl<'a> StringFormatter<'a> {
|
||||
}
|
||||
|
||||
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 {
|
||||
format
|
||||
.into_iter()
|
||||
.flat_map(|el| match el {
|
||||
FormatElement::Text(text) => {
|
||||
vec![_new_segment("_text".into(), text.into_owned(), style)]
|
||||
}
|
||||
@ -127,11 +125,8 @@ impl<'a> StringFormatter<'a> {
|
||||
}
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
result.append(&mut segments);
|
||||
}
|
||||
|
||||
result
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
_parse_format(self.format, default_style, &self.variables)
|
||||
@ -241,6 +236,18 @@ mod tests {
|
||||
match_next!(result_iter, "text1", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scoped_variable() {
|
||||
const FORMAT_STR: &str = "${env:PWD}";
|
||||
|
||||
let formatter = StringFormatter::new(FORMAT_STR)
|
||||
.unwrap()
|
||||
.map(|variable| Some(format!("${{{}}}", variable)));
|
||||
let result = formatter.parse(None);
|
||||
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\)"#;
|
||||
|
Loading…
Reference in New Issue
Block a user