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 eml_parser::eml::*;
use eml_parser::EmlParser;
use indexmap::map::IndexMap;
use nu_plugin::{EvaluatedCall, LabeledError};
use nu_protocol::{PluginExample, ShellError, Span, Spanned, Value};
use nu_protocol::{record, PluginExample, Record, ShellError, Span, Value};
const DEFAULT_BODY_PREVIEW: usize = 50;
pub const CMD_NAME: &str = "from eml";
@ -24,7 +24,7 @@ Subject: Welcome
To: someone@somewhere.com
Test' | from eml"
.into(),
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"Subject".to_string(),
"From".to_string(),
@ -33,26 +33,23 @@ Test' | from eml"
],
vals: vec![
Value::test_string("Welcome"),
Value::Record {
Value::test_record(Record {
cols: vec!["Name".to_string(), "Address".to_string()],
vals: vec![
Value::nothing(Span::test_data()),
Value::test_string("test@email.com"),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["Name".to_string(), "Address".to_string()],
vals: vec![
Value::nothing(Span::test_data()),
Value::test_string("someone@somewhere.com"),
],
span: Span::test_data(),
},
}),
Value::test_string("Test"),
],
span: Span::test_data(),
}),
})),
},
PluginExample {
description: "Convert eml structured data into record".into(),
@ -61,7 +58,7 @@ Subject: Welcome
To: someone@somewhere.com
Test' | from eml -b 1"
.into(),
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"Subject".to_string(),
"From".to_string(),
@ -70,26 +67,23 @@ Test' | from eml -b 1"
],
vals: vec![
Value::test_string("Welcome"),
Value::Record {
Value::test_record(Record {
cols: vec!["Name".to_string(), "Address".to_string()],
vals: vec![
Value::nothing(Span::test_data()),
Value::test_string("test@email.com"),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["Name".to_string(), "Address".to_string()],
vals: vec![
Value::nothing(Span::test_data()),
Value::test_string("someone@somewhere.com"),
],
span: Span::test_data(),
},
}),
Value::test_string("T"),
],
span: Span::test_data(),
}),
})),
},
]
}
@ -115,11 +109,13 @@ fn emailaddress_to_value(span: Span, email_address: &EmailAddress) -> Value {
),
};
Value::Record {
cols: vec!["Name".to_string(), "Address".to_string()],
vals: vec![n, a],
Value::record(
record! {
"Name" => n,
"Address" => a,
},
span,
}
)
}
fn headerfieldvalue_to_value(head: Span, value: &HeaderFieldValue) -> Value {
@ -186,8 +182,5 @@ fn from_eml(input: &Value, body_preview: usize, head: Span) -> Result<Value, Lab
);
}
Ok(Value::from(Spanned {
item: collected,
span: head,
}))
Ok(Value::record(collected.into_iter().collect(), head))
}

View File

