Move 'nth' into 'select' (#4385)

This commit is contained in:
JT 2022-02-09 09:59:40 -05:00 committed by GitHub
parent 43850bf20e
commit 5a1d81221f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 190 additions and 259 deletions

View File

@ -176,10 +176,10 @@ using Nushell commands.
To get the 3rd row in the table, you can use the `nth` command: To get the 3rd row in the table, you can use the `nth` command:
``` ```
ls | nth 2 ls | select 2
``` ```
This will get the 3rd (note that `nth` is zero-based) row in the table created This will get the 3rd (note that `select` is zero-based) row in the table created
by the `ls` command. You can use `nth` on any table created by other commands by the `ls` command. You can use `select` on any table created by other commands
as well. as well.
You can also access the column of data in one of two ways. If you want You can also access the column of data in one of two ways. If you want
@ -218,7 +218,7 @@ like lists and tables.
To reach a cell of data from a table, you can combine a row operation and a To reach a cell of data from a table, you can combine a row operation and a
column operation. column operation.
``` ```
ls | nth 4 | get name ls | select 4 | get name
``` ```
You can combine these operations into one step using a shortcut. You can combine these operations into one step using a shortcut.
``` ```

View File

@ -84,7 +84,6 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
Last, Last,
Length, Length,
Lines, Lines,
Nth,
ParEach, ParEach,
ParEachGroup, ParEachGroup,
Prepend, Prepend,

View File

@ -35,7 +35,7 @@ impl Command for Columns {
result: None, result: None,
}, },
Example { Example {
example: "[[name,age,grade]; [bill,20,a]] | columns | nth 1", example: "[[name,age,grade]; [bill,20,a]] | columns | select 1",
description: "Get the second column from the table", description: "Get the second column from the table",
result: None, result: None,
}, },

View File

@ -22,7 +22,6 @@ mod length;
mod lines; mod lines;
mod merge; mod merge;
mod move_; mod move_;
mod nth;
mod par_each; mod par_each;
mod par_each_group; mod par_each_group;
mod prepend; mod prepend;
@ -69,7 +68,6 @@ pub use length::Length;
pub use lines::Lines; pub use lines::Lines;
pub use merge::Merge; pub use merge::Merge;
pub use move_::Move; pub use move_::Move;
pub use nth::Nth;
pub use par_each::ParEach; pub use par_each::ParEach;
pub use par_each_group::ParEachGroup; pub use par_each_group::ParEachGroup;
pub use prepend::Prepend; pub use prepend::Prepend;

View File

@ -1,152 +0,0 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, PipelineIterator, ShellError,
Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct Nth;
impl Command for Nth {
fn name(&self) -> &str {
"nth"
}
fn signature(&self) -> Signature {
Signature::build("nth")
.rest("rest", SyntaxShape::Int, "the number of the row to return")
.switch("skip", "Skip the rows instead of selecting them", Some('s'))
.category(Category::Filters)
}
fn usage(&self) -> &str {
"Return or skip only the selected rows."
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "[sam,sarah,2,3,4,5] | nth 0 1 2",
description: "Get the first, second, and third row",
result: Some(Value::List {
vals: vec![
Value::test_string("sam"),
Value::test_string("sarah"),
Value::test_int(2),
],
span: Span::test_data(),
}),
},
Example {
example: "[0,1,2,3,4,5] | nth 0 1 2",
description: "Get the first, second, and third row",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
}),
},
Example {
example: "[0,1,2,3,4,5] | nth -s 0 1 2",
description: "Skip the first, second, and third row",
result: Some(Value::List {
vals: vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)],
span: Span::test_data(),
}),
},
Example {
example: "[0,1,2,3,4,5] | nth 0 2 4",
description: "Get the first, third, and fifth row",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(4)],
span: Span::test_data(),
}),
},
Example {
example: "[0,1,2,3,4,5] | nth 2 0 4",
description: "Get the first, third, and fifth row",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(4)],
span: Span::test_data(),
}),
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut rows: Vec<usize> = call.rest(engine_state, stack, 0)?;
rows.sort_unstable();
let skip = call.has_flag("skip");
let pipeline_iter: PipelineIterator = input.into_iter();
Ok(NthIterator {
input: pipeline_iter,
rows,
skip,
current: 0,
}
.into_pipeline_data(engine_state.ctrlc.clone()))
}
}
struct NthIterator {
input: PipelineIterator,
rows: Vec<usize>,
skip: bool,
current: usize,
}
impl Iterator for NthIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
loop {
if !self.skip {
if let Some(row) = self.rows.get(0) {
if self.current == *row {
self.rows.remove(0);
self.current += 1;
return self.input.next();
} else {
self.current += 1;
let _ = self.input.next();
continue;
}
} else {
return None;
}
} else if let Some(row) = self.rows.get(0) {
if self.current == *row {
self.rows.remove(0);
self.current += 1;
let _ = self.input.next();
continue;
} else {
self.current += 1;
return self.input.next();
}
} else {
return self.input.next();
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Nth {})
}
}

