mirror of
https://github.com/nushell/nushell.git
synced 2024-11-26 18:33:50 +01:00
Add reduce command (#700)
* Add reduce command * Fix example and missing test commands * Add forgotten file
This commit is contained in:
parent
f964ce9bc0
commit
3b467bedd9
@ -75,6 +75,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||||||
ParEach,
|
ParEach,
|
||||||
Prepend,
|
Prepend,
|
||||||
Range,
|
Range,
|
||||||
|
Reduce,
|
||||||
Reject,
|
Reject,
|
||||||
Reverse,
|
Reverse,
|
||||||
Select,
|
Select,
|
||||||
|
@ -12,7 +12,10 @@ use nu_protocol::{
|
|||||||
use crate::To;
|
use crate::To;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use super::{Ansi, Date, From, Into, Math, Path, Random, Split, Str, StrCollect, Url};
|
use super::{
|
||||||
|
Ansi, Date, From, If, Into, Math, Path, Random, Split, Str, StrCollect, StrFindReplace,
|
||||||
|
StrLength, Url,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn test_examples(cmd: impl Command + 'static) {
|
pub fn test_examples(cmd: impl Command + 'static) {
|
||||||
@ -27,8 +30,11 @@ pub fn test_examples(cmd: impl Command + 'static) {
|
|||||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||||
working_set.add_decl(Box::new(Str));
|
working_set.add_decl(Box::new(Str));
|
||||||
working_set.add_decl(Box::new(StrCollect));
|
working_set.add_decl(Box::new(StrCollect));
|
||||||
|
working_set.add_decl(Box::new(StrLength));
|
||||||
|
working_set.add_decl(Box::new(StrFindReplace));
|
||||||
working_set.add_decl(Box::new(BuildString));
|
working_set.add_decl(Box::new(BuildString));
|
||||||
working_set.add_decl(Box::new(From));
|
working_set.add_decl(Box::new(From));
|
||||||
|
working_set.add_decl(Box::new(If));
|
||||||
working_set.add_decl(Box::new(To));
|
working_set.add_decl(Box::new(To));
|
||||||
working_set.add_decl(Box::new(Into));
|
working_set.add_decl(Box::new(Into));
|
||||||
working_set.add_decl(Box::new(Random));
|
working_set.add_decl(Box::new(Random));
|
||||||
|
@ -19,6 +19,7 @@ mod nth;
|
|||||||
mod par_each;
|
mod par_each;
|
||||||
mod prepend;
|
mod prepend;
|
||||||
mod range;
|
mod range;
|
||||||
|
mod reduce;
|
||||||
mod reject;
|
mod reject;
|
||||||
mod reverse;
|
mod reverse;
|
||||||
mod select;
|
mod select;
|
||||||
@ -51,6 +52,7 @@ pub use nth::Nth;
|
|||||||
pub use par_each::ParEach;
|
pub use par_each::ParEach;
|
||||||
pub use prepend::Prepend;
|
pub use prepend::Prepend;
|
||||||
pub use range::Range;
|
pub use range::Range;
|
||||||
|
pub use reduce::Reduce;
|
||||||
pub use reject::Reject;
|
pub use reject::Reject;
|
||||||
pub use reverse::Reverse;
|
pub use reverse::Reverse;
|
||||||
pub use select::Select;
|
pub use select::Select;
|
||||||
|
220
crates/nu-command/src/filters/reduce.rs
Normal file
220
crates/nu-command/src/filters/reduce.rs
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
use nu_engine::{eval_block, CallExt};
|
||||||
|
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Reduce;
|
||||||
|
|
||||||
|
impl Command for Reduce {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"reduce"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("reduce")
|
||||||
|
.named(
|
||||||
|
"fold",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"reduce with initial value",
|
||||||
|
Some('f'),
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"block",
|
||||||
|
SyntaxShape::Block(Some(vec![SyntaxShape::Any])),
|
||||||
|
"reducing function",
|
||||||
|
)
|
||||||
|
.switch("numbered", "iterate with an index", Some('n'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Aggregate a list table to a single value using an accumulator block."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
example: "[ 1 2 3 4 ] | reduce { $it.acc + $it.item }",
|
||||||
|
description: "Sum values of a list (same as 'math sum')",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 10,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
example: "[ 1 2 3 4 ] | reduce -f 10 { $it.acc + $it.item }",
|
||||||
|
description: "Sum values with a starting value (fold)",
|
||||||
|
result: Some(Value::Int {
|
||||||
|
val: 20,
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
example: r#"[ i o t ] | reduce -f "Arthur, King of the Britons" { $it.acc | str find-replace -a $it.item "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(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
example: r#"[ one longest three bar ] | reduce -n {
|
||||||
|
if ($it.item | str length) > ($it.acc | str length) {
|
||||||
|
$it.item
|
||||||
|
} else {
|
||||||
|
$it.acc
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
description: "Find the longest string and its index",
|
||||||
|
result: Some(Value::Record {
|
||||||
|
cols: vec!["index".to_string(), "item".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Int {
|
||||||
|
val: 3,
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
Value::String {
|
||||||
|
val: "longest".to_string(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
// TODO: How to make this interruptible?
|
||||||
|
// TODO: Change the vars to $acc and $it instead of $it.acc and $it.item
|
||||||
|
// (requires parser change)
|
||||||
|
|
||||||
|
let span = call.head;
|
||||||
|
|
||||||
|
let fold: Option<Value> = call.get_flag(engine_state, stack, "fold")?;
|
||||||
|
let numbered = call.has_flag("numbered");
|
||||||
|
let block = if let Some(block_id) = call.nth(0).and_then(|b| b.as_block()) {
|
||||||
|
engine_state.get_block(block_id)
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::SpannedLabeledError(
|
||||||
|
"Internal Error".to_string(),
|
||||||
|
"expected block".to_string(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stack = stack.collect_captures(&block.captures);
|
||||||
|
let orig_env_vars = stack.env_vars.clone();
|
||||||
|
let orig_env_hidden = stack.env_hidden.clone();
|
||||||
|
|
||||||
|
let mut input_iter = input.into_iter();
|
||||||
|
|
||||||
|
let (off, start_val) = if let Some(val) = fold {
|
||||||
|
(0, val)
|
||||||
|
} else if let Some(val) = input_iter.next() {
|
||||||
|
(1, val)
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::SpannedLabeledError(
|
||||||
|
"Expected input".to_string(),
|
||||||
|
"needs input".to_string(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(input_iter
|
||||||
|
.enumerate()
|
||||||
|
.fold(start_val, move |acc, (idx, x)| {
|
||||||
|
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||||
|
|
||||||
|
// if the acc coming from previous iter is indexed, drop the index
|
||||||
|
let acc = if let Value::Record { cols, vals, .. } = &acc {
|
||||||
|
if cols.len() == 2 && vals.len() == 2 {
|
||||||
|
if cols[0].eq("index") && cols[1].eq("item") {
|
||||||
|
vals[1].clone()
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
|
if let Some(var_id) = &var.var_id {
|
||||||
|
let it = if numbered {
|
||||||
|
Value::Record {
|
||||||
|
cols: vec![
|
||||||
|
"index".to_string(),
|
||||||
|
"acc".to_string(),
|
||||||
|
"item".to_string(),
|
||||||
|
],
|
||||||
|
vals: vec![
|
||||||
|
Value::Int {
|
||||||
|
val: idx as i64 + off,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
acc,
|
||||||
|
x,
|
||||||
|
],
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["acc".to_string(), "item".to_string()],
|
||||||
|
vals: vec![acc, x],
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
stack.add_var(*var_id, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let v = match eval_block(engine_state, &mut stack, block, PipelineData::new(span)) {
|
||||||
|
Ok(v) => v.into_value(span),
|
||||||
|
Err(error) => Value::Error { error },
|
||||||
|
};
|
||||||
|
|
||||||
|
if numbered {
|
||||||
|
// make sure the output is indexed
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["index".to_string(), "item".to_string()],
|
||||||
|
vals: vec![
|
||||||
|
Value::Int {
|
||||||
|
val: idx as i64 + off,
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
v,
|
||||||
|
],
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Reduce {})
|
||||||
|
}
|
||||||
|
}
|
@ -2815,6 +2815,7 @@ pub fn parse_block_expression(
|
|||||||
let (output, err) = lite_parse(&output[amt_to_skip..]);
|
let (output, err) = lite_parse(&output[amt_to_skip..]);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
|
// TODO: Finish this
|
||||||
if let SyntaxShape::Block(Some(v)) = shape {
|
if let SyntaxShape::Block(Some(v)) = shape {
|
||||||
if signature.is_none() && v.len() == 1 {
|
if signature.is_none() && v.len() == 1 {
|
||||||
// We'll assume there's an `$it` present
|
// We'll assume there's an `$it` present
|
||||||
|
Loading…
Reference in New Issue
Block a user