Make get hole errors and cell path hole errors identical (improvement on #7002) (#7647)

# Description

This closes #7498, as well as fixes an issue reported in
https://github.com/nushell/nushell/pull/7002#issuecomment-1368340773

BEFORE:
```
〉[{foo: 'bar'} {}] | get foo
Error: nu:🐚:column_not_found (link)

  × Cannot find column
   ╭─[entry #5:1:1]
 1 │ [{foo: 'bar'} {}] | get foo
   · ────────┬────────   ─┬─
   ·         │            ╰── value originates here
   ·         ╰── cannot find column 'Empty cell'
   ╰────

〉[{foo: 'bar'} {}].foo
╭───┬─────╮
│ 0 │ bar │
│ 1 │     │
╰───┴─────╯
```
AFTER:
```
〉[{foo: 'bar'} {}] | get foo
Error: nu:🐚:column_not_found (link)

  × Cannot find column
   ╭─[entry #1:1:1]
 1 │ [{foo: 'bar'} {}] | get foo
   ·               ─┬        ─┬─
   ·                │         ╰── cannot find column 'foo'
   ·                ╰── value originates here
   ╰────

〉[{foo: 'bar'} {}].foo
Error: nu:🐚:column_not_found (link)

  × Cannot find column
   ╭─[entry #3:1:1]
 1 │ [{foo: 'bar'} {}].foo
   ·               ─┬  ─┬─
   ·                │   ╰── cannot find column 'foo'
   ·                ╰── value originates here       
   ╰────
```

EDIT: This also changes the semantics of `get`/`select` `-i` somewhat.
I've decided to leave it like this because it works more intuitively
with `default` and `compact`.
BEFORE:
```
〉[{a:1} {b:2} {a:3}] | select -i foo | to nuon
null
```
AFTER:
```
〉[{a:1} {b:2} {a:3}] | select -i foo | to nuon
[[foo]; [null], [null], [null]]
```

# User-Facing Changes

See above. EDIT: the issue with holes in cases like ` [{foo: 'bar'}
{}].foo.0` versus ` [{foo: 'bar'} {}].0.foo` has been resolved.

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
Leon
2023-01-03 08:45:43 +10:00
committed by GitHub
parent 614bc2a943
commit 65d0b5b9d9
22 changed files with 326 additions and 222 deletions

View File

@ -109,7 +109,10 @@ fn dropcol(
let mut vals = vec![];
for path in &keep_columns {
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
let fetcher =
input_val
.clone()
.follow_cell_path(&path.members, false, false)?;
cols.push(path.into_string());
vals.push(fetcher);
}
@ -133,7 +136,10 @@ fn dropcol(
let mut vals = vec![];
for path in &keep_columns {
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
let fetcher =
input_val
.clone()
.follow_cell_path(&path.members, false, false)?;
cols.push(path.into_string());
vals.push(fetcher);
}
@ -149,7 +155,9 @@ fn dropcol(
let mut vals = vec![];
for cell_path in &keep_columns {
let result = v.clone().follow_cell_path(&cell_path.members, false)?;
let result = v
.clone()
.follow_cell_path(&cell_path.members, false, false)?;
cols.push(cell_path.into_string());
vals.push(result);

View File

@ -73,7 +73,7 @@ fn empty(
for val in input {
for column in &columns {
let val = val.clone();
match val.follow_cell_path(&column.members, false) {
match val.follow_cell_path(&column.members, false, false) {
Ok(Value::Nothing { .. }) => {}
Ok(_) => return Ok(Value::boolean(false, head).into_pipeline_data()),
Err(err) => return Err(err),

View File

@ -64,40 +64,9 @@ impl Command for Get {
let metadata = input.metadata();
if rest.is_empty() {
let output = input
.follow_cell_path(&cell_path.members, call.head, !sensitive)
.map(|x| x.into_pipeline_data());
if ignore_errors {
match output {
Ok(output) => Ok(output),
Err(_) => Ok(Value::Nothing { span: call.head }.into_pipeline_data()),
}
} else {
match output {
Ok(val) => {
let val_check = val.into_value(span);
match val_check {
Value::List {
ref vals,
span: spanchild,
} => {
if vals.iter().any(|unit| unit.is_empty()) {
Err(nu_protocol::ShellError::CantFindColumn(
"Empty cell".to_string(),
spanchild,
span,
))
} else {
Ok(val_check.into_pipeline_data())
}
}
val => Ok(val.into_pipeline_data()),
}
}
Err(e) => Err(e),
}
}
input
.follow_cell_path(&cell_path.members, call.head, !sensitive, ignore_errors)
.map(|x| x.into_pipeline_data())
} else {
let mut output = vec![];
@ -106,25 +75,11 @@ impl Command for Get {
let input = input.into_value(span);
for path in paths {
let val = input.clone().follow_cell_path(&path.members, !sensitive);
let val = input
.clone()
.follow_cell_path(&path.members, !sensitive, false);
if ignore_errors {
match val {
Ok(Value::Nothing { span: spanchild }) => {
return Err(nu_protocol::ShellError::CantFindColumn(
"Nothing".to_string(),
spanchild,
span,
));
}
Ok(val) => {
output.push(val);
}
Err(_) => {}
}
} else {
output.push(val?);
}
output.push(val?);
}
Ok(output.into_iter().into_pipeline_data(ctrlc))
@ -152,17 +107,13 @@ impl Command for Get {
result: Some(Value::test_string("A0")),
},
Example {
description: "Extract the name of files as a list",
example: "ls | get name",
result: None,
},
Example {
description: "Extract the name of the 3rd entry of a file list",
description:
"Extract the name of the 3rd record in a list (same as `ls | $in.name`)",
example: "ls | get name.2",
result: None,
},
Example {
description: "Extract the name of the 3rd entry of a file list (alternative)",
description: "Extract the name of the 3rd record in a list",
example: "ls | get 2.name",
result: None,
},

View File

@ -81,7 +81,17 @@ fn length_row(call: &Call, input: PipelineData) -> Result<PipelineData, ShellErr
PipelineData::Value(Value::Nothing { .. }, ..) => {
Ok(Value::int(0, call.head).into_pipeline_data())
}
_ => Ok(Value::int(input.into_iter().count() as i64, call.head).into_pipeline_data()),
_ => {
let mut count: i64 = 0;
// Check for and propagate errors
for value in input.into_iter() {
if let Value::Error { error } = value {
return Err(error);
}
count += 1
}
Ok(Value::int(count, call.head).into_pipeline_data())
}
}
}

View File

@ -156,7 +156,11 @@ fn select(
let mut vals = vec![];
for path in &columns {
//FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) {
match input_val.clone().follow_cell_path(
&path.members,
false,
ignore_errors,
) {
Ok(fetcher) => {
allempty = false;
cols.push(path.into_string().replace('.', "_"));
@ -166,12 +170,7 @@ fn select(
}
}
Err(e) => {
if ignore_errors {
cols.push(path.into_string().replace('.', "_"));
vals.push(Value::Nothing { span })
} else {
return Err(e);
}
return Err(e);
}
}
}
@ -199,17 +198,15 @@ fn select(
let mut vals = vec![];
for path in &columns {
//FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) {
match x
.clone()
.follow_cell_path(&path.members, false, ignore_errors)
{
Ok(value) => {
cols.push(path.into_string().replace('.', "_"));
vals.push(value);
}
Err(e) => {
if ignore_errors {
return Ok(Value::nothing(call_span).into_pipeline_data());
}
return Err(e);
}
Err(e) => return Err(e),
}
}
values.push(Value::Record {
@ -233,18 +230,15 @@ fn select(
for cell_path in columns {
// FIXME: remove clone
match v.clone().follow_cell_path(&cell_path.members, false) {
match v
.clone()
.follow_cell_path(&cell_path.members, false, ignore_errors)
{
Ok(result) => {
cols.push(cell_path.into_string().replace('.', "_"));
vals.push(result);
}
Err(e) => {
if ignore_errors {
return Ok(Value::nothing(call_span).into_pipeline_data());
}
return Err(e);
}
Err(e) => return Err(e),
}
}

View File

@ -282,7 +282,10 @@ fn format_record(
span: *span,
})
.collect();
match data_as_value.clone().follow_cell_path(&path_members, false) {
match data_as_value
.clone()
.follow_cell_path(&path_members, false, false)
{
Ok(value_at_column) => {
output.push_str(value_at_column.into_string(", ", config).as_str())
}

View File

@ -76,7 +76,7 @@ prints out the list properly."#
match input {
PipelineData::Value(Value::List { vals, .. }, ..) => {
// dbg!("value::list");
let data = convert_to_list(vals, config, call.head);
let data = convert_to_list(vals, config, call.head)?;
if let Some(items) = data {
Ok(create_grid_output(
items,
@ -93,7 +93,7 @@ prints out the list properly."#
}
PipelineData::ListStream(stream, ..) => {
// dbg!("value::stream");
let data = convert_to_list(stream, config, call.head);
let data = convert_to_list(stream, config, call.head)?;
if let Some(items) = data {
Ok(create_grid_output(
items,
@ -247,11 +247,12 @@ fn create_grid_output(
)
}
#[allow(clippy::type_complexity)]
fn convert_to_list(
iter: impl IntoIterator<Item = Value>,
config: &Config,
head: Span,
) -> Option<Vec<(usize, String, String)>> {
) -> Result<Option<Vec<(usize, String, String)>>, ShellError> {
let mut iter = iter.into_iter().peekable();
if let Some(first) = iter.peek() {
@ -267,7 +268,7 @@ fn convert_to_list(
let mut row = vec![row_num.to_string()];
if headers.is_empty() {
row.push(item.into_string(", ", config))
row.push(item.nonerror_into_string(", ", config)?)
} else {
for header in headers.iter().skip(1) {
let result = match item {
@ -277,12 +278,13 @@ fn convert_to_list(
span: head,
}],
false,
false,
),
_ => Ok(item.clone()),
};
match result {
Ok(value) => row.push(value.into_string(", ", config)),
Ok(value) => row.push(value.nonerror_into_string(", ", config)?),
Err(_) => row.push(String::new()),
}
}
@ -315,9 +317,9 @@ fn convert_to_list(
}
}
Some(interleaved)
Ok(Some(interleaved))
} else {
None
Ok(None)
}
}

View File

@ -5,10 +5,10 @@ use nu_engine::{column::get_columns, env_to_string, CallExt};
use nu_protocol::TrimStrategy;
use nu_protocol::{
ast::{Call, PathMember},
engine::{Command, EngineState, Stack, StateWorkingSet},
format_error, Category, Config, DataSource, Example, FooterMode, IntoPipelineData, ListStream,
PipelineData, PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape,
TableIndexMode, Type, Value,
engine::{Command, EngineState, Stack},
Category, Config, DataSource, Example, FooterMode, IntoPipelineData, ListStream, PipelineData,
PipelineMetadata, RawStream, ShellError, Signature, Span, SyntaxShape, TableIndexMode, Type,
Value,
};
use nu_table::{string_width, Table as NuTable, TableConfig, TableTheme};
use nu_utils::get_ls_colors;
@ -331,12 +331,9 @@ fn handle_table_command(
Ok(val.into_pipeline_data())
}
PipelineData::Value(Value::Error { error }, ..) => {
let working_set = StateWorkingSet::new(engine_state);
Ok(Value::String {
val: format_error(&working_set, &error),
span: call.head,
}
.into_pipeline_data())
// Propagate this error outward, so that it goes to stderr
// instead of stdout.
Err(error)
}
PipelineData::Value(Value::CustomValue { val, span }, ..) => {
let base_pipeline = val.to_base_value(span)?.into_pipeline_data();
@ -903,7 +900,7 @@ fn convert_to_table(
val: text.clone(),
span: head,
};
let val = item.clone().follow_cell_path(&[path], false);
let val = item.clone().follow_cell_path(&[path], false, false);
match val {
Ok(val) => DeferredStyleComputation::Value { value: val },
@ -1264,7 +1261,7 @@ fn create_table2_entry_basic(
Value::Record { .. } => {
let val = header.to_owned();
let path = PathMember::String { val, span: head };
let val = item.clone().follow_cell_path(&[path], false);
let val = item.clone().follow_cell_path(&[path], false, false);
match val {
Ok(val) => value_to_styled_string(&val, config, style_computer),
@ -1292,7 +1289,7 @@ fn create_table2_entry(
Value::Record { .. } => {
let val = header.to_owned();
let path = PathMember::String { val, span: head };
let val = item.clone().follow_cell_path(&[path], false);
let val = item.clone().follow_cell_path(&[path], false, false);
match val {
Ok(val) => convert_to_table2_entry(