View File

@ -1,9 +1,9 @@
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath}; use nu_protocol::ast::{Call, CellPath, PathMember};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
Signature, Span, SyntaxShape, Value, PipelineIterator, ShellError, Signature, Span, SyntaxShape, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -14,6 +14,7 @@ impl Command for Select {
"select" "select"
} }
// FIXME: also add support for --skip
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("select") Signature::build("select")
.rest( .rest(
@ -63,9 +64,44 @@ fn select(
columns: Vec<CellPath>, columns: Vec<CellPath>,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
if columns.is_empty() { let mut rows = vec![];
return Err(ShellError::CantFindColumn(span, span)); //FIXME?
let mut new_columns = vec![];
for column in columns {
let CellPath { ref members } = column;
match members.get(0) {
Some(PathMember::Int { val, span }) => {
if members.len() > 1 {
return Err(ShellError::SpannedLabeledError(
"Select only allows row numbers for rows".into(),
"extra after row number".into(),
*span,
));
}
rows.push(*val);
}
_ => new_columns.push(column),
};
} }
let columns = new_columns;
let input = if !rows.is_empty() {
rows.sort_unstable();
// let skip = call.has_flag("skip");
let pipeline_iter: PipelineIterator = input.into_iter();
NthIterator {
input: pipeline_iter,
rows,
skip: false,
current: 0,
}
.into_pipeline_data(engine_state.ctrlc.clone())
} else {
input
};
match input { match input {
PipelineData::Value( PipelineData::Value(
@ -78,17 +114,21 @@ fn select(
let mut output = vec![]; let mut output = vec![];
for input_val in input_vals { for input_val in input_vals {
let mut cols = vec![]; if !columns.is_empty() {
let mut vals = vec![]; let mut cols = vec![];
for path in &columns { let mut vals = vec![];
//FIXME: improve implementation to not clone for path in &columns {
let fetcher = input_val.clone().follow_cell_path(&path.members)?; //FIXME: improve implementation to not clone
let fetcher = input_val.clone().follow_cell_path(&path.members)?;
cols.push(path.into_string()); cols.push(path.into_string());
vals.push(fetcher); vals.push(fetcher);
}
output.push(Value::Record { cols, vals, span })
} else {
output.push(input_val)
} }
output.push(Value::Record { cols, vals, span })
} }
Ok(output Ok(output
@ -97,39 +137,89 @@ fn select(
} }
PipelineData::ListStream(stream, ..) => Ok(stream PipelineData::ListStream(stream, ..) => Ok(stream
.map(move |x| { .map(move |x| {
let mut cols = vec![]; if !columns.is_empty() {
let mut vals = vec![]; let mut cols = vec![];
for path in &columns { let mut vals = vec![];
//FIXME: improve implementation to not clone for path in &columns {
match x.clone().follow_cell_path(&path.members) { //FIXME: improve implementation to not clone
Ok(value) => { match x.clone().follow_cell_path(&path.members) {
cols.push(path.into_string()); Ok(value) => {
vals.push(value); cols.push(path.into_string());
} vals.push(value);
Err(error) => { }
cols.push(path.into_string()); Err(error) => {
vals.push(Value::Error { error }); cols.push(path.into_string());
vals.push(Value::Error { error });
}
} }
} }
Value::Record { cols, vals, span }
} else {
x
} }
Value::Record { cols, vals, span }
}) })
.into_pipeline_data(engine_state.ctrlc.clone())), .into_pipeline_data(engine_state.ctrlc.clone())),
PipelineData::Value(v, ..) => { PipelineData::Value(v, ..) => {
let mut cols = vec![]; if !columns.is_empty() {
let mut vals = vec![]; let mut cols = vec![];
let mut vals = vec![];
for cell_path in columns { for cell_path in columns {
// FIXME: remove clone // FIXME: remove clone
let result = v.clone().follow_cell_path(&cell_path.members)?; let result = v.clone().follow_cell_path(&cell_path.members)?;
cols.push(cell_path.into_string()); cols.push(cell_path.into_string());
vals.push(result); vals.push(result);
}
Ok(Value::Record { cols, vals, span }.into_pipeline_data())
} else {
Ok(v.into_pipeline_data())
} }
Ok(Value::Record { cols, vals, span }.into_pipeline_data())
} }
_ => Ok(PipelineData::new(span)), _ => Ok(PipelineData::new(span)),
} }
} }
struct NthIterator {
input: PipelineIterator,
rows: Vec<usize>,
skip: bool,
current: usize,
}
impl Iterator for NthIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
loop {
if !self.skip {
if let Some(row) = self.rows.get(0) {
if self.current == *row {
self.rows.remove(0);
self.current += 1;
return self.input.next();
} else {
self.current += 1;
let _ = self.input.next();
continue;
}
} else {
return None;
}
} else if let Some(row) = self.rows.get(0) {
if self.current == *row {
self.rows.remove(0);
self.current += 1;
let _ = self.input.next();
continue;
} else {
self.current += 1;
return self.input.next();
}
} else {
return self.input.next();
}
}
}
}

View File

@ -108,7 +108,7 @@ fn fetches_more_than_one_column_path() {
arepas = 1 arepas = 1
[[fortune_tellers]] [[fortune_tellers]]
name = "Jonathan Turner" name = "JT"
arepas = 1 arepas = 1
[[fortune_tellers]] [[fortune_tellers]]
@ -126,7 +126,7 @@ fn fetches_more_than_one_column_path() {
"# "#
)); ));
assert_eq!(actual.out, "Jonathan Turner"); assert_eq!(actual.out, "JT");
}) })
} }

