mirror of
https://github.com/nushell/nushell.git
synced 2025-05-05 15:32:56 +00:00
refactor Value::follow_cell_path
to reduce clones and return Cow
(#15640)
# Description While working on something else, I noticed that `Value::follow_cell_path` receives `self`. While it would be ideal for the signature to be `(&'a self, cell_path) -> &'a Value`, that's not possible because: 1. Selecting a row from a list and field from a record can be done with a reference but selecting a column from a table requires creating a new list. 2. `Value::Custom` returns new `Value`s when indexed. So the signature becomes `(&'a self, cell_path) -> Cow<'a, Value>`. Another complication that arises is, once a new `Value` is created, and it is further indexed, the `current` variable 1. can't be `&'a Value`, as the lifetime requirement means it can't refer to local variables 2. _shouldn't_ be `Cow<'a, Value>`, as once it becomes an owned value, it can't be borrowed ever again, as `current` is derived from its previous value in further iterations. So once it's owned, it can't be indexed by reference, leading to more clones We need `current` to have _two_ possible lifetimes 1. `'out`: references derived from `&self` 2. `'local`: references derived from an owned value stored in a local variable ```rust enum MultiLife<'out, 'local, T> where 'out: 'local, T: ?Sized, { Out(&'out T), Local(&'local T), } ``` With `current: MultiLife<'out, '_, Value>`, we can traverse values with minimal clones, and we can transform it to `Cow<'out, Value>` easily (`MultiLife::Out -> Cow::Borrowed, MultiLife::Local -> Cow::Owned`) to return it # User-Facing Changes # Tests + Formatting # After Submitting --------- Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
This commit is contained in:
parent
deca337a56
commit
55de232a1c
@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
|
||||||
use nu_engine::{column::get_columns, eval_variable};
|
use nu_engine::{column::get_columns, eval_variable};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
@ -101,7 +103,9 @@ pub(crate) fn eval_cell_path(
|
|||||||
} else {
|
} else {
|
||||||
eval_constant(working_set, head)
|
eval_constant(working_set, head)
|
||||||
}?;
|
}?;
|
||||||
head_value.follow_cell_path(path_members, false)
|
head_value
|
||||||
|
.follow_cell_path(path_members, false)
|
||||||
|
.map(Cow::into_owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_suggestions_by_value(
|
fn get_suggestions_by_value(
|
||||||
|
@ -253,12 +253,11 @@ fn format_record(
|
|||||||
optional: false,
|
optional: false,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
match data_as_value.clone().follow_cell_path(&path_members, false) {
|
|
||||||
Ok(value_at_column) => {
|
let expanded_string = data_as_value
|
||||||
output.push_str(value_at_column.to_expanded_string(", ", config).as_str())
|
.follow_cell_path(&path_members, false)?
|
||||||
}
|
.to_expanded_string(", ", config);
|
||||||
Err(se) => return Err(se),
|
output.push_str(expanded_string.as_str())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,17 +15,8 @@ pub fn empty(
|
|||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
for val in input {
|
for val in input {
|
||||||
for column in &columns {
|
for column in &columns {
|
||||||
let val = val.clone();
|
if !val.follow_cell_path(&column.members, false)?.is_nothing() {
|
||||||
match val.follow_cell_path(&column.members, false) {
|
return Ok(Value::bool(negate, head).into_pipeline_data());
|
||||||
Ok(Value::Nothing { .. }) => {}
|
|
||||||
Ok(_) => {
|
|
||||||
if negate {
|
|
||||||
return Ok(Value::bool(true, head).into_pipeline_data());
|
|
||||||
} else {
|
|
||||||
return Ok(Value::bool(false, head).into_pipeline_data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_protocol::{ast::PathMember, Signals};
|
use nu_protocol::{ast::PathMember, Signals};
|
||||||
|
|
||||||
@ -188,9 +190,11 @@ fn action(
|
|||||||
let input = input.into_value(span)?;
|
let input = input.into_value(span)?;
|
||||||
|
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let val = input.clone().follow_cell_path(&path.members, !sensitive);
|
output.push(
|
||||||
|
input
|
||||||
output.push(val?);
|
.follow_cell_path(&path.members, !sensitive)?
|
||||||
|
.into_owned(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(output.into_iter().into_pipeline_data(span, signals))
|
Ok(output.into_iter().into_pipeline_data(span, signals))
|
||||||
@ -223,10 +227,10 @@ pub fn follow_cell_path_into_stream(
|
|||||||
.map(move |value| {
|
.map(move |value| {
|
||||||
let span = value.span();
|
let span = value.span();
|
||||||
|
|
||||||
match value.follow_cell_path(&cell_path, insensitive) {
|
value
|
||||||
Ok(v) => v,
|
.follow_cell_path(&cell_path, insensitive)
|
||||||
Err(error) => Value::error(error, span),
|
.map(Cow::into_owned)
|
||||||
}
|
.unwrap_or_else(|error| Value::error(error, span))
|
||||||
})
|
})
|
||||||
.into_pipeline_data(head, signals);
|
.into_pipeline_data(head, signals);
|
||||||
|
|
||||||
|
@ -322,11 +322,9 @@ fn group_cell_path(
|
|||||||
let mut groups = IndexMap::<_, Vec<_>>::new();
|
let mut groups = IndexMap::<_, Vec<_>>::new();
|
||||||
|
|
||||||
for value in values.into_iter() {
|
for value in values.into_iter() {
|
||||||
let key = value
|
let key = value.follow_cell_path(&column_name.members, false)?;
|
||||||
.clone()
|
|
||||||
.follow_cell_path(&column_name.members, false)?;
|
|
||||||
|
|
||||||
if matches!(key, Value::Nothing { .. }) {
|
if key.is_nothing() {
|
||||||
continue; // likely the result of a failed optional access, ignore this value
|
continue; // likely the result of a failed optional access, ignore this value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
||||||
use nu_protocol::ast::PathMember;
|
use nu_protocol::ast::PathMember;
|
||||||
|
|
||||||
@ -299,8 +301,8 @@ fn insert_value_by_closure(
|
|||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = if first_path_member_int {
|
let value_at_path = if first_path_member_int {
|
||||||
value
|
value
|
||||||
.clone()
|
|
||||||
.follow_cell_path(cell_path, false)
|
.follow_cell_path(cell_path, false)
|
||||||
|
.map(Cow::into_owned)
|
||||||
.unwrap_or(Value::nothing(span))
|
.unwrap_or(Value::nothing(span))
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
@ -319,8 +321,8 @@ fn insert_single_value_by_closure(
|
|||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = if first_path_member_int {
|
let value_at_path = if first_path_member_int {
|
||||||
value
|
value
|
||||||
.clone()
|
|
||||||
.follow_cell_path(cell_path, false)
|
.follow_cell_path(cell_path, false)
|
||||||
|
.map(Cow::into_owned)
|
||||||
.unwrap_or(Value::nothing(span))
|
.unwrap_or(Value::nothing(span))
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
|
@ -229,45 +229,37 @@ fn select(
|
|||||||
match v {
|
match v {
|
||||||
Value::List {
|
Value::List {
|
||||||
vals: input_vals, ..
|
vals: input_vals, ..
|
||||||
} => {
|
} => Ok(input_vals
|
||||||
Ok(input_vals
|
.into_iter()
|
||||||
.into_iter()
|
.map(move |input_val| {
|
||||||
.map(move |input_val| {
|
if !columns.is_empty() {
|
||||||
if !columns.is_empty() {
|
let mut record = Record::new();
|
||||||
let mut record = Record::new();
|
for path in &columns {
|
||||||
for path in &columns {
|
match input_val.follow_cell_path(&path.members, false) {
|
||||||
//FIXME: improve implementation to not clone
|
Ok(fetcher) => {
|
||||||
match input_val.clone().follow_cell_path(&path.members, false) {
|
record.push(path.to_column_name(), fetcher.into_owned());
|
||||||
Ok(fetcher) => {
|
|
||||||
record.push(path.to_column_name(), fetcher);
|
|
||||||
}
|
|
||||||
Err(e) => return Value::error(e, call_span),
|
|
||||||
}
|
}
|
||||||
|
Err(e) => return Value::error(e, call_span),
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::record(record, span)
|
|
||||||
} else {
|
|
||||||
input_val.clone()
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.into_pipeline_data_with_metadata(
|
Value::record(record, span)
|
||||||
call_span,
|
} else {
|
||||||
engine_state.signals().clone(),
|
input_val.clone()
|
||||||
metadata,
|
}
|
||||||
))
|
})
|
||||||
}
|
.into_pipeline_data_with_metadata(
|
||||||
|
call_span,
|
||||||
|
engine_state.signals().clone(),
|
||||||
|
metadata,
|
||||||
|
)),
|
||||||
_ => {
|
_ => {
|
||||||
if !columns.is_empty() {
|
if !columns.is_empty() {
|
||||||
let mut record = Record::new();
|
let mut record = Record::new();
|
||||||
|
|
||||||
for cell_path in columns {
|
for cell_path in columns {
|
||||||
// FIXME: remove clone
|
let result = v.follow_cell_path(&cell_path.members, false)?;
|
||||||
match v.clone().follow_cell_path(&cell_path.members, false) {
|
record.push(cell_path.to_column_name(), result.into_owned());
|
||||||
Ok(result) => {
|
|
||||||
record.push(cell_path.to_column_name(), result);
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::record(record, call_span)
|
Ok(Value::record(record, call_span)
|
||||||
@ -278,31 +270,24 @@ fn select(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PipelineData::ListStream(stream, metadata, ..) => {
|
PipelineData::ListStream(stream, metadata, ..) => Ok(stream
|
||||||
Ok(stream
|
.map(move |x| {
|
||||||
.map(move |x| {
|
if !columns.is_empty() {
|
||||||
if !columns.is_empty() {
|
let mut record = Record::new();
|
||||||
let mut record = Record::new();
|
for path in &columns {
|
||||||
for path in &columns {
|
match x.follow_cell_path(&path.members, false) {
|
||||||
//FIXME: improve implementation to not clone
|
Ok(value) => {
|
||||||
match x.clone().follow_cell_path(&path.members, false) {
|
record.push(path.to_column_name(), value.into_owned());
|
||||||
Ok(value) => {
|
|
||||||
record.push(path.to_column_name(), value);
|
|
||||||
}
|
|
||||||
Err(e) => return Value::error(e, call_span),
|
|
||||||
}
|
}
|
||||||
|
Err(e) => return Value::error(e, call_span),
|
||||||
}
|
}
|
||||||
Value::record(record, call_span)
|
|
||||||
} else {
|
|
||||||
x
|
|
||||||
}
|
}
|
||||||
})
|
Value::record(record, call_span)
|
||||||
.into_pipeline_data_with_metadata(
|
} else {
|
||||||
call_span,
|
x
|
||||||
engine_state.signals().clone(),
|
}
|
||||||
metadata,
|
})
|
||||||
))
|
.into_pipeline_data_with_metadata(call_span, engine_state.signals().clone(), metadata)),
|
||||||
}
|
|
||||||
_ => Ok(PipelineData::empty()),
|
_ => Ok(PipelineData::empty()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,17 +243,17 @@ fn update_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
|
let value_at_path = value.follow_cell_path(cell_path, false)?;
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
&value_at_path
|
value_at_path.as_ref()
|
||||||
} else {
|
} else {
|
||||||
&*value
|
&*value
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_value = closure
|
let new_value = closure
|
||||||
.add_arg(arg.clone())
|
.add_arg(arg.clone())
|
||||||
.run_with_input(value_at_path.into_pipeline_data())?
|
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
|
||||||
.into_value(span)?;
|
.into_value(span)?;
|
||||||
|
|
||||||
value.update_data_at_cell_path(cell_path, new_value)
|
value.update_data_at_cell_path(cell_path, new_value)
|
||||||
@ -266,17 +266,17 @@ fn update_single_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.clone().follow_cell_path(cell_path, false)?;
|
let value_at_path = value.follow_cell_path(cell_path, false)?;
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
&value_at_path
|
value_at_path.as_ref()
|
||||||
} else {
|
} else {
|
||||||
&*value
|
&*value
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_value = closure
|
let new_value = closure
|
||||||
.add_arg(arg.clone())
|
.add_arg(arg.clone())
|
||||||
.run_with_input(value_at_path.into_pipeline_data())?
|
.run_with_input(value_at_path.into_owned().into_pipeline_data())?
|
||||||
.into_value(span)?;
|
.into_value(span)?;
|
||||||
|
|
||||||
value.update_data_at_cell_path(cell_path, new_value)
|
value.update_data_at_cell_path(cell_path, new_value)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
use nu_engine::{command_prelude::*, ClosureEval, ClosureEvalOnce};
|
||||||
use nu_protocol::ast::PathMember;
|
use nu_protocol::ast::PathMember;
|
||||||
|
|
||||||
@ -319,15 +321,19 @@ fn upsert_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.clone().follow_cell_path(cell_path, false);
|
let value_at_path = value.follow_cell_path(cell_path, false);
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
value_at_path.clone().unwrap_or(Value::nothing(span))
|
value_at_path
|
||||||
|
.as_deref()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Value::nothing(span))
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let input = value_at_path
|
let input = value_at_path
|
||||||
|
.map(Cow::into_owned)
|
||||||
.map(IntoPipelineData::into_pipeline_data)
|
.map(IntoPipelineData::into_pipeline_data)
|
||||||
.unwrap_or(PipelineData::Empty);
|
.unwrap_or(PipelineData::Empty);
|
||||||
|
|
||||||
@ -346,15 +352,19 @@ fn upsert_single_value_by_closure(
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
first_path_member_int: bool,
|
first_path_member_int: bool,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let value_at_path = value.clone().follow_cell_path(cell_path, false);
|
let value_at_path = value.follow_cell_path(cell_path, false);
|
||||||
|
|
||||||
let arg = if first_path_member_int {
|
let arg = if first_path_member_int {
|
||||||
value_at_path.clone().unwrap_or(Value::nothing(span))
|
value_at_path
|
||||||
|
.as_deref()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Value::nothing(span))
|
||||||
} else {
|
} else {
|
||||||
value.clone()
|
value.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let input = value_at_path
|
let input = value_at_path
|
||||||
|
.map(Cow::into_owned)
|
||||||
.map(IntoPipelineData::into_pipeline_data)
|
.map(IntoPipelineData::into_pipeline_data)
|
||||||
.unwrap_or(PipelineData::Empty);
|
.unwrap_or(PipelineData::Empty);
|
||||||
|
|
||||||
|
@ -89,8 +89,7 @@ impl Command for InputList {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |val| {
|
.map(move |val| {
|
||||||
let display_value = if let Some(ref cellpath) = display_path {
|
let display_value = if let Some(ref cellpath) = display_path {
|
||||||
val.clone()
|
val.follow_cell_path(&cellpath.members, false)?
|
||||||
.follow_cell_path(&cellpath.members, false)?
|
|
||||||
.to_expanded_string(", ", &config)
|
.to_expanded_string(", ", &config)
|
||||||
} else {
|
} else {
|
||||||
val.to_expanded_string(", ", &config)
|
val.to_expanded_string(", ", &config)
|
||||||
|
@ -239,8 +239,8 @@ pub fn compare_cell_path(
|
|||||||
insensitive: bool,
|
insensitive: bool,
|
||||||
natural: bool,
|
natural: bool,
|
||||||
) -> Result<Ordering, ShellError> {
|
) -> Result<Ordering, ShellError> {
|
||||||
let left = left.clone().follow_cell_path(&cell_path.members, false)?;
|
let left = left.follow_cell_path(&cell_path.members, false)?;
|
||||||
let right = right.clone().follow_cell_path(&cell_path.members, false)?;
|
let right = right.follow_cell_path(&cell_path.members, false)?;
|
||||||
compare_values(&left, &right, insensitive, natural)
|
compare_values(&left, &right, insensitive, natural)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +269,7 @@ pub fn eval_expression_with_input<D: DebugContext>(
|
|||||||
input = eval_subexpression::<D>(engine_state, stack, block, input)?
|
input = eval_subexpression::<D>(engine_state, stack, block, input)?
|
||||||
.into_value(*span)?
|
.into_value(*span)?
|
||||||
.follow_cell_path(&full_cell_path.tail, false)?
|
.follow_cell_path(&full_cell_path.tail, false)?
|
||||||
|
.into_owned()
|
||||||
.into_pipeline_data()
|
.into_pipeline_data()
|
||||||
} else {
|
} else {
|
||||||
input = eval_subexpression::<D>(engine_state, stack, block, input)?;
|
input = eval_subexpression::<D>(engine_state, stack, block, input)?;
|
||||||
@ -604,7 +605,7 @@ impl Eval for EvalRuntime {
|
|||||||
|
|
||||||
let is_config = original_key == "config";
|
let is_config = original_key == "config";
|
||||||
|
|
||||||
stack.add_env_var(original_key, value);
|
stack.add_env_var(original_key, value.into_owned());
|
||||||
|
|
||||||
// Trigger the update to config, if we modified that.
|
// Trigger the update to config, if we modified that.
|
||||||
if is_config {
|
if is_config {
|
||||||
|
@ -694,9 +694,8 @@ fn eval_instruction<D: DebugContext>(
|
|||||||
let value = ctx.clone_reg_value(*src, *span)?;
|
let value = ctx.clone_reg_value(*src, *span)?;
|
||||||
let path = ctx.take_reg(*path);
|
let path = ctx.take_reg(*path);
|
||||||
if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
|
if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path {
|
||||||
// TODO: make follow_cell_path() not have to take ownership, probably using Cow
|
|
||||||
let value = value.follow_cell_path(&path.members, true)?;
|
let value = value.follow_cell_path(&path.members, true)?;
|
||||||
ctx.put_reg(*dst, value.into_pipeline_data());
|
ctx.put_reg(*dst, value.into_owned().into_pipeline_data());
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
} else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
|
} else if let PipelineData::Value(Value::Error { error, .. }, _) = path {
|
||||||
Err(*error)
|
Err(*error)
|
||||||
|
@ -63,7 +63,7 @@ impl LanguageServer {
|
|||||||
let var = working_set.get_variable(*var_id);
|
let var = working_set.get_variable(*var_id);
|
||||||
Some(
|
Some(
|
||||||
var.const_val
|
var.const_val
|
||||||
.clone()
|
.as_ref()
|
||||||
.and_then(|val| val.follow_cell_path(cell_path, false).ok())
|
.and_then(|val| val.follow_cell_path(cell_path, false).ok())
|
||||||
.map(|val| val.span())
|
.map(|val| val.span())
|
||||||
.unwrap_or(var.declaration_span),
|
.unwrap_or(var.declaration_span),
|
||||||
|
@ -160,16 +160,15 @@ impl LanguageServer {
|
|||||||
let var = working_set.get_variable(var_id);
|
let var = working_set.get_variable(var_id);
|
||||||
markdown_hover(
|
markdown_hover(
|
||||||
var.const_val
|
var.const_val
|
||||||
.clone()
|
.as_ref()
|
||||||
.and_then(|val| val.follow_cell_path(&cell_path, false).ok())
|
.and_then(|val| val.follow_cell_path(&cell_path, false).ok())
|
||||||
.map(|val| {
|
.map(|val| {
|
||||||
let ty = val.get_type().clone();
|
let ty = val.get_type();
|
||||||
let value_string = val
|
if let Ok(s) = val.coerce_str() {
|
||||||
.coerce_into_string()
|
format!("```\n{}\n```\n---\n{}", ty, s)
|
||||||
.ok()
|
} else {
|
||||||
.map(|s| format!("\n---\n{}", s))
|
format!("```\n{}\n```", ty)
|
||||||
.unwrap_or_default();
|
}
|
||||||
format!("```\n{}\n```{}", ty, value_string)
|
|
||||||
})
|
})
|
||||||
.unwrap_or("`unknown`".into()),
|
.unwrap_or("`unknown`".into()),
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
debugger::DebugContext,
|
debugger::DebugContext,
|
||||||
BlockId, Config, GetSpan, Range, Record, ShellError, Span, Value, VarId, ENV_VARIABLE_ID,
|
BlockId, Config, GetSpan, Range, Record, ShellError, Span, Value, VarId, ENV_VARIABLE_ID,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{borrow::Cow, collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
/// To share implementations for regular eval and const eval
|
/// To share implementations for regular eval and const eval
|
||||||
pub trait Eval {
|
pub trait Eval {
|
||||||
@ -43,11 +43,8 @@ pub trait Eval {
|
|||||||
|
|
||||||
// Cell paths are usually case-sensitive, but we give $env
|
// Cell paths are usually case-sensitive, but we give $env
|
||||||
// special treatment.
|
// special treatment.
|
||||||
if cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID) {
|
let insensitive = cell_path.head.expr == Expr::Var(ENV_VARIABLE_ID);
|
||||||
value.follow_cell_path(&cell_path.tail, true)
|
value.follow_cell_path(&cell_path.tail, insensitive).map(Cow::into_owned)
|
||||||
} else {
|
|
||||||
value.follow_cell_path(&cell_path.tail, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Expr::DateTime(dt) => Ok(Value::date(*dt, expr_span)),
|
Expr::DateTime(dt) => Ok(Value::date(*dt, expr_span)),
|
||||||
Expr::List(list) => {
|
Expr::List(list) => {
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError,
|
ByteStream, ByteStreamType, Config, ListStream, OutDest, PipelineMetadata, Range, ShellError,
|
||||||
Signals, Span, Type, Value,
|
Signals, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use std::io::Write;
|
use std::{borrow::Cow, io::Write};
|
||||||
|
|
||||||
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
|
const LINE_ENDING_PATTERN: &[char] = &['\r', '\n'];
|
||||||
|
|
||||||
@ -416,8 +416,11 @@ impl PipelineData {
|
|||||||
match self {
|
match self {
|
||||||
// FIXME: there are probably better ways of doing this
|
// FIXME: there are probably better ways of doing this
|
||||||
PipelineData::ListStream(stream, ..) => Value::list(stream.into_iter().collect(), head)
|
PipelineData::ListStream(stream, ..) => Value::list(stream.into_iter().collect(), head)
|
||||||
.follow_cell_path(cell_path, insensitive),
|
.follow_cell_path(cell_path, insensitive)
|
||||||
PipelineData::Value(v, ..) => v.follow_cell_path(cell_path, insensitive),
|
.map(Cow::into_owned),
|
||||||
|
PipelineData::Value(v, ..) => v
|
||||||
|
.follow_cell_path(cell_path, insensitive)
|
||||||
|
.map(Cow::into_owned),
|
||||||
PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
|
PipelineData::Empty => Err(ShellError::IncompatiblePathAccess {
|
||||||
type_name: "empty pipeline".to_string(),
|
type_name: "empty pipeline".to_string(),
|
||||||
span: head,
|
span: head,
|
||||||
|
@ -38,7 +38,7 @@ use std::{
|
|||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
fmt::{Debug, Display, Write},
|
fmt::{Debug, Display, Write},
|
||||||
ops::Bound,
|
ops::{Bound, ControlFlow, Deref},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1080,224 +1080,83 @@ impl Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
|
/// Follow a given cell path into the value: for example accessing select elements in a stream or list
|
||||||
pub fn follow_cell_path(
|
pub fn follow_cell_path<'out>(
|
||||||
self,
|
&'out self,
|
||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
insensitive: bool,
|
insensitive: bool,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Cow<'out, Value>, ShellError> {
|
||||||
let mut current = self;
|
enum MultiLife<'out, 'local, T>
|
||||||
|
where
|
||||||
|
'out: 'local,
|
||||||
|
T: ?Sized,
|
||||||
|
{
|
||||||
|
Out(&'out T),
|
||||||
|
Local(&'local T),
|
||||||
|
}
|
||||||
|
|
||||||
for member in cell_path {
|
impl<'out, 'local, T> Deref for MultiLife<'out, 'local, T>
|
||||||
match member {
|
where
|
||||||
PathMember::Int {
|
'out: 'local,
|
||||||
val: count,
|
T: ?Sized,
|
||||||
span: origin_span,
|
{
|
||||||
optional,
|
type Target = T;
|
||||||
} => {
|
|
||||||
// Treat a numeric path member as `select <val>`
|
|
||||||
match current {
|
|
||||||
Value::List { mut vals, .. } => {
|
|
||||||
if *count < vals.len() {
|
|
||||||
// `vals` is owned and will be dropped right after this,
|
|
||||||
// so we can `swap_remove` the value at index `count`
|
|
||||||
// without worrying about preserving order.
|
|
||||||
current = vals.swap_remove(*count);
|
|
||||||
} else if *optional {
|
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
|
||||||
} else if vals.is_empty() {
|
|
||||||
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::AccessBeyondEnd {
|
|
||||||
max_idx: vals.len() - 1,
|
|
||||||
span: *origin_span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Binary { val, .. } => {
|
|
||||||
if let Some(item) = val.get(*count) {
|
|
||||||
current = Value::int(*item as i64, *origin_span);
|
|
||||||
} else if *optional {
|
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
|
||||||
} else if val.is_empty() {
|
|
||||||
return Err(ShellError::AccessEmptyContent { span: *origin_span });
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::AccessBeyondEnd {
|
|
||||||
max_idx: val.len() - 1,
|
|
||||||
span: *origin_span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Range { ref val, .. } => {
|
|
||||||
if let Some(item) = val
|
|
||||||
.into_range_iter(current.span(), Signals::empty())
|
|
||||||
.nth(*count)
|
|
||||||
{
|
|
||||||
current = item;
|
|
||||||
} else if *optional {
|
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::AccessBeyondEndOfStream {
|
|
||||||
span: *origin_span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Custom { ref val, .. } => {
|
|
||||||
current =
|
|
||||||
match val.follow_path_int(current.span(), *count, *origin_span) {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(err) => {
|
|
||||||
if *optional {
|
|
||||||
return Ok(Value::nothing(*origin_span));
|
|
||||||
// short-circuit
|
|
||||||
} else {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Value::Nothing { .. } if *optional => {
|
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
|
||||||
}
|
|
||||||
// Records (and tables) are the only built-in which support column names,
|
|
||||||
// so only use this message for them.
|
|
||||||
Value::Record { .. } => {
|
|
||||||
return Err(ShellError::TypeMismatch {
|
|
||||||
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
|
||||||
span: *origin_span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Value::Error { error, .. } => return Err(*error),
|
|
||||||
x => {
|
|
||||||
return Err(ShellError::IncompatiblePathAccess {
|
|
||||||
type_name: format!("{}", x.get_type()),
|
|
||||||
span: *origin_span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PathMember::String {
|
|
||||||
val: column_name,
|
|
||||||
span: origin_span,
|
|
||||||
optional,
|
|
||||||
} => {
|
|
||||||
let span = current.span();
|
|
||||||
|
|
||||||
match current {
|
fn deref(&self) -> &Self::Target {
|
||||||
Value::Record { mut val, .. } => {
|
match *self {
|
||||||
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
MultiLife::Out(x) => x,
|
||||||
if let Some(found) = val.to_mut().iter_mut().rev().find(|x| {
|
MultiLife::Local(x) => x,
|
||||||
if insensitive {
|
|
||||||
x.0.eq_ignore_case(column_name)
|
|
||||||
} else {
|
|
||||||
x.0 == column_name
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
current = std::mem::take(found.1);
|
|
||||||
} else if *optional {
|
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
|
||||||
} else if let Some(suggestion) =
|
|
||||||
did_you_mean(val.columns(), column_name)
|
|
||||||
{
|
|
||||||
return Err(ShellError::DidYouMean {
|
|
||||||
suggestion,
|
|
||||||
span: *origin_span,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::CantFindColumn {
|
|
||||||
col_name: column_name.clone(),
|
|
||||||
span: Some(*origin_span),
|
|
||||||
src_span: span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// String access of Lists always means Table access.
|
|
||||||
// Create a List which contains each matching value for contained
|
|
||||||
// records in the source list.
|
|
||||||
Value::List { vals, .. } => {
|
|
||||||
let list = vals
|
|
||||||
.into_iter()
|
|
||||||
.map(|val| {
|
|
||||||
let val_span = val.span();
|
|
||||||
match val {
|
|
||||||
Value::Record { mut val, .. } => {
|
|
||||||
if let Some(found) =
|
|
||||||
val.to_mut().iter_mut().rev().find(|x| {
|
|
||||||
if insensitive {
|
|
||||||
x.0.eq_ignore_case(column_name)
|
|
||||||
} else {
|
|
||||||
x.0 == column_name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Ok(std::mem::take(found.1))
|
|
||||||
} else if *optional {
|
|
||||||
Ok(Value::nothing(*origin_span))
|
|
||||||
} else if let Some(suggestion) =
|
|
||||||
did_you_mean(val.columns(), column_name)
|
|
||||||
{
|
|
||||||
Err(ShellError::DidYouMean {
|
|
||||||
suggestion,
|
|
||||||
span: *origin_span,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(ShellError::CantFindColumn {
|
|
||||||
col_name: column_name.clone(),
|
|
||||||
span: Some(*origin_span),
|
|
||||||
src_span: val_span,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Nothing { .. } if *optional => {
|
|
||||||
Ok(Value::nothing(*origin_span))
|
|
||||||
}
|
|
||||||
_ => Err(ShellError::CantFindColumn {
|
|
||||||
col_name: column_name.clone(),
|
|
||||||
span: Some(*origin_span),
|
|
||||||
src_span: val_span,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<_, _>>()?;
|
|
||||||
|
|
||||||
current = Value::list(list, span);
|
|
||||||
}
|
|
||||||
Value::Custom { ref val, .. } => {
|
|
||||||
current = match val.follow_path_string(
|
|
||||||
current.span(),
|
|
||||||
column_name.clone(),
|
|
||||||
*origin_span,
|
|
||||||
) {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(err) => {
|
|
||||||
if *optional {
|
|
||||||
return Ok(Value::nothing(*origin_span));
|
|
||||||
// short-circuit
|
|
||||||
} else {
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Nothing { .. } if *optional => {
|
|
||||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
|
||||||
}
|
|
||||||
Value::Error { error, .. } => return Err(*error),
|
|
||||||
x => {
|
|
||||||
return Err(ShellError::IncompatiblePathAccess {
|
|
||||||
type_name: format!("{}", x.get_type()),
|
|
||||||
span: *origin_span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A dummy value is required, otherwise rust doesn't allow references, which we need for
|
||||||
|
// the `std::ptr::eq` comparison
|
||||||
|
let mut store: Value = Value::test_nothing();
|
||||||
|
let mut current: MultiLife<'out, '_, Value> = MultiLife::Out(self);
|
||||||
|
|
||||||
|
for member in cell_path {
|
||||||
|
current = match current {
|
||||||
|
MultiLife::Out(current) => match get_value_member(current, member, insensitive)? {
|
||||||
|
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
|
||||||
|
ControlFlow::Continue(x) => match x {
|
||||||
|
Cow::Borrowed(x) => MultiLife::Out(x),
|
||||||
|
Cow::Owned(x) => {
|
||||||
|
store = x;
|
||||||
|
MultiLife::Local(&store)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MultiLife::Local(current) => {
|
||||||
|
match get_value_member(current, member, insensitive)? {
|
||||||
|
ControlFlow::Break(span) => return Ok(Cow::Owned(Value::nothing(span))),
|
||||||
|
ControlFlow::Continue(x) => match x {
|
||||||
|
Cow::Borrowed(x) => MultiLife::Local(x),
|
||||||
|
Cow::Owned(x) => {
|
||||||
|
store = x;
|
||||||
|
MultiLife::Local(&store)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// If a single Value::Error was produced by the above (which won't happen if nullify_errors is true), unwrap it now.
|
// If a single Value::Error was produced by the above (which won't happen if nullify_errors is true), unwrap it now.
|
||||||
// Note that Value::Errors inside Lists remain as they are, so that the rest of the list can still potentially be used.
|
// Note that Value::Errors inside Lists remain as they are, so that the rest of the list can still potentially be used.
|
||||||
if let Value::Error { error, .. } = current {
|
if let Value::Error { error, .. } = &*current {
|
||||||
Err(*error)
|
Err(error.as_ref().clone())
|
||||||
} else {
|
} else {
|
||||||
Ok(current)
|
Ok(match current {
|
||||||
|
MultiLife::Out(x) => Cow::Borrowed(x),
|
||||||
|
MultiLife::Local(x) => {
|
||||||
|
let x = if std::ptr::eq(x, &store) {
|
||||||
|
store
|
||||||
|
} else {
|
||||||
|
x.clone()
|
||||||
|
};
|
||||||
|
Cow::Owned(x)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1307,9 +1166,7 @@ impl Value {
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let orig = self.clone();
|
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
|
||||||
|
|
||||||
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
|
|
||||||
|
|
||||||
match new_val {
|
match new_val {
|
||||||
Value::Error { error, .. } => Err(*error),
|
Value::Error { error, .. } => Err(*error),
|
||||||
@ -1409,9 +1266,7 @@ impl Value {
|
|||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
callback: Box<dyn FnOnce(&Value) -> Value + 'a>,
|
callback: Box<dyn FnOnce(&Value) -> Value + 'a>,
|
||||||
) -> Result<(), ShellError> {
|
) -> Result<(), ShellError> {
|
||||||
let orig = self.clone();
|
let new_val = callback(self.follow_cell_path(cell_path, false)?.as_ref());
|
||||||
|
|
||||||
let new_val = callback(&orig.follow_cell_path(cell_path, false)?);
|
|
||||||
|
|
||||||
match new_val {
|
match new_val {
|
||||||
Value::Error { error, .. } => Err(*error),
|
Value::Error { error, .. } => Err(*error),
|
||||||
@ -2147,6 +2002,198 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_value_member<'a>(
|
||||||
|
current: &'a Value,
|
||||||
|
member: &PathMember,
|
||||||
|
insensitive: bool,
|
||||||
|
) -> Result<ControlFlow<Span, Cow<'a, Value>>, ShellError> {
|
||||||
|
match member {
|
||||||
|
PathMember::Int {
|
||||||
|
val: count,
|
||||||
|
span: origin_span,
|
||||||
|
optional,
|
||||||
|
} => {
|
||||||
|
// Treat a numeric path member as `select <val>`
|
||||||
|
match current {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
if *count < vals.len() {
|
||||||
|
Ok(ControlFlow::Continue(Cow::Borrowed(&vals[*count])))
|
||||||
|
} else if *optional {
|
||||||
|
Ok(ControlFlow::Break(*origin_span))
|
||||||
|
// short-circuit
|
||||||
|
} else if vals.is_empty() {
|
||||||
|
Err(ShellError::AccessEmptyContent { span: *origin_span })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::AccessBeyondEnd {
|
||||||
|
max_idx: vals.len() - 1,
|
||||||
|
span: *origin_span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Binary { val, .. } => {
|
||||||
|
if let Some(item) = val.get(*count) {
|
||||||
|
Ok(ControlFlow::Continue(Cow::Owned(Value::int(
|
||||||
|
*item as i64,
|
||||||
|
*origin_span,
|
||||||
|
))))
|
||||||
|
} else if *optional {
|
||||||
|
Ok(ControlFlow::Break(*origin_span))
|
||||||
|
// short-circuit
|
||||||
|
} else if val.is_empty() {
|
||||||
|
Err(ShellError::AccessEmptyContent { span: *origin_span })
|
||||||
|
} else {
|
||||||
|
Err(ShellError::AccessBeyondEnd {
|
||||||
|
max_idx: val.len() - 1,
|
||||||
|
span: *origin_span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Range { ref val, .. } => {
|
||||||
|
if let Some(item) = val
|
||||||
|
.into_range_iter(current.span(), Signals::empty())
|
||||||
|
.nth(*count)
|
||||||
|
{
|
||||||
|
Ok(ControlFlow::Continue(Cow::Owned(item)))
|
||||||
|
} else if *optional {
|
||||||
|
Ok(ControlFlow::Break(*origin_span))
|
||||||
|
// short-circuit
|
||||||
|
} else {
|
||||||
|
Err(ShellError::AccessBeyondEndOfStream {
|
||||||
|
span: *origin_span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Custom { ref val, .. } => {
|
||||||
|
match val.follow_path_int(current.span(), *count, *origin_span)
|
||||||
|
{
|
||||||
|
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
|
||||||
|
Err(err) => {
|
||||||
|
if *optional {
|
||||||
|
Ok(ControlFlow::Break(*origin_span))
|
||||||
|
// short-circuit
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
|
||||||
|
// Records (and tables) are the only built-in which support column names,
|
||||||
|
// so only use this message for them.
|
||||||
|
Value::Record { .. } => Err(ShellError::TypeMismatch {
|
||||||
|
err_message:"Can't access record values with a row index. Try specifying a column name instead".into(),
|
||||||
|
span: *origin_span,
|
||||||
|
}),
|
||||||
|
Value::Error { error, .. } => Err(*error.clone()),
|
||||||
|
x => Err(ShellError::IncompatiblePathAccess { type_name: format!("{}", x.get_type()), span: *origin_span }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathMember::String {
|
||||||
|
val: column_name,
|
||||||
|
span: origin_span,
|
||||||
|
optional,
|
||||||
|
} => {
|
||||||
|
let span = current.span();
|
||||||
|
match current {
|
||||||
|
Value::Record { val, .. } => {
|
||||||
|
if let Some(found) = val.iter().rev().find(|x| {
|
||||||
|
if insensitive {
|
||||||
|
x.0.eq_ignore_case(column_name)
|
||||||
|
} else {
|
||||||
|
x.0 == column_name
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Ok(ControlFlow::Continue(Cow::Borrowed(found.1)))
|
||||||
|
} else if *optional {
|
||||||
|
Ok(ControlFlow::Break(*origin_span))
|
||||||
|
// short-circuit
|
||||||
|
} else if let Some(suggestion) = did_you_mean(val.columns(), column_name) {
|
||||||
|
Err(ShellError::DidYouMean {
|
||||||
|
suggestion,
|
||||||
|
span: *origin_span,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(ShellError::CantFindColumn {
|
||||||
|
col_name: column_name.clone(),
|
||||||
|
span: Some(*origin_span),
|
||||||
|
src_span: span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// String access of Lists always means Table access.
|
||||||
|
// Create a List which contains each matching value for contained
|
||||||
|
// records in the source list.
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
let list = vals
|
||||||
|
.iter()
|
||||||
|
.map(|val| {
|
||||||
|
let val_span = val.span();
|
||||||
|
match val {
|
||||||
|
Value::Record { val, .. } => {
|
||||||
|
if let Some(found) = val.iter().rev().find(|x| {
|
||||||
|
if insensitive {
|
||||||
|
x.0.eq_ignore_case(column_name)
|
||||||
|
} else {
|
||||||
|
x.0 == column_name
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Ok(found.1.clone())
|
||||||
|
} else if *optional {
|
||||||
|
Ok(Value::nothing(*origin_span))
|
||||||
|
} else if let Some(suggestion) =
|
||||||
|
did_you_mean(val.columns(), column_name)
|
||||||
|
{
|
||||||
|
Err(ShellError::DidYouMean {
|
||||||
|
suggestion,
|
||||||
|
span: *origin_span,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(ShellError::CantFindColumn {
|
||||||
|
col_name: column_name.clone(),
|
||||||
|
span: Some(*origin_span),
|
||||||
|
src_span: val_span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Nothing { .. } if *optional => {
|
||||||
|
Ok(Value::nothing(*origin_span))
|
||||||
|
}
|
||||||
|
_ => Err(ShellError::CantFindColumn {
|
||||||
|
col_name: column_name.clone(),
|
||||||
|
span: Some(*origin_span),
|
||||||
|
src_span: val_span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
Ok(ControlFlow::Continue(Cow::Owned(Value::list(list, span))))
|
||||||
|
}
|
||||||
|
Value::Custom { ref val, .. } => {
|
||||||
|
match val.follow_path_string(current.span(), column_name.clone(), *origin_span)
|
||||||
|
{
|
||||||
|
Ok(val) => Ok(ControlFlow::Continue(Cow::Owned(val))),
|
||||||
|
Err(err) => {
|
||||||
|
if *optional {
|
||||||
|
Ok(ControlFlow::Break(*origin_span))
|
||||||
|
// short-circuit
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Nothing { .. } if *optional => Ok(ControlFlow::Break(*origin_span)),
|
||||||
|
Value::Error { error, .. } => Err(error.as_ref().clone()),
|
||||||
|
x => Err(ShellError::IncompatiblePathAccess {
|
||||||
|
type_name: format!("{}", x.get_type()),
|
||||||
|
span: *origin_span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Value {
|
impl Default for Value {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Value::Nothing {
|
Value::Nothing {
|
||||||
|
@ -91,8 +91,7 @@ impl Inc {
|
|||||||
|
|
||||||
pub fn inc(&self, head: Span, value: &Value) -> Result<Value, LabeledError> {
|
pub fn inc(&self, head: Span, value: &Value) -> Result<Value, LabeledError> {
|
||||||
if let Some(cell_path) = &self.cell_path {
|
if let Some(cell_path) = &self.cell_path {
|
||||||
let working_value = value.clone();
|
let cell_value = value.follow_cell_path(&cell_path.members, false)?;
|
||||||
let cell_value = working_value.follow_cell_path(&cell_path.members, false)?;
|
|
||||||
|
|
||||||
let cell_value = self.inc_value(head, &cell_value)?;
|
let cell_value = self.inc_value(head, &cell_value)?;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user