Use Record::get instead of Value functions (#10925)

# Description
Where appropriate, this PR replaces instances of
`Value::get_data_by_key` and `Value::follow_cell_path` with
`Record::get`. This avoids some unnecessary clones and simplifies the
code in some places.
This commit is contained in:
Ian Manske 2023-11-08 20:47:37 +00:00 committed by GitHub
parent 435abadd8a
commit 59ea28cf06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 230 additions and 289 deletions

View File

@ -79,21 +79,20 @@ impl Completer for CustomCompletion {
.map(|pd| {
let value = pd.into_value(span);
match &value {
Value::Record { .. } => {
let completions = value
.get_data_by_key("completions")
Value::Record { val, .. } => {
let completions = val
.get("completions")
.and_then(|val| {
val.as_list()
.ok()
.map(|it| map_value_completions(it.iter(), span, offset))
})
.unwrap_or_default();
let options = value.get_data_by_key("options");
let options = val.get("options");
if let Some(Value::Record { .. }) = &options {
let options = options.unwrap_or_default();
if let Some(Value::Record { val: options, .. }) = &options {
let should_sort = options
.get_data_by_key("sort")
.get("sort")
.and_then(|val| val.as_bool().ok())
.unwrap_or(false);
@ -103,11 +102,11 @@ impl Completer for CustomCompletion {
custom_completion_options = Some(CompletionOptions {
case_sensitive: options
.get_data_by_key("case_sensitive")
.get("case_sensitive")
.and_then(|val| val.as_bool().ok())
.unwrap_or(true),
positional: options
.get_data_by_key("positional")
.get("positional")
.and_then(|val| val.as_bool().ok())
.unwrap_or(true),
sort_by: if should_sort {
@ -115,9 +114,7 @@ impl Completer for CustomCompletion {
} else {
SortBy::None
},
match_algorithm: match options
.get_data_by_key("completion_algorithm")
{
match_algorithm: match options.get("completion_algorithm") {
Some(option) => option
.as_string()
.ok()

View File

@ -80,24 +80,18 @@ fn convert_to_suggestions(
only_buffer_difference: bool,
) -> Vec<Suggestion> {
match value {
Value::Record { .. } => {
let text = value
.get_data_by_key("value")
Value::Record { val, .. } => {
let text = val
.get("value")
.and_then(|val| val.as_string().ok())
.unwrap_or_else(|| "No value key".to_string());
let description = value
.get_data_by_key("description")
.and_then(|val| val.as_string().ok());
let description = val.get("description").and_then(|val| val.as_string().ok());
let span = match value.get_data_by_key("span") {
Some(span @ Value::Record { .. }) => {
let start = span
.get_data_by_key("start")
.and_then(|val| val.as_int().ok());
let end = span
.get_data_by_key("end")
.and_then(|val| val.as_int().ok());
let span = match val.get("span") {
Some(Value::Record { val: span, .. }) => {
let start = span.get("start").and_then(|val| val.as_int().ok());
let end = span.get("end").and_then(|val| val.as_int().ok());
match (start, end) {
(Some(start), Some(end)) => {
let start = start.min(end);
@ -126,12 +120,12 @@ fn convert_to_suggestions(
},
};
let extra = match value.get_data_by_key("extra") {
let extra = match val.get("extra") {
Some(Value::List { vals, .. }) => {
let extra: Vec<String> = vals
.into_iter()
.iter()
.filter_map(|extra| match extra {
Value::String { val, .. } => Some(val),
Value::String { val, .. } => Some(val.clone()),
_ => None,
})
.collect();

View File

@ -2,7 +2,6 @@ use crate::util::get_guaranteed_cwd;
use miette::Result;
use nu_engine::{eval_block, eval_block_with_early_return};
use nu_parser::parse;
use nu_protocol::ast::PathMember;
use nu_protocol::cli_error::{report_error, report_error_new};
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
use nu_protocol::{BlockId, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId};
@ -62,28 +61,8 @@ pub fn eval_hook(
value: &Value,
hook_name: &str,
) -> Result<PipelineData, ShellError> {
let value_span = value.span();
// Hooks can optionally be a record in this form:
// {
// condition: {|before, after| ... } # block that evaluates to true/false
// code: # block or a string
// }
// The condition block will be run to check whether the main hook (in `code`) should be run.
// If it returns true (the default if a condition block is not specified), the hook should be run.
let condition_path = PathMember::String {
val: "condition".to_string(),
span: value_span,
optional: false,
};
let mut output = PipelineData::empty();
let code_path = PathMember::String {
val: "code".to_string(),
span: value_span,
optional: false,
};
let span = value.span();
match value {
Value::String { val, .. } => {
@ -161,10 +140,15 @@ pub fn eval_hook(
)?;
}
}
Value::Record { .. } => {
let do_run_hook = if let Ok(condition) =
value.clone().follow_cell_path(&[condition_path], false)
{
Value::Record { val, .. } => {
// Hooks can optionally be a record in this form:
// {
// condition: {|before, after| ... } # block that evaluates to true/false
// code: # block or a string
// }
// The condition block will be run to check whether the main hook (in `code`) should be run.
// If it returns true (the default if a condition block is not specified), the hook should be run.
let do_run_hook = if let Some(condition) = val.get("condition") {
let other_span = condition.span();
if let Ok(block_id) = condition.as_block() {
match run_hook_block(
@ -204,7 +188,13 @@ pub fn eval_hook(
};
if do_run_hook {
let follow = value.clone().follow_cell_path(&[code_path], false)?;
let Some(follow) = val.get("code") else {
return Err(ShellError::CantFindColumn {
col_name: "code".into(),
span,
src_span: span,
});
};
let source_span = follow.span();
match follow {
Value::String { val, .. } => {
@ -270,7 +260,7 @@ pub fn eval_hook(
run_hook_block(
engine_state,
stack,
block_id,
*block_id,
input,
arguments,
source_span,

View File

@ -402,15 +402,15 @@ fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> Strin
for row in table {
let span = row.span();
if let Value::Record { .. } = row {
if let Value::Record { val: row, .. } = row {
output_string.push_str("<tr>");
for header in &headers {
let data = row.get_data_by_key(header);
let data = row
.get(header)
.cloned()
.unwrap_or_else(|| Value::nothing(span));
output_string.push_str("<td>");
output_string.push_str(&html_value(
data.unwrap_or_else(|| Value::nothing(span)),
config,
));
output_string.push_str(&html_value(data, config));
output_string.push_str("</td>");
}
output_string.push_str("</tr>");

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -118,8 +118,9 @@ impl Command for ErrorMake {
const UNABLE_TO_PARSE: &str = "Unable to parse error format.";
fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
let span = value.span();
let value = match value {
Value::Record { .. } => value,
Value::Record { val, .. } => val,
_ => {
return ShellError::GenericError(
"Creating error value not supported.".into(),
@ -131,13 +132,13 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
}
};
let msg = match value.get_data_by_key("msg") {
Some(Value::String { val, .. }) => val,
let msg = match value.get("msg") {
Some(Value::String { val, .. }) => val.clone(),
Some(_) => {
return ShellError::GenericError(
UNABLE_TO_PARSE.into(),
"`$.msg` has wrong type, must be string".into(),
Some(value.span()),
Some(span),
None,
Vec::new(),
)
@ -146,20 +147,29 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
return ShellError::GenericError(
UNABLE_TO_PARSE.into(),
"missing required member `$.msg`".into(),
Some(value.span()),
Some(span),
None,
Vec::new(),
)
}
};
let help = match value.get_data_by_key("help") {
Some(Value::String { val, .. }) => Some(val),
let help = match value.get("help") {
Some(Value::String { val, .. }) => Some(val.clone()),
_ => None,
};
let label = match value.get_data_by_key("label") {
Some(value) => value,
let (label, label_span) = match value.get("label") {
Some(value @ Value::Record { val, .. }) => (val, value.span()),
Some(_) => {
return ShellError::GenericError(
UNABLE_TO_PARSE.into(),
"`$.label` has wrong type, must be a record".into(),
Some(span),
None,
Vec::new(),
)
}
// correct return: no label
None => {
return ShellError::GenericError(
@ -173,23 +183,23 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
};
// remove after a few versions
if label.get_data_by_key("start").is_some() || label.get_data_by_key("end").is_some() {
if label.get("start").is_some() || label.get("end").is_some() {
return ShellError::GenericError(
UNABLE_TO_PARSE.into(),
"`start` and `end` are deprecated".into(),
Some(value.span()),
Some(span),
Some("Use `$.label.span` instead".into()),
Vec::new(),
);
}
let text = match label.get_data_by_key("text") {
Some(Value::String { val, .. }) => val,
let text = match label.get("text") {
Some(Value::String { val, .. }) => val.clone(),
Some(_) => {
return ShellError::GenericError(
UNABLE_TO_PARSE.into(),
"`$.label.text` has wrong type, must be string".into(),
Some(label.span()),
Some(label_span),
None,
Vec::new(),
)
@ -198,15 +208,15 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
return ShellError::GenericError(
UNABLE_TO_PARSE.into(),
"missing required member `$.label.text`".into(),
Some(label.span()),
Some(label_span),
None,
Vec::new(),
)
}
};
let span = match label.get_data_by_key("span") {
Some(val @ Value::Record { .. }) => val,
let (span, span_span) = match label.get("span") {
Some(value @ Value::Record { val, .. }) => (val, value.span()),
Some(value) => {
return ShellError::GenericError(
UNABLE_TO_PARSE.into(),
@ -220,11 +230,11 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
None => return ShellError::GenericError(msg, text, throw_span, help, Vec::new()),
};
let span_start = match get_span_sides(&span, "start") {
let span_start = match get_span_sides(span, span_span, "start") {
Ok(val) => val,
Err(err) => return err,
};
let span_end = match get_span_sides(&span, "end") {
let span_end = match get_span_sides(span, span_span, "end") {
Ok(val) => val,
Err(err) => return err,
};
@ -233,7 +243,7 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
return ShellError::GenericError(
"invalid error format.".into(),
"`$.label.start` should be smaller than `$.label.end`".into(),
Some(label.span()),
Some(label_span),
Some(format!("{} > {}", span_start, span_end)),
Vec::new(),
);
@ -249,20 +259,20 @@ fn make_other_error(value: &Value, throw_span: Option<Span>) -> ShellError {
)
}
fn get_span_sides(span: &Value, side: &str) -> Result<i64, ShellError> {
match span.get_data_by_key(side) {
Some(Value::Int { val, .. }) => Ok(val),
fn get_span_sides(span: &Record, span_span: Span, side: &str) -> Result<i64, ShellError> {
match span.get(side) {
Some(Value::Int { val, .. }) => Ok(*val),
Some(_) => Err(ShellError::GenericError(
UNABLE_TO_PARSE.into(),
format!("`$.span.{side}` must be int"),
Some(span.span()),
Some(span_span),
None,
Vec::new(),
)),
None => Err(ShellError::GenericError(
UNABLE_TO_PARSE.into(),
format!("`$.span.{side}` must be present, if span is specified."),
Some(span.span()),
Some(span_span),
None,
Vec::new(),
)),

View File

@ -194,7 +194,7 @@ fn push_empty_column(data: &mut Vec<Vec<String>>) {
mod util {
use crate::debug::explain::debug_string_without_formatting;
use nu_engine::get_columns;
use nu_protocol::{ast::PathMember, Span, Value};
use nu_protocol::Value;
/// Try to build column names and a table grid.
pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<String>>) {
@ -265,30 +265,19 @@ mod util {
}
fn record_create_row(headers: &[String], item: &Value) -> Vec<String> {
let mut rows = vec![String::default(); headers.len()];
for (i, header) in headers.iter().enumerate() {
let value = record_lookup_value(item, header);
rows[i] = debug_string_without_formatting(&value);
}
rows
}
fn record_lookup_value(item: &Value, header: &str) -> Value {
match item {
Value::Record { .. } => {
let path = PathMember::String {
val: header.to_owned(),
span: Span::unknown(),
optional: false,
};
item.clone()
.follow_cell_path(&[path], false)
.unwrap_or_else(|_| item.clone())
}
item => item.clone(),
if let Value::Record { val, .. } = item {
headers
.iter()
.map(|col| {
val.get(col)
.map(debug_string_without_formatting)
.unwrap_or_else(String::new)
})
.collect()
} else {
// should never reach here due to `get_columns` above which will return
// empty columns if any value in the list is not a record
vec![String::new(); headers.len()]
}
}
}

View File

@ -110,7 +110,7 @@ pub fn compact(
Value::Nothing { .. } => false,
Value::Record { val, .. } => {
for column in columns.iter() {
match item.get_data_by_key(column) {
match val.get(column) {
None => return false,
Some(x) => {
if let Value::Nothing { .. } = x {

View File

@ -258,7 +258,7 @@ fn join_rows(
val: this_record, ..
} = this_row
{
if let Some(this_valkey) = this_row.get_data_by_key(this_join_key) {
if let Some(this_valkey) = this_record.get(this_join_key) {
if let Some(other_rows) = other.get(&this_valkey.into_string(sep, config)) {
if matches!(include_inner, IncludeInner::Yes) {
for other_record in other_rows {
@ -287,8 +287,9 @@ fn join_rows(
.iter()
.map(|key| {
let val = if Some(key.as_ref()) == shared_join_key {
this_row
.get_data_by_key(key)
this_record
.get(key)
.cloned()
.unwrap_or_else(|| Value::nothing(span))
} else {
Value::nothing(span)
@ -339,7 +340,7 @@ fn lookup_table<'a>(
let mut map = HashMap::<String, Vec<&'a Record>>::with_capacity(cap);
for row in rows {
if let Value::Record { val: record, .. } = row {
if let Some(val) = &row.get_data_by_key(on) {
if let Some(val) = record.get(on) {
let valkey = val.into_string(sep, config);
map.entry(valkey).or_default().push(record);
}

View File

@ -125,13 +125,21 @@ pub fn split(
match grouper {
Grouper::ByColumn(Some(column_name)) => {
let block = move |_, row: &Value| match row.get_data_by_key(&column_name.item) {
Some(group_key) => Ok(group_key.as_string()?),
None => Err(ShellError::CantFindColumn {
col_name: column_name.item.to_string(),
span: column_name.span,
src_span: row.span(),
}),
let block = move |_, row: &Value| {
let group_key = if let Value::Record { val: row, .. } = row {
row.get(&column_name.item)
} else {
None
};
match group_key {
Some(group_key) => Ok(group_key.as_string()?),
None => Err(ShellError::CantFindColumn {
col_name: column_name.item.to_string(),
span: column_name.span,
src_span: row.span(),
}),
}
};
data_split(values, Some(&block), span)

View File

@ -75,14 +75,17 @@ fn table_to_delimited(
.expect("can not write.");
for l in vals {
let mut row = vec![];
for desc in &merged_descriptors {
row.push(match l.to_owned().get_data_by_key(desc) {
Some(s) => to_string_tagged_value(&s, config, head, span)?,
None => String::new(),
});
// should always be true because of `find_non_record` above
if let Value::Record { val: l, .. } = l {
let mut row = vec![];
for desc in &merged_descriptors {
row.push(match l.get(desc) {
Some(s) => to_string_tagged_value(s, config, head, span)?,
None => String::new(),
});
}
wtr.write_record(&row).expect("can not write");
}
wtr.write_record(&row).expect("can not write");
}
}
writer_to_string(wtr).map_err(|_| make_conversion_error("table", span))

View File

@ -105,27 +105,24 @@ fn to_md(
}
fn fragment(input: Value, pretty: bool, config: &Config) -> String {
let headers = input.columns();
let mut out = String::new();
if headers.len() == 1 {
let markup = match headers[0].to_ascii_lowercase().as_ref() {
"h1" => "# ".to_string(),
"h2" => "## ".to_string(),
"h3" => "### ".to_string(),
"blockquote" => "> ".to_string(),
if let Value::Record { val, .. } = &input {
match val.get_index(0) {
Some((header, data)) if val.len() == 1 => {
let markup = match header.to_ascii_lowercase().as_ref() {
"h1" => "# ".to_string(),
"h2" => "## ".to_string(),
"h3" => "### ".to_string(),
"blockquote" => "> ".to_string(),
_ => return table(input.into_pipeline_data(), pretty, config),
};
_ => return table(input.into_pipeline_data(), pretty, config),
};
out.push_str(&markup);
let data = match input.get_data_by_key(&headers[0]) {
Some(v) => v,
None => input,
};
out.push_str(&data.into_string("|", config));
} else if let Value::Record { .. } = input {
out = table(input.into_pipeline_data(), pretty, config)
out.push_str(&markup);
out.push_str(&data.into_string("|", config));
}
_ => out = table(input.into_pipeline_data(), pretty, config),
}
} else {
out = input.into_string("|", config)
}
@ -164,10 +161,11 @@ fn table(input: PipelineData, pretty: bool, config: &Config) -> String {
let span = row.span();
match row.to_owned() {
Value::Record { .. } => {
Value::Record { val: row, .. } => {
for i in 0..headers.len() {
let data = row.get_data_by_key(&headers[i]);
let value_string = data
let value_string = row
.get(&headers[i])
.cloned()
.unwrap_or_else(|| Value::nothing(span))
.into_string(", ", config);
let new_column_width = value_string.len();

View File

@ -109,47 +109,50 @@ fn to_xml_entry<W: Write>(
return to_xml_text(val.as_str(), span, writer);
}
if !matches!(entry, Value::Record { .. }) {
return Err(ShellError::CantConvert {
if let Value::Record { val: record, .. } = &entry {
// If key is not found it is assumed to be nothing. This way
// user can write a tag like {tag: a content: [...]} instead
// of longer {tag: a attributes: {} content: [...]}
let tag = record
.get(COLUMN_TAG_NAME)
.cloned()
.unwrap_or_else(|| Value::nothing(Span::unknown()));
let attrs = record
.get(COLUMN_ATTRS_NAME)
.cloned()
.unwrap_or_else(|| Value::nothing(Span::unknown()));
let content = record
.get(COLUMN_CONTENT_NAME)
.cloned()
.unwrap_or_else(|| Value::nothing(Span::unknown()));
let content_span = content.span();
let tag_span = tag.span();
match (tag, attrs, content) {
(Value::Nothing { .. }, Value::Nothing { .. }, Value::String { val, .. }) => {
// Strings can not appear on top level of document
if top_level {
return Err(ShellError::CantConvert {
to_type: "XML".into(),
from_type: entry.get_type().to_string(),
span: entry_span,
help: Some("Strings can not be a root element of document".into()),
});
}
to_xml_text(val.as_str(), content_span, writer)
}
(Value::String { val: tag_name, .. }, attrs, children) => to_tag_like(
entry_span, tag_name, tag_span, attrs, children, top_level, writer,
),
_ => Ok(()),
}
} else {
Err(ShellError::CantConvert {
to_type: "XML".into(),
from_type: entry.get_type().to_string(),
span: entry_span,
help: Some("Xml entry expected to be a record".into()),
});
};
// If key is not found it is assumed to be nothing. This way
// user can write a tag like {tag: a content: [...]} instead
// of longer {tag: a attributes: {} content: [...]}
let tag = entry
.get_data_by_key(COLUMN_TAG_NAME)
.unwrap_or_else(|| Value::nothing(Span::unknown()));
let attrs = entry
.get_data_by_key(COLUMN_ATTRS_NAME)
.unwrap_or_else(|| Value::nothing(Span::unknown()));
let content = entry
.get_data_by_key(COLUMN_CONTENT_NAME)
.unwrap_or_else(|| Value::nothing(Span::unknown()));
let content_span = content.span();
let tag_span = tag.span();
match (tag, attrs, content) {
(Value::Nothing { .. }, Value::Nothing { .. }, Value::String { val, .. }) => {
// Strings can not appear on top level of document
if top_level {
return Err(ShellError::CantConvert {
to_type: "XML".into(),
from_type: entry.get_type().to_string(),
span: entry_span,
help: Some("Strings can not be a root element of document".into()),
});
}
to_xml_text(val.as_str(), content_span, writer)
}
(Value::String { val: tag_name, .. }, attrs, children) => to_tag_like(
entry_span, tag_name, tag_span, attrs, children, top_level, writer,
),
_ => Ok(()),
})
}
}

View File

@ -4,10 +4,10 @@ use lscolors::Style;
use nu_engine::env_to_string;
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, PathMember},
ast::Call,
engine::{Command, EngineState, Stack},
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
Type, Value,
};
use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions};
use nu_utils::get_ls_colors;
@ -77,7 +77,7 @@ prints out the list properly."#
match input {
PipelineData::Value(Value::List { vals, .. }, ..) => {
// dbg!("value::list");
let data = convert_to_list(vals, config, call.head)?;
let data = convert_to_list(vals, config)?;
if let Some(items) = data {
Ok(create_grid_output(
items,
@ -94,7 +94,7 @@ prints out the list properly."#
}
PipelineData::ListStream(stream, ..) => {
// dbg!("value::stream");
let data = convert_to_list(stream, config, call.head)?;
let data = convert_to_list(stream, config)?;
if let Some(items) = data {
Ok(create_grid_output(
items,
@ -253,7 +253,6 @@ fn create_grid_output(
fn convert_to_list(
iter: impl IntoIterator<Item = Value>,
config: &Config,
head: Span,
) -> Result<Option<Vec<(usize, String, String)>>, ShellError> {
let mut iter = iter.into_iter().peekable();
@ -273,21 +272,14 @@ fn convert_to_list(
row.push(item.nonerror_into_string(", ", config)?)
} else {
for header in headers.iter().skip(1) {
let result = match item {
Value::Record { .. } => item.clone().follow_cell_path(
&[PathMember::String {
val: header.into(),
span: head,
optional: false,
}],
false,
),
_ => Ok(item.clone()),
let result = match &item {
Value::Record { val, .. } => val.get(header),
item => Some(item),
};
match result {
Ok(value) => row.push(value.nonerror_into_string(", ", config)?),
Err(_) => row.push(String::new()),
Some(value) => row.push(value.nonerror_into_string(", ", config)?),
None => row.push(String::new()),
}
}
}

View File

@ -1,9 +1,7 @@
use std::collections::HashMap;
use nu_engine::get_columns;
use nu_protocol::{
ast::PathMember, record, ListStream, PipelineData, PipelineMetadata, RawStream, Value,
};
use nu_protocol::{record, ListStream, PipelineData, PipelineMetadata, RawStream, Value};
use super::NuSpan;
@ -155,30 +153,15 @@ fn create_table_for_record(headers: &[String], items: &[Value]) -> Vec<Vec<Value
}
fn record_create_row(headers: &[String], item: &Value) -> Vec<Value> {
let mut rows = vec![Value::default(); headers.len()];
for (i, header) in headers.iter().enumerate() {
let value = record_lookup_value(item, header);
rows[i] = value;
}
rows
}
fn record_lookup_value(item: &Value, header: &str) -> Value {
match item {
Value::Record { .. } => {
let path = PathMember::String {
val: header.to_owned(),
span: NuSpan::unknown(),
optional: false,
};
item.clone()
.follow_cell_path(&[path], false)
.unwrap_or_else(|_| unknown_error_value())
}
item => item.clone(),
if let Value::Record { val, .. } = item {
headers
.iter()
.map(|col| val.get(col).cloned().unwrap_or_else(unknown_error_value))
.collect()
} else {
// should never reach here due to `get_columns` above which will return
// empty columns if any value in the list is not a record
vec![Value::default(); headers.len()]
}
}

View File

@ -628,21 +628,17 @@ impl Value {
pub fn get_data_by_key(&self, name: &str) -> Option<Value> {
let span = self.span();
match self {
Value::Record { val, .. } => val
.iter()
.find(|(col, _)| col == &name)
.map(|(_, val)| val.clone()),
Value::Record { val, .. } => val.get(name).cloned(),
Value::List { vals, .. } => {
let mut out = vec![];
for item in vals {
match item {
Value::Record { .. } => match item.get_data_by_key(name) {
Some(v) => out.push(v),
None => out.push(Value::nothing(span)),
},
_ => out.push(Value::nothing(span)),
}
}
let out = vals
.iter()
.map(|item| {
item.as_record()
.ok()
.and_then(|val| val.get(name).cloned())
.unwrap_or(Value::nothing(span))
})
.collect::<Vec<_>>();
if !out.is_empty() {
Some(Value::list(out, span))

View File

@ -3,7 +3,7 @@ use std::collections::HashMap;
use nu_color_config::{Alignment, StyleComputer, TextStyle};
use nu_engine::column::get_columns;
use nu_protocol::{ast::PathMember, Config, Record, ShellError, Span, TableIndexMode, Value};
use nu_protocol::{Config, Record, ShellError, Span, TableIndexMode, Value};
use tabled::grid::config::Position;
use crate::{
@ -116,10 +116,11 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult {
}
let index = row + cfg.opts.row_offset;
let text = matches!(item, Value::Record { .. })
.then(|| {
lookup_index_value(item, cfg.opts.config).unwrap_or_else(|| index.to_string())
})
let text = item
.as_record()
.ok()
.and_then(|val| val.get(INDEX_COLUMN_NAME))
.map(|value| value.into_string("", cfg.opts.config))
.unwrap_or_else(|| index.to_string());
let row = row + with_header as usize;
@ -461,20 +462,10 @@ fn get_key_style(cfg: &Cfg<'_>) -> TextStyle {
fn expanded_table_entry(item: &Value, header: &str, cfg: Cfg<'_>) -> NuText {
match item {
Value::Record { .. } => {
let val = header.to_owned();
let path = PathMember::String {
val,
span: cfg.opts.span,
optional: false,
};
let val = item.clone().follow_cell_path(&[path], false);
match val {
Ok(val) => expanded_table_entry2(&val, cfg),
Err(_) => error_sign(cfg.opts.style_computer),
}
}
Value::Record { val, .. } => match val.get(header) {
Some(val) => expanded_table_entry2(val, cfg),
None => error_sign(cfg.opts.style_computer),
},
_ => expanded_table_entry2(item, cfg),
}
}
@ -564,11 +555,6 @@ fn dive_options<'b>(cfg: &Cfg<'b>, span: Span) -> Cfg<'b> {
cfg
}
fn lookup_index_value(item: &Value, config: &Config) -> Option<String> {
item.get_data_by_key(INDEX_COLUMN_NAME)
.map(|value| value.into_string("", config))
}
fn maybe_expand_table(out: TableOutput, term_width: usize, opts: &TableOpts<'_>) -> StringResult {
let mut table_config = create_nu_table_config(opts.config, opts.style_computer, &out, false);
let total_width = out.table.total_width(&table_config);

View File

@ -1,6 +1,6 @@
use nu_color_config::TextStyle;
use nu_engine::column::get_columns;
use nu_protocol::{ast::PathMember, Config, Record, ShellError, Span, TableIndexMode, Value};
use nu_protocol::{Config, Record, ShellError, TableIndexMode, Value};
use crate::{
clean_charset, colorize_space,
@ -185,19 +185,10 @@ fn to_table_with_no_header(
fn get_string_value_with_header(item: &Value, header: &str, opts: &TableOpts) -> NuText {
match item {
Value::Record { .. } => {
let path = PathMember::String {
val: header.to_owned(),
span: Span::unknown(),
optional: false,
};
let value = item.clone().follow_cell_path(&[path], false);
match value {
Ok(value) => get_string_value(&value, opts),
Err(_) => get_empty_style(opts.style_computer),
}
}
Value::Record { val, .. } => match val.get(header) {
Some(value) => get_string_value(value, opts),
None => get_empty_style(opts.style_computer),
},
value => get_string_value(value, opts),
}
}
@ -214,8 +205,8 @@ fn get_string_value(item: &Value, opts: &TableOpts) -> NuText {
fn get_table_row_index(item: &Value, config: &Config, row: usize, offset: usize) -> String {
match item {
Value::Record { .. } => item
.get_data_by_key(INDEX_COLUMN_NAME)
Value::Record { val, .. } => val
.get(INDEX_COLUMN_NAME)
.map(|value| value.into_string("", config))
.unwrap_or_else(|| (row + offset).to_string()),
_ => (row + offset).to_string(),