View File

@ -34,7 +34,6 @@ mod math;
mod merge; mod merge;
mod mkdir; mod mkdir;
mod move_; mod move_;
mod nth;
mod open; mod open;
mod parse; mod parse;
mod path; mod path;

View File

@ -133,7 +133,7 @@ fn moves_columns_after() {
open sample.csv open sample.csv
| move letters and_more --after column1 | move letters and_more --after column1
| get | get
| nth 1 2 | select 1 2
| str collect | str collect
"# "#
)); ));

View File

@ -1,41 +0,0 @@
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn selects_a_row() {
Playground::setup("nth_test_1", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| sort-by name
| nth 0
| get name.0
"#
));
assert_eq!(actual.out, "arepas.txt");
});
}
#[test]
fn selects_many_rows() {
Playground::setup("nth_test_2", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| nth 1 0
| length
"#
));
assert_eq!(actual.out, "2");
});
}

View File

@ -59,7 +59,7 @@ fn parses_csv() {
fn parses_bson() { fn parses_bson() {
let actual = nu!( let actual = nu!(
cwd: "tests/fixtures/formats", cwd: "tests/fixtures/formats",
"open sample.bson | get root | nth 0 | get b" "open sample.bson | get root | select 0 | get b"
); );
assert_eq!(actual.out, "hello"); assert_eq!(actual.out, "hello");
@ -73,7 +73,7 @@ fn parses_more_bson_complexity() {
r#" r#"
open sample.bson open sample.bson
| get root | get root
| nth 6 | select 6
| get b | get b
| get '$binary_subtype' | get '$binary_subtype'
"# "#
@ -120,7 +120,7 @@ fn parses_more_bson_complexity() {
// `ints` has just `z`, and `floats` has only the column `f`. This means, in general, when working // `ints` has just `z`, and `floats` has only the column `f`. This means, in general, when working
// with sqlite, one will want to select a single table, e.g.: // with sqlite, one will want to select a single table, e.g.:
// //
// open sample.db | nth 1 | get table_values // open sample.db | select 1 | get table_values
// ━━━┯━━━━━━ // ━━━┯━━━━━━
// # │ z // # │ z
// ───┼────── // ───┼──────
@ -139,7 +139,7 @@ fn parses_sqlite() {
r#" r#"
open sample.db open sample.db
| get table_values | get table_values
| nth 2 | select 2
| get x | get x
"# "#
)); ));

View File

@ -25,7 +25,7 @@ mod simple {
open key_value_separated_arepa_ingredients.txt open key_value_separated_arepa_ingredients.txt
| lines | lines
| each { echo $it | parse "{Name}={Value}" } | each { echo $it | parse "{Name}={Value}" }
| nth 1 | select 1
| get Value | get Value
"# "#
)); ));

View File

