Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
enum CurrentQuoteStrategy {
|
||||
None,
|
||||
Single,
|
||||
Double,
|
||||
AnsiC,
|
||||
}
|
||||
|
||||
/// Unescape alias outputs in single quoting and ANSI-C quoting format.
|
||||
/// For single quoting, we should take the literal meaning of all characters within
|
||||
/// the quoting. For ANSI-C quoting, we need to translate escape sequences to their
|
||||
/// unicode values.
|
||||
///
|
||||
/// Note that we don't unescape double quotes here as they require the knowledge of
|
||||
/// shell variables' values. E.g. If we have the following string `echo "'$apple'"`,
|
||||
/// we could unescape it without knowing what the value of $apple.
|
||||
pub fn unescape_quotes(s: &str) -> Result<String> {
|
||||
let mut current_quoting = CurrentQuoteStrategy::None;
|
||||
|
||||
let mut chars = s.chars().enumerate().peekable();
|
||||
let mut res = String::with_capacity(s.len());
|
||||
|
||||
while let Some((idx, c)) = chars.next() {
|
||||
match (c, ¤t_quoting) {
|
||||
// If in single / Ansi-C quote, end the quote escaping.
|
||||
('\'', CurrentQuoteStrategy::Single | CurrentQuoteStrategy::AnsiC) => {
|
||||
current_quoting = CurrentQuoteStrategy::None
|
||||
}
|
||||
('\'', CurrentQuoteStrategy::None) => current_quoting = CurrentQuoteStrategy::Single,
|
||||
('\"', CurrentQuoteStrategy::Double) => current_quoting = CurrentQuoteStrategy::None,
|
||||
('\"', CurrentQuoteStrategy::None) => current_quoting = CurrentQuoteStrategy::Double,
|
||||
('\\', CurrentQuoteStrategy::AnsiC) => {
|
||||
match chars.next() {
|
||||
None => {
|
||||
return Err(anyhow!("invalid escape at char {} in string {}", idx, s));
|
||||
}
|
||||
Some((_, next_character)) => {
|
||||
// Referenced from the table here: https://en.wikipedia.org/wiki/Escape_sequences_in_C
|
||||
res.push(match next_character {
|
||||
'a' => '\u{07}',
|
||||
'b' => '\u{08}',
|
||||
'e' | 'E' => '\u{1B}',
|
||||
'f' => '\u{0C}',
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'v' => '\u{0B}',
|
||||
// TODO(kevin): Need to add escaping to unicode
|
||||
// characters here. But this should be rare.
|
||||
next_character => next_character,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
('\\', CurrentQuoteStrategy::Double) => {
|
||||
match chars.next() {
|
||||
None => {
|
||||
return Err(anyhow!("invalid escape at char {} in string {}", idx, s));
|
||||
}
|
||||
Some((_, next_character)) => {
|
||||
// The backslash retains special meaning when followed by
|
||||
// ‘$’, ‘`’, ‘"’, ‘\’. Otherwise is treated as a literal.
|
||||
// Referenced from here:
|
||||
// https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html
|
||||
match next_character {
|
||||
'$' | '`' | '"' | '\\' => res.push(next_character),
|
||||
// Newlines are ignored after a backslash, see:
|
||||
// https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Escape-Character
|
||||
'\n' => {}
|
||||
_ => {
|
||||
res.push('\\');
|
||||
res.push(next_character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
('\\', CurrentQuoteStrategy::None) => match chars.next() {
|
||||
None => {
|
||||
return Err(anyhow!("invalid escape at char {} in string {}", idx, s));
|
||||
}
|
||||
Some((_, next_character)) => match next_character {
|
||||
// Newlines are ignored after a backslash, see:
|
||||
// https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Escape-Character
|
||||
'\n' => {}
|
||||
_ => res.push(next_character),
|
||||
},
|
||||
},
|
||||
('$', CurrentQuoteStrategy::None) => {
|
||||
match chars.peek() {
|
||||
// ANSI-C quoting starts with $'
|
||||
Some((_, '\'')) => {
|
||||
current_quoting = CurrentQuoteStrategy::AnsiC;
|
||||
chars.next();
|
||||
}
|
||||
_ => res.push('$'),
|
||||
}
|
||||
}
|
||||
_ => res.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unescape_quotes() {
|
||||
assert_eq!(unescape_quotes("東方").unwrap(), "東方".to_string());
|
||||
assert_eq!(unescape_quotes(r#"$'\"\"'"#).unwrap(), r#""""#.to_string());
|
||||
assert_eq!(unescape_quotes(r#"'"'"#).unwrap(), r#"""#.to_string());
|
||||
assert_eq!(
|
||||
unescape_quotes(r#"$'foo"barbaz\'quux'"#).unwrap(),
|
||||
r#"foo"barbaz'quux"#.to_string()
|
||||
);
|
||||
// Every escape between ANSI-C quoting.
|
||||
assert_eq!(
|
||||
unescape_quotes(r"$'\a\b\v\f\n\r\t\e\E'").unwrap(),
|
||||
"\u{07}\u{08}\u{0b}\u{0c}\u{0a}\u{0d}\u{09}\u{1b}\u{1b}".to_string()
|
||||
);
|
||||
// Failure case when the escape character is at end of string.
|
||||
assert!(unescape_quotes(r"$'\").is_err());
|
||||
|
||||
// Chars following escape chars should be taken literally when not currently
|
||||
// in a quote strategy.
|
||||
assert_eq!(
|
||||
unescape_quotes(r"'echo '\''hello\nworld'\'").unwrap(),
|
||||
"echo 'hello\\nworld'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unescape_double_quotes() {
|
||||
assert_eq!(unescape_quotes("\"hello world\"").unwrap(), "hello world");
|
||||
assert_eq!(unescape_quotes(r#""hello\$world""#).unwrap(), "hello$world");
|
||||
assert_eq!(unescape_quotes(r#""hello\`world""#).unwrap(), "hello`world");
|
||||
assert_eq!(
|
||||
unescape_quotes(r#""hello\"world""#).unwrap(),
|
||||
"hello\"world"
|
||||
);
|
||||
assert_eq!(
|
||||
unescape_quotes(r#""hello\\world""#).unwrap(),
|
||||
"hello\\world"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unescape_double_quotes_nonspecial_chars() {
|
||||
assert_eq!(
|
||||
unescape_quotes(r#""hello\aworld""#).unwrap(),
|
||||
r"hello\aworld"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unescape_backslash_with_newline() {
|
||||
// With no quoting strategy.
|
||||
assert_eq!(unescape_quotes("hello\\\nworld").unwrap(), "helloworld");
|
||||
// With double quotes.
|
||||
assert_eq!(unescape_quotes("\"hello\\\nworld\"").unwrap(), "helloworld");
|
||||
}
|
||||
Reference in New Issue
Block a user