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,8 @@ use nu_engine::{eval_block, eval_expression, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Block, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
record, Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type,
Value,
};
#[derive(Clone)]
@ -91,11 +92,13 @@ impl Command for For {
stack.add_var(
var_id,
if numbered {
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![Value::int(idx as i64, head), x],
span: head,
}
Value::record(
record! {
"index" => Value::int(idx as i64, head),
"item" => x,
},
head,
)
} else {
x
},
@ -135,11 +138,13 @@ impl Command for For {
stack.add_var(
var_id,
if numbered {
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![Value::int(idx as i64, head), x],
span: head,
}
Value::record(
record! {
"index" => Value::int(idx as i64, head),
"item" => x,
},
head,
)
} else {
x
},

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Block, Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -137,15 +137,14 @@ fn intercept_block_control(error: ShellError) -> Result<ShellError, ShellError>
/// Convert from `error` to [`Value::Record`] so the error information can be easily accessed in catch.
fn err_to_record(error: ShellError, head: Span) -> Value {
let cols = vec!["msg".to_string(), "debug".to_string(), "raw".to_string()];
let vals = vec![
Value::string(error.to_string(), head),
Value::string(format!("{error:?}"), head),
Value::Error {
error: Box::new(error),
Value::record(
record! {
"msg" => Value::string(error.to_string(), head),
"debug" => Value::string(format!("{error:?}"), head),
"raw" => Value::error(error),
},
];
Value::record(cols, vals, head)
head,
)
}
#[cfg(test)]

View File

@ -1,7 +1,7 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Type, Value,
};
use shadow_rs::shadow;
@ -63,74 +63,64 @@ pub fn version(
// - build_rust_channel
// - features
// - installed_plugins
let mut cols = Vec::with_capacity(12);
let mut vals = Vec::with_capacity(12);
let mut record = Record::with_capacity(12);
cols.push("version".to_string());
vals.push(Value::string(env!("CARGO_PKG_VERSION"), call.head));
record.push(
"version",
Value::string(env!("CARGO_PKG_VERSION"), call.head),
);
cols.push("branch".to_string());
vals.push(Value::string(build::BRANCH, call.head));
record.push("branch", Value::string(build::BRANCH, call.head));
let commit_hash = option_env!("NU_COMMIT_HASH");
if let Some(commit_hash) = commit_hash {
cols.push("commit_hash".to_string());
vals.push(Value::string(commit_hash, call.head));
record.push("commit_hash", Value::string(commit_hash, call.head));
}
let build_os = Some(build::BUILD_OS).filter(|x| !x.is_empty());
if let Some(build_os) = build_os {
cols.push("build_os".to_string());
vals.push(Value::string(build_os, call.head));
record.push("build_os", Value::string(build_os, call.head));
}
let build_target = Some(build::BUILD_TARGET).filter(|x| !x.is_empty());
if let Some(build_target) = build_target {
cols.push("build_target".to_string());
vals.push(Value::string(build_target, call.head));
record.push("build_target", Value::string(build_target, call.head));
}
let rust_version = Some(build::RUST_VERSION).filter(|x| !x.is_empty());
if let Some(rust_version) = rust_version {
cols.push("rust_version".to_string());
vals.push(Value::string(rust_version, call.head));
record.push("rust_version", Value::string(rust_version, call.head));
}
let rust_channel = Some(build::RUST_CHANNEL).filter(|x| !x.is_empty());
if let Some(rust_channel) = rust_channel {
cols.push("rust_channel".to_string());
vals.push(Value::string(rust_channel, call.head));
record.push("rust_channel", Value::string(rust_channel, call.head));
}
let cargo_version = Some(build::CARGO_VERSION).filter(|x| !x.is_empty());
if let Some(cargo_version) = cargo_version {
cols.push("cargo_version".to_string());
vals.push(Value::string(cargo_version, call.head));
record.push("cargo_version", Value::string(cargo_version, call.head));
}
let build_time = Some(build::BUILD_TIME).filter(|x| !x.is_empty());
if let Some(build_time) = build_time {
cols.push("build_time".to_string());
vals.push(Value::string(build_time, call.head));
record.push("build_time", Value::string(build_time, call.head));
}
let build_rust_channel = Some(build::BUILD_RUST_CHANNEL).filter(|x| !x.is_empty());
if let Some(build_rust_channel) = build_rust_channel {
cols.push("build_rust_channel".to_string());
vals.push(Value::string(build_rust_channel, call.head));
record.push(
"build_rust_channel",
Value::string(build_rust_channel, call.head),
);
}
cols.push("allocator".to_string());
vals.push(Value::String {
val: global_allocator().to_string(),
span: call.head,
});
record.push("allocator", Value::string(global_allocator(), call.head));
cols.push("features".to_string());
vals.push(Value::String {
val: features_enabled().join(", "),
span: call.head,
});
record.push(
"features",
Value::string(features_enabled().join(", "), call.head),
);
// Get a list of command names and check for plugins
let installed_plugins = engine_state
@ -139,18 +129,12 @@ pub fn version(
.map(|x| x.name())
.collect::<Vec<_>>();
cols.push("installed_plugins".to_string());
vals.push(Value::String {
val: installed_plugins.join(", "),
span: call.head,
});
record.push(
"installed_plugins",
Value::string(installed_plugins.join(", "), call.head),
);
Ok(Value::Record {
cols,
vals,
span: call.head,
}
.into_pipeline_data())
Ok(Value::record(record, call.head).into_pipeline_data())
}
fn global_allocator() -> &'static str {