forked from extern/nushell
Create Record
type (#10103)
# Description This PR creates a new `Record` type to reduce duplicate code and possibly bugs as well. (This is an edited version of #9648.) - `Record` implements `FromIterator` and `IntoIterator` and so can be iterated over or collected into. For example, this helps with conversions to and from (hash)maps. (Also, no more `cols.iter().zip(vals)`!) - `Record` has a `push(col, val)` function to help insure that the number of columns is equal to the number of values. I caught a few potential bugs thanks to this (e.g. in the `ls` command). - Finally, this PR also adds a `record!` macro that helps simplify record creation. It is used like so: ```rust record! { "key1" => some_value, "key2" => Value::string("text", span), "key3" => Value::int(optional_int.unwrap_or(0), span), "key4" => Value::bool(config.setting, span), } ``` Since macros hinder formatting, etc., the right hand side values should be relatively short and sweet like the examples above. Where possible, prefer `record!` or `.collect()` on an iterator instead of multiple `Record::push`s, since the first two automatically set the record capacity and do less work overall. # User-Facing Changes Besides the changes in `nu-protocol` the only other breaking changes are to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
use crate::{ShellError, Span, Value};
|
||||
use crate::{record, Record, ShellError, Span, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -323,7 +323,8 @@ impl Value {
|
||||
// $env.config.ls.use_ls_colors = 2 results in an error, so
|
||||
// the current use_ls_colors config setting is converted to a Value::Boolean and inserted in the
|
||||
// record in place of the 2.
|
||||
if let Value::Record { cols, vals, span } = self {
|
||||
if let Value::Record { val, span } = self {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
// Because this whole algorithm removes while iterating, this must iterate in reverse.
|
||||
for index in (0..cols.len()).rev() {
|
||||
@ -332,7 +333,8 @@ impl Value {
|
||||
match key {
|
||||
// Grouped options
|
||||
"ls" => {
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
@ -363,17 +365,17 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec!["use_ls_colors".into(), "clickable_links".into()],
|
||||
vec![
|
||||
Value::bool(config.use_ls_colors, span),
|
||||
Value::bool(config.show_clickable_links_in_ls, span),
|
||||
],
|
||||
record! {
|
||||
"use_ls_colors" => Value::bool(config.use_ls_colors, span),
|
||||
"clickable_links" => Value::bool(config.show_clickable_links_in_ls, span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
"cd" => {
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
let key2 = cols[index].as_str();
|
||||
@ -396,17 +398,17 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec!["use_ls_colors".into(), "clickable_links".into()],
|
||||
vec![
|
||||
Value::bool(config.use_ls_colors, span),
|
||||
Value::bool(config.show_clickable_links_in_ls, span),
|
||||
],
|
||||
record! {
|
||||
"use_ls_colors" => Value::bool(config.use_ls_colors, span),
|
||||
"clickable_links" => Value::bool(config.show_clickable_links_in_ls, span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
"rm" => {
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
let key2 = cols[index].as_str();
|
||||
@ -429,8 +431,9 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec!["always_trash".into()],
|
||||
vec![Value::bool(config.rm_always_trash, span)],
|
||||
record! {
|
||||
"always_trash" => Value::bool(config.rm_always_trash, span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
@ -447,7 +450,8 @@ impl Value {
|
||||
)
|
||||
};
|
||||
}
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
@ -504,18 +508,12 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec![
|
||||
"sync_on_enter".into(),
|
||||
"max_size".into(),
|
||||
"file_format".into(),
|
||||
"isolation".into(),
|
||||
],
|
||||
vec![
|
||||
Value::bool(config.sync_history_on_enter, span),
|
||||
Value::int(config.max_history_size, span),
|
||||
reconstruct_history_file_format!(span),
|
||||
Value::bool(config.history_isolation, span),
|
||||
],
|
||||
record! {
|
||||
"sync_on_enter" => Value::bool(config.sync_history_on_enter, span),
|
||||
"max_size" => Value::int(config.max_history_size, span),
|
||||
"file_format" => reconstruct_history_file_format!(span),
|
||||
"isolation" => Value::bool(config.history_isolation, span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
@ -536,17 +534,17 @@ impl Value {
|
||||
macro_rules! reconstruct_external {
|
||||
($span: expr) => {
|
||||
Value::record(
|
||||
vec!["max_results".into(), "completer".into(), "enable".into()],
|
||||
vec![
|
||||
Value::int(config.max_external_completion_results, $span),
|
||||
reconstruct_external_completer!($span),
|
||||
Value::bool(config.enable_external_completion, $span),
|
||||
],
|
||||
record! {
|
||||
"max_results" => Value::int(config.max_external_completion_results, $span),
|
||||
"completer" => reconstruct_external_completer!($span),
|
||||
"enable" => Value::bool(config.enable_external_completion, $span),
|
||||
},
|
||||
$span,
|
||||
)
|
||||
};
|
||||
}
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
@ -596,8 +594,8 @@ impl Value {
|
||||
)
|
||||
}
|
||||
"external" => {
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index]
|
||||
{
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
@ -672,20 +670,13 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct record
|
||||
vals[index] = Value::record(
|
||||
vec![
|
||||
"quick".into(),
|
||||
"partial".into(),
|
||||
"algorithm".into(),
|
||||
"case_sensitive".into(),
|
||||
"external".into(),
|
||||
],
|
||||
vec![
|
||||
Value::bool(config.quick_completions, span),
|
||||
Value::bool(config.partial_completions, span),
|
||||
Value::string(config.completion_algorithm.clone(), span),
|
||||
Value::bool(config.case_sensitive_completions, span),
|
||||
reconstruct_external!(span),
|
||||
],
|
||||
record! {
|
||||
"quick" => Value::bool(config.quick_completions, span),
|
||||
"partial" => Value::bool(config.partial_completions, span),
|
||||
"algorithm" => Value::string(config.completion_algorithm.clone(), span),
|
||||
"case_sensitive" => Value::bool(config.case_sensitive_completions, span),
|
||||
"external" => reconstruct_external!(span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
@ -706,7 +697,8 @@ impl Value {
|
||||
)
|
||||
};
|
||||
}
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
@ -870,12 +862,11 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec!["vi_insert".into(), "vi_normal".into(), "emacs".into()],
|
||||
vec![
|
||||
reconstruct_cursor_shape!(config.cursor_shape_vi_insert, span),
|
||||
reconstruct_cursor_shape!(config.cursor_shape_vi_normal, span),
|
||||
reconstruct_cursor_shape!(config.cursor_shape_emacs, span),
|
||||
],
|
||||
record! {
|
||||
"vi_insert" => reconstruct_cursor_shape!(config.cursor_shape_vi_insert, span),
|
||||
"vi_normal" => reconstruct_cursor_shape!(config.cursor_shape_vi_normal, span),
|
||||
"emacs" => reconstruct_cursor_shape!(config.cursor_shape_emacs, span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
@ -897,34 +888,30 @@ impl Value {
|
||||
($span:expr) => {
|
||||
match &config.trim_strategy {
|
||||
TrimStrategy::Wrap { try_to_keep_words } => Value::record(
|
||||
vec![
|
||||
"methodology".into(),
|
||||
"wrapping_try_keep_words".into(),
|
||||
],
|
||||
vec![
|
||||
Value::string("wrapping", $span),
|
||||
Value::bool(*try_to_keep_words, $span),
|
||||
],
|
||||
record! {
|
||||
"methodology" => Value::string("wrapping", $span),
|
||||
"wrapping_try_keep_words" => Value::bool(*try_to_keep_words, $span),
|
||||
},
|
||||
$span,
|
||||
),
|
||||
TrimStrategy::Truncate { suffix } => Value::record(
|
||||
vec!["methodology".into(), "truncating_suffix".into()],
|
||||
match suffix {
|
||||
Some(s) => vec![
|
||||
Value::string("truncating", $span),
|
||||
Value::string(s.clone(), $span),
|
||||
],
|
||||
None => vec![
|
||||
Value::string("truncating", $span),
|
||||
Value::Nothing { span: $span },
|
||||
],
|
||||
Some(s) => record! {
|
||||
"methodology" => Value::string("truncating", $span),
|
||||
"truncating_suffix" => Value::string(s.clone(), $span),
|
||||
},
|
||||
None => record! {
|
||||
"methodology" => Value::string("truncating", $span),
|
||||
"truncating_suffix" => Value::Nothing { span: $span },
|
||||
},
|
||||
},
|
||||
$span,
|
||||
),
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
@ -951,7 +938,8 @@ impl Value {
|
||||
config.table_indent.left = *val as usize;
|
||||
config.table_indent.right = *val as usize;
|
||||
}
|
||||
Value::Record { vals, cols, .. } => {
|
||||
Value::Record { val, .. } => {
|
||||
let Record { cols, vals } = val;
|
||||
let left = cols.iter().position(|e| e == "left");
|
||||
let right = cols.iter().position(|e| e == "right");
|
||||
|
||||
@ -1044,24 +1032,19 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec![
|
||||
"mode".into(),
|
||||
"index_mode".into(),
|
||||
"trim".into(),
|
||||
"show_empty".into(),
|
||||
],
|
||||
vec![
|
||||
Value::string(config.table_mode.clone(), span),
|
||||
reconstruct_index_mode!(span),
|
||||
reconstruct_trim_strategy!(span),
|
||||
Value::bool(config.table_show_empty, span),
|
||||
],
|
||||
record! {
|
||||
"mode" => Value::string(config.table_mode.clone(), span),
|
||||
"index_mode" => reconstruct_index_mode!(span),
|
||||
"trim" => reconstruct_trim_strategy!(span),
|
||||
"show_empty" => Value::bool(config.table_show_empty, span),
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
}
|
||||
"filesize" => {
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
@ -1095,11 +1078,10 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec!["metric".into(), "format".into()],
|
||||
vec![
|
||||
Value::bool(config.filesize_metric, span),
|
||||
Value::string(config.filesize_format.clone(), span),
|
||||
],
|
||||
record! {
|
||||
"metric" => Value::bool(config.filesize_metric, span),
|
||||
"format" => Value::string(config.filesize_format.clone(), span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
@ -1110,7 +1092,14 @@ impl Value {
|
||||
} else {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record_from_hashmap(&config.explore, span);
|
||||
vals[index] = Value::record(
|
||||
config
|
||||
.explore
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Misc. options
|
||||
@ -1120,7 +1109,14 @@ impl Value {
|
||||
} else {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record_from_hashmap(&config.color_config, span);
|
||||
vals[index] = Value::record(
|
||||
config
|
||||
.color_config
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
"use_grid_icons" => {
|
||||
@ -1206,25 +1202,17 @@ impl Value {
|
||||
menu_type, // WARNING: this is not the same name as what is used in Config.nu! ("type")
|
||||
source,
|
||||
}| {
|
||||
Value::Record {
|
||||
cols: vec![
|
||||
"name".into(),
|
||||
"only_buffer_difference".into(),
|
||||
"marker".into(),
|
||||
"style".into(),
|
||||
"type".into(),
|
||||
"source".into(),
|
||||
],
|
||||
vals: vec![
|
||||
name.clone(),
|
||||
only_buffer_difference.clone(),
|
||||
marker.clone(),
|
||||
style.clone(),
|
||||
menu_type.clone(),
|
||||
source.clone(),
|
||||
],
|
||||
Value::record(
|
||||
record! {
|
||||
"name" => name.clone(),
|
||||
"only_buffer_difference" => only_buffer_difference.clone(),
|
||||
"marker" => marker.clone(),
|
||||
"style" => style.clone(),
|
||||
"type" => menu_type.clone(),
|
||||
"source" => source.clone(),
|
||||
},
|
||||
span,
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
@ -1250,21 +1238,15 @@ impl Value {
|
||||
mode,
|
||||
event,
|
||||
}| {
|
||||
Value::Record {
|
||||
cols: vec![
|
||||
"modifier".into(),
|
||||
"keycode".into(),
|
||||
"mode".into(),
|
||||
"event".into(),
|
||||
],
|
||||
vals: vec![
|
||||
modifier.clone(),
|
||||
keycode.clone(),
|
||||
mode.clone(),
|
||||
event.clone(),
|
||||
],
|
||||
Value::record(
|
||||
record! {
|
||||
"modifier" => modifier.clone(),
|
||||
"keycode" => keycode.clone(),
|
||||
"mode" => mode.clone(),
|
||||
"event" => event.clone(),
|
||||
},
|
||||
span,
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
@ -1279,33 +1261,26 @@ impl Value {
|
||||
invalid!(Some(span), "should be a valid hooks list");
|
||||
errors.push(e);
|
||||
// Reconstruct
|
||||
let mut hook_cols = vec![];
|
||||
let mut hook_vals = vec![];
|
||||
let mut hook = Record::new();
|
||||
if let Some(ref value) = config.hooks.pre_prompt {
|
||||
hook_cols.push("pre_prompt".into());
|
||||
hook_vals.push(value.clone());
|
||||
hook.push("pre_prompt", value.clone());
|
||||
}
|
||||
if let Some(ref value) = config.hooks.pre_execution {
|
||||
hook_cols.push("pre_execution".into());
|
||||
hook_vals.push(value.clone());
|
||||
hook.push("pre_execution", value.clone());
|
||||
}
|
||||
if let Some(ref value) = config.hooks.env_change {
|
||||
hook_cols.push("env_change".into());
|
||||
hook_vals.push(value.clone());
|
||||
hook.push("env_change", value.clone());
|
||||
}
|
||||
if let Some(ref value) = config.hooks.display_output {
|
||||
hook_cols.push("display_output".into());
|
||||
hook_vals.push(value.clone());
|
||||
hook.push("display_output", value.clone());
|
||||
}
|
||||
vals.push(Value::Record {
|
||||
cols: hook_cols,
|
||||
vals: hook_vals,
|
||||
span,
|
||||
});
|
||||
vals.push(Value::record(hook, span));
|
||||
}
|
||||
},
|
||||
"datetime_format" => {
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
if let Value::Record { val, span } = &mut vals[index] {
|
||||
let Record { cols, vals } = val;
|
||||
let span = *span;
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
let key2 = cols[index].as_str();
|
||||
@ -1314,14 +1289,14 @@ impl Value {
|
||||
if let Ok(v) = value.as_string() {
|
||||
config.datetime_normal_format = Some(v);
|
||||
} else {
|
||||
invalid!(Some(*span), "should be a string");
|
||||
invalid!(Some(span), "should be a string");
|
||||
}
|
||||
}
|
||||
"table" => {
|
||||
if let Ok(v) = value.as_string() {
|
||||
config.datetime_table_format = Some(v);
|
||||
} else {
|
||||
invalid!(Some(*span), "should be a string");
|
||||
invalid!(Some(span), "should be a string");
|
||||
}
|
||||
}
|
||||
x => {
|
||||
@ -1339,11 +1314,10 @@ impl Value {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec!["metric".into(), "format".into()],
|
||||
vec![
|
||||
Value::bool(config.filesize_metric, span),
|
||||
Value::string(config.filesize_format.clone(), span),
|
||||
],
|
||||
record! {
|
||||
"metric" => Value::bool(config.filesize_metric, span),
|
||||
"format" => Value::string(config.filesize_format.clone(), span),
|
||||
},
|
||||
span,
|
||||
);
|
||||
}
|
||||
@ -1481,29 +1455,26 @@ fn try_parse_trim_methodology(value: &Value) -> Option<TrimStrategy> {
|
||||
}
|
||||
|
||||
fn create_map(value: &Value) -> Result<HashMap<String, Value>, ShellError> {
|
||||
let (cols, inner_vals) = value.as_record()?;
|
||||
let mut hm: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
for (k, v) in cols.iter().zip(inner_vals) {
|
||||
hm.insert(k.to_string(), v.clone());
|
||||
}
|
||||
|
||||
Ok(hm)
|
||||
Ok(value
|
||||
.as_record()?
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
// Parse the hooks to find the blocks to run when the hooks fire
|
||||
fn create_hooks(value: &Value) -> Result<Hooks, ShellError> {
|
||||
match value {
|
||||
Value::Record { cols, vals, span } => {
|
||||
Value::Record { val, span } => {
|
||||
let mut hooks = Hooks::new();
|
||||
|
||||
for idx in 0..cols.len() {
|
||||
match cols[idx].as_str() {
|
||||
"pre_prompt" => hooks.pre_prompt = Some(vals[idx].clone()),
|
||||
"pre_execution" => hooks.pre_execution = Some(vals[idx].clone()),
|
||||
"env_change" => hooks.env_change = Some(vals[idx].clone()),
|
||||
"display_output" => hooks.display_output = Some(vals[idx].clone()),
|
||||
"command_not_found" => hooks.command_not_found = Some(vals[idx].clone()),
|
||||
for (col, val) in val {
|
||||
match col.as_str() {
|
||||
"pre_prompt" => hooks.pre_prompt = Some(val.clone()),
|
||||
"pre_execution" => hooks.pre_execution = Some(val.clone()),
|
||||
"env_change" => hooks.env_change = Some(val.clone()),
|
||||
"display_output" => hooks.display_output = Some(val.clone()),
|
||||
"command_not_found" => hooks.command_not_found = Some(val.clone()),
|
||||
x => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"'pre_prompt', 'pre_execution', 'env_change', 'display_output', 'command_not_found'"
|
||||
@ -1528,13 +1499,13 @@ fn create_hooks(value: &Value) -> Result<Hooks, ShellError> {
|
||||
// Parses the config object to extract the strings that will compose a keybinding for reedline
|
||||
fn create_keybindings(value: &Value) -> Result<Vec<ParsedKeybinding>, ShellError> {
|
||||
match value {
|
||||
Value::Record { cols, vals, span } => {
|
||||
Value::Record { val, span } => {
|
||||
let span = *span;
|
||||
// Finding the modifier value in the record
|
||||
let modifier = extract_value("modifier", cols, vals, span)?.clone();
|
||||
let keycode = extract_value("keycode", cols, vals, span)?.clone();
|
||||
let mode = extract_value("mode", cols, vals, span)?.clone();
|
||||
let event = extract_value("event", cols, vals, span)?.clone();
|
||||
let modifier = extract_value("modifier", val, span)?.clone();
|
||||
let keycode = extract_value("keycode", val, span)?.clone();
|
||||
let mode = extract_value("mode", val, span)?.clone();
|
||||
let event = extract_value("event", val, span)?.clone();
|
||||
|
||||
let keybinding = ParsedKeybinding {
|
||||
modifier,
|
||||
@ -1566,18 +1537,18 @@ fn create_keybindings(value: &Value) -> Result<Vec<ParsedKeybinding>, ShellError
|
||||
// Parses the config object to extract the strings that will compose a keybinding for reedline
|
||||
pub fn create_menus(value: &Value) -> Result<Vec<ParsedMenu>, ShellError> {
|
||||
match value {
|
||||
Value::Record { cols, vals, span } => {
|
||||
Value::Record { val, span } => {
|
||||
let span = *span;
|
||||
// Finding the modifier value in the record
|
||||
let name = extract_value("name", cols, vals, span)?.clone();
|
||||
let marker = extract_value("marker", cols, vals, span)?.clone();
|
||||
let name = extract_value("name", val, span)?.clone();
|
||||
let marker = extract_value("marker", val, span)?.clone();
|
||||
let only_buffer_difference =
|
||||
extract_value("only_buffer_difference", cols, vals, span)?.clone();
|
||||
let style = extract_value("style", cols, vals, span)?.clone();
|
||||
let menu_type = extract_value("type", cols, vals, span)?.clone();
|
||||
extract_value("only_buffer_difference", val, span)?.clone();
|
||||
let style = extract_value("style", val, span)?.clone();
|
||||
let menu_type = extract_value("type", val, span)?.clone();
|
||||
|
||||
// Source is an optional value
|
||||
let source = match extract_value("source", cols, vals, span) {
|
||||
let source = match extract_value("source", val, span) {
|
||||
Ok(source) => source.clone(),
|
||||
Err(_) => Value::Nothing { span },
|
||||
};
|
||||
@ -1609,12 +1580,11 @@ pub fn create_menus(value: &Value) -> Result<Vec<ParsedMenu>, ShellError> {
|
||||
|
||||
pub fn extract_value<'record>(
|
||||
name: &str,
|
||||
cols: &'record [String],
|
||||
vals: &'record [Value],
|
||||
record: &'record Record,
|
||||
span: Span,
|
||||
) -> Result<&'record Value, ShellError> {
|
||||
cols.iter()
|
||||
.position(|col| col.as_str() == name)
|
||||
.and_then(|index| vals.get(index))
|
||||
record
|
||||
.iter()
|
||||
.find_map(|(col, val)| if col == name { Some(val) } else { None })
|
||||
.ok_or_else(|| ShellError::MissingConfigValue(name.to_string(), span))
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ impl Matcher for Pattern {
|
||||
Pattern::IgnoreRest => false, // `..` and `..$foo` only match in specific contexts
|
||||
Pattern::Rest(_) => false, // so we return false here and handle them elsewhere
|
||||
Pattern::Record(field_patterns) => match value {
|
||||
Value::Record { cols, vals, .. } => {
|
||||
Value::Record { val, .. } => {
|
||||
'top: for field_pattern in field_patterns {
|
||||
for (col_idx, col) in cols.iter().enumerate() {
|
||||
for (col, val) in val {
|
||||
if col == &field_pattern.0 {
|
||||
// We have found the field
|
||||
let result = field_pattern.1.match_value(&vals[col_idx], matches);
|
||||
let result = field_pattern.1.match_value(val, matches);
|
||||
if !result {
|
||||
return false;
|
||||
} else {
|
||||
|
@ -136,16 +136,14 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
let mut const_cols = vec![];
|
||||
let mut const_vals = vec![];
|
||||
|
||||
for (name, val) in const_rows {
|
||||
const_cols.push(String::from_utf8_lossy(&name).to_string());
|
||||
const_vals.push(val);
|
||||
}
|
||||
|
||||
let span = self.span.unwrap_or(backup_span);
|
||||
let const_record = Value::record(const_cols, const_vals, span);
|
||||
let const_record = Value::record(
|
||||
const_rows
|
||||
.into_iter()
|
||||
.map(|(name, val)| (String::from_utf8_lossy(&name).to_string(), val))
|
||||
.collect(),
|
||||
span,
|
||||
);
|
||||
|
||||
return (
|
||||
ResolvedImportPattern::new(
|
||||
|
@ -4,8 +4,7 @@ use std::str::FromStr;
|
||||
|
||||
use crate::ast::{CellPath, MatchPattern, PathMember};
|
||||
use crate::engine::{Block, Closure};
|
||||
use crate::ShellError;
|
||||
use crate::{Range, Spanned, Value};
|
||||
use crate::{Range, Record, ShellError, Spanned, Value};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
|
||||
pub trait FromValue: Sized {
|
||||
@ -490,11 +489,10 @@ impl FromValue for Vec<Value> {
|
||||
}
|
||||
}
|
||||
|
||||
// A record
|
||||
impl FromValue for (Vec<String>, Vec<Value>) {
|
||||
impl FromValue for Record {
|
||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||
match v {
|
||||
Value::Record { cols, vals, .. } => Ok((cols.clone(), vals.clone())),
|
||||
Value::Record { val, .. } => Ok(val.clone()),
|
||||
v => Err(ShellError::CantConvert {
|
||||
to_type: "Record".into(),
|
||||
from_type: v.get_type().to_string(),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{ShellError, Span, Value};
|
||||
use crate::{Record, ShellError, Span, Value};
|
||||
use std::fmt;
|
||||
|
||||
// Trait definition for a lazy record (where columns are evaluated on-demand)
|
||||
@ -15,20 +15,14 @@ pub trait LazyRecord<'a>: fmt::Debug + Send + Sync {
|
||||
|
||||
// Convert the lazy record into a regular Value::Record by collecting all its columns
|
||||
fn collect(&'a self) -> Result<Value, ShellError> {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
for column in self.column_names() {
|
||||
cols.push(column.into());
|
||||
let val = self.get_column_value(column)?;
|
||||
vals.push(val);
|
||||
}
|
||||
|
||||
Ok(Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: self.span(),
|
||||
})
|
||||
self.column_names()
|
||||
.into_iter()
|
||||
.map(|col| {
|
||||
let val = self.get_column_value(col)?;
|
||||
Ok((col.to_owned(), val))
|
||||
})
|
||||
.collect::<Result<Record, _>>()
|
||||
.map(|record| Value::record(record, self.span()))
|
||||
}
|
||||
|
||||
fn clone_value(&self, span: Span) -> Value;
|
||||
|
@ -3,6 +3,7 @@ mod from;
|
||||
mod from_value;
|
||||
mod lazy_record;
|
||||
mod range;
|
||||
mod record;
|
||||
mod stream;
|
||||
mod unit;
|
||||
|
||||
@ -18,19 +19,18 @@ use chrono_humanize::HumanTime;
|
||||
pub use custom_value::CustomValue;
|
||||
use fancy_regex::Regex;
|
||||
pub use from_value::FromValue;
|
||||
use indexmap::map::IndexMap;
|
||||
pub use lazy_record::LazyRecord;
|
||||
use nu_utils::get_system_locale;
|
||||
use nu_utils::locale::get_system_locale_string;
|
||||
use num_format::ToFormattedString;
|
||||
pub use range::*;
|
||||
pub use record::Record;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
iter,
|
||||
path::PathBuf,
|
||||
{cmp::Ordering, fmt::Debug},
|
||||
};
|
||||
@ -75,8 +75,7 @@ pub enum Value {
|
||||
span: Span,
|
||||
},
|
||||
Record {
|
||||
cols: Vec<String>,
|
||||
vals: Vec<Value>,
|
||||
val: Record,
|
||||
span: Span,
|
||||
},
|
||||
List {
|
||||
@ -148,9 +147,8 @@ impl Clone for Value {
|
||||
val: val.clone(),
|
||||
span: *span,
|
||||
},
|
||||
Value::Record { cols, vals, span } => Value::Record {
|
||||
cols: cols.clone(),
|
||||
vals: vals.clone(),
|
||||
Value::Record { val, span } => Value::Record {
|
||||
val: val.clone(),
|
||||
span: *span,
|
||||
},
|
||||
Value::LazyRecord { val, span } => val.clone_value(*span),
|
||||
@ -367,9 +365,9 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_record(&self) -> Result<(&[String], &[Value]), ShellError> {
|
||||
pub fn as_record(&self) -> Result<&Record, ShellError> {
|
||||
match self {
|
||||
Value::Record { cols, vals, .. } => Ok((cols, vals)),
|
||||
Value::Record { val, .. } => Ok(val),
|
||||
x => Err(ShellError::CantConvert {
|
||||
to_type: "record".into(),
|
||||
from_type: x.get_type().to_string(),
|
||||
@ -547,12 +545,9 @@ impl Value {
|
||||
Value::Date { .. } => Type::Date,
|
||||
Value::Range { .. } => Type::Range,
|
||||
Value::String { .. } => Type::String,
|
||||
Value::Record { cols, vals, .. } => Type::Record(
|
||||
cols.iter()
|
||||
.zip(vals.iter())
|
||||
.map(|(x, y)| (x.clone(), y.get_type()))
|
||||
.collect(),
|
||||
),
|
||||
Value::Record { val, .. } => {
|
||||
Type::Record(val.iter().map(|(x, y)| (x.clone(), y.get_type())).collect())
|
||||
}
|
||||
Value::List { vals, .. } => {
|
||||
let mut ty = None;
|
||||
for val in vals {
|
||||
@ -595,9 +590,8 @@ impl Value {
|
||||
|
||||
pub fn get_data_by_key(&self, name: &str) -> Option<Value> {
|
||||
match self {
|
||||
Value::Record { cols, vals, .. } => cols
|
||||
Value::Record { val, .. } => val
|
||||
.iter()
|
||||
.zip(vals.iter())
|
||||
.find(|(col, _)| col == &name)
|
||||
.map(|(_, val)| val.clone()),
|
||||
Value::List { vals, span } => {
|
||||
@ -676,10 +670,9 @@ impl Value {
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator)
|
||||
),
|
||||
Value::Record { cols, vals, .. } => format!(
|
||||
Value::Record { val, .. } => format!(
|
||||
"{{{}}}",
|
||||
cols.iter()
|
||||
.zip(vals.iter())
|
||||
val.iter()
|
||||
.map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator)
|
||||
@ -739,10 +732,10 @@ impl Value {
|
||||
)
|
||||
}
|
||||
}
|
||||
Value::Record { cols, .. } => format!(
|
||||
Value::Record { val, .. } => format!(
|
||||
"{{record {} field{}}}",
|
||||
cols.len(),
|
||||
if cols.len() == 1 { "" } else { "s" }
|
||||
val.len(),
|
||||
if val.len() == 1 { "" } else { "s" }
|
||||
),
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => val.into_abbreviated_string(config),
|
||||
@ -800,10 +793,9 @@ impl Value {
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator)
|
||||
),
|
||||
Value::Record { cols, vals, .. } => format!(
|
||||
Value::Record { val, .. } => format!(
|
||||
"{{{}}}",
|
||||
cols.iter()
|
||||
.zip(vals.iter())
|
||||
val.iter()
|
||||
.map(|(x, y)| format!("{}: {}", x, y.into_string_parsable(", ", config)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator)
|
||||
@ -838,10 +830,9 @@ impl Value {
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator)
|
||||
),
|
||||
Value::Record { cols, vals, .. } => format!(
|
||||
Value::Record { val, .. } => format!(
|
||||
"{{{}}}",
|
||||
cols.iter()
|
||||
.zip(vals.iter())
|
||||
val.iter()
|
||||
.map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(separator)
|
||||
@ -866,7 +857,7 @@ impl Value {
|
||||
match self {
|
||||
Value::String { val, .. } => val.is_empty(),
|
||||
Value::List { vals, .. } => vals.is_empty(),
|
||||
Value::Record { cols, .. } => cols.is_empty(),
|
||||
Value::Record { val, .. } => val.is_empty(),
|
||||
Value::Binary { val, .. } => val.is_empty(),
|
||||
Value::Nothing { .. } => true,
|
||||
_ => false,
|
||||
@ -981,12 +972,11 @@ impl Value {
|
||||
span: origin_span,
|
||||
optional,
|
||||
} => match &mut current {
|
||||
Value::Record { cols, vals, span } => {
|
||||
let cols = cols.clone();
|
||||
Value::Record { val, span } => {
|
||||
let span = *span;
|
||||
|
||||
// Make reverse iterate to avoid duplicate column leads to first value, actually last value is expected.
|
||||
if let Some(found) = cols.iter().zip(vals.iter()).rev().find(|x| {
|
||||
if let Some(found) = val.iter().rev().find(|x| {
|
||||
if insensitive {
|
||||
x.0.to_lowercase() == column_name.to_lowercase()
|
||||
} else {
|
||||
@ -998,7 +988,7 @@ impl Value {
|
||||
return Ok(Value::nothing(*origin_span)); // short-circuit
|
||||
} else {
|
||||
if from_user_input {
|
||||
if let Some(suggestion) = did_you_mean(&cols, column_name) {
|
||||
if let Some(suggestion) = did_you_mean(&val.cols, column_name) {
|
||||
return Err(ShellError::DidYouMean(suggestion, *origin_span));
|
||||
}
|
||||
}
|
||||
@ -1126,12 +1116,12 @@ impl Value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record { cols, vals, .. } => {
|
||||
Value::Record { val: record, .. } => {
|
||||
let mut found = false;
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
found = true;
|
||||
col.1.upsert_data_at_cell_path(
|
||||
val.upsert_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val.clone(),
|
||||
)?
|
||||
@ -1139,15 +1129,11 @@ impl Value {
|
||||
}
|
||||
if !found {
|
||||
if cell_path.len() == 1 {
|
||||
cols.push(col_name.clone());
|
||||
vals.push(new_val);
|
||||
record.push(col_name, new_val);
|
||||
break;
|
||||
} else {
|
||||
let mut new_col = Value::Record {
|
||||
cols: vec![],
|
||||
vals: vec![],
|
||||
span: new_val.span()?,
|
||||
};
|
||||
let mut new_col =
|
||||
Value::record(Record::new(), new_val.span()?);
|
||||
new_col.upsert_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val,
|
||||
@ -1168,30 +1154,25 @@ impl Value {
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Record { cols, vals, .. } => {
|
||||
Value::Record { val: record, .. } => {
|
||||
let mut found = false;
|
||||
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
found = true;
|
||||
|
||||
col.1
|
||||
.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||
val.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
cols.push(col_name.clone());
|
||||
if cell_path.len() == 1 {
|
||||
vals.push(new_val);
|
||||
let new_col = if cell_path.len() == 1 {
|
||||
new_val
|
||||
} else {
|
||||
let mut new_col = Value::Record {
|
||||
cols: vec![],
|
||||
vals: vec![],
|
||||
span: new_val.span()?,
|
||||
};
|
||||
let mut new_col = Value::record(Record::new(), new_val.span()?);
|
||||
new_col.upsert_data_at_cell_path(&cell_path[1..], new_val)?;
|
||||
vals.push(new_col);
|
||||
}
|
||||
new_col
|
||||
};
|
||||
|
||||
record.push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::LazyRecord { val, .. } => {
|
||||
@ -1275,15 +1256,14 @@ impl Value {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
found = true;
|
||||
col.1.update_data_at_cell_path(
|
||||
val.update_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val.clone(),
|
||||
)?
|
||||
@ -1309,18 +1289,15 @@ impl Value {
|
||||
}
|
||||
}
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
found = true;
|
||||
|
||||
col.1
|
||||
.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||
val.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
@ -1392,16 +1369,15 @@ impl Value {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
let mut index = 0;
|
||||
cols.retain_mut(|col| {
|
||||
record.cols.retain_mut(|col| {
|
||||
if col == col_name {
|
||||
found = true;
|
||||
vals.remove(index);
|
||||
record.vals.remove(index);
|
||||
false
|
||||
} else {
|
||||
index += 1;
|
||||
@ -1428,18 +1404,21 @@ impl Value {
|
||||
Ok(())
|
||||
}
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
for (i, col) in cols.clone().iter().enumerate() {
|
||||
let mut index = 0;
|
||||
record.cols.retain_mut(|col| {
|
||||
if col == col_name {
|
||||
cols.remove(i);
|
||||
vals.remove(i);
|
||||
found = true;
|
||||
record.vals.remove(index);
|
||||
false
|
||||
} else {
|
||||
index += 1;
|
||||
true
|
||||
}
|
||||
}
|
||||
});
|
||||
if !found && !optional {
|
||||
return Err(ShellError::CantFindColumn {
|
||||
col_name: col_name.to_string(),
|
||||
@ -1501,15 +1480,14 @@ impl Value {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
found = true;
|
||||
col.1.remove_data_at_cell_path(&cell_path[1..])?
|
||||
val.remove_data_at_cell_path(&cell_path[1..])?
|
||||
}
|
||||
}
|
||||
if !found && !optional {
|
||||
@ -1532,17 +1510,15 @@ impl Value {
|
||||
Ok(())
|
||||
}
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
let mut found = false;
|
||||
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
found = true;
|
||||
|
||||
col.1.remove_data_at_cell_path(&cell_path[1..])?
|
||||
val.remove_data_at_cell_path(&cell_path[1..])?
|
||||
}
|
||||
}
|
||||
if !found && !optional {
|
||||
@ -1613,12 +1589,11 @@ impl Value {
|
||||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
if cell_path.len() == 1 {
|
||||
return Err(ShellError::ColumnAlreadyExists {
|
||||
col_name: col_name.to_string(),
|
||||
@ -1626,7 +1601,7 @@ impl Value {
|
||||
src_span: *v_span,
|
||||
});
|
||||
} else {
|
||||
return col.1.insert_data_at_cell_path(
|
||||
return val.insert_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val,
|
||||
head_span,
|
||||
@ -1635,8 +1610,7 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
cols.push(col_name.clone());
|
||||
vals.push(new_val.clone());
|
||||
record.push(col_name, new_val.clone());
|
||||
}
|
||||
// SIGH...
|
||||
Value::Error { error } => return Err(*error.clone()),
|
||||
@ -1652,12 +1626,11 @@ impl Value {
|
||||
}
|
||||
}
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
val: record,
|
||||
span: v_span,
|
||||
} => {
|
||||
for col in cols.iter().zip(vals.iter_mut()) {
|
||||
if col.0 == col_name {
|
||||
for (col, val) in record.iter_mut() {
|
||||
if col == col_name {
|
||||
if cell_path.len() == 1 {
|
||||
return Err(ShellError::ColumnAlreadyExists {
|
||||
col_name: col_name.to_string(),
|
||||
@ -1665,7 +1638,7 @@ impl Value {
|
||||
src_span: *v_span,
|
||||
});
|
||||
} else {
|
||||
return col.1.insert_data_at_cell_path(
|
||||
return val.insert_data_at_cell_path(
|
||||
&cell_path[1..],
|
||||
new_val,
|
||||
head_span,
|
||||
@ -1674,8 +1647,7 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
cols.push(col_name.clone());
|
||||
vals.push(new_val);
|
||||
record.push(col_name, new_val);
|
||||
}
|
||||
Value::LazyRecord { val, span } => {
|
||||
// convert to Record first.
|
||||
@ -1734,7 +1706,7 @@ impl Value {
|
||||
|
||||
pub fn columns(&self) -> &[String] {
|
||||
match self {
|
||||
Value::Record { cols, .. } => cols,
|
||||
Value::Record { val, .. } => &val.cols,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
@ -1777,18 +1749,8 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record(cols: Vec<String>, vals: Vec<Value>, span: Span) -> Value {
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
|
||||
pub fn record_from_hashmap(map: &HashMap<String, Value>, span: Span) -> Value {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
for (key, val) in map.iter() {
|
||||
cols.push(key.clone());
|
||||
vals.push(val.clone());
|
||||
}
|
||||
Value::record(cols, vals, span)
|
||||
pub fn record(val: Record, span: Span) -> Value {
|
||||
Value::Record { val, span }
|
||||
}
|
||||
|
||||
pub fn list(vals: Vec<Value>, span: Span) -> Value {
|
||||
@ -1894,12 +1856,8 @@ impl Value {
|
||||
|
||||
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
|
||||
/// when used in errors.
|
||||
pub fn test_record(cols: Vec<impl Into<String>>, vals: Vec<Value>) -> Value {
|
||||
Value::record(
|
||||
cols.into_iter().map(|s| s.into()).collect(),
|
||||
vals,
|
||||
Span::test_data(),
|
||||
)
|
||||
pub fn test_record(val: Record) -> Value {
|
||||
Value::record(val, Span::test_data())
|
||||
}
|
||||
|
||||
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
|
||||
@ -2148,14 +2106,7 @@ impl PartialOrd for Value {
|
||||
Value::CustomValue { .. } => Some(Ordering::Less),
|
||||
Value::MatchPattern { .. } => Some(Ordering::Less),
|
||||
},
|
||||
(
|
||||
Value::Record {
|
||||
cols: lhs_cols,
|
||||
vals: lhs_vals,
|
||||
..
|
||||
},
|
||||
rhs,
|
||||
) => match rhs {
|
||||
(Value::Record { val: lhs, .. }, rhs) => match rhs {
|
||||
Value::Bool { .. } => Some(Ordering::Greater),
|
||||
Value::Int { .. } => Some(Ordering::Greater),
|
||||
Value::Float { .. } => Some(Ordering::Greater),
|
||||
@ -2164,18 +2115,12 @@ impl PartialOrd for Value {
|
||||
Value::Date { .. } => Some(Ordering::Greater),
|
||||
Value::Range { .. } => Some(Ordering::Greater),
|
||||
Value::String { .. } => Some(Ordering::Greater),
|
||||
Value::Record {
|
||||
cols: rhs_cols,
|
||||
vals: rhs_vals,
|
||||
..
|
||||
} => {
|
||||
Value::Record { val: rhs, .. } => {
|
||||
// reorder cols and vals to make more logically compare.
|
||||
// more general, if two record have same col and values,
|
||||
// the order of cols shouldn't affect the equal property.
|
||||
let (lhs_cols_ordered, lhs_vals_ordered) =
|
||||
reorder_record_inner(lhs_cols, lhs_vals);
|
||||
let (rhs_cols_ordered, rhs_vals_ordered) =
|
||||
reorder_record_inner(rhs_cols, rhs_vals);
|
||||
let (lhs_cols_ordered, lhs_vals_ordered) = reorder_record_inner(lhs);
|
||||
let (rhs_cols_ordered, rhs_vals_ordered) = reorder_record_inner(rhs);
|
||||
|
||||
let result = lhs_cols_ordered.partial_cmp(&rhs_cols_ordered);
|
||||
if result == Some(Ordering::Equal) {
|
||||
@ -3190,8 +3135,8 @@ impl Value {
|
||||
val: rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(Value::String { val: lhs, .. }, Value::Record { cols: rhs, .. }) => Ok(Value::Bool {
|
||||
val: rhs.contains(lhs),
|
||||
(Value::String { val: lhs, .. }, Value::Record { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: rhs.cols.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => {
|
||||
@ -3247,8 +3192,8 @@ impl Value {
|
||||
val: !rhs.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(Value::String { val: lhs, .. }, Value::Record { cols: rhs, .. }) => Ok(Value::Bool {
|
||||
val: !rhs.contains(lhs),
|
||||
(Value::String { val: lhs, .. }, Value::Record { val: rhs, .. }) => Ok(Value::Bool {
|
||||
val: !rhs.cols.contains(lhs),
|
||||
span,
|
||||
}),
|
||||
(Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => {
|
||||
@ -3648,36 +3593,10 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn reorder_record_inner(cols: &[String], vals: &[Value]) -> (Vec<String>, Vec<Value>) {
|
||||
let mut kv_pairs =
|
||||
iter::zip(cols.to_owned(), vals.to_owned()).collect::<Vec<(String, Value)>>();
|
||||
kv_pairs.sort_by(|a, b| {
|
||||
a.0.partial_cmp(&b.0)
|
||||
.expect("Columns should support compare")
|
||||
});
|
||||
let (mut cols, mut vals) = (vec![], vec![]);
|
||||
for (col, val) in kv_pairs {
|
||||
cols.push(col);
|
||||
vals.push(val);
|
||||
}
|
||||
(cols, vals)
|
||||
}
|
||||
|
||||
/// Create a Value::Record from a spanned hashmap
|
||||
impl From<Spanned<HashMap<String, Value>>> for Value {
|
||||
fn from(input: Spanned<HashMap<String, Value>>) -> Self {
|
||||
let span = input.span;
|
||||
let (cols, vals) = input
|
||||
.item
|
||||
.into_iter()
|
||||
.fold((vec![], vec![]), |mut acc, (k, v)| {
|
||||
acc.0.push(k);
|
||||
acc.1.push(v);
|
||||
acc
|
||||
});
|
||||
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
fn reorder_record_inner(record: &Record) -> (Vec<&String>, Vec<&Value>) {
|
||||
let mut kv_pairs = record.iter().collect::<Vec<_>>();
|
||||
kv_pairs.sort_by_key(|(col, _)| *col);
|
||||
kv_pairs.into_iter().unzip()
|
||||
}
|
||||
|
||||
fn type_compatible(a: Type, b: Type) -> bool {
|
||||
@ -3688,23 +3607,6 @@ fn type_compatible(a: Type, b: Type) -> bool {
|
||||
matches!((a, b), (Type::Int, Type::Float) | (Type::Float, Type::Int))
|
||||
}
|
||||
|
||||
/// Create a Value::Record from a spanned indexmap
|
||||
impl From<Spanned<IndexMap<String, Value>>> for Value {
|
||||
fn from(input: Spanned<IndexMap<String, Value>>) -> Self {
|
||||
let span = input.span;
|
||||
let (cols, vals) = input
|
||||
.item
|
||||
.into_iter()
|
||||
.fold((vec![], vec![]), |mut acc, (k, v)| {
|
||||
acc.0.push(k);
|
||||
acc.1.push(v);
|
||||
acc
|
||||
});
|
||||
|
||||
Value::Record { cols, vals, span }
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the given year a leap year?
|
||||
#[allow(clippy::nonminimal_bool)]
|
||||
pub fn is_leap_year(year: i32) -> bool {
|
||||
@ -3985,7 +3887,7 @@ fn get_filesize_format(format_value: &str, filesize_metric: Option<bool>) -> (By
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::{Span, Value};
|
||||
use super::{Record, Span, Value};
|
||||
|
||||
mod is_empty {
|
||||
use super::*;
|
||||
@ -4013,26 +3915,24 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_record() {
|
||||
let no_columns_nor_cell_values = Value::Record {
|
||||
cols: vec![],
|
||||
vals: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let one_column_and_one_cell_value_with_empty_strings = Value::Record {
|
||||
let no_columns_nor_cell_values = Value::test_record(Record::new());
|
||||
|
||||
let one_column_and_one_cell_value_with_empty_strings = Value::test_record(Record {
|
||||
cols: vec![String::from("")],
|
||||
vals: vec![Value::string("", Span::unknown())],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let one_column_with_a_string_and_one_cell_value_with_empty_string = Value::Record {
|
||||
cols: vec![String::from("column")],
|
||||
vals: vec![Value::string("", Span::unknown())],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let one_column_with_empty_string_and_one_value_with_a_string = Value::Record {
|
||||
cols: vec![String::from("")],
|
||||
vals: vec![Value::string("text", Span::unknown())],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
});
|
||||
|
||||
let one_column_with_a_string_and_one_cell_value_with_empty_string =
|
||||
Value::test_record(Record {
|
||||
cols: vec![String::from("column")],
|
||||
vals: vec![Value::string("", Span::unknown())],
|
||||
});
|
||||
|
||||
let one_column_with_empty_string_and_one_value_with_a_string =
|
||||
Value::test_record(Record {
|
||||
cols: vec![String::from("")],
|
||||
vals: vec![Value::string("text", Span::unknown())],
|
||||
});
|
||||
|
||||
assert!(no_columns_nor_cell_values.is_empty());
|
||||
assert!(!one_column_and_one_cell_value_with_empty_strings.is_empty());
|
||||
|
99
crates/nu-protocol/src/value/record.rs
Normal file
99
crates/nu-protocol/src/value/record.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use crate::Value;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct Record {
|
||||
pub cols: Vec<String>,
|
||||
pub vals: Vec<Value>,
|
||||
}
|
||||
|
||||
impl Record {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
cols: Vec::with_capacity(capacity),
|
||||
vals: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter {
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> IterMut {
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.cols.is_empty() || self.vals.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
usize::min(self.cols.len(), self.vals.len())
|
||||
}
|
||||
|
||||
pub fn push(&mut self, col: impl Into<String>, val: Value) {
|
||||
self.cols.push(col.into());
|
||||
self.vals.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, Value)> for Record {
|
||||
fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
|
||||
let (cols, vals) = iter.into_iter().unzip();
|
||||
Self { cols, vals }
|
||||
}
|
||||
}
|
||||
|
||||
pub type IntoIter = std::iter::Zip<std::vec::IntoIter<String>, std::vec::IntoIter<Value>>;
|
||||
|
||||
impl IntoIterator for Record {
|
||||
type Item = (String, Value);
|
||||
|
||||
type IntoIter = IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.cols.into_iter().zip(self.vals)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Iter<'a> = std::iter::Zip<std::slice::Iter<'a, String>, std::slice::Iter<'a, Value>>;
|
||||
|
||||
impl<'a> IntoIterator for &'a Record {
|
||||
type Item = (&'a String, &'a Value);
|
||||
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.cols.iter().zip(&self.vals)
|
||||
}
|
||||
}
|
||||
|
||||
pub type IterMut<'a> = std::iter::Zip<std::slice::Iter<'a, String>, std::slice::IterMut<'a, Value>>;
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Record {
|
||||
type Item = (&'a String, &'a mut Value);
|
||||
|
||||
type IntoIter = IterMut<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.cols.iter().zip(&mut self.vals)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! record {
|
||||
{$($col:expr => $val:expr),+ $(,)?} => {
|
||||
$crate::Record {
|
||||
cols: vec![$($col.into(),)+],
|
||||
vals: vec![$($val,)+]
|
||||
}
|
||||
};
|
||||
{} => {
|
||||
$crate::Record::new()
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user