@ -2,7 +2,7 @@ use ical::parser::ical::component::*;
use ical::property::Property;
use indexmap::map::IndexMap;
use nu_plugin::{EvaluatedCall, LabeledError};
use nu_protocol::{PluginExample, ShellError, Span, Spanned, Value};
use nu_protocol::{record, PluginExample, Record, ShellError, Span, Value};
use std::io::BufReader;
pub const CMD_NAME: &str = "from ics";
@ -50,7 +50,7 @@ pub fn examples() -> Vec<PluginExample> {
.into(),
description: "Converts ics formatted string to table".into(),
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"properties".to_string(),
"events".to_string(),
@ -90,37 +90,25 @@ pub fn examples() -> Vec<PluginExample> {
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
}]
}
fn calendar_to_value(calendar: IcalCalendar, span: Span) -> Value {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(calendar.properties, span),
);
row.insert("events".to_string(), events_to_value(calendar.events, span));
row.insert("alarms".to_string(), alarms_to_value(calendar.alarms, span));
row.insert("to-Dos".to_string(), todos_to_value(calendar.todos, span));
row.insert(
"journals".to_string(),
journals_to_value(calendar.journals, span),
);
row.insert(
"free-busys".to_string(),
free_busys_to_value(calendar.free_busys, span),
);
row.insert(
"timezones".to_string(),
timezones_to_value(calendar.timezones, span),
);
Value::from(Spanned { item: row, span })
Value::record(
record! {
"properties" => properties_to_value(calendar.properties, span),
"events" => events_to_value(calendar.events, span),
"alarms" => alarms_to_value(calendar.alarms, span),
"to-Dos" => todos_to_value(calendar.todos, span),
"journals" => journals_to_value(calendar.journals, span),
"free-busys" => free_busys_to_value(calendar.free_busys, span),
"timezones" => timezones_to_value(calendar.timezones, span),
},
span,
)
}
fn events_to_value(events: Vec<IcalEvent>, span: Span) -> Value {
@ -128,13 +116,13 @@ fn events_to_value(events: Vec<IcalEvent>, span: Span) -> Value {
vals: events
.into_iter()
.map(|event| {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(event.properties, span),
);
row.insert("alarms".to_string(), alarms_to_value(event.alarms, span));
Value::from(Spanned { item: row, span })
Value::record(
record! {
"properties" => properties_to_value(event.properties, span),
"alarms" => alarms_to_value(event.alarms, span),
},
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -146,12 +134,10 @@ fn alarms_to_value(alarms: Vec<IcalAlarm>, span: Span) -> Value {
vals: alarms
.into_iter()
.map(|alarm| {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(alarm.properties, span),
);
Value::from(Spanned { item: row, span })
Value::record(
record! { "properties" => properties_to_value(alarm.properties, span), },
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -163,13 +149,13 @@ fn todos_to_value(todos: Vec<IcalTodo>, span: Span) -> Value {
vals: todos
.into_iter()
.map(|todo| {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(todo.properties, span),
);
row.insert("alarms".to_string(), alarms_to_value(todo.alarms, span));
Value::from(Spanned { item: row, span })
Value::record(
record! {
"properties" => properties_to_value(todo.properties, span),
"alarms" => alarms_to_value(todo.alarms, span),
},
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -181,12 +167,10 @@ fn journals_to_value(journals: Vec<IcalJournal>, span: Span) -> Value {
vals: journals
.into_iter()
.map(|journal| {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(journal.properties, span),
);
Value::from(Spanned { item: row, span })
Value::record(
record! { "properties" => properties_to_value(journal.properties, span), },
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -198,12 +182,10 @@ fn free_busys_to_value(free_busys: Vec<IcalFreeBusy>, span: Span) -> Value {
vals: free_busys
.into_iter()
.map(|free_busy| {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(free_busy.properties, span),
);
Value::from(Spanned { item: row, span })
Value::record(
record! { "properties" => properties_to_value(free_busy.properties, span) },
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -215,16 +197,13 @@ fn timezones_to_value(timezones: Vec<IcalTimeZone>, span: Span) -> Value {
vals: timezones
.into_iter()
.map(|timezone| {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(timezone.properties, span),
);
row.insert(
"transitions".to_string(),
timezone_transitions_to_value(timezone.transitions, span),
);
Value::from(Spanned { item: row, span })
Value::record(
record! {
"properties" => properties_to_value(timezone.properties, span),
"transitions" => timezone_transitions_to_value(timezone.transitions, span),
},
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -236,12 +215,10 @@ fn timezone_transitions_to_value(transitions: Vec<IcalTimeZoneTransition>, span:
vals: transitions
.into_iter()
.map(|transition| {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(transition.properties, span),
);
Value::from(Spanned { item: row, span })
Value::record(
record! { "properties" => properties_to_value(transition.properties, span) },
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -253,8 +230,6 @@ fn properties_to_value(properties: Vec<Property>, span: Span) -> Value {
vals: properties
.into_iter()
.map(|prop| {
let mut row = IndexMap::new();
let name = Value::String {
val: prop.name,
span,
@ -268,10 +243,14 @@ fn properties_to_value(properties: Vec<Property>, span: Span) -> Value {
None => Value::nothing(span),
};
row.insert("name".to_string(), name);
row.insert("value".to_string(), value);
row.insert("params".to_string(), params);
Value::from(Spanned { item: row, span })
Value::record(
record! {
"name" => name,
"value" => value,
"params" => params,
},
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -290,5 +269,5 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, span: Span) -> Value {
row.insert(param_name, values);
}
Value::from(Spanned { item: row, span })
Value::record(row.into_iter().collect(), span)
}

View File

@ -1,5 +1,5 @@
use nu_plugin::{EvaluatedCall, LabeledError};
use nu_protocol::{PluginExample, ShellError, Span, Value};
use nu_protocol::{PluginExample, Record, ShellError, Value};
pub const CMD_NAME: &str = "from ini";
@ -11,52 +11,34 @@ pub fn from_ini_call(call: &EvaluatedCall, input: &Value) -> Result<Value, Label
let ini_config: Result<ini::Ini, ini::ParseError> = ini::Ini::load_from_str(&input_string);
match ini_config {
Ok(config) => {
let mut sections: Vec<String> = Vec::new();
let mut sections_key_value_pairs: Vec<Value> = Vec::new();
let mut sections = Record::new();
for (section, properties) in config.iter() {
let mut keys_for_section: Vec<String> = Vec::new();
let mut values_for_section: Vec<Value> = Vec::new();
let mut section_record = Record::new();
// section's key value pairs
for (key, value) in properties.iter() {
section_record.push(key, Value::string(value, span));
}
let section_record = Value::record(section_record, span);
// section
match section {
Some(section_name) => {
sections.push(section_name.to_owned());
sections.push(section_name, section_record);
}
None => {
// Section (None) allows for key value pairs without a section
if !properties.is_empty() {
sections.push(String::new());
sections.push(String::new(), section_record);
}
}
}
// section's key value pairs
for (key, value) in properties.iter() {
keys_for_section.push(key.to_owned());
values_for_section.push(Value::String {
val: value.to_owned(),
span,
});
}
// section with its key value pairs
// Only add section if contains key,value pair
if !properties.is_empty() {
sections_key_value_pairs.push(Value::Record {
cols: keys_for_section,
vals: values_for_section,
span,
});
}
}
// all sections with all its key value pairs
Ok(Value::Record {
cols: sections,
vals: sections_key_value_pairs,
span,
})
Ok(Value::record(sections, span))
}
Err(err) => Err(ShellError::UnsupportedInput(
format!("Could not load ini: {err}"),
@ -75,14 +57,12 @@ a=1
b=2' | from ini"
.into(),
description: "Converts ini formatted string to record".into(),
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["foo".to_string()],
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_string("1"), Value::test_string("2")],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
})],
})),
}]
}

View File

@ -2,7 +2,7 @@ use ical::parser::vcard::component::*;
use ical::property::Property;
use indexmap::map::IndexMap;
use nu_plugin::{EvaluatedCall, LabeledError};
use nu_protocol::{PluginExample, ShellError, Span, Spanned, Value};
use nu_protocol::{record, PluginExample, Record, ShellError, Span, Value};
pub const CMD_NAME: &str = "from vcf";
@ -50,11 +50,11 @@ END:VCARD' | from vcf"
.into(),
description: "Converts ics formatted string to table".into(),
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["properties".to_string()],
vals: vec![Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec![
"name".to_string(),
"value".to_string(),
@ -67,9 +67,8 @@ END:VCARD' | from vcf"
span: Span::test_data(),
},
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec![
"name".to_string(),
"value".to_string(),
@ -82,9 +81,8 @@ END:VCARD' | from vcf"
span: Span::test_data(),
},
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec![
"name".to_string(),
"value".to_string(),
@ -97,25 +95,21 @@ END:VCARD' | from vcf"
span: Span::test_data(),
},
],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
}]
}
fn contact_to_value(contact: VcardContact, span: Span) -> Value {
let mut row = IndexMap::new();
row.insert(
"properties".to_string(),
properties_to_value(contact.properties, span),
);
Value::from(Spanned { item: row, span })
Value::record(
record! { "properties" => properties_to_value(contact.properties, span) },
span,
)
}
fn properties_to_value(properties: Vec<Property>, span: Span) -> Value {
@ -123,8 +117,6 @@ fn properties_to_value(properties: Vec<Property>, span: Span) -> Value {
vals: properties
.into_iter()
.map(|prop| {
let mut row = IndexMap::new();
let name = Value::String {
val: prop.name,
span,
@ -138,10 +130,14 @@ fn properties_to_value(properties: Vec<Property>, span: Span) -> Value {
None => Value::Nothing { span },
};
row.insert("name".to_string(), name);
row.insert("value".to_string(), value);
row.insert("params".to_string(), params);
Value::from(Spanned { item: row, span })
Value::record(
record! {
"name" => name,
"value" => value,
"params" => params,
},
span,
)
})
.collect::<Vec<Value>>(),
span,
@ -160,5 +156,5 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, span: Span) -> Value {
row.insert(param_name, values);
}
Value::from(Spanned { item: row, span })
Value::record(row.into_iter().collect(), span)
}