Removes more quotes on external command arguments (#13883)

# Description
Fixes: #13662

I don't think nushell need to parse and keep nested quote on external
command arguments. Some nested quote is safe to removed. After the pr,
nushell will behave more likely to bash.

# User-Facing Changes
#### Before
```
> ^echo {a:1,b:'c',c:'d'}
{a:1,b:c',c:'d} 
```
#### After
```
> ^echo {a:1,b:'c',c:'d'}
{a:1,b:c,c:d}
```

# Tests + Formatting
Added some tests to cover the behavior
This commit is contained in:
Wind 2024-09-23 19:44:51 +08:00 committed by GitHub
parent 03ee54a4df
commit 183c2221bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 23 deletions

View File

@ -258,7 +258,6 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre
from: usize,
quote_char: u8,
escaped: bool,
depth: i32,
},
}
// Find the spans of parts of the string that can be parsed as their own strings for
@ -287,7 +286,6 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre
from: index,
quote_char: ch,
escaped: false,
depth: 1,
};
}
b'$' => {
@ -300,7 +298,6 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre
from: index,
quote_char,
escaped: false,
depth: 1,
};
// Skip over two chars (the dollar sign and the quote)
index += 2;
@ -314,29 +311,13 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre
from,
quote_char,
escaped,
depth,
} => match ch {
ch if ch == *quote_char && !*escaped => {
// Count if there are more than `depth` quotes remaining
if contents[index..]
.iter()
.filter(|b| *b == quote_char)
.count() as i32
> *depth
{
// Increment depth to be greedy
*depth += 1;
} else {
// Decrement depth
*depth -= 1;
}
if *depth == 0 {
// End of string
// quoted string ended, just make a new span for it.
spans.push(make_span(*from, index + 1));
// go back to Bare state
// go back to Bare state.
state = State::Bare { from: index + 1 };
}
}
b'\\' if !*escaped && *quote_char == b'"' => {
// The next token is escaped so it doesn't count (only for double quote)
*escaped = true;

View File

@ -996,6 +996,31 @@ pub fn test_external_call_head_interpolated_string(
r"foo\external call",
"backtick quote with backslash"
)]
#[case(
r#"^foo --flag="value""#,
r#"--flag=value"#,
"flag value with double quote"
)]
#[case(
r#"^foo --flag='value'"#,
r#"--flag=value"#,
"flag value with single quote"
)]
#[case(
r#"^foo {a:1,b:'c',c:'d'}"#,
r#"{a:1,b:c,c:d}"#,
"value with many inner single quotes"
)]
#[case(
r#"^foo {a:1,b:"c",c:"d"}"#,
r#"{a:1,b:c,c:d}"#,
"value with many double quotes"
)]
#[case(
r#"^foo {a:1,b:'c',c:"d"}"#,
r#"{a:1,b:c,c:d}"#,
"value with single quote and double quote"
)]
pub fn test_external_call_arg_glob(#[case] input: &str, #[case] expected: &str, #[case] tag: &str) {
test_external_call(input, tag, |name, args| {
match &name.expr {