forked from extern/nushell
Simplify string interpolation (#3401)
* [DRAFT] simplify string interpolation * Fix test
This commit is contained in:
parent
25a8caa9b0
commit
311c0e3f50
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3710,7 +3710,6 @@ dependencies = [
|
|||||||
name = "nu-table"
|
name = "nu-table"
|
||||||
version = "0.31.0"
|
version = "0.31.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
||||||
"nu-ansi-term 0.31.0",
|
"nu-ansi-term 0.31.0",
|
||||||
"regex 1.5.3",
|
"regex 1.5.3",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
@ -52,7 +52,7 @@ impl WholeStreamCommand for Each {
|
|||||||
Example {
|
Example {
|
||||||
description: "Number each item and echo a message",
|
description: "Number each item and echo a message",
|
||||||
example:
|
example:
|
||||||
"echo ['bob' 'fred'] | each --numbered { echo `{{$it.index}} is {{$it.item}}` }",
|
"echo ['bob' 'fred'] | each --numbered { echo $\"{$it.index} is {$it.item}\" }",
|
||||||
result: Some(vec![Value::from("0 is bob"), Value::from("1 is fred")]),
|
result: Some(vec![Value::from("0 is bob"), Value::from("1 is fred")]),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -8,7 +8,7 @@ fn reduce_table_column() {
|
|||||||
echo "[{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}]"
|
echo "[{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}]"
|
||||||
| from json
|
| from json
|
||||||
| get total
|
| get total
|
||||||
| reduce -f 20 { $it + ( math eval `{{$acc}}^1.05` )}
|
| reduce -f 20 { $it + (math eval $"{$acc}^1.05")}
|
||||||
| str from -d 1
|
| str from -d 1
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
@ -21,7 +21,7 @@ fn reduce_table_column() {
|
|||||||
r#"
|
r#"
|
||||||
echo "[{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}]"
|
echo "[{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}]"
|
||||||
| from json
|
| from json
|
||||||
| reduce -f 20 { $it.total + ( math eval `{{$acc}}^1.05` )}
|
| reduce -f 20 { $it.total + (math eval $"{$acc}^1.05")}
|
||||||
| str from -d 1
|
| str from -d 1
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
|
@ -467,7 +467,10 @@ fn parse_dollar_expr(
|
|||||||
scope: &dyn ParserScope,
|
scope: &dyn ParserScope,
|
||||||
) -> (SpannedExpression, Option<ParseError>) {
|
) -> (SpannedExpression, Option<ParseError>) {
|
||||||
trace!("Parsing dollar expression: {:?}", lite_arg.item);
|
trace!("Parsing dollar expression: {:?}", lite_arg.item);
|
||||||
if let (expr, None) = parse_range(lite_arg, scope) {
|
if lite_arg.item.starts_with("$\"") && lite_arg.item.len() > 1 && lite_arg.item.ends_with('"') {
|
||||||
|
// This is an interpolated string
|
||||||
|
parse_interpolated_string(&lite_arg, scope)
|
||||||
|
} else if let (expr, None) = parse_range(lite_arg, scope) {
|
||||||
(expr, None)
|
(expr, None)
|
||||||
} else if let (expr, None) = parse_full_column_path(lite_arg, scope) {
|
} else if let (expr, None) = parse_full_column_path(lite_arg, scope) {
|
||||||
(expr, None)
|
(expr, None)
|
||||||
@ -493,79 +496,60 @@ fn format(input: &str, start: usize) -> (Vec<FormatCommand>, Option<ParseError>)
|
|||||||
loop {
|
loop {
|
||||||
let mut before = String::new();
|
let mut before = String::new();
|
||||||
|
|
||||||
let mut found_start = false;
|
loop {
|
||||||
while let Some(c) = loop_input.next() {
|
|
||||||
end += 1;
|
end += 1;
|
||||||
if c == '{' {
|
if let Some(c) = loop_input.next() {
|
||||||
if let Some(x) = loop_input.peek() {
|
if c == '{' {
|
||||||
if *x == '{' {
|
break;
|
||||||
found_start = true;
|
|
||||||
end += 1;
|
|
||||||
let _ = loop_input.next();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
before.push(c);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
before.push(c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !before.is_empty() {
|
if !before.is_empty() {
|
||||||
if found_start {
|
output.push(FormatCommand::Text(
|
||||||
output.push(FormatCommand::Text(
|
before.to_string().spanned(Span::new(start, end - 1)),
|
||||||
before.to_string().spanned(Span::new(start, end - 2)),
|
));
|
||||||
));
|
|
||||||
} else {
|
|
||||||
output.push(FormatCommand::Text(before.spanned(Span::new(start, end))));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Look for column as we're now at one
|
// Look for column as we're now at one
|
||||||
let mut column = String::new();
|
let mut column = String::new();
|
||||||
start = end;
|
start = end;
|
||||||
|
|
||||||
let mut previous_c = ' ';
|
|
||||||
let mut found_end = false;
|
let mut found_end = false;
|
||||||
|
let mut open_count = 1;
|
||||||
while let Some(c) = loop_input.next() {
|
while let Some(c) = loop_input.next() {
|
||||||
end += 1;
|
end += 1;
|
||||||
if c == '}' && previous_c == '}' {
|
if c == '{' {
|
||||||
let _ = column.pop();
|
open_count += 1;
|
||||||
found_end = true;
|
} else if c == '}' {
|
||||||
break;
|
open_count -= 1;
|
||||||
|
if open_count == 0 {
|
||||||
|
found_end = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
previous_c = c;
|
|
||||||
column.push(c);
|
column.push(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !column.is_empty() {
|
if !column.is_empty() {
|
||||||
if found_end {
|
output.push(FormatCommand::Column(
|
||||||
output.push(FormatCommand::Column(
|
column.to_string().spanned(Span::new(start, end)),
|
||||||
column.to_string().spanned(Span::new(start, end - 2)),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
output.push(FormatCommand::Column(
|
|
||||||
column.to_string().spanned(Span::new(start, end)),
|
|
||||||
));
|
|
||||||
|
|
||||||
if error.is_none() {
|
|
||||||
error = Some(ParseError::argument_error(
|
|
||||||
input.spanned(Span::new(original_start, end)),
|
|
||||||
ArgumentError::MissingValueForName("unclosed {{ }}".to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if found_start && !found_end {
|
|
||||||
error = Some(ParseError::argument_error(
|
|
||||||
input.spanned(Span::new(original_start, end)),
|
|
||||||
ArgumentError::MissingValueForName("unclosed {{ }}".to_string()),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if before.is_empty() && column.is_empty() {
|
if column.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !found_end {
|
||||||
|
error = Some(ParseError::argument_error(
|
||||||
|
input.spanned(Span::new(original_start, end)),
|
||||||
|
ArgumentError::MissingValueForName("unclosed { }".to_string()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
start = end;
|
start = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,10 +562,16 @@ fn parse_interpolated_string(
|
|||||||
scope: &dyn ParserScope,
|
scope: &dyn ParserScope,
|
||||||
) -> (SpannedExpression, Option<ParseError>) {
|
) -> (SpannedExpression, Option<ParseError>) {
|
||||||
trace!("Parse_interpolated_string");
|
trace!("Parse_interpolated_string");
|
||||||
let inner_string = trim_quotes(&lite_arg.item);
|
let string_len = lite_arg.item.len();
|
||||||
|
let inner_string = lite_arg
|
||||||
|
.item
|
||||||
|
.chars()
|
||||||
|
.skip(2)
|
||||||
|
.take(string_len - 3)
|
||||||
|
.collect::<String>();
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
|
|
||||||
let (format_result, err) = format(&inner_string, lite_arg.span.start() + 1);
|
let (format_result, err) = format(&inner_string, lite_arg.span.start() + 2);
|
||||||
|
|
||||||
if error.is_none() {
|
if error.is_none() {
|
||||||
error = err;
|
error = err;
|
||||||
@ -598,11 +588,18 @@ fn parse_interpolated_string(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
FormatCommand::Column(c) => {
|
FormatCommand::Column(c) => {
|
||||||
let (o, err) = parse_full_column_path(&c, scope);
|
let result = parse(&c, c.span.start(), scope);
|
||||||
if error.is_none() {
|
match result {
|
||||||
error = err;
|
(classified_block, None) => {
|
||||||
|
output.push(SpannedExpression {
|
||||||
|
expr: Expression::Invocation(classified_block),
|
||||||
|
span: c.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(_, Some(err)) => {
|
||||||
|
return (garbage(c.span), Some(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
output.push(o);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -649,12 +646,6 @@ fn parse_external_arg(
|
|||||||
parse_dollar_expr(&lite_arg, scope)
|
parse_dollar_expr(&lite_arg, scope)
|
||||||
} else if lite_arg.item.starts_with('(') {
|
} else if lite_arg.item.starts_with('(') {
|
||||||
parse_invocation(&lite_arg, scope)
|
parse_invocation(&lite_arg, scope)
|
||||||
} else if lite_arg.item.starts_with('`')
|
|
||||||
&& lite_arg.item.len() > 1
|
|
||||||
&& lite_arg.item.ends_with('`')
|
|
||||||
{
|
|
||||||
// This is an interpolated string
|
|
||||||
parse_interpolated_string(&lite_arg, scope)
|
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
SpannedExpression::new(Expression::string(lite_arg.item.clone()), lite_arg.span),
|
SpannedExpression::new(Expression::string(lite_arg.item.clone()), lite_arg.span),
|
||||||
@ -811,19 +802,11 @@ fn parse_arg(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SyntaxShape::String => {
|
SyntaxShape::String => {
|
||||||
if lite_arg.item.starts_with('`')
|
let trimmed = trim_quotes(&lite_arg.item);
|
||||||
&& lite_arg.item.len() > 1
|
(
|
||||||
&& lite_arg.item.ends_with('`')
|
SpannedExpression::new(Expression::string(trimmed), lite_arg.span),
|
||||||
{
|
None,
|
||||||
// This is an interpolated string
|
)
|
||||||
parse_interpolated_string(&lite_arg, scope)
|
|
||||||
} else {
|
|
||||||
let trimmed = trim_quotes(&lite_arg.item);
|
|
||||||
(
|
|
||||||
SpannedExpression::new(Expression::string(trimmed), lite_arg.span),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SyntaxShape::GlobPattern => {
|
SyntaxShape::GlobPattern => {
|
||||||
let trimmed = trim_quotes(&lite_arg.item);
|
let trimmed = trim_quotes(&lite_arg.item);
|
||||||
|
@ -19,7 +19,6 @@ pub(crate) fn trim_quotes(input: &str) -> String {
|
|||||||
match (chars.next(), chars.next_back()) {
|
match (chars.next(), chars.next_back()) {
|
||||||
(Some('\''), Some('\'')) => chars.collect(),
|
(Some('\''), Some('\'')) => chars.collect(),
|
||||||
(Some('"'), Some('"')) => chars.collect(),
|
(Some('"'), Some('"')) => chars.collect(),
|
||||||
(Some('`'), Some('`')) => chars.collect(),
|
|
||||||
_ => input.to_string(),
|
_ => input.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,55 +118,19 @@ fn string_interpolation_with_it() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".",
|
cwd: ".",
|
||||||
r#"
|
r#"
|
||||||
echo "foo" | each { echo `{{$it}}` }
|
echo "foo" | each { echo $"{$it}" }
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(actual.out, "foo");
|
assert_eq!(actual.out, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_interpolation_with_column() {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: ".",
|
|
||||||
r#"
|
|
||||||
echo [[name]; [bob]] | each { echo `{{name}} is cool` }
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "bob is cool");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_interpolation_with_column2() {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: ".",
|
|
||||||
r#"
|
|
||||||
echo [[name]; [fred]] | each { echo `also {{name}} is cool` }
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "also fred is cool");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_interpolation_with_column3() {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: ".",
|
|
||||||
r#"
|
|
||||||
echo [[name]; [sally]] | each { echo `also {{name}}` }
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "also sally");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_interpolation_with_it_column_path() {
|
fn string_interpolation_with_it_column_path() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".",
|
cwd: ".",
|
||||||
r#"
|
r#"
|
||||||
echo [[name]; [sammie]] | each { echo `{{$it.name}}` }
|
echo [[name]; [sammie]] | each { echo $"{$it.name}" }
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user