From 1d15bbc95b8d56a421f64f185ede0f029e183302 Mon Sep 17 00:00:00 2001 From: Wind Date: Thu, 10 Oct 2024 20:57:30 +0800 Subject: [PATCH] Making nushell works better with external args which surrounded by backtick quotes (#13910) # Description Fixes: #13431 Fixes: #13578 The issue happened because nushell thinks external program name and external arg with totally same rule. But actually they are a little bit different. When parsing external program name, backtick is a thing and it should be keeped. But when parsing external args, backtick is just a mark that it's a **bareword which may contain space**. So in this context, it's already useless. # User-Facing Changes After the pr, the following command will work as intended. ```nushell > ^echo `"hello"` hello ``` # Tests + Formatting Added 3 test cases. --- crates/nu-parser/src/parser.rs | 24 ++++++++++++++++++++---- crates/nu-parser/tests/test_parser.rs | 15 +++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 4ac161bb84..5a35c95e72 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -240,10 +240,26 @@ fn parse_unknown_arg( /// string, where each balanced pair of quotes is parsed as a separate part of the string, and then /// 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"` -fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expression { - let contents = &working_set.get_span_contents(span); +fn parse_external_string( + working_set: &mut StateWorkingSet, + 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#") { parse_raw_string(working_set, span) } else if contents @@ -441,7 +457,7 @@ fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) -> } else if contents.starts_with(b"[") { parse_list_expression(working_set, span, &SyntaxShape::Any) } else { - parse_external_string(working_set, span) + parse_external_string(working_set, span, false) } } @@ -463,7 +479,7 @@ pub fn parse_external_call(working_set: &mut StateWorkingSet, spans: &[Span]) -> let arg = parse_expression(working_set, &[head_span]); Box::new(arg) } else { - Box::new(parse_external_string(working_set, head_span)) + Box::new(parse_external_string(working_set, head_span, true)) }; let args = spans[1..] diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index b26ae20db9..1ac3d7e900 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1021,6 +1021,11 @@ pub fn test_external_call_head_interpolated_string( r#"{a:1,b:c,c:d}"#, "value with single quote and double quote" )] +#[case( + r#"^foo `hello world`"#, + r#"hello world"#, + "value is surrounded by backtick 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 { @@ -1115,6 +1120,16 @@ pub fn test_external_call_arg_raw_string( r#"foo\external call"#, "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( #[case] input: &str, #[case] expected: &str,