Skip to content

Commit

Permalink
additional escaped chars support for c quoting, tests and comments
Browse files Browse the repository at this point in the history
  • Loading branch information
matrixhead committed Jul 10, 2024
1 parent 5096348 commit f9141fb
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 13 deletions.
83 changes: 71 additions & 12 deletions src/uucore/src/lib/features/quoting_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl EscapedChar {
}
}

fn new_c(c: char, quotes: Quotes) -> Self {
fn new_c(c: char, quotes: Quotes, force_escape: &[char]) -> Self {
use EscapeState::*;
let init_state = match c {
'\x07' => Backslash('a'),
Expand All @@ -147,6 +147,7 @@ impl EscapedChar {
_ => Char(' '),
},
_ if c.is_ascii_control() => Octal(EscapeOctal::from(c)),
_ if force_escape.contains(&c) => Backslash(c),
_ => Char(c),
};
Self { state: init_state }
Expand Down Expand Up @@ -285,7 +286,15 @@ fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) {
}

/// Escape a name according to the given quoting style.
pub fn escape_name(name: &OsStr, style: &QuotingStyle, quote_if_contains: &[char]) -> String {
/// # Arguments
/// * `name` - The name to be escaped.
/// * `style` - The quoting style to use for escaping the name
/// * `additional_escaped_chars` - A list of extra characters that should be treated as escaped chars for a specific context.
pub fn escape_name(
name: &OsStr,
style: &QuotingStyle,
additional_escaped_chars: &[char],
) -> String {
match style {
QuotingStyle::Literal { show_control } => {
if *show_control {
Expand All @@ -301,7 +310,7 @@ pub fn escape_name(name: &OsStr, style: &QuotingStyle, quote_if_contains: &[char
let escaped_str: String = name
.to_string_lossy()
.chars()
.flat_map(|c| EscapedChar::new_c(c, *quotes))
.flat_map(|c| EscapedChar::new_c(c, *quotes, additional_escaped_chars))
.collect();

match quotes {
Expand All @@ -316,9 +325,9 @@ pub fn escape_name(name: &OsStr, style: &QuotingStyle, quote_if_contains: &[char
show_control,
} => {
let name = name.to_string_lossy();
let (quotes, must_quote) = if name
.contains(|v| ['"', '`', '$', '\\'].contains(&v) || quote_if_contains.contains(&v))
{
let (quotes, must_quote) = if name.contains(|v| {
['"', '`', '$', '\\'].contains(&v) || additional_escaped_chars.contains(&v)
}) {
(Quotes::Single, true)
} else if name.contains('\'') {
(Quotes::Double, true)
Expand Down Expand Up @@ -431,10 +440,14 @@ mod tests {
}
}

fn check_names(name: &str, map: &[(&str, &str)]) {
fn check_names(name: &str, map: &[(&str, &str)], additional_escaped_chars: &[char]) {
assert_eq!(
map.iter()
.map(|(_, style)| escape_name(name.as_ref(), &get_style(style), &[]))
.map(|(_, style)| escape_name(
name.as_ref(),
&get_style(style),
additional_escaped_chars
))
.collect::<Vec<String>>(),
map.iter()
.map(|(correct, _)| correct.to_string())
Expand All @@ -458,6 +471,7 @@ mod tests {
("one_two", "shell-escape"),
("\'one_two\'", "shell-escape-always"),
],
&[],
);
}

Expand All @@ -477,6 +491,7 @@ mod tests {
("\'one two\'", "shell-escape"),
("\'one two\'", "shell-escape-always"),
],
&[],
);

check_names(
Expand All @@ -493,6 +508,7 @@ mod tests {
("' one'", "shell-escape"),
("' one'", "shell-escape-always"),
],
&[],
);
}

Expand All @@ -513,6 +529,7 @@ mod tests {
("'one\"two'", "shell-escape"),
("'one\"two'", "shell-escape-always"),
],
&[],
);

// One single quote
Expand All @@ -530,6 +547,7 @@ mod tests {
("\"one'two\"", "shell-escape"),
("\"one'two\"", "shell-escape-always"),
],
&[],
);

// One single quote and one double quote
Expand All @@ -547,6 +565,7 @@ mod tests {
("'one'\\''two\"three'", "shell-escape"),
("'one'\\''two\"three'", "shell-escape-always"),
],
&[],
);

// Consecutive quotes
Expand All @@ -564,6 +583,7 @@ mod tests {
("'one'\\'''\\''two\"\"three'", "shell-escape"),
("'one'\\'''\\''two\"\"three'", "shell-escape-always"),
],
&[],
);
}

Expand All @@ -584,6 +604,7 @@ mod tests {
("'one'$'\\n''two'", "shell-escape"),
("'one'$'\\n''two'", "shell-escape-always"),
],
&[],
);

// A control character followed by a special shell character
Expand All @@ -601,6 +622,7 @@ mod tests {
("'one'$'\\n''&two'", "shell-escape"),
("'one'$'\\n''&two'", "shell-escape-always"),
],
&[],
);

// The first 16 control characters. NUL is also included, even though it is of
Expand Down Expand Up @@ -640,6 +662,7 @@ mod tests {
"shell-escape-always",
),
],
&[],
);

// The last 16 control characters.
Expand Down Expand Up @@ -678,6 +701,7 @@ mod tests {
"shell-escape-always",
),
],
&[]
);

