diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 77993b7e55..32cb38aa89 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -58,6 +58,7 @@ pub fn create_default_context() -> EngineState { Mv, ParEach, Ps, + Range, Rm, Select, Size, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 595fe396cd..f322ca3092 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -4,6 +4,7 @@ mod last; mod length; mod lines; mod par_each; +mod range; mod select; mod where_; mod wrap; @@ -14,6 +15,7 @@ pub use last::Last; pub use length::Length; pub use lines::Lines; pub use par_each::ParEach; +pub use range::Range; pub use select::Select; pub use where_::Where; pub use wrap::Wrap; diff --git a/crates/nu-command/src/filters/range.rs b/crates/nu-command/src/filters/range.rs new file mode 100644 index 0000000000..0280f51822 --- /dev/null +++ b/crates/nu-command/src/filters/range.rs @@ -0,0 +1,127 @@ +use nu_engine::CallExt; + +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Value, +}; + +#[derive(Clone)] +pub struct Range; + +impl Command for Range { + fn name(&self) -> &str { + "range" + } + + fn signature(&self) -> Signature { + Signature::build("range").optional( + "rows", + SyntaxShape::Range, + "range of rows to return: Eg) 4..7 (=> from 4 to 7)", + ) + } + + fn usage(&self) -> &str { + "Return only the selected rows." + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[0,1,2,3,4,5] | range 4..5", + description: "Get the last 2 items", + result: Some(Value::List { + vals: vec![Value::test_int(4), Value::test_int(5)], + span: Span::unknown(), + }), + }, + Example { + example: "[0,1,2,3,4,5] | range (-2)..", + description: "Get the last 2 items", + result: Some(Value::List { + vals: vec![Value::test_int(4), Value::test_int(5)], + span: Span::unknown(), + }), + }, + Example { + example: "[0,1,2,3,4,5] | range (-3)..-2", + description: "Get the next to last 2 items", + result: Some(Value::List { + vals: vec![Value::test_int(3), Value::test_int(4)], + span: Span::unknown(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let rows: nu_protocol::Range = call.req(engine_state, stack, 0)?; + + let rows_from = get_range_val(rows.from); + let rows_to = get_range_val(rows.to); + + // only collect the input if we have any negative indices + if rows_from < 0 || rows_to < 0 { + let v: Vec<_> = input.into_iter().collect(); + let vlen: i64 = v.len() as i64; + + let from = if rows_from < 0 { + (vlen + rows_from) as usize + } else { + rows_from as usize + }; + + let to = if rows_to < 0 { + (vlen + rows_to) as usize + } else if rows_to > v.len() as i64 { + v.len() + } else { + rows_to as usize + }; + + if from > to { + Ok(PipelineData::Value(Value::Nothing { span: call.head })) + } else { + let iter = v.into_iter().skip(from).take(to - from + 1); + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) + } + } else { + let from = rows_from as usize; + let to = rows_to as usize; + + if from > to { + Ok(PipelineData::Value(Value::Nothing { span: call.head })) + } else { + let iter = input.into_iter().skip(from).take(to - from + 1); + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) + } + } + } +} + +fn get_range_val(rows_val: Value) -> i64 { + match rows_val { + Value::Int { val: x, .. } => x, + _ => 0, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Range {}) + } +}