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

@ -1,8 +1,8 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError,
Signature, Span, Type, Value,
record, Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData,
ShellError, Signature, Span, Type, Value,
};
use reedline::{
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
@ -95,20 +95,15 @@ impl Command for History {
.ok()
})
.map(move |entries| {
entries
.into_iter()
.enumerate()
.map(move |(idx, entry)| Value::Record {
cols: vec!["command".to_string(), "index".to_string()],
vals: vec![
Value::String {
val: entry.command_line,
span: head,
},
Value::int(idx as i64, head),
],
span: head,
})
entries.into_iter().enumerate().map(move |(idx, entry)| {
Value::record(
record! {
"command" => Value::string(entry.command_line, head),
"index" => Value::int(idx as i64, head),
},
head,
)
})
})
.ok_or(ShellError::FileNotFound(head))?
.into_pipeline_data(ctrlc)),
@ -217,48 +212,30 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
let index_value = Value::int(idx as i64, head);
if long {
Value::Record {
cols: vec![
"item_id".into(),
"start_timestamp".into(),
"command".to_string(),
"session_id".into(),
"hostname".into(),
"cwd".into(),
"duration".into(),
"exit_status".into(),
"idx".to_string(),
],
vals: vec![
item_id_value,
start_timestamp_value,
command_value,
session_id_value,
hostname_value,
cwd_value,
duration_value,
exit_status_value,
index_value,
],
span: head,
}
Value::record(
record! {
"item_id" => item_id_value,
"start_timestamp" => start_timestamp_value,
"command" => command_value,
"session_id" => session_id_value,
"hostname" => hostname_value,
"cwd" => cwd_value,
"duration" => duration_value,
"exit_status" => exit_status_value,
"idx" => index_value,
},
head,
)
} else {
Value::Record {
cols: vec![
"start_timestamp".into(),
"command".to_string(),
"cwd".into(),
"duration".into(),
"exit_status".into(),
],
vals: vec![
start_timestamp_value,
command_value,
cwd_value,
duration_value,
exit_status_value,
],
span: head,
}
Value::record(
record! {
"start_timestamp" => start_timestamp_value,
"command" => command_value,
"cwd" => cwd_value,
"duration" => duration_value,
"exit_status" => exit_status_value,
},
head,
)
}
}

View File

@ -1,7 +1,7 @@
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
};
use reedline::get_reedline_default_keybindings;
@ -41,36 +41,15 @@ impl Command for KeybindingsDefault {
let records = get_reedline_default_keybindings()
.into_iter()
.map(|(mode, modifier, code, event)| {
let mode = Value::String {
val: mode,
span: call.head,
};
let modifier = Value::String {
val: modifier,
span: call.head,
};
let code = Value::String {
val: code,
span: call.head,
};
let event = Value::String {
val: event,
span: call.head,
};
Value::Record {
cols: vec![
"mode".to_string(),
"modifier".to_string(),
"code".to_string(),
"event".to_string(),
],
vals: vec![mode, modifier, code, event],
span: call.head,
}
Value::record(
record! {
"mode" => Value::string(mode, call.head),
"modifier" => Value::string(modifier, call.head),
"code" => Value::string(code, call.head),
"event" => Value::string(event, call.head),
},
call.head,
)
})
.collect();

View File

@ -1,7 +1,8 @@
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
Value,
};
use reedline::{
get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes,
@ -96,15 +97,13 @@ fn get_records(entry_type: &str, span: Span) -> Vec<Value> {
}
fn convert_to_record(edit: &str, entry_type: &str, span: Span) -> Value {
let entry_type = Value::string(entry_type, span);
let name = Value::string(edit, span);
Value::Record {
cols: vec!["type".to_string(), "name".to_string()],
vals: vec![entry_type, name],
Value::record(
record! {
"type" => Value::string(entry_type, span),
"name" => Value::string(edit, span),
},
span,
}
)
}
// Helper to sort a vec and return a vec

View File

@ -3,7 +3,8 @@ use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
Value,
};
use std::io::{stdout, Write};
@ -78,9 +79,8 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
let v = print_events_helper(event)?;
// Print out the record
let o = match v {
Value::Record { cols, vals, .. } => cols
Value::Record { val, .. } => val
.iter()
.zip(vals.iter())
.map(|(x, y)| format!("{}: {}", x, y.into_string("", config)))
.collect::<Vec<String>>()
.join(", "),
@ -111,54 +111,29 @@ fn print_events_helper(event: Event) -> Result<Value, ShellError> {
{
match code {
KeyCode::Char(c) => {
let record = Value::Record {
cols: vec![
"char".into(),
"code".into(),
"modifier".into(),
"flags".into(),
"kind".into(),
"state".into(),
],
vals: vec![
Value::string(format!("{c}"), Span::unknown()),
Value::string(format!("{:#08x}", u32::from(c)), Span::unknown()),
Value::string(format!("{modifiers:?}"), Span::unknown()),
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
Value::string(format!("{kind:?}"), Span::unknown()),
Value::string(format!("{state:?}"), Span::unknown()),
],
span: Span::unknown(),
let record = record! {
"char" => Value::string(format!("{c}"), Span::unknown()),
"code" => Value::string(format!("{:#08x}", u32::from(c)), Span::unknown()),
"modifier" => Value::string(format!("{modifiers:?}"), Span::unknown()),
"flags" => Value::string(format!("{modifiers:#08b}"), Span::unknown()),
"kind" => Value::string(format!("{kind:?}"), Span::unknown()),
"state" => Value::string(format!("{state:?}"), Span::unknown()),
};
Ok(record)
Ok(Value::record(record, Span::unknown()))
}
_ => {
let record = Value::Record {
cols: vec![
"code".into(),
"modifier".into(),
"flags".into(),
"kind".into(),
"state".into(),
],
vals: vec![
Value::string(format!("{code:?}"), Span::unknown()),
Value::string(format!("{modifiers:?}"), Span::unknown()),
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
Value::string(format!("{kind:?}"), Span::unknown()),
Value::string(format!("{state:?}"), Span::unknown()),
],
span: Span::unknown(),
let record = record! {
"code" => Value::string(format!("{code:?}"), Span::unknown()),
"modifier" => Value::string(format!("{modifiers:?}"), Span::unknown()),
"flags" => Value::string(format!("{modifiers:#08b}"), Span::unknown()),
"kind" => Value::string(format!("{kind:?}"), Span::unknown()),
"state" => Value::string(format!("{state:?}"), Span::unknown()),
};
Ok(record)
Ok(Value::record(record, Span::unknown()))
}
}
} else {
let record = Value::Record {
cols: vec!["event".into()],
vals: vec![Value::string(format!("{event:?}"), Span::unknown())],
span: Span::unknown(),
};
Ok(record)
let record = record! { "event" => Value::string(format!("{event:?}"), Span::unknown()) };
Ok(Value::record(record, Span::unknown()))
}
}