// DEL
Expand All @@ -695,6 +719,7 @@ mod tests {
("''$'\\177'", "shell-escape"),
("''$'\\177'", "shell-escape-always"),
],
&[],
);
}

Expand All @@ -717,6 +742,7 @@ mod tests {
("'one?two'", "shell-escape"),
("'one?two'", "shell-escape-always"),
],
&[],
);
}

Expand All @@ -735,32 +761,45 @@ mod tests {
("'one\\two'", "shell-escape"),
("'one\\two'", "shell-escape-always"),
],
&[],
);
}

#[test]
fn test_tilde_and_hash() {
check_names("~", &[("'~'", "shell"), ("'~'", "shell-escape")]);
check_names("~", &[("'~'", "shell"), ("'~'", "shell-escape")], &[]);
check_names(
"~name",
&[("'~name'", "shell"), ("'~name'", "shell-escape")],
&[],
);
check_names(
"some~name",
&[("some~name", "shell"), ("some~name", "shell-escape")],
&[],
);
check_names(
"name~",
&[("name~", "shell"), ("name~", "shell-escape")],
&[],
);
check_names("name~", &[("name~", "shell"), ("name~", "shell-escape")]);

check_names("#", &[("'#'", "shell"), ("'#'", "shell-escape")]);
check_names("#", &[("'#'", "shell"), ("'#'", "shell-escape")], &[]);
check_names(
"#name",
&[("'#name'", "shell"), ("'#name'", "shell-escape")],
&[],
);
check_names(
"some#name",
&[("some#name", "shell"), ("some#name", "shell-escape")],
&[],
);
check_names(
"name#",
&[("name#", "shell"), ("name#", "shell-escape")],
&[],
);
check_names("name#", &[("name#", "shell"), ("name#", "shell-escape")]);
}

#[test]
Expand All @@ -773,6 +812,7 @@ mod tests {
("'can'\\''$t'", "shell-escape"),
("'can'\\''$t'", "shell-escape-always"),
],
&[],
);

check_names(
Expand All @@ -783,6 +823,7 @@ mod tests {
("'can'\\''`t'", "shell-escape"),
("'can'\\''`t'", "shell-escape-always"),
],
&[],
);

check_names(
Expand All @@ -793,6 +834,24 @@ mod tests {
("'can'\\''\\t'", "shell-escape"),
("'can'\\''\\t'", "shell-escape-always"),
],
&[],
);
}

#[test]
fn test_additional_escaped_chars() {
check_names(
"one:two",
&[
("one:two", "literal"),
("'one:two'", "shell"),
("'one:two'", "shell-always"),
("'one:two'", "shell-escape"),
("'one:two'", "shell-escape-always"),
("\"one\\:two\"", "c"),
("one\\:two", "escape"),
],
&[':'],
);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/by-util/test_ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4950,7 +4950,7 @@ fn test_ls_color_clear_to_eol() {
// cspell:disable-next-line
result.stdout_contains("\x1b[0m\x1b[31;42mzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.foo\x1b[0m\x1b[K");
}

#[cfg(unix)]
#[test]
fn test_ls_quotes_dirname_when_a_column_exists() {
let scene = TestScenario::new(util_name!());
Expand Down

0 comments on commit f9141fb

Please sign in to comment.