@ -1,4 +1,4 @@
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; use nu_test_support::fs::Stub::{EmptyFile, FileWithContentToBeTrimmed};
use nu_test_support::playground::Playground; use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline}; use nu_test_support::{nu, pipeline};
@ -126,3 +126,41 @@ fn ignores_duplicate_columns_selected() {
assert_eq!(actual.out, "first name last name"); assert_eq!(actual.out, "first name last name");
} }
#[test]
fn selects_a_row() {
Playground::setup("select_test_1", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| sort-by name
| select 0
| get name.0
"#
));
assert_eq!(actual.out, "arepas.txt");
});
}
#[test]
fn selects_many_rows() {
Playground::setup("select_test_2", |dirs, sandbox| {
sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls
| get name
| select 1 0
| length
"#
));
assert_eq!(actual.out, "2");
});
}

View File

@ -11,7 +11,7 @@ fn table_to_sqlite_and_back_into_table() {
| to sqlite | to sqlite
| from sqlite | from sqlite
| get table_values | get table_values
| nth 2 | select 2
| get x | get x
"# "#
)); ));

View File

@ -43,7 +43,7 @@ fn table_to_tsv_text() {
| last 1 | last 1
| to tsv | to tsv
| lines | lines
| nth 1 | select 1
"# "#
)); ));

View File

@ -564,7 +564,7 @@ impl Value {
Value::Nothing { span } Value::Nothing { span }
} }
/// Follow a given column path into the value: for example accessing nth elements in a stream or list /// Follow a given column path into the value: for example accessing select elements in a stream or list
pub fn follow_cell_path(self, cell_path: &[PathMember]) -> Result<Value, ShellError> { pub fn follow_cell_path(self, cell_path: &[PathMember]) -> Result<Value, ShellError> {
let mut current = self; let mut current = self;
for member in cell_path { for member in cell_path {
@ -575,7 +575,7 @@ impl Value {
val: count, val: count,
span: origin_span, span: origin_span,
} => { } => {
// Treat a numeric path member as `nth <val>` // Treat a numeric path member as `select <val>`
match &mut current { match &mut current {
Value::List { vals: val, .. } => { Value::List { vals: val, .. } => {
if let Some(item) = val.get(*count) { if let Some(item) = val.get(*count) {
@ -671,7 +671,7 @@ impl Value {
Ok(current) Ok(current)
} }
/// Follow a given column path into the value: for example accessing nth elements in a stream or list /// Follow a given column path into the value: for example accessing select elements in a stream or list
pub fn update_cell_path( pub fn update_cell_path(
&mut self, &mut self,
cell_path: &[PathMember], cell_path: &[PathMember],

View File

@ -26,7 +26,7 @@ Basic usage:
One useful application is piping the contents of file into `lines`. This example extracts a certain line from a given file. One useful application is piping the contents of file into `lines`. This example extracts a certain line from a given file.
```shell ```shell
> cat lines.md | lines | nth 6 > cat lines.md | lines | select 6
## Examples ## Examples
``` ```

View File

@ -1,12 +1,12 @@
# nth # nth
This command returns the nth row of a table, starting from 0. This command returns the select row of a table, starting from 0.
If the number given is less than 0 or more than the number of rows, nothing is returned. If the number given is less than 0 or more than the number of rows, nothing is returned.
## Usage ## Usage
```shell ```shell
> [input-command] | nth <row number> ...args > [input-command] | select <row number> ...args
``` ```
## Parameters ## Parameters
@ -45,7 +45,7 @@ If the number given is less than 0 or more than the number of rows, nothing is r
``` ```
```shell ```shell
> ls | nth 0 > ls | select 0
──────────┬──────────────────── ──────────┬────────────────────
name │ CODE_OF_CONDUCT.md name │ CODE_OF_CONDUCT.md
type │ File type │ File
@ -55,7 +55,7 @@ If the number given is less than 0 or more than the number of rows, nothing is r
``` ```
```shell ```shell
> ls | nth 0 2 > ls | select 0 2
───┬────────────────────┬──────┬──────────┬───────────── ───┬────────────────────┬──────┬──────────┬─────────────
# │ name │ type │ size │ modified # │ name │ type │ size │ modified
───┼────────────────────┼──────┼──────────┼───────────── ───┼────────────────────┼──────┼──────────┼─────────────
@ -65,7 +65,7 @@ If the number given is less than 0 or more than the number of rows, nothing is r
``` ```
```shell ```shell
> ls | nth 5 > ls | select 5
──────────┬─────────────── ──────────┬───────────────
name │ Makefile.toml name │ Makefile.toml
type │ File type │ File