mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
don't run subcommand if it's surrounded with backtick quote (#14210)
# Description Fixes: #14202 After looking into the issue, I think #13910 it's not good to cut the span if it's in external argument. This pr is somehow revert the change, and fix https://github.com/nushell/nushell/issues/13431 in another way. It introduce a new state named `State::BackTickQuote`, so if an external arg include backtick quote, it enters the state, so backtick quote won't be the body of a string. # User-Facing Changes ### Before ```nushell > ^echo `(echo aa)` aa > ^echo `"aa"` # maybe it's not right to remove the inner quote. aa ``` ### After ```nushell > ^echo `(echo aa)` (echo aa) > ^echo `"aa"` # inner quote is keeped if there are backtick quote outside. "aa" ``` # Tests + Formatting Added 3 tests.
This commit is contained in:
parent
4907575d3d
commit
0a2fb137af
@ -240,36 +240,23 @@ fn parse_unknown_arg(
|
|||||||
/// string, where each balanced pair of quotes is parsed as a separate part of the string, and then
|
/// string, where each balanced pair of quotes is parsed as a separate part of the string, and then
|
||||||
/// concatenated together.
|
/// concatenated together.
|
||||||
///
|
///
|
||||||
/// `keep_surround_backtick_quote` should be true when parsing it as command name. Or else it
|
|
||||||
/// should be false.
|
|
||||||
///
|
|
||||||
/// For example, `-foo="bar\nbaz"` becomes `$"-foo=bar\nbaz"`
|
/// For example, `-foo="bar\nbaz"` becomes `$"-foo=bar\nbaz"`
|
||||||
fn parse_external_string(
|
fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
|
||||||
working_set: &mut StateWorkingSet,
|
let contents = working_set.get_span_contents(span);
|
||||||
mut span: Span,
|
|
||||||
keep_surround_bakctick_quote: bool,
|
|
||||||
) -> Expression {
|
|
||||||
let mut contents = working_set.get_span_contents(span);
|
|
||||||
|
|
||||||
if !keep_surround_bakctick_quote
|
|
||||||
&& contents.len() > 1
|
|
||||||
&& contents.starts_with(b"`")
|
|
||||||
&& contents.ends_with(b"`")
|
|
||||||
{
|
|
||||||
contents = &contents[1..contents.len() - 1];
|
|
||||||
// backtick quote is useless in this case, so span is required to updated.
|
|
||||||
span = Span::new(span.start + 1, span.end - 1);
|
|
||||||
}
|
|
||||||
if contents.starts_with(b"r#") {
|
if contents.starts_with(b"r#") {
|
||||||
parse_raw_string(working_set, span)
|
parse_raw_string(working_set, span)
|
||||||
} else if contents
|
} else if contents
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| matches!(b, b'"' | b'\'' | b'(' | b')'))
|
.any(|b| matches!(b, b'"' | b'\'' | b'(' | b')' | b'`'))
|
||||||
{
|
{
|
||||||
enum State {
|
enum State {
|
||||||
Bare {
|
Bare {
|
||||||
from: usize,
|
from: usize,
|
||||||
},
|
},
|
||||||
|
BackTickQuote {
|
||||||
|
from: usize,
|
||||||
|
},
|
||||||
Quote {
|
Quote {
|
||||||
from: usize,
|
from: usize,
|
||||||
quote_char: u8,
|
quote_char: u8,
|
||||||
@ -320,6 +307,12 @@ fn parse_external_string(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
b'`' => {
|
||||||
|
if index != *from {
|
||||||
|
spans.push(make_span(*from, index))
|
||||||
|
}
|
||||||
|
state = State::BackTickQuote { from: index }
|
||||||
|
}
|
||||||
// Continue to consume
|
// Continue to consume
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
@ -342,13 +335,21 @@ fn parse_external_string(
|
|||||||
*escaped = false;
|
*escaped = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
State::BackTickQuote { from } => {
|
||||||
|
if ch == b'`' {
|
||||||
|
spans.push(make_span(*from, index + 1));
|
||||||
|
state = State::Bare { from: index + 1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the final span
|
// Add the final span
|
||||||
match state {
|
match state {
|
||||||
State::Bare { from } | State::Quote { from, .. } => {
|
State::Bare { from }
|
||||||
|
| State::Quote { from, .. }
|
||||||
|
| State::BackTickQuote { from, .. } => {
|
||||||
if from < contents.len() {
|
if from < contents.len() {
|
||||||
spans.push(make_span(from, contents.len()));
|
spans.push(make_span(from, contents.len()));
|
||||||
}
|
}
|
||||||
@ -457,7 +458,7 @@ fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) ->
|
|||||||
} else if contents.starts_with(b"[") {
|
} else if contents.starts_with(b"[") {
|
||||||
parse_list_expression(working_set, span, &SyntaxShape::Any)
|
parse_list_expression(working_set, span, &SyntaxShape::Any)
|
||||||
} else {
|
} else {
|
||||||
parse_external_string(working_set, span, false)
|
parse_external_string(working_set, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,7 +480,7 @@ pub fn parse_external_call(working_set: &mut StateWorkingSet, spans: &[Span]) ->
|
|||||||
let arg = parse_expression(working_set, &[head_span]);
|
let arg = parse_expression(working_set, &[head_span]);
|
||||||
Box::new(arg)
|
Box::new(arg)
|
||||||
} else {
|
} else {
|
||||||
Box::new(parse_external_string(working_set, head_span, true))
|
Box::new(parse_external_string(working_set, head_span))
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = spans[1..]
|
let args = spans[1..]
|
||||||
|
@ -1026,6 +1026,16 @@ pub fn test_external_call_head_interpolated_string(
|
|||||||
r#"hello world"#,
|
r#"hello world"#,
|
||||||
"value is surrounded by backtick quote"
|
"value is surrounded by backtick quote"
|
||||||
)]
|
)]
|
||||||
|
#[case(
|
||||||
|
r#"^foo `"hello world"`"#,
|
||||||
|
"\"hello world\"",
|
||||||
|
"value is surrounded by backtick quote, with inner double quote"
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
r#"^foo `'hello world'`"#,
|
||||||
|
"'hello world'",
|
||||||
|
"value is surrounded by backtick quote, with inner single quote"
|
||||||
|
)]
|
||||||
pub fn test_external_call_arg_glob(#[case] input: &str, #[case] expected: &str, #[case] tag: &str) {
|
pub fn test_external_call_arg_glob(#[case] input: &str, #[case] expected: &str, #[case] tag: &str) {
|
||||||
test_external_call(input, tag, |name, args| {
|
test_external_call(input, tag, |name, args| {
|
||||||
match &name.expr {
|
match &name.expr {
|
||||||
@ -1120,16 +1130,6 @@ pub fn test_external_call_arg_raw_string(
|
|||||||
r#"foo\external call"#,
|
r#"foo\external call"#,
|
||||||
"double quote with backslash"
|
"double quote with backslash"
|
||||||
)]
|
)]
|
||||||
#[case(
|
|
||||||
r#"^foo `"hello world"`"#,
|
|
||||||
r#"hello world"#,
|
|
||||||
"value is surrounded by backtick quote, with inner double quote"
|
|
||||||
)]
|
|
||||||
#[case(
|
|
||||||
r#"^foo `'hello world'`"#,
|
|
||||||
r#"hello world"#,
|
|
||||||
"value is surrounded by backtick quote, with inner single quote"
|
|
||||||
)]
|
|
||||||
pub fn test_external_call_arg_string(
|
pub fn test_external_call_arg_string(
|
||||||
#[case] input: &str,
|
#[case] input: &str,
|
||||||
#[case] expected: &str,
|
#[case] expected: &str,
|
||||||
|
@ -642,3 +642,13 @@ fn exit_code_stops_execution_for_loop() {
|
|||||||
assert!(actual.out.is_empty());
|
assert!(actual.out.is_empty());
|
||||||
assert!(!actual.err.contains("exited with code 42"));
|
assert!(!actual.err.contains("exited with code 42"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arg_dont_run_subcommand_if_surrounded_with_quote() {
|
||||||
|
let actual = nu!("nu --testbin cococo `(echo aa)`");
|
||||||
|
assert_eq!(actual.out, "(echo aa)");
|
||||||
|
let actual = nu!("nu --testbin cococo \"(echo aa)\"");
|
||||||
|
assert_eq!(actual.out, "(echo aa)");
|
||||||
|
let actual = nu!("nu --testbin cococo '(echo aa)'");
|
||||||
|
assert_eq!(actual.out, "(echo aa)");
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user