Rename/deprecate range to slice (#14825)

# Description
As the `range` command has an ambiguous name (does it construct a range
type?, does it iterate a range like `seq`) replace it with a more
descriptive verb of what it does: `slice`

Closes #14130
# User-Facing Changes
`range` is now deprecated and replaced in whole by `slice` with the same
behavior.
`range` will be removed in `0.103.0`

# Tests + Formatting
Tests have been updated to use `slice`

# After submitting

- [ ] prepare PR for `nu_scripts` (several usages of `range` to be
fixed)
- [ ] update documentation usages of `range` after release
This commit is contained in:
Stefan Holderbach 2025-01-17 13:21:32 +01:00 committed by GitHub
parent 089c5221cc
commit 4dcaf2a201
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 167 additions and 74 deletions

View File

@ -79,6 +79,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Skip,
SkipUntil,
SkipWhile,
Slice,
Sort,
SortBy,
SplitList,

View File

@ -40,6 +40,7 @@ mod select;
#[cfg(feature = "rand")]
mod shuffle;
mod skip;
mod slice;
mod sort;
mod sort_by;
mod take;
@ -99,6 +100,7 @@ pub use select::Select;
#[cfg(feature = "rand")]
pub use shuffle::Shuffle;
pub use skip::*;
pub use slice::Slice;
pub use sort::Sort;
pub use sort_by::SortBy;
pub use take::*;

View File

@ -1,6 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::Range as NumRange;
use std::ops::Bound;
use nu_protocol::{report_parse_warning, ParseWarning};
#[derive(Clone)]
pub struct Range;
@ -17,7 +16,7 @@ impl Command for Range {
Type::List(Box::new(Type::Any)),
)])
.required("rows", SyntaxShape::Range, "Range of rows to return.")
.category(Category::Filters)
.category(Category::Deprecated)
}
fn description(&self) -> &str {
@ -65,69 +64,16 @@ impl Command for Range {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let metadata = input.metadata();
let rows: Spanned<NumRange> = call.req(engine_state, stack, 0)?;
match rows.item {
NumRange::IntRange(range) => {
let start = range.start();
let end = match range.end() {
Bound::Included(end) => end,
Bound::Excluded(end) => end - 1,
Bound::Unbounded => {
if range.step() < 0 {
i64::MIN
} else {
i64::MAX
}
}
};
// only collect the input if we have any negative indices
if start < 0 || end < 0 {
let v: Vec<_> = input.into_iter().collect();
let vlen: i64 = v.len() as i64;
let from = if start < 0 {
(vlen + start) as usize
} else {
start as usize
};
let to = if end < 0 {
(vlen + end) as usize
} else if end > v.len() as i64 {
v.len()
} else {
end as usize
};
if from > to {
Ok(PipelineData::Value(Value::nothing(head), None))
} else {
let iter = v.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(head, engine_state.signals().clone()))
}
} else {
let from = start as usize;
let to = end as usize;
if from > to {
Ok(PipelineData::Value(Value::nothing(head), None))
} else {
let iter = input.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(head, engine_state.signals().clone()))
}
}
.map(|x| x.set_metadata(metadata))
}
NumRange::FloatRange(_) => Err(ShellError::UnsupportedInput {
msg: "float range".into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: rows.span,
}),
}
report_parse_warning(
&StateWorkingSet::new(engine_state),
&ParseWarning::DeprecatedWarning {
old_command: "range".into(),
new_suggestion: "use `slice`".into(),
span: head,
url: "`help slice`".into(),
},
);
super::Slice::run(&super::Slice, engine_state, stack, call, input)
}
}

View File

@ -0,0 +1,144 @@
use nu_engine::command_prelude::*;
use nu_protocol::Range;
use std::ops::Bound;
#[derive(Clone)]
pub struct Slice;
impl Command for Slice {
fn name(&self) -> &str {
"slice"
}
fn signature(&self) -> Signature {
Signature::build("slice")
.input_output_types(vec![(
Type::List(Box::new(Type::Any)),
Type::List(Box::new(Type::Any)),
)])
.required("rows", SyntaxShape::Range, "Range of rows to return.")
.category(Category::Filters)
}
fn description(&self) -> &str {
"Return only the selected rows."
}
fn search_terms(&self) -> Vec<&str> {
vec!["filter", "head", "tail", "range"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "[0,1,2,3,4,5] | slice 4..5",
description: "Get the last 2 items",
result: Some(Value::list(
vec![Value::test_int(4), Value::test_int(5)],
Span::test_data(),
)),
},
Example {
example: "[0,1,2,3,4,5] | slice (-2)..",
description: "Get the last 2 items",
result: Some(Value::list(
vec![Value::test_int(4), Value::test_int(5)],
Span::test_data(),
)),
},
Example {
example: "[0,1,2,3,4,5] | slice (-3)..-2",
description: "Get the next to last 2 items",
result: Some(Value::list(
vec![Value::test_int(3), Value::test_int(4)],
Span::test_data(),
)),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let metadata = input.metadata();
let rows: Spanned<Range> = call.req(engine_state, stack, 0)?;
match rows.item {
Range::IntRange(range) => {
let start = range.start();
let end = match range.end() {
Bound::Included(end) => end,
Bound::Excluded(end) => end - 1,
Bound::Unbounded => {
if range.step() < 0 {
i64::MIN
} else {
i64::MAX
}
}
};
// only collect the input if we have any negative indices
if start < 0 || end < 0 {
let v: Vec<_> = input.into_iter().collect();
let vlen: i64 = v.len() as i64;
let from = if start < 0 {
(vlen + start) as usize
} else {
start as usize
};
let to = if end < 0 {
(vlen + end) as usize
} else if end > v.len() as i64 {
v.len()
} else {
end as usize
};
if from > to {
Ok(PipelineData::Value(Value::nothing(head), None))
} else {
let iter = v.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(head, engine_state.signals().clone()))
}
} else {
let from = start as usize;
let to = end as usize;
if from > to {
Ok(PipelineData::Value(Value::nothing(head), None))
} else {
let iter = input.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(head, engine_state.signals().clone()))
}
}
.map(|x| x.set_metadata(metadata))
}
Range::FloatRange(_) => Err(ShellError::UnsupportedInput {
msg: "float range".into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: rows.span,
}),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Slice {})
}
}

View File

@ -82,7 +82,6 @@ mod print;
#[cfg(feature = "sqlite")]
mod query;
mod random;
mod range;
mod redirection;
mod reduce;
mod reject;
@ -100,6 +99,7 @@ mod seq;
mod seq_char;
mod seq_date;
mod skip;
mod slice;
mod sort;
mod sort_by;
mod source_env;

View File

@ -4,7 +4,7 @@ use nu_test_support::{nu, pipeline};
#[test]
fn selects_a_row() {
Playground::setup("range_test_1", |dirs, sandbox| {
Playground::setup("slice_test_1", |dirs, sandbox| {
sandbox.with_files(&[EmptyFile("notes.txt"), EmptyFile("tests.txt")]);
let actual = nu!(
@ -12,7 +12,7 @@ fn selects_a_row() {
"
ls
| sort-by name
| range 0..0
| slice 0..0
| get name.0
"
));
@ -23,7 +23,7 @@ fn selects_a_row() {
#[test]
fn selects_some_rows() {
Playground::setup("range_test_2", |dirs, sandbox| {
Playground::setup("slice_test_2", |dirs, sandbox| {
sandbox.with_files(&[
EmptyFile("notes.txt"),
EmptyFile("tests.txt"),
@ -35,7 +35,7 @@ fn selects_some_rows() {
"
ls
| get name
| range 1..2
| slice 1..2
| length
"
));
@ -46,7 +46,7 @@ fn selects_some_rows() {
#[test]
fn negative_indices() {
Playground::setup("range_test_negative_indices", |dirs, sandbox| {
Playground::setup("slice_test_negative_indices", |dirs, sandbox| {
sandbox.with_files(&[
EmptyFile("notes.txt"),
EmptyFile("tests.txt"),
@ -58,7 +58,7 @@ fn negative_indices() {
"
ls
| get name
| range (-1..)
| slice (-1..)
| length
"
));

View File

@ -441,7 +441,7 @@ fn better_operator_spans() -> TestResult {
#[test]
fn range_right_exclusive() -> TestResult {
run_test(r#"[1, 4, 5, 8, 9] | range 1..<3 | math sum"#, "9")
run_test(r#"[1, 4, 5, 8, 9] | slice 1..<3 | math sum"#, "9")
}
/// Issue #7872