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,7 +1,7 @@
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Type, Value,
};
use std::thread;
@ -42,8 +42,7 @@ impl Command for Complete {
exit_code,
..
} => {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
// use a thread to receive stderr message.
// Or we may get a deadlock if child process sends out too much bytes to stdout.
@ -78,46 +77,35 @@ impl Command for Complete {
});
if let Some(stdout) = stdout {
cols.push("stdout".to_string());
let stdout = stdout.into_bytes()?;
if let Ok(st) = String::from_utf8(stdout.item.clone()) {
vals.push(Value::String {
val: st,
span: stdout.span,
})
} else {
vals.push(Value::Binary {
val: stdout.item,
span: stdout.span,
})
}
record.push(
"stdout",
if let Ok(st) = String::from_utf8(stdout.item.clone()) {
Value::string(st, stdout.span)
} else {
Value::binary(stdout.item, stdout.span)
},
)
}
if let Some((handler, stderr_span)) = stderr_handler {
cols.push("stderr".to_string());
let res = handler.join().map_err(|err| ShellError::ExternalCommand {
label: "Fail to receive external commands stderr message".to_string(),
help: format!("{err:?}"),
span: stderr_span,
})??;
vals.push(res)
record.push("stderr", res)
};
if let Some(exit_code) = exit_code {
let mut v: Vec<_> = exit_code.collect();
if let Some(v) = v.pop() {
cols.push("exit_code".to_string());
vals.push(v);
record.push("exit_code", v);
}
}
Ok(Value::Record {
cols,
vals,
span: call.head,
}
.into_pipeline_data())
Ok(Value::record(record, call.head).into_pipeline_data())
}
_ => Err(ShellError::GenericError(
"Complete only works with external streams".to_string(),

View File

@ -3,8 +3,8 @@ use std::time::Duration;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type,
Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Type, Value,
};
#[derive(Clone)]
@ -82,81 +82,41 @@ fn run_ps(engine_state: &EngineState, call: &Call) -> Result<PipelineData, Shell
let long = call.has_flag("long");
for proc in nu_system::collect_proc(Duration::from_millis(100), false) {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
cols.push("pid".to_string());
vals.push(Value::Int {
val: proc.pid() as i64,
span,
});
cols.push("ppid".to_string());
vals.push(Value::Int {
val: proc.ppid() as i64,
span,
});
cols.push("name".to_string());
vals.push(Value::String {
val: proc.name(),
span,
});
record.push("pid", Value::int(proc.pid() as i64, span));
record.push("ppid", Value::int(proc.ppid() as i64, span));
record.push("name", Value::string(proc.name(), span));
#[cfg(not(windows))]
{
// Hide status on Windows until we can find a good way to support it
cols.push("status".to_string());
vals.push(Value::String {
val: proc.status(),
span,
});
record.push("status", Value::string(proc.status(), span));
}
cols.push("cpu".to_string());
vals.push(Value::Float {
val: proc.cpu_usage(),
span,
});
cols.push("mem".to_string());
vals.push(Value::Filesize {
val: proc.mem_size() as i64,
span,
});
cols.push("virtual".to_string());
vals.push(Value::Filesize {
val: proc.virtual_size() as i64,
span,
});
record.push("cpu", Value::float(proc.cpu_usage(), span));
record.push("mem", Value::filesize(proc.mem_size() as i64, span));
record.push("virtual", Value::filesize(proc.virtual_size() as i64, span));
if long {
cols.push("command".to_string());
vals.push(Value::String {
val: proc.command(),
span,
});
record.push("command", Value::string(proc.command(), span));
#[cfg(windows)]
{
cols.push("cwd".to_string());
vals.push(Value::String {
val: proc.cwd(),
span,
});
cols.push("environment".to_string());
vals.push(Value::List {
vals: proc
.environ()
.iter()
.map(|x| Value::string(x.to_string(), span))
.collect(),
span,
});
record.push("cwd", Value::string(proc.cwd(), span));
record.push(
"environment",
Value::list(
proc.environ()
.iter()
.map(|x| Value::string(x.to_string(), span))
.collect(),
span,
),
);
}
}
output.push(Value::Record { cols, vals, span });
output.push(Value::record(record, span));
}
Ok(output

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Spanned, SyntaxShape, Type, Value,
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
use winreg::{enums::*, RegKey};
@ -120,15 +120,14 @@ fn registry_query(
let mut reg_values = vec![];
for (name, val) in reg_key.enum_values().flatten() {
let (nu_value, reg_type) = reg_value_to_nu_value(val, call_span);
reg_values.push(Value::Record {
cols: vec!["name".to_string(), "value".to_string(), "type".to_string()],
vals: vec![
Value::string(name, call_span),
nu_value,
Value::string(format!("{:?}", reg_type), call_span),
],
span: *registry_key_span,
})
reg_values.push(Value::record(
record! {
"name" => Value::string(name, call_span),
"value" => nu_value,
"type" => Value::string(format!("{:?}", reg_type), call_span),
},
*registry_key_span,
))
}
Ok(reg_values.into_pipeline_data(engine_state.ctrlc.clone()))
} else {
@ -138,15 +137,14 @@ fn registry_query(
match reg_value {
Ok(val) => {
let (nu_value, reg_type) = reg_value_to_nu_value(val, call_span);
Ok(Value::Record {
cols: vec!["name".to_string(), "value".to_string(), "type".to_string()],
vals: vec![
Value::string(value.item, call_span),
nu_value,
Value::string(format!("{:?}", reg_type), call_span),
],
span: value.span,
}
Ok(Value::record(
record! {
"name" => Value::string(value.item, call_span),
"value" => nu_value,
"type" => Value::string(format!("{:?}", reg_type), call_span),
},
value.span,
)
.into_pipeline_data())
}
Err(_) => Err(ShellError::GenericError(

View File

@ -3,8 +3,8 @@ use chrono::Local;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, LazyRecord, PipelineData, ShellError, Signature, Span,
Type, Value,
record, Category, Example, IntoPipelineData, LazyRecord, PipelineData, Record, ShellError,
Signature, Span, Type, Value,
};
use std::time::{Duration, UNIX_EPOCH};
use sysinfo::{
@ -118,52 +118,20 @@ pub fn disks(span: Span) -> Value {
let mut output = vec![];
for disk in sys.disks() {
let mut cols = vec![];
let mut vals = vec![];
let device = trim_cstyle_null(disk.name().to_string_lossy().to_string());
let typ = trim_cstyle_null(String::from_utf8_lossy(disk.file_system()).to_string());
cols.push("device".into());
vals.push(Value::String {
val: trim_cstyle_null(disk.name().to_string_lossy().to_string()),
span,
});
let record = record! {
"device" => Value::string(device, span),
"type" => Value::string(typ, span),
"mount" => Value::string(disk.mount_point().to_string_lossy(), span),
"total" => Value::filesize(disk.total_space() as i64, span),
"free" => Value::filesize(disk.available_space() as i64, span),
"removable" => Value::bool(disk.is_removable(), span),
"kind" => Value::string(format!("{:?}", disk.kind()), span),
};
cols.push("type".into());
vals.push(Value::String {
val: trim_cstyle_null(String::from_utf8_lossy(disk.file_system()).to_string()),
span,
});
cols.push("mount".into());
vals.push(Value::String {
val: disk.mount_point().to_string_lossy().to_string(),
span,
});
cols.push("total".into());
vals.push(Value::Filesize {
val: disk.total_space() as i64,
span,
});
cols.push("free".into());
vals.push(Value::Filesize {
val: disk.available_space() as i64,
span,
});
cols.push("removable".into());
vals.push(Value::Bool {
val: disk.is_removable(),
span,
});
cols.push("kind".into());
vals.push(Value::String {
val: format!("{:?}", disk.kind()),
span,
});
output.push(Value::Record { cols, vals, span });
output.push(Value::record(record, span));
}
Value::List { vals: output, span }
}
@ -175,28 +143,13 @@ pub fn net(span: Span) -> Value {
let mut output = vec![];
for (iface, data) in sys.networks() {
let mut cols = vec![];
let mut vals = vec![];
let record = record! {
"name" => Value::string(trim_cstyle_null(iface.to_string()), span),
"sent" => Value::filesize(data.total_transmitted() as i64, span),
"recv" => Value::filesize(data.total_received() as i64, span),
};
cols.push("name".into());
vals.push(Value::String {
val: trim_cstyle_null(iface.to_string()),
span,
});
cols.push("sent".into());
vals.push(Value::Filesize {
val: data.total_transmitted() as i64,
span,
});
cols.push("recv".into());
vals.push(Value::Filesize {
val: data.total_received() as i64,
span,
});
output.push(Value::Record { cols, vals, span });
output.push(Value::record(record, span));
}
Value::List { vals: output, span }
}
@ -212,54 +165,26 @@ pub fn cpu(span: Span) -> Value {
let mut output = vec![];
for cpu in sys.cpus() {
let mut cols = vec![];
let mut vals = vec![];
cols.push("name".into());
vals.push(Value::String {
val: trim_cstyle_null(cpu.name().to_string()),
span,
});
cols.push("brand".into());
vals.push(Value::String {
val: trim_cstyle_null(cpu.brand().to_string()),
span,
});
cols.push("freq".into());
vals.push(Value::Int {
val: cpu.frequency() as i64,
span,
});
cols.push("cpu_usage".into());
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
vals.push(Value::Float {
val: rounded_usage as f64,
span,
});
let load_avg = sys.load_average();
cols.push("load_average".into());
vals.push(Value::String {
val: trim_cstyle_null(format!(
"{:.2}, {:.2}, {:.2}",
load_avg.one, load_avg.five, load_avg.fifteen
)),
span,
});
let load_avg = trim_cstyle_null(format!(
"{:.2}, {:.2}, {:.2}",
load_avg.one, load_avg.five, load_avg.fifteen
));
cols.push("vendor_id".into());
vals.push(Value::String {
val: trim_cstyle_null(cpu.vendor_id().to_string()),
span,
});
let record = record! {
"name" => Value::string(trim_cstyle_null(cpu.name().to_string()), span),
"brand" => Value::string(trim_cstyle_null(cpu.brand().to_string()), span),
"freq" => Value::int(cpu.frequency() as i64, span),
"cpu_usage" => Value::float(rounded_usage as f64, span),
"load_average" => Value::string(load_avg, span),
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id().to_string()), span),
};
output.push(Value::Record { cols, vals, span });
output.push(Value::record(record, span));
}
Value::List { vals: output, span }
@ -269,9 +194,6 @@ pub fn mem(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_memory();
let mut cols = vec![];
let mut vals = vec![];
let total_mem = sys.total_memory();
let free_mem = sys.free_memory();
let used_mem = sys.used_memory();
@ -281,101 +203,53 @@ pub fn mem(span: Span) -> Value {
let free_swap = sys.free_swap();
let used_swap = sys.used_swap();
cols.push("total".into());
vals.push(Value::Filesize {
val: total_mem as i64,
span,
});
let record = record! {
"total" => Value::filesize(total_mem as i64, span),
"free" => Value::filesize(free_mem as i64, span),
"used" => Value::filesize(used_mem as i64, span),
"available" => Value::filesize(avail_mem as i64, span),
"swap total" => Value::filesize(total_swap as i64, span),
"swap free" => Value::filesize(free_swap as i64, span),
"swap used" => Value::filesize(used_swap as i64, span),
};
cols.push("free".into());
vals.push(Value::Filesize {
val: free_mem as i64,
span,
});
cols.push("used".into());
vals.push(Value::Filesize {
val: used_mem as i64,
span,
});
cols.push("available".into());
vals.push(Value::Filesize {
val: avail_mem as i64,
span,
});
cols.push("swap total".into());
vals.push(Value::Filesize {
val: total_swap as i64,
span,
});
cols.push("swap free".into());
vals.push(Value::Filesize {
val: free_swap as i64,
span,
});
cols.push("swap used".into());
vals.push(Value::Filesize {
val: used_swap as i64,
span,
});
Value::Record { cols, vals, span }
Value::record(record, span)
}
pub fn host(span: Span) -> Value {
let mut sys = System::new();
sys.refresh_users_list();
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
if let Some(name) = sys.name() {
cols.push("name".into());
vals.push(Value::String {
val: trim_cstyle_null(name),
span,
});
record.push("name", Value::string(trim_cstyle_null(name), span));
}
if let Some(version) = sys.os_version() {
cols.push("os_version".into());
vals.push(Value::String {
val: trim_cstyle_null(version),
span,
});
record.push("os_version", Value::string(trim_cstyle_null(version), span));
}
if let Some(long_version) = sys.long_os_version() {
cols.push("long_os_version".into());
vals.push(Value::String {
val: trim_cstyle_null(long_version),
span,
});
record.push(
"long_os_version",
Value::string(trim_cstyle_null(long_version), span),
);
}
if let Some(version) = sys.kernel_version() {
cols.push("kernel_version".into());
vals.push(Value::String {
val: trim_cstyle_null(version),
span,
});
record.push(
"kernel_version",
Value::string(trim_cstyle_null(version), span),
);
}
if let Some(hostname) = sys.host_name() {
cols.push("hostname".into());
vals.push(Value::String {
val: trim_cstyle_null(hostname),
span,
});
record.push("hostname", Value::string(trim_cstyle_null(hostname), span));
}
cols.push("uptime".into());
vals.push(Value::Duration {
val: 1000000000 * sys.uptime() as i64,
span,
});
record.push(
"uptime",
Value::duration(1000000000 * sys.uptime() as i64, span),
);
// Creates a new SystemTime from the specified number of whole seconds
let d = UNIX_EPOCH + Duration::from_secs(sys.boot_time());
@ -384,23 +258,10 @@ pub fn host(span: Span) -> Value {
// Convert to local time and then rfc3339
let timestamp_str = datetime.with_timezone(datetime.offset()).to_rfc3339();
cols.push("boot_time".into());
vals.push(Value::String {
val: timestamp_str,
span,
});
record.push("boot_time", Value::string(timestamp_str, span));
let mut users = vec![];
for user in sys.users() {
let mut cols = vec![];
let mut vals = vec![];
cols.push("name".into());
vals.push(Value::String {
val: trim_cstyle_null(user.name().to_string()),
span,
});
let mut groups = vec![];
for group in user.groups() {
groups.push(Value::String {
@ -409,18 +270,19 @@ pub fn host(span: Span) -> Value {
});
}
cols.push("groups".into());
vals.push(Value::List { vals: groups, span });
let record = record! {
"name" => Value::string(trim_cstyle_null(user.name().to_string()), span),
"groups" => Value::list(groups, span),
};
users.push(Value::Record { cols, vals, span });
users.push(Value::record(record, span));
}
if !users.is_empty() {
cols.push("sessions".into());
vals.push(Value::List { vals: users, span });
record.push("sessions", Value::list(users, span));
}
Value::Record { cols, vals, span }
Value::record(record, span)
}
pub fn temp(span: Span) -> Value {
@ -431,35 +293,16 @@ pub fn temp(span: Span) -> Value {
let mut output = vec![];
for component in sys.components() {
let mut cols = vec![];
let mut vals = vec![];
cols.push("unit".into());
vals.push(Value::String {
val: component.label().to_string(),
span,
});
cols.push("temp".into());
vals.push(Value::Float {
val: component.temperature() as f64,
span,
});
cols.push("high".into());
vals.push(Value::Float {
val: component.max() as f64,
span,
});
let mut record = record! {
"unit" => Value::string(component.label(), span),
"temp" => Value::float(component.temperature() as f64, span),
"high" => Value::float(component.max() as f64, span),
};
if let Some(critical) = component.critical() {
cols.push("critical".into());
vals.push(Value::Float {
val: critical as f64,
span,
});
record.push("critical", Value::float(critical as f64, span));
}
output.push(Value::Record { cols, vals, span });
output.push(Value::record(record, span));
}
Value::List { vals: output, span }

View File

@ -1,6 +1,7 @@
use log::trace;
use nu_engine::env;
use nu_engine::CallExt;
use nu_protocol::record;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
@ -63,19 +64,14 @@ fn entry(
cmd_type: impl Into<String>,
span: Span,
) -> Value {
let mut cols = vec![];
let mut vals = vec![];
cols.push("command".to_string());
vals.push(Value::string(arg.into(), span));
cols.push("path".to_string());
vals.push(Value::string(path.into(), span));
cols.push("type".to_string());
vals.push(Value::string(cmd_type.into(), span));
Value::Record { cols, vals, span }
Value::record(
record! {
"command" => Value::string(arg.into(), span),
"path" => Value::string(path.into(), span),
"type" => Value::string(cmd_type.into(), span),
},
span,
)
}
fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {