fix(escaping): move escaping to individual variables (#3107)

This commit is contained in:
Fred Cox
2021-11-01 14:18:45 -07:00
committed by GitHub
parent 73277d37c6
commit c1f2d345aa
68 changed files with 259 additions and 175 deletions
+132 -22
View File
@@ -7,6 +7,7 @@ use std::error::Error;
use std::fmt;
use crate::config::parse_style_string;
use crate::context::{Context, Shell};
use crate::segment::Segment;
use super::model::*;
@@ -15,6 +16,7 @@ use super::parser::{parse, Rule};
#[derive(Clone)]
enum VariableValue<'a> {
Plain(Cow<'a, str>),
NoEscapingPlain(Cow<'a, str>),
Styled(Vec<Segment>),
Meta(Vec<FormatElement<'a>>),
}
@@ -123,6 +125,27 @@ impl<'a> StringFormatter<'a> {
self
}
/// Maps variable name into a value which is wrapped to prevent escaping later
///
/// This should be used for variables that should not be escaped before inclusion in the prompt
///
/// See `StringFormatter::map` for description on the parameters.
///
pub fn map_no_escaping<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::NoEscapingPlain(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
@@ -206,11 +229,16 @@ impl<'a> StringFormatter<'a> {
///
/// - 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> {
pub fn parse(
self,
default_style: Option<Style>,
context: Option<&Context>,
) -> Result<Vec<Segment>, StringFormatterError> {
fn parse_textgroup<'a>(
textgroup: TextGroup<'a>,
variables: &'a VariableMapType<'a>,
style_variables: &'a StyleVariableMapType<'a>,
context: Option<&Context>,
) -> Result<Vec<Segment>, StringFormatterError> {
let style = parse_style(textgroup.style, style_variables);
parse_format(
@@ -218,6 +246,7 @@ impl<'a> StringFormatter<'a> {
style.transpose()?,
variables,
style_variables,
context,
)
}
@@ -252,6 +281,7 @@ impl<'a> StringFormatter<'a> {
style: Option<Style>,
variables: &'a VariableMapType<'a>,
style_variables: &'a StyleVariableMapType<'a>,
context: Option<&Context>,
) -> Result<Vec<Segment>, StringFormatterError> {
let results: Result<Vec<Vec<Segment>>, StringFormatterError> = format
.into_iter()
@@ -263,7 +293,7 @@ impl<'a> StringFormatter<'a> {
format: textgroup.format,
style: textgroup.style,
};
parse_textgroup(textgroup, variables, style_variables)
parse_textgroup(textgroup, variables, style_variables, context)
}
FormatElement::Variable(name) => variables
.get(name.as_ref())
@@ -278,14 +308,26 @@ impl<'a> StringFormatter<'a> {
segment
})
.collect()),
VariableValue::Plain(text) => Ok(Segment::from_text(style, text)),
VariableValue::Plain(text) => Ok(Segment::from_text(
style,
shell_prompt_escape(
text,
match context {
None => Shell::Unknown,
Some(c) => c.shell,
},
),
)),
VariableValue::NoEscapingPlain(text) => {
Ok(Segment::from_text(style, text))
}
VariableValue::Meta(format) => {
let formatter = StringFormatter {
format,
variables: clone_without_meta(variables),
style_variables: style_variables.clone(),
};
formatter.parse(style)
formatter.parse(style, context)
}
})
.unwrap_or_else(|| Ok(Vec::new())),
@@ -320,6 +362,9 @@ impl<'a> StringFormatter<'a> {
VariableValue::Plain(plain_value) => {
!plain_value.is_empty()
}
VariableValue::NoEscapingPlain(
no_escaping_plain_value,
) => !no_escaping_plain_value.is_empty(),
VariableValue::Styled(segments) => segments
.iter()
.any(|x| !x.value().is_empty()),
@@ -331,7 +376,7 @@ impl<'a> StringFormatter<'a> {
let should_show: bool = should_show_elements(&format, variables);
if should_show {
parse_format(format, style, variables, style_variables)
parse_format(format, style, variables, style_variables, context)
} else {
Ok(Vec::new())
}
@@ -347,6 +392,7 @@ impl<'a> StringFormatter<'a> {
default_style,
&self.variables,
&self.style_variables,
context,
)
}
}
@@ -380,6 +426,28 @@ fn clone_without_meta<'a>(variables: &VariableMapType<'a>) -> VariableMapType<'a
.collect()
}
/// Escape interpretable characters for the shell prompt
pub fn shell_prompt_escape<T>(text: T, shell: Shell) -> String
where
T: Into<String>,
{
// Handle other interpretable characters
match shell {
// Bash might interepret baskslashes, backticks and $
// see #658 for more details
Shell::Bash => text
.into()
.replace('\\', r"\\")
.replace('$', r"\$")
.replace('`', r"\`"),
Shell::Zsh => {
// % is an escape in zsh, see PROMPT in `man zshmisc`
text.into().replace('%', "%%")
}
_ => text.into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -404,7 +472,7 @@ mod tests {
let style = Some(Color::Red.bold());
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let result = formatter.parse(style).unwrap();
let result = formatter.parse(style, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "text", style);
}
@@ -413,7 +481,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).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "text", Some(Color::Red.bold()));
}
@@ -428,7 +496,7 @@ mod tests {
"var1" => Some(Ok("text1".to_owned())),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "text1", None);
}
@@ -444,7 +512,7 @@ mod tests {
"style" => Some(Ok("red bold".to_owned())),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "root", root_style);
}
@@ -456,7 +524,7 @@ mod tests {
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map(|variable| Some(Ok(format!("${{{}}}", variable))));
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "${env:PWD}", None);
}
@@ -466,7 +534,7 @@ mod tests {
const FORMAT_STR: &str = r#"\\\[\$text\]\(red bold\)"#;
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, r#"\[$text](red bold)"#, None);
}
@@ -479,7 +547,7 @@ mod tests {
let inner_style = Some(Color::Blue.normal());
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let result = formatter.parse(outer_style).unwrap();
let result = formatter.parse(outer_style, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "outer ", outer_style);
match_next!(result_iter, "middle ", middle_style);
@@ -497,7 +565,7 @@ mod tests {
"var" => Some(Ok("text".to_owned())),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "text", var_style);
}
@@ -523,7 +591,7 @@ mod tests {
"var" => Some(Ok(segments.clone())),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "styless", var_style);
match_next!(result_iter, "styled", styled_style);
@@ -546,7 +614,7 @@ mod tests {
"b" => Some(Ok("$b")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$a", None);
match_next!(result_iter, "$b", None);
@@ -568,7 +636,7 @@ mod tests {
"c" => Some(Ok("$c")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$a", None);
match_next!(result_iter, "$b", None);
@@ -585,7 +653,7 @@ mod tests {
"some" => Some(Ok("$some")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$some", None);
match_next!(result_iter, " should render but ", None);
@@ -602,7 +670,7 @@ mod tests {
"empty" => Some(Ok("")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
assert_eq!(result.len(), 0);
}
@@ -616,7 +684,7 @@ mod tests {
"empty" => Some(Ok("")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
assert_eq!(result.len(), 0);
}
@@ -630,7 +698,7 @@ mod tests {
"some" => Some(Ok("$some")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$some", None);
match_next!(result_iter, " ", None);
@@ -649,7 +717,7 @@ mod tests {
"all" => Some("$some"),
_ => None,
});
let result = formatter.parse(None).unwrap();
let result = formatter.parse(None, None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, " ", None);
}
@@ -703,8 +771,50 @@ mod tests {
"never" => Some(Err(never_error.clone())),
_ => None,
})
.parse(None)
.parse(None, None)
});
assert!(segments.is_err());
}
#[test]
fn test_bash_escape() {
let test = "$(echo a)";
assert_eq!(
shell_prompt_escape(test.to_owned(), Shell::Bash),
r"\$(echo a)"
);
assert_eq!(
shell_prompt_escape(test.to_owned(), Shell::PowerShell),
test
);
let test = r"\$(echo a)";
assert_eq!(
shell_prompt_escape(test.to_owned(), Shell::Bash),
r"\\\$(echo a)"
);
assert_eq!(
shell_prompt_escape(test.to_owned(), Shell::PowerShell),
test
);
let test = r"`echo a`";
assert_eq!(
shell_prompt_escape(test.to_owned(), Shell::Bash),
r"\`echo a\`"
);
assert_eq!(
shell_prompt_escape(test.to_owned(), Shell::PowerShell),
test
);
}
#[test]
fn test_zsh_escape() {
let test = "10%";
assert_eq!(shell_prompt_escape(test.to_owned(), Shell::Zsh), "10%%");
assert_eq!(
shell_prompt_escape(test.to_owned(), Shell::PowerShell),
test
);
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ impl<'a> VersionFormatter<'a> {
},
_ => None,
})
.parse(None);
.parse(None, None);
formatted.map(|segments| {
segments
+1 -1
View File
@@ -164,7 +164,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"duration" => duration.as_ref().map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -52,7 +52,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
_ => None,
});
match formatter.parse(None) {
match formatter.parse(None, Some(context)) {
Ok(format_string) => {
module.set_segments(format_string);
Some(module)
+1 -1
View File
@@ -53,7 +53,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"symbol" => Some(symbol),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -37,7 +37,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"duration" => Some(Ok(render_time(elapsed, config.show_milliseconds))),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -37,7 +37,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"environment" => Some(Ok(conda_env.as_str())),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -44,7 +44,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+2 -2
View File
@@ -56,7 +56,7 @@ pub fn module<'a>(name: &str, context: &'a Context) -> Option<Module<'a>> {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
.map_no_escaping(|variable| match variable {
"output" => {
let output = exec_command(config.command, &config.shell.0)?;
let trimmed = output.trim();
@@ -69,7 +69,7 @@ pub fn module<'a>(name: &str, context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
match parsed {
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -113,7 +113,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -71,7 +71,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"context" => Some(Ok(ctx.as_str())),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -74,7 +74,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"tfm" => find_current_tfm(&dotnet_files).map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -55,7 +55,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -68,7 +68,7 @@ fn env_var_module<'a>(module_config_path: Vec<&str>, context: &'a Context) -> Op
"env_value" => Some(Ok(&env_value)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -151,7 +151,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"active" => Some(Ok(gcloud_context.config_name.clone())),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -95,7 +95,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -68,7 +68,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"tag" => format_tag(config.tag_symbol, &tag_name),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -50,7 +50,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"deleted" => GitDiff::get_variable(config.only_nonzero_diffs, stats.deleted),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -34,7 +34,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"progress_total" => state_description.total.as_ref().map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+44 -26
View File
@@ -49,49 +49,52 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let info = Arc::clone(&info);
let segments = match variable {
"stashed" => info.get_stashed().and_then(|count| {
format_count(config.stashed, "git_status.stashed", count)
format_count(config.stashed, "git_status.stashed", context, count)
}),
"ahead_behind" => info.get_ahead_behind().and_then(|(ahead, behind)| {
let (ahead, behind) = (ahead?, behind?);
if ahead > 0 && behind > 0 {
format_text(config.diverged, "git_status.diverged", |variable| {
match variable {
format_text(
config.diverged,
"git_status.diverged",
context,
|variable| match variable {
"ahead_count" => Some(ahead.to_string()),
"behind_count" => Some(behind.to_string()),
_ => None,
}
})
},
)
} else if ahead > 0 && behind == 0 {
format_count(config.ahead, "git_status.ahead", ahead)
format_count(config.ahead, "git_status.ahead", context, ahead)
} else if behind > 0 && ahead == 0 {
format_count(config.behind, "git_status.behind", behind)
format_count(config.behind, "git_status.behind", context, behind)
} else {
format_symbol(config.up_to_date, "git_status.up_to_date")
format_symbol(config.up_to_date, "git_status.up_to_date", context)
}
}),
"conflicted" => info.get_conflicted().and_then(|count| {
format_count(config.conflicted, "git_status.conflicted", count)
format_count(config.conflicted, "git_status.conflicted", context, count)
}),
"deleted" => info.get_deleted().and_then(|count| {
format_count(config.deleted, "git_status.deleted", count)
format_count(config.deleted, "git_status.deleted", context, count)
}),
"renamed" => info.get_renamed().and_then(|count| {
format_count(config.renamed, "git_status.renamed", count)
format_count(config.renamed, "git_status.renamed", context, count)
}),
"modified" => info.get_modified().and_then(|count| {
format_count(config.modified, "git_status.modified", count)
format_count(config.modified, "git_status.modified", context, count)
}),
"staged" => info.get_staged().and_then(|count| {
format_count(config.staged, "git_status.staged", context, count)
}),
"staged" => info
.get_staged()
.and_then(|count| format_count(config.staged, "git_status.staged", count)),
"untracked" => info.get_untracked().and_then(|count| {
format_count(config.untracked, "git_status.untracked", count)
format_count(config.untracked, "git_status.untracked", context, count)
}),
_ => None,
};
segments.map(Ok)
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
@@ -288,14 +291,19 @@ impl RepoStatus {
}
}
fn format_text<F>(format_str: &str, config_path: &str, mapper: F) -> Option<Vec<Segment>>
fn format_text<F>(
format_str: &str,
config_path: &str,
context: &Context,
mapper: F,
) -> Option<Vec<Segment>>
where
F: Fn(&str) -> Option<String> + Send + Sync,
{
if let Ok(formatter) = StringFormatter::new(format_str) {
formatter
.map(|variable| mapper(variable).map(Ok))
.parse(None)
.parse(None, Some(context))
.ok()
} else {
log::warn!("Error parsing format string `{}`", &config_path);
@@ -303,19 +311,29 @@ where
}
}
fn format_count(format_str: &str, config_path: &str, count: usize) -> Option<Vec<Segment>> {
fn format_count(
format_str: &str,
config_path: &str,
context: &Context,
count: usize,
) -> Option<Vec<Segment>> {
if count == 0 {
return None;
}
format_text(format_str, config_path, |variable| match variable {
"count" => Some(count.to_string()),
_ => None,
})
format_text(
format_str,
config_path,
context,
|variable| match variable {
"count" => Some(count.to_string()),
_ => None,
},
)
}
fn format_symbol(format_str: &str, config_path: &str) -> Option<Vec<Segment>> {
format_text(format_str, config_path, |_variable| None)
fn format_symbol(format_str: &str, config_path: &str, context: &Context) -> Option<Vec<Segment>> {
format_text(format_str, config_path, context, |_variable| None)
}
#[cfg(test)]
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -46,7 +46,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -60,7 +60,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"branch" => Some(Ok(truncated_and_symbol.as_str())),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -51,7 +51,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"hostname" => Some(Ok(host)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -45,7 +45,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -88,7 +88,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"number" => Some(Ok(module_number.clone())),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -46,7 +46,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -104,7 +104,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"namespace" => kube_ns.as_ref().map(|s| Ok(Cow::Borrowed(s.as_str()))),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -45,7 +45,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -86,7 +86,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"swap_pct" if total_swap_kib > 0 => Some(Ok(&swap_pct)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -44,7 +44,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -46,7 +46,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"name" => shell_name.as_ref().map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -73,7 +73,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -73,7 +73,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -69,7 +69,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"project" => osp_project.as_ref().map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -29,7 +29,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"version" => Some(Ok(&module_version)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"stack" => stack_name(&project_file, context).map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
match parsed {
+1 -1
View File
@@ -41,7 +41,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -61,7 +61,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"pyenv_prefix" => Some(Ok(pyenv_prefix.to_string())),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -41,7 +41,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -41,7 +41,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"version" => get_module_version(context, &config).map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -46,7 +46,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"unknown_indicator" => Some(Ok(config.unknown_indicator)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -47,7 +47,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"shlvl" => Some(Ok(shlvl_str)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -26,7 +26,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"env" => Some(Ok(&singularity_env)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+4 -3
View File
@@ -61,7 +61,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
PipeStatusStatus::Pipe(pipestatus) => pipestatus
.iter()
.map(
|ec| match format_exit_code(ec.as_str(), config.format, None, &config) {
|ec| match format_exit_code(ec.as_str(), config.format, None, &config, context) {
Ok(segments) => segments
.into_iter()
.map(|s| s.to_string())
@@ -79,7 +79,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
PipeStatusStatus::Pipe(_) => config.pipestatus_format,
_ => config.format,
};
let parsed = format_exit_code(exit_code, main_format, Some(&pipestatus), &config);
let parsed = format_exit_code(exit_code, main_format, Some(&pipestatus), &config, context);
module.set_segments(match parsed {
Ok(segments) => segments,
@@ -96,6 +96,7 @@ fn format_exit_code<'a>(
format: &'a str,
pipestatus: Option<&str>,
config: &'a StatusConfig,
context: &'a Context,
) -> Result<Vec<Segment>, StringFormatterError> {
let exit_code_int: ExitCode = match exit_code.parse() {
Ok(i) => i,
@@ -164,7 +165,7 @@ fn format_exit_code<'a>(
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
})
}
+1 -1
View File
@@ -43,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -49,7 +49,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"workspace" => get_terraform_workspace(context).map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -54,7 +54,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"time" => Some(Ok(&formatted_time_string)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -48,7 +48,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"user" => Some(Ok(&username)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
Ok(segments) => segments,
+1 -1
View File
@@ -44,7 +44,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -30,7 +30,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
"repo" => Some(Ok(&repo)),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
.map(Ok),
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
_ => None,
})
.parse(None)
.parse(None, Some(context))
});
module.set_segments(match parsed {
+1 -1
View File
@@ -109,7 +109,7 @@ pub fn get_prompt(context: Context) -> String {
let mut root_module = Module::new("Starship Root", "The root module", None);
root_module.set_segments(
formatter
.parse(None)
.parse(None, Some(&context))
.expect("Unexpected error returned in root format variables"),
);
+2 -60
View File
@@ -318,25 +318,8 @@ CMake suite maintained and supported by Kitware (kitware.com/cmake).\n",
}
}
/// Wraps ANSI color escape sequences and other interpretable characters
/// that need to be escaped in the shell-appropriate wrappers.
pub fn wrap_colorseq_for_shell(mut ansi: String, shell: Shell) -> String {
// Handle other interpretable characters
match shell {
// Bash might interepret baskslashes, backticks and $
// see #658 for more details
Shell::Bash => {
ansi = ansi.replace('\\', r"\\");
ansi = ansi.replace('$', r"\$");
ansi = ansi.replace('`', r"\`");
}
Shell::Zsh => {
// % is an escape in zsh, see PROMPT in `man zshmisc`
ansi = ansi.replace('%', "%%");
}
_ => {}
};
/// Wraps ANSI color escape sequences in the shell-appropriate wrappers.
pub fn wrap_colorseq_for_shell(ansi: String, shell: Shell) -> String {
const ESCAPE_BEGIN: char = '\u{1b}';
const ESCAPE_END: char = 'm';
wrap_seq_for_shell(ansi, shell, ESCAPE_BEGIN, ESCAPE_END)
@@ -674,47 +657,6 @@ mod tests {
assert_eq!(&bresult5, "");
}
#[test]
fn test_bash_escape() {
let test = "$(echo a)";
assert_eq!(
wrap_colorseq_for_shell(test.to_owned(), Shell::Bash),
r"\$(echo a)"
);
assert_eq!(
wrap_colorseq_for_shell(test.to_owned(), Shell::PowerShell),
test
);
let test = r"\$(echo a)";
assert_eq!(
wrap_colorseq_for_shell(test.to_owned(), Shell::Bash),
r"\\\$(echo a)"
);
assert_eq!(
wrap_colorseq_for_shell(test.to_owned(), Shell::PowerShell),
test
);
let test = r"`echo a`";
assert_eq!(
wrap_colorseq_for_shell(test.to_owned(), Shell::Bash),
r"\`echo a\`"
);
assert_eq!(
wrap_colorseq_for_shell(test.to_owned(), Shell::PowerShell),
test
);
}
#[test]
fn test_zsh_escape() {
let test = "10%";
assert_eq!(wrap_colorseq_for_shell(test.to_owned(), Shell::Zsh), "10%%");
assert_eq!(
wrap_colorseq_for_shell(test.to_owned(), Shell::PowerShell),
test
);
}
#[test]
fn test_get_command_string_output() {
let case1 = CommandOutput {