forked from extern/nushell
Allow iteration blocks to have an optional extra index parameter (alternative to -n
flags) (#6994)
Alters `all`, `any`, `each while`, `each`, `insert`, `par-each`, `reduce`, `update`, `upsert` and `where`, so that their blocks take an optional parameter containing the index.
This commit is contained in:
parent
899383c30c
commit
833825ae9a
@ -54,6 +54,11 @@ impl Command for All {
|
||||
example: "[2 4 6 8] | all {|e| $e mod 2 == 0 }",
|
||||
result: Some(Value::test_bool(true)),
|
||||
},
|
||||
Example {
|
||||
description: "Check that all values are equal to twice their index",
|
||||
example: "[0 2 4 6] | all {|el ind| $el == $ind * 2 }",
|
||||
result: Some(Value::test_bool(true)),
|
||||
},
|
||||
]
|
||||
}
|
||||
// This is almost entirely a copy-paste of `any`'s run(), so make sure any changes to this are
|
||||
@ -81,7 +86,7 @@ impl Command for All {
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
|
||||
for value in input.into_interruptible_iter(ctrlc) {
|
||||
for (idx, value) in input.into_interruptible_iter(ctrlc).enumerate() {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
// a different set of environment variables.
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
@ -90,6 +95,18 @@ impl Command for All {
|
||||
if let Some(var_id) = var_id {
|
||||
stack.add_var(var_id, value.clone());
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let eval = eval_block(
|
||||
&engine_state,
|
||||
|
@ -53,6 +53,11 @@ impl Command for Any {
|
||||
example: "[2 4 1 6 8] | any {|e| $e mod 2 == 1 }",
|
||||
result: Some(Value::test_bool(true)),
|
||||
},
|
||||
Example {
|
||||
description: "Check if any value is equal to twice its own index",
|
||||
example: "[9 8 7 6] | any {|el ind| $el == $ind * 2 }",
|
||||
result: Some(Value::test_bool(true)),
|
||||
},
|
||||
]
|
||||
}
|
||||
// This is almost entirely a copy-paste of `all`'s run(), so make sure any changes to this are
|
||||
@ -80,7 +85,7 @@ impl Command for Any {
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
|
||||
for value in input.into_interruptible_iter(ctrlc) {
|
||||
for (idx, value) in input.into_interruptible_iter(ctrlc).enumerate() {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
// a different set of environment variables.
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
@ -89,6 +94,18 @@ impl Command for Any {
|
||||
if let Some(var_id) = var_id {
|
||||
stack.add_var(var_id, value.clone());
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let eval = eval_block(
|
||||
&engine_state,
|
||||
|
@ -16,7 +16,7 @@ impl Command for Each {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a closure on each row of input"
|
||||
"Run a closure on each row of the input list, creating a new list with the results."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
@ -41,11 +41,15 @@ with 'transpose' first."#
|
||||
)])
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
|
||||
"the closure to run",
|
||||
)
|
||||
.switch("keep-empty", "keep empty result cells", Some('k'))
|
||||
.switch("numbered", "iterate with an index", Some('n'))
|
||||
.switch(
|
||||
"numbered",
|
||||
"iterate with an index (deprecated; use a two-parameter block instead)",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
@ -80,7 +84,7 @@ with 'transpose' first."#
|
||||
|
||||
vec![
|
||||
Example {
|
||||
example: "[1 2 3] | each { |it| 2 * $it }",
|
||||
example: "[1 2 3] | each {|e| 2 * $e }",
|
||||
description: "Multiplies elements in list",
|
||||
result: Some(Value::List {
|
||||
vals: stream_test_1,
|
||||
@ -88,19 +92,26 @@ with 'transpose' first."#
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: r#"[1 2 3] | each { |it| if $it == 2 { echo "found 2!"} }"#,
|
||||
description: "Iterate over each element, keeping only values that succeed",
|
||||
example: r#"[1 2 3 2] | each {|e| if $e == 2 { "two" } }"#,
|
||||
description: "Produce a list that has \"two\" for each 2 in the input",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::String {
|
||||
val: "found 2!".to_string(),
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "two".to_string(),
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
},
|
||||
Value::String {
|
||||
val: "two".to_string(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: r#"[1 2 3] | each -n { |it| if $it.item == 2 { echo $"found 2 at ($it.index)!"} }"#,
|
||||
description: "Iterate over each element, print the matching value and its index",
|
||||
example: r#"[1 2 3] | each {|el ind| if $el == 2 { $"found 2 at ($ind)!"} }"#,
|
||||
description:
|
||||
"Iterate over each element, producing a list showing indexes of any 2s",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::String {
|
||||
val: "found 2 at 1!".to_string(),
|
||||
@ -110,7 +121,7 @@ with 'transpose' first."#
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: r#"[1 2 3] | each --keep-empty { |it| if $it == 2 { echo "found 2!"} }"#,
|
||||
example: r#"[1 2 3] | each --keep-empty {|e| if $e == 2 { echo "found 2!"} }"#,
|
||||
description: "Iterate over each element, keeping all results",
|
||||
result: Some(Value::List {
|
||||
vals: stream_test_2,
|
||||
@ -148,6 +159,8 @@ with 'transpose' first."#
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream { .. } => Ok(input
|
||||
// To enumerate over the input (for the index argument),
|
||||
// it must be converted into an iterator using into_iter().
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, x)| {
|
||||
@ -158,6 +171,7 @@ with 'transpose' first."#
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
// -n changes the first argument into an {index, item} record.
|
||||
if numbered {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
@ -178,6 +192,18 @@ with 'transpose' first."#
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional second index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let input_span = x.span();
|
||||
match eval_block(
|
||||
@ -254,6 +280,8 @@ with 'transpose' first."#
|
||||
}
|
||||
})
|
||||
.into_pipeline_data(ctrlc)),
|
||||
// This match allows non-iterables to be accepted,
|
||||
// which is currently considered undesirable (Nov 2022).
|
||||
PipelineData::Value(x, ..) => {
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
|
@ -15,7 +15,7 @@ impl Command for EachWhile {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a block on each element of input until a null is found"
|
||||
"Run a block on each row of the input list until a null is found, then create a new list with the results."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -30,10 +30,14 @@ impl Command for EachWhile {
|
||||
)])
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
|
||||
"the closure to run",
|
||||
)
|
||||
.switch("numbered", "iterate with an index", Some('n'))
|
||||
.switch(
|
||||
"numbered",
|
||||
"iterate with an index (deprecated; use a two-parameter block instead)",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
@ -61,24 +65,24 @@ impl Command for EachWhile {
|
||||
|
||||
vec![
|
||||
Example {
|
||||
example: "[1 2 3] | each while { |it| if $it < 3 { $it * 2 } else { null } }",
|
||||
description: "Multiplies elements below three by two",
|
||||
example: "[1 2 3 2 1] | each while {|e| if $e < 3 { $e * 2 } }",
|
||||
description: "Produces a list of each element before the 3, doubled",
|
||||
result: Some(Value::List {
|
||||
vals: stream_test_1,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: r#"[1 2 stop 3 4] | each while { |it| if $it == 'stop' { null } else { $"Output: ($it)" } }"#,
|
||||
description: "Output elements till reaching 'stop'",
|
||||
example: r#"[1 2 stop 3 4] | each while {|e| if $e != 'stop' { $"Output: ($e)" } }"#,
|
||||
description: "Output elements until reaching 'stop'",
|
||||
result: Some(Value::List {
|
||||
vals: stream_test_2,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: r#"[1 2 3] | each while -n { |it| if $it.item < 2 { $"value ($it.item) at ($it.index)!"} else { null } }"#,
|
||||
description: "Iterate over each element, print the matching value and its index",
|
||||
example: r#"[1 2 3] | each while {|el ind| if $el < 2 { $"value ($el) at ($ind)!"} }"#,
|
||||
description: "Iterate over each element, printing the matching value and its index",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::String {
|
||||
val: "value 1 at 0!".to_string(),
|
||||
@ -115,6 +119,9 @@ impl Command for EachWhile {
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream { .. } => Ok(input
|
||||
// To enumerate over the input (for the index argument),
|
||||
// it must be converted into an iterator using into_iter().
|
||||
// TODO: Could this be changed to .into_interruptible_iter(ctrlc) ?
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map_while(move |(idx, x)| {
|
||||
@ -145,6 +152,18 @@ impl Command for EachWhile {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional second index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
@ -229,6 +248,8 @@ impl Command for EachWhile {
|
||||
})
|
||||
.fuse()
|
||||
.into_pipeline_data(ctrlc)),
|
||||
// This match allows non-iterables to be accepted,
|
||||
// which is currently considered undesirable (Nov 2022).
|
||||
PipelineData::Value(x, ..) => {
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
@ -253,7 +274,17 @@ impl Command for EachWhile {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[7 8 9 10] | each while {|el ind| $el + $ind } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[7, 9, 11, 13]");
|
||||
}
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
@ -38,7 +38,7 @@ impl Command for Insert {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Insert a new column."
|
||||
"Insert a new column, using an expression or block to create each row's values."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -57,7 +57,7 @@ impl Command for Insert {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Insert a new entry into a record",
|
||||
description: "Insert a new entry into a single record",
|
||||
example: "{'name': 'nu', 'stars': 5} | insert alias 'Nushell'",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["name".into(), "stars".into(), "alias".into()],
|
||||
@ -68,6 +68,34 @@ impl Command for Insert {
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}, Example {
|
||||
description: "Insert a column with values equal to their row index, plus the value of 'foo' in each row",
|
||||
example: "[[foo]; [7] [8] [9]] | insert bar {|el ind| $el.foo + $ind }",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["foo".into(), "bar".into()],
|
||||
vals: vec![
|
||||
Value::test_int(7),
|
||||
Value::test_int(7),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}, Value::Record {
|
||||
cols: vec!["foo".into(), "bar".into()],
|
||||
vals: vec![
|
||||
Value::test_int(8),
|
||||
Value::test_int(9),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}, Value::Record {
|
||||
cols: vec!["foo".into(), "bar".into()],
|
||||
vals: vec![
|
||||
Value::test_int(9),
|
||||
Value::test_int(11),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
}
|
||||
@ -98,6 +126,9 @@ fn insert(
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
// enumerate() can't be used here because it converts records into tables
|
||||
// when combined with into_pipeline_data(). Hence, the index is tracked manually like so.
|
||||
let mut idx: i64 = 0;
|
||||
input.map(
|
||||
move |mut input| {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
@ -105,11 +136,19 @@ fn insert(
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
// Element argument
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, input.clone())
|
||||
}
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, Value::Int { val: idx, span });
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
let output = eval_block(
|
||||
&engine_state,
|
||||
|
@ -18,7 +18,7 @@ impl Command for ParEach {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Run a closure on each element of input in parallel"
|
||||
"Run a closure on each row of the input list in parallel, creating a new list with the results."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
@ -29,18 +29,23 @@ impl Command for ParEach {
|
||||
)])
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
|
||||
"the closure to run",
|
||||
)
|
||||
.switch("numbered", "iterate with an index", Some('n'))
|
||||
.switch(
|
||||
"numbered",
|
||||
"iterate with an index (deprecated; use a two-parameter block instead)",
|
||||
Some('n'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
example: "[1 2 3] | par-each { |it| 2 * $it }",
|
||||
description: "Multiplies elements in list",
|
||||
example: "[1 2 3] | par-each { 2 * $in }",
|
||||
description:
|
||||
"Multiplies each number. Note that the list will become arbitrarily disordered.",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
@ -107,6 +112,18 @@ impl Command for ParEach {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional second index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let val_span = x.span();
|
||||
match eval_block(
|
||||
@ -159,6 +176,18 @@ impl Command for ParEach {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional second index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let val_span = x.span();
|
||||
match eval_block(
|
||||
@ -210,6 +239,18 @@ impl Command for ParEach {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional second index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let val_span = x.span();
|
||||
match eval_block(
|
||||
@ -270,6 +311,18 @@ impl Command for ParEach {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional second index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(
|
||||
engine_state,
|
||||
@ -287,6 +340,8 @@ impl Command for ParEach {
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data(ctrlc)),
|
||||
// This match allows non-iterables to be accepted,
|
||||
// which is currently considered undesirable (Nov 2022).
|
||||
PipelineData::Value(x, ..) => {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
@ -313,6 +368,17 @@ impl Command for ParEach {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[7,8,9,10] | par-each {|el ind| $ind } | describe"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "list<int>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
|
@ -27,14 +27,22 @@ impl Command for Reduce {
|
||||
)
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Any])),
|
||||
SyntaxShape::Closure(Some(vec![
|
||||
SyntaxShape::Any,
|
||||
SyntaxShape::Any,
|
||||
SyntaxShape::Int,
|
||||
])),
|
||||
"reducing function",
|
||||
)
|
||||
.switch("numbered", "iterate with an index", Some('n'))
|
||||
.switch(
|
||||
"numbered",
|
||||
"iterate with an index (deprecated; use a 3-parameter block instead)",
|
||||
Some('n'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Aggregate a list table to a single value using an accumulator block."
|
||||
"Aggregate a list to a single value using an accumulator block."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -52,10 +60,10 @@ impl Command for Reduce {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
example: "[ 1 2 3 ] | reduce -n {|it, acc| $acc.item + $it.item }",
|
||||
description: "Sum values of a list (same as 'math sum')",
|
||||
example: "[ 8 7 6 ] | reduce {|it, acc, ind| $acc + $it + $ind }",
|
||||
description: "Sum values of a list, plus their indexes",
|
||||
result: Some(Value::Int {
|
||||
val: 6,
|
||||
val: 22,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
@ -70,24 +78,13 @@ impl Command for Reduce {
|
||||
Example {
|
||||
example: r#"[ i o t ] | reduce -f "Arthur, King of the Britons" {|it, acc| $acc | str replace -a $it "X" }"#,
|
||||
description: "Replace selected characters in a string with 'X'",
|
||||
result: Some(Value::String {
|
||||
val: "ArXhur, KXng Xf Xhe BrXXXns".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
result: Some(Value::test_string("ArXhur, KXng Xf Xhe BrXXXns")),
|
||||
},
|
||||
Example {
|
||||
example: r#"[ one longest three bar ] | reduce -n { |it, acc|
|
||||
if ($it.item | str length) > ($acc.item | str length) {
|
||||
$it.item
|
||||
} else {
|
||||
$acc.item
|
||||
}
|
||||
}"#,
|
||||
description: "Find the longest string and its index",
|
||||
result: Some(Value::String {
|
||||
val: "longest".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
example: r#"['foo.gz', 'bar.gz', 'baz.gz'] | reduce -f '' {|str all ind| $"($all)(if $ind != 0 {'; '})($ind + 1)-($str)" }"#,
|
||||
description:
|
||||
"Add ascending numbers to each of the filenames, and join with semicolons.",
|
||||
result: Some(Value::test_string("1-foo.gz; 2-bar.gz; 3-baz.gz")),
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -114,6 +111,8 @@ impl Command for Reduce {
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
// To enumerate over the input (for the index argument),
|
||||
// it must be converted into an iterator using into_iter().
|
||||
let mut input_iter = input.into_iter();
|
||||
|
||||
let (off, start_val) = if let Some(val) = fold {
|
||||
@ -170,12 +169,14 @@ impl Command for Reduce {
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
// Element argument
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, x);
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulator argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
acc = if numbered {
|
||||
@ -201,6 +202,18 @@ impl Command for Reduce {
|
||||
stack.add_var(*var_id, acc);
|
||||
}
|
||||
}
|
||||
// Optional third index argument
|
||||
if let Some(var) = block.signature.get_positional(2) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
acc = eval_block(
|
||||
engine_state,
|
||||
|
@ -25,7 +25,7 @@ impl Command for Update {
|
||||
.required(
|
||||
"replacement value",
|
||||
SyntaxShape::Any,
|
||||
"the new value to give the cell(s)",
|
||||
"the new value to give the cell(s), or a block to create the value",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
@ -48,7 +48,7 @@ impl Command for Update {
|
||||
vec![
|
||||
Example {
|
||||
description: "Update a column value",
|
||||
example: "echo {'name': 'nu', 'stars': 5} | update name 'Nushell'",
|
||||
example: "{'name': 'nu', 'stars': 5} | update name 'Nushell'",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["name".into(), "stars".into()],
|
||||
vals: vec![Value::test_string("Nushell"), Value::test_int(5)],
|
||||
@ -57,16 +57,21 @@ impl Command for Update {
|
||||
},
|
||||
Example {
|
||||
description: "Use in block form for more involved updating logic",
|
||||
example: "echo [[count fruit]; [1 'apple']] | update count {|f| $f.count + 1}",
|
||||
example: "[[count fruit]; [1 'apple']] | update count {|row index| ($row.fruit | str length) + $index }",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["count".into(), "fruit".into()],
|
||||
vals: vec![Value::test_int(2), Value::test_string("apple")],
|
||||
vals: vec![Value::test_int(5), Value::test_string("apple")],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Alter each value in the 'authors' column to use a single string instead of a list",
|
||||
example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|row| $row.authors | str join ','}",
|
||||
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -97,6 +102,9 @@ fn update(
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
// enumerate() can't be used here because it converts records into tables
|
||||
// when combined with into_pipeline_data(). Hence, the index is tracked manually like so.
|
||||
let mut idx: i64 = 0;
|
||||
input.map(
|
||||
move |mut input| {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
@ -109,6 +117,13 @@ fn update(
|
||||
stack.add_var(*var_id, input.clone())
|
||||
}
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, Value::Int { val: idx, span });
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
let output = eval_block(
|
||||
&engine_state,
|
||||
|
@ -25,7 +25,7 @@ impl Command for Upsert {
|
||||
.required(
|
||||
"replacement value",
|
||||
SyntaxShape::Any,
|
||||
"the new value to give the cell(s)",
|
||||
"the new value to give the cell(s), or a block to create the value",
|
||||
)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
@ -50,21 +50,17 @@ impl Command for Upsert {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Update a column value",
|
||||
description: "Update a record's value",
|
||||
example: "{'name': 'nu', 'stars': 5} | upsert name 'Nushell'",
|
||||
result: Some(Value::Record { cols: vec!["name".into(), "stars".into()], vals: vec![Value::test_string("Nushell"), Value::test_int(5)], span: Span::test_data()}),
|
||||
}, Example {
|
||||
description: "Insert a new column",
|
||||
description: "Insert a new entry into a single record",
|
||||
example: "{'name': 'nu', 'stars': 5} | upsert language 'Rust'",
|
||||
result: Some(Value::Record { cols: vec!["name".into(), "stars".into(), "language".into()], vals: vec![Value::test_string("nu"), Value::test_int(5), Value::test_string("Rust")], span: Span::test_data()}),
|
||||
}, Example {
|
||||
description: "Use in block form for more involved updating logic",
|
||||
example: "[[count fruit]; [1 'apple']] | upsert count {|f| $f.count + 1}",
|
||||
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["count".into(), "fruit".into()], vals: vec![Value::test_int(2), Value::test_string("apple")], span: Span::test_data()}], span: Span::test_data()}),
|
||||
}, Example {
|
||||
description: "Use in block form for more involved updating logic",
|
||||
example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | upsert authors {|a| $a.authors | str join ','}",
|
||||
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}),
|
||||
description: "Use in closure form for more involved updating logic",
|
||||
example: "[[count fruit]; [1 'apple']] | upsert count {|row index| ($row.fruit | str length) + $index }",
|
||||
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["count".into(), "fruit".into()], vals: vec![Value::test_int(5), Value::test_string("apple")], span: Span::test_data()}], span: Span::test_data()}),
|
||||
},
|
||||
Example {
|
||||
description: "Upsert an int into a list, updating an existing value based on the index",
|
||||
@ -117,6 +113,9 @@ fn upsert(
|
||||
let orig_env_vars = stack.env_vars.clone();
|
||||
let orig_env_hidden = stack.env_hidden.clone();
|
||||
|
||||
// enumerate() can't be used here because it converts records into tables
|
||||
// when combined with into_pipeline_data(). Hence, the index is tracked manually like so.
|
||||
let mut idx: i64 = 0;
|
||||
input.map(
|
||||
move |mut input| {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
@ -129,6 +128,13 @@ fn upsert(
|
||||
stack.add_var(*var_id, input.clone())
|
||||
}
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, Value::Int { val: idx, span });
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
let output = eval_block(
|
||||
&engine_state,
|
||||
|
@ -31,8 +31,8 @@ impl Command for Where {
|
||||
.optional("cond", SyntaxShape::RowCondition, "condition")
|
||||
.named(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
"use where with a closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
|
||||
"use with a closure instead",
|
||||
Some('b'),
|
||||
)
|
||||
.category(Category::Filters)
|
||||
@ -65,8 +65,11 @@ impl Command for Where {
|
||||
PipelineData::Value(Value::Range { .. }, ..)
|
||||
| PipelineData::Value(Value::List { .. }, ..)
|
||||
| PipelineData::ListStream { .. } => Ok(input
|
||||
// To enumerate over the input (for the index argument),
|
||||
// it must be converted into an iterator using into_iter().
|
||||
.into_iter()
|
||||
.filter_map(move |x| {
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, x)| {
|
||||
// with_env() is used here to ensure that each iteration uses
|
||||
// a different set of environment variables.
|
||||
// Hence, a 'cd' in the first loop won't affect the next loop.
|
||||
@ -77,6 +80,18 @@ impl Command for Where {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
@ -108,7 +123,8 @@ impl Command for Where {
|
||||
..
|
||||
} => Ok(stream
|
||||
.into_iter()
|
||||
.filter_map(move |x| {
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, x)| {
|
||||
// see note above about with_env()
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
@ -122,6 +138,18 @@ impl Command for Where {
|
||||
stack.add_var(*var_id, x.clone());
|
||||
}
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
@ -145,6 +173,8 @@ impl Command for Where {
|
||||
}
|
||||
})
|
||||
.into_pipeline_data(ctrlc)),
|
||||
// This match allows non-iterables to be accepted,
|
||||
// which is currently considered undesirable (Nov 2022).
|
||||
PipelineData::Value(x, ..) => {
|
||||
// see note above about with_env()
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
@ -197,7 +227,8 @@ impl Command for Where {
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
Ok(input
|
||||
.into_iter()
|
||||
.filter_map(move |value| {
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, value)| {
|
||||
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
@ -205,6 +236,18 @@ impl Command for Where {
|
||||
stack.add_var(*var_id, value.clone());
|
||||
}
|
||||
}
|
||||
// Optional index argument
|
||||
if let Some(var) = block.signature.get_positional(1) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(
|
||||
*var_id,
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
let result = eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
@ -283,7 +326,7 @@ impl Command for Where {
|
||||
// TODO: This should work but does not. (Note that `Let` must be present in the working_set in `example_test.rs`).
|
||||
// See https://github.com/nushell/nushell/issues/7034
|
||||
// Example {
|
||||
// description: "Get all numbers above 3 with an existing block condition",
|
||||
// description: "List all numbers above 3, using an existing closure condition",
|
||||
// example: "let a = {$in > 3}; [1, 2, 5, 6] | where -b $a",
|
||||
// result: Some(Value::List {
|
||||
// vals: vec![
|
||||
|
@ -108,6 +108,16 @@ fn early_exits_with_0_param_blocks() {
|
||||
assert_eq!(actual.out, "1false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[7 8 9] | all {|el ind| print $ind | true }"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "012true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unique_env_each_iteration() {
|
||||
let actual = nu!(
|
||||
|
@ -84,6 +84,16 @@ fn early_exits_with_0_param_blocks() {
|
||||
assert_eq!(actual.out, "1true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[7 8 9] | any {|el ind| print $ind | false }"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "012false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unique_env_each_iteration() {
|
||||
let actual = nu!(
|
||||
|
@ -71,3 +71,33 @@ fn each_implicit_it_in_block() {
|
||||
|
||||
assert_eq!(actual.out, "ace");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[7 8 9 10] | each {|el ind| $ind } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[0, 1, 2, 3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_while_uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[7 8 9 10] | each while {|el ind| $ind } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[0, 1, 2, 3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn par_each_uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[7 8 9 10] | par-each {|el ind| $ind } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[0, 1, 2, 3]");
|
||||
}
|
||||
|
@ -14,6 +14,15 @@ fn insert_the_column() {
|
||||
assert_eq!(actual.out, "0.7.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_convert_record_to_table() {
|
||||
let actual = nu!(
|
||||
cwd: ".", r#"{a:1} | insert b 2 | to nuon"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "{a: 1, b: 2}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_the_column_conflict() {
|
||||
let actual = nu!(
|
||||
@ -76,3 +85,13 @@ fn insert_past_end_list() {
|
||||
|
||||
assert_eq!(actual.out, r#"[1,2,3,null,null,"abc"]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[[a]; [7] [6]] | insert b {|el ind| $ind + 1 + $el.a } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[[a, b]; [7, 8], [6, 8]]");
|
||||
}
|
||||
|
@ -119,3 +119,13 @@ fn error_reduce_empty() {
|
||||
|
||||
assert!(actual.err.contains("needs input"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[18 19 20] | reduce -f 0 {|elem accum index| $accum + $index } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "3");
|
||||
}
|
||||
|
@ -14,6 +14,15 @@ fn sets_the_column() {
|
||||
assert_eq!(actual.out, "0.7.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_convert_record_to_table() {
|
||||
let actual = nu!(
|
||||
cwd: ".", r#"{a:1} | update a 2 | to nuon"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "{a: 2}");
|
||||
}
|
||||
|
||||
#[cfg(features = "inc")]
|
||||
#[test]
|
||||
fn sets_the_column_from_a_block_run_output() {
|
||||
@ -105,3 +114,13 @@ fn update_nonexistent_column() {
|
||||
|
||||
assert!(actual.err.contains("cannot find column 'b'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[[a]; [7] [6]] | update a {|el ind| $ind + 1 + $el.a } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[[a]; [8], [8]]");
|
||||
}
|
||||
|
@ -14,6 +14,15 @@ fn sets_the_column() {
|
||||
assert_eq!(actual.out, "0.7.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_convert_record_to_table() {
|
||||
let actual = nu!(
|
||||
cwd: ".", r#"{a:1} | upsert a 2 | to nuon"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "{a: 2}");
|
||||
}
|
||||
|
||||
#[cfg(features = "inc")]
|
||||
#[test]
|
||||
fn sets_the_column_from_a_block_run_output() {
|
||||
@ -58,3 +67,23 @@ fn sets_the_column_from_a_subexpression() {
|
||||
|
||||
assert_eq!(actual.out, "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument_inserting() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[[a]; [7] [6]] | upsert b {|el ind| $ind + 1 + $el.a } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[[a, b]; [7, 8], [6, 8]]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument_updating() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"[[a]; [7] [6]] | upsert a {|el ind| $ind + 1 + $el.a } | to nuon"#
|
||||
));
|
||||
|
||||
assert_eq!(actual.out, "[[a]; [8], [8]]");
|
||||
}
|
||||
|
@ -72,6 +72,16 @@ fn where_not_in_table() {
|
||||
assert_eq!(actual.out, "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_optional_index_argument() {
|
||||
let actual = nu!(
|
||||
cwd: ".",
|
||||
r#"[7 8 9 10] | where {|el ind| $ind < 2 } | to nuon"#
|
||||
);
|
||||
|
||||
assert_eq!(actual.out, "[7, 8]");
|
||||
}
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
#[test]
|
||||
fn binary_operator_comparisons() {
|
||||
|
@ -349,7 +349,7 @@ fn proper_missing_param() -> TestResult {
|
||||
|
||||
#[test]
|
||||
fn block_arity_check1() -> TestResult {
|
||||
fail_test(r#"ls | each { |x, y| 1}"#, "expected 1 block parameter")
|
||||
fail_test(r#"ls | each { |x, y, z| 1}"#, "expected 2 block parameters")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Loading…
Reference in New Issue
Block a user