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:
Ian Manske
2023-08-24 19:50:29 +00:00
committed by GitHub
parent 030e749fe7
commit 8da27a1a09
195 changed files with 4211 additions and 6245 deletions

View File

@ -2,7 +2,7 @@ use std::io::Result;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
record, Value,
};
use crate::{
@ -158,10 +158,14 @@ impl ViewCommand for ConfigCmd {
fn create_default_value() -> Value {
let span = NuSpan::unknown();
let record = |i: usize| Value::Record {
cols: vec![String::from("key"), String::from("value")],
vals: vec![nu_str(format!("key-{i}")), nu_str(format!("{i}"))],
span,
let record = |i: usize| {
Value::record(
record! {
"key" => nu_str(format!("key-{i}")),
"value" => nu_str(format!("{i}")),
},
span,
)
};
Value::List {

View File

@ -1,6 +1,6 @@
use nu_protocol::{
engine::{EngineState, Stack},
Value,
record, Value,
};
use ratatui::layout::Rect;
use std::collections::HashMap;
@ -157,8 +157,8 @@ fn convert_styles_value(value: &mut Value) {
convert_styles_value(value);
}
}
Value::Record { vals, .. } => {
for value in vals {
Value::Record { val, .. } => {
for value in &mut val.vals {
convert_styles_value(value);
}
}
@ -168,17 +168,13 @@ fn convert_styles_value(value: &mut Value) {
fn convert_style_from_string(s: &str) -> Option<Value> {
let style = nu_json::from_str::<nu_color_config::NuStyle>(s).ok()?;
let cols = vec![String::from("bg"), String::from("fg"), String::from("attr")];
let vals = vec![
Value::string(style.bg.unwrap_or_default(), NuSpan::unknown()),
Value::string(style.fg.unwrap_or_default(), NuSpan::unknown()),
Value::string(style.attr.unwrap_or_default(), NuSpan::unknown()),
];
Some(Value::Record {
cols,
vals,
span: NuSpan::unknown(),
})
Some(Value::record(
record! {
"bg" => Value::string(style.bg.unwrap_or_default(), NuSpan::unknown()),
"fg" => Value::string(style.fg.unwrap_or_default(), NuSpan::unknown()),
"attr" => Value::string(style.attr.unwrap_or_default(), NuSpan::unknown()),
},
NuSpan::unknown(),
))
}

View File

@ -4,7 +4,7 @@ use std::io::{self, Result};
use crossterm::event::KeyEvent;
use nu_protocol::{
engine::{EngineState, Stack},
Value,
record, Record, Value,
};
use ratatui::layout::Rect;
@ -169,11 +169,7 @@ fn help_frame_data(
let (cols, mut vals) = help_manual_data(manual, aliases);
let vals = vals.remove(0);
Value::Record {
cols,
vals,
span: NuSpan::unknown(),
}
Value::record(Record { cols, vals }, NuSpan::unknown())
})
.collect();
let commands = Value::List {
@ -192,10 +188,14 @@ fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Ve
let arguments = manual
.arguments
.iter()
.map(|e| Value::Record {
cols: vec![String::from("example"), String::from("description")],
vals: vec![nu_str(&e.example), nu_str(&e.description)],
span: NuSpan::unknown(),
.map(|e| {
Value::record(
record! {
"example" => nu_str(&e.example),
"description" => nu_str(&e.description),
},
NuSpan::unknown(),
)
})
.collect();
@ -207,10 +207,14 @@ fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Ve
let examples = manual
.examples
.iter()
.map(|e| Value::Record {
cols: vec![String::from("example"), String::from("description")],
vals: vec![nu_str(&e.example), nu_str(&e.description)],
span: NuSpan::unknown(),
.map(|e| {
Value::record(
record! {
"example" => nu_str(&e.example),
"description" => nu_str(&e.description),
},
NuSpan::unknown(),
)
})
.collect();
let examples = Value::List {
@ -221,14 +225,15 @@ fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Ve
let inputs = manual
.input
.iter()
.map(|e| Value::Record {
cols: vec![
String::from("name"),
String::from("context"),
String::from("description"),
],
vals: vec![nu_str(&e.code), nu_str(&e.context), nu_str(&e.description)],
span: NuSpan::unknown(),
.map(|e| {
Value::record(
record! {
"name" => nu_str(&e.code),
"context" => nu_str(&e.context),
"description" => nu_str(&e.description),
},
NuSpan::unknown(),
)
})
.collect();
let inputs = Value::List {
@ -243,10 +248,14 @@ fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Ve
let values = o
.values
.iter()
.map(|v| Value::Record {
cols: vec![String::from("example"), String::from("description")],
vals: vec![nu_str(&v.example), nu_str(&v.description)],
span: NuSpan::unknown(),
.map(|v| {
Value::record(
record! {
"example" => nu_str(&v.example),
"description" => nu_str(&v.description),
},
NuSpan::unknown(),
)
})
.collect();
let values = Value::List {
@ -254,21 +263,15 @@ fn help_manual_data(manual: &HelpManual, aliases: &[String]) -> (Vec<String>, Ve
span: NuSpan::unknown(),
};
Value::Record {
cols: vec![
String::from("name"),
String::from("context"),
String::from("description"),
String::from("values"),
],
vals: vec![
nu_str(&o.group),
nu_str(&o.key),
nu_str(&o.description),
values,
],
span: NuSpan::unknown(),
}
Value::record(
record! {
"name" => nu_str(&o.group),
"context" => nu_str(&o.key),
"description" => nu_str(&o.description),
"values" => values,
},
NuSpan::unknown(),
)
})
.collect();
let configuration = Value::List {

View File

@ -296,10 +296,8 @@ fn prepare_default_config(config: &mut HashMap<String, Value>) {
}
fn parse_hash_map(value: &Value) -> Option<HashMap<String, Value>> {
value.as_record().ok().map(|(cols, vals)| {
cols.iter()
.take(vals.len())
.zip(vals)
value.as_record().ok().map(|val| {
val.iter()
.map(|(col, val)| (col.clone(), val.clone()))
.collect::<HashMap<_, _>>()
})

View File

@ -1,5 +1,5 @@
use nu_color_config::StyleComputer;
use nu_protocol::{Span, Value};
use nu_protocol::{Record, Span, Value};
use nu_table::{
common::{nu_value_to_string, nu_value_to_string_clean},
ExpandedTable, TableOpts,
@ -17,9 +17,7 @@ pub fn try_build_table(
) -> String {
match value {
Value::List { vals, span } => try_build_list(vals, ctrlc, config, span, style_computer),
Value::Record { cols, vals, span } => {
try_build_map(cols, vals, span, style_computer, ctrlc, config)
}
Value::Record { val, span } => try_build_map(val, span, style_computer, ctrlc, config),
val if matches!(val, Value::String { .. }) => {
nu_value_to_string_clean(&val, config, style_computer).0
}
@ -28,8 +26,7 @@ pub fn try_build_table(
}
fn try_build_map(
cols: Vec<String>,
vals: Vec<Value>,
record: Record,
span: Span,
style_computer: &StyleComputer,
ctrlc: Option<Arc<AtomicBool>>,
@ -44,11 +41,11 @@ fn try_build_map(
usize::MAX,
(config.table_indent.left, config.table_indent.right),
);
let result = ExpandedTable::new(None, false, String::new()).build_map(&cols, &vals, opts);
let result = ExpandedTable::new(None, false, String::new()).build_map(&record, opts);
match result {
Ok(Some(result)) => result,
Ok(None) | Err(_) => {
nu_value_to_string(&Value::Record { cols, vals, span }, config, style_computer).0
nu_value_to_string(&Value::record(record, span), config, style_computer).0
}
}
}

View File

@ -1,7 +1,9 @@
use std::collections::HashMap;
use nu_engine::get_columns;
use nu_protocol::{ast::PathMember, ListStream, PipelineData, PipelineMetadata, RawStream, Value};
use nu_protocol::{
ast::PathMember, record, ListStream, PipelineData, PipelineMetadata, RawStream, Value,
};
use super::NuSpan;
@ -80,14 +82,7 @@ fn collect_external_stream(
data.push(val);
}
if metadata.is_some() {
let val = Value::Record {
cols: vec![String::from("data_source")],
vals: vec![Value::String {
val: String::from("ls"),
span,
}],
span,
};
let val = Value::record(record! { "data_source" => Value::string("ls", span) }, span);
columns.push(String::from("metadata"));
data.push(val);
@ -98,7 +93,7 @@ fn collect_external_stream(
/// Try to build column names and a table grid.
pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<Value>>) {
match value {
Value::Record { cols, vals, .. } => (cols, vec![vals]),
Value::Record { val: record, .. } => (record.cols, vec![record.vals]),
Value::List { vals, .. } => {
let mut columns = get_columns(&vals);
let data = convert_records_to_dataset(&columns, vals);
@ -191,30 +186,18 @@ fn record_lookup_value(item: &Value, header: &str) -> Value {
}
pub fn create_map(value: &Value) -> Option<HashMap<String, Value>> {
let (cols, inner_vals) = value.as_record().ok()?;
let mut hm: HashMap<String, Value> = HashMap::new();
for (k, v) in cols.iter().zip(inner_vals) {
hm.insert(k.to_string(), v.clone());
}
Some(hm)
Some(
value
.as_record()
.ok()?
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
)
}
pub fn map_into_value(hm: HashMap<String, Value>) -> Value {
let mut columns = Vec::with_capacity(hm.len());
let mut values = Vec::with_capacity(hm.len());
for (key, value) in hm {
columns.push(key);
values.push(value);
}
Value::Record {
cols: columns,
vals: values,
span: NuSpan::unknown(),
}
Value::record(hm.into_iter().collect(), NuSpan::unknown())
}
pub fn nu_str<S: AsRef<str>>(s: S) -> Value {

View File

@ -23,7 +23,7 @@ use lscolors::LsColors;
use nu_color_config::{lookup_ansi_color_style, StyleComputer};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
Record, Value,
};
use ratatui::{backend::CrosstermBackend, layout::Rect, widgets::Block};
@ -990,11 +990,7 @@ fn set_config(hm: &mut HashMap<String, Value>, path: &[&str], value: Value) -> b
if !hm.contains_key(key) {
hm.insert(
key.to_string(),
Value::Record {
cols: vec![],
vals: vec![],
span: NuSpan::unknown(),
},
Value::record(Record::new(), NuSpan::unknown()),
);
}
@ -1006,27 +1002,26 @@ fn set_config(hm: &mut HashMap<String, Value>, path: &[&str], value: Value) -> b
}
match val {
Value::Record { cols, vals, .. } => {
Value::Record { val: record, .. } => {
if path.len() == 2 {
if cols.len() != vals.len() {
if record.cols.len() != record.vals.len() {
return false;
}
let key = &path[1];
let pos = cols.iter().position(|v| v == key);
let pos = record.cols.iter().position(|v| v == key);
match pos {
Some(i) => {
vals[i] = value;
record.vals[i] = value;
}
None => {
cols.push(key.to_string());
vals.push(value);
record.push(*key, value);
}
}
} else {
let mut hm2: HashMap<String, Value> = HashMap::new();
for (k, v) in cols.iter().zip(vals) {
for (k, v) in record.iter() {
hm2.insert(k.to_string(), v.clone());
}

View File

@ -8,7 +8,7 @@ use crossterm::event::{KeyCode, KeyEvent};
use nu_color_config::{get_color_map, StyleComputer};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
Record, Value,
};
use ratatui::{layout::Rect, widgets::Block};
@ -683,10 +683,14 @@ fn build_table_as_list(v: &RecordView) -> Value {
.records
.iter()
.cloned()
.map(|vals| Value::Record {
cols: headers.clone(),
vals,
span: NuSpan::unknown(),
.map(|vals| {
Value::record(
Record {
cols: headers.clone(),
vals,
},
NuSpan::unknown(),
)
})
.collect();
@ -702,11 +706,7 @@ fn build_table_as_record(v: &RecordView) -> Value {
let cols = layer.columns.to_vec();
let vals = layer.records.get(0).map_or(Vec::new(), |row| row.clone());
Value::Record {
cols,
vals,
span: NuSpan::unknown(),
}
Value::record(Record { cols, vals }, NuSpan::unknown())
}
fn report_cursor_position(mode: UIMode, cursor: XYCursor) -> String {