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,6 +2,7 @@ use indexmap::indexmap;
use indexmap::map::IndexMap;
use nu_engine::CallExt;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::record;
use nu_protocol::{
ast::Call, engine::Command, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData,
PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
@ -232,9 +233,6 @@ impl Command for Char {
return Ok(CHAR_MAP
.iter()
.map(move |(name, s)| {
let cols = vec!["name".into(), "character".into(), "unicode".into()];
let name: Value = Value::string(String::from(*name), call_span);
let character = Value::string(s, call_span);
let unicode = Value::string(
s.chars()
.map(|c| format!("{:x}", c as u32))
@ -242,12 +240,13 @@ impl Command for Char {
.join(" "),
call_span,
);
let vals = vec![name, character, unicode];
Value::Record {
cols,
vals,
span: call_span,
}
let record = record! {
"name" => Value::string(*name, call_span),
"character" => Value::string(s, call_span),
"unicode" => unicode,
};
Value::record(record, call_span)
})
.into_pipeline_data(engine_state.ctrlc.clone()));
}

View File

@ -6,8 +6,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, Range, ShellError, Signature,
Span, Spanned, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Range, Record, ShellError,
Signature, Span, Spanned, SyntaxShape, Type, Value,
};
type Input<'t> = Peekable<CharIndices<'t>>;
@ -64,7 +64,7 @@ impl Command for DetectColumns {
description: "Splits string across multiple columns",
example: "'a b c' | detect columns -n",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"column0".to_string(),
"column1".to_string(),
@ -75,8 +75,7 @@ impl Command for DetectColumns {
Value::test_string("b"),
Value::test_string("c"),
],
span,
}],
})],
span,
}),
},
@ -211,11 +210,7 @@ fn detect_columns(
};
if !(l_idx <= r_idx && (r_idx >= 0 || l_idx < (cols.len() as isize))) {
return Value::Record {
cols,
vals,
span: name_span,
};
return Value::record(Record { cols, vals }, name_span);
}
(l_idx.max(0) as usize, (r_idx as usize + 1).min(cols.len()))
@ -228,11 +223,7 @@ fn detect_columns(
}
}
} else {
return Value::Record {
cols,
vals,
span: name_span,
};
return Value::record(Record { cols, vals }, name_span);
};
// Merge Columns
@ -254,11 +245,7 @@ fn detect_columns(
vals.push(binding);
last_seg.into_iter().for_each(|v| vals.push(v));
Value::Record {
cols,
vals,
span: name_span,
}
Value::record(Record { cols, vals }, name_span)
})
.into_pipeline_data(ctrlc))
} else {

View File

@ -6,8 +6,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, ListStream, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
Type, Value,
Category, Example, ListStream, PipelineData, Record, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -44,11 +44,10 @@ impl Command for Parse {
fn examples(&self) -> Vec<Example> {
let result = Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["foo".to_string(), "bar".to_string()],
vals: vec![Value::test_string("hi"), Value::test_string("there")],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
};
@ -67,11 +66,10 @@ impl Command for Parse {
description: "Parse a string using fancy-regex named capture group pattern",
example: "\"foo bar.\" | parse -r '\\s*(?<name>\\w+)(?=\\.)'",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["name".to_string()],
vals: vec![Value::test_string("bar")],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -80,16 +78,14 @@ impl Command for Parse {
example: "\"foo! bar.\" | parse -r '(\\w+)(?=\\.)|(\\w+)(?=!)'",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["capture0".to_string(), "capture1".to_string()],
vals: vec![Value::test_string(""), Value::test_string("foo")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["capture0".to_string(), "capture1".to_string()],
vals: vec![Value::test_string("bar"), Value::test_string("")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -99,14 +95,13 @@ impl Command for Parse {
example:
"\" @another(foo bar) \" | parse -r '\\s*(?<=[() ])(@\\w+)(\\([^)]*\\))?\\s*'",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["capture0".to_string(), "capture1".to_string()],
vals: vec![
Value::test_string("@another"),
Value::test_string("(foo bar)"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -114,11 +109,10 @@ impl Command for Parse {
description: "Parse a string using fancy-regex look ahead atomic group pattern",
example: "\"abcd\" | parse -r '^a(bc(?=d)|b)cd$'",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["capture0".to_string()],
vals: vec![Value::test_string("b")],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -179,7 +173,6 @@ fn operate(
let results = regex_pattern.captures_iter(&s);
for c in results {
let mut cols = Vec::with_capacity(columns.len());
let captures = match c {
Ok(c) => c,
Err(e) => {
@ -192,22 +185,18 @@ fn operate(
))
}
};
let mut vals = Vec::with_capacity(captures.len());
for (column_name, cap) in columns.iter().zip(captures.iter().skip(1)) {
let cap_string = cap.map(|v| v.as_str()).unwrap_or("").to_string();
cols.push(column_name.clone());
vals.push(Value::String {
val: cap_string,
span: v.span()?,
});
}
let v_span = v.span()?;
let record = columns
.iter()
.zip(captures.iter().skip(1))
.map(|(column_name, cap)| {
let cap_string = cap.map(|v| v.as_str()).unwrap_or("");
(column_name.clone(), Value::string(cap_string, v_span))
})
.collect();
parsed.push(Value::Record {
cols,
vals,
span: head,
});
parsed.push(Value::record(record, head));
}
}
Err(_) => {
@ -449,7 +438,6 @@ fn stream_helper(
let results = regex.captures_iter(&s);
for c in results {
let mut cols = Vec::with_capacity(columns.len());
let captures = match c {
Ok(c) => c,
Err(e) => {
@ -464,18 +452,17 @@ fn stream_helper(
})
}
};
let mut vals = Vec::with_capacity(captures.len());
for (column_name, cap) in columns.iter().zip(captures.iter().skip(1)) {
let cap_string = cap.map(|v| v.as_str()).unwrap_or("").to_string();
cols.push(column_name.clone());
vals.push(Value::String {
val: cap_string,
span,
});
}
let record = columns
.iter()
.zip(captures.iter().skip(1))
.map(|(column_name, cap)| {
let cap_string = cap.map(|v| v.as_str()).unwrap_or("");
(column_name.clone(), Value::string(cap_string, span))
})
.collect();
excess.push(Value::Record { cols, vals, span });
excess.push(Value::record(record, span));
}
if !excess.is_empty() {

View File

@ -1,7 +1,9 @@
use fancy_regex::Regex;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
use nu_protocol::{
record, Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
use std::collections::BTreeMap;
use std::{fmt, str};
use unicode_segmentation::UnicodeSegmentation;
@ -46,7 +48,7 @@ impl Command for Size {
Example {
description: "Count the number of words in a string",
example: r#""There are seven words in this sentence" | size"#,
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"lines".into(),
"words".into(),
@ -61,13 +63,12 @@ impl Command for Size {
Value::test_int(38),
Value::test_int(38),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Counts unicode characters",
example: r#"'今天天气真好' | size "#,
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"lines".into(),
"words".into(),
@ -82,13 +83,12 @@ impl Command for Size {
Value::test_int(6),
Value::test_int(6),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Counts Unicode characters correctly in a string",
example: r#""Amélie Amelie" | size"#,
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"lines".into(),
"words".into(),
@ -103,8 +103,7 @@ impl Command for Size {
Value::test_int(14),
Value::test_int(13),
],
span: Span::test_data(),
}),
})),
},
]
}
@ -145,55 +144,20 @@ fn size(
fn counter(contents: &str, span: Span) -> Value {
let counts = uwc_count(&ALL_COUNTERS[..], contents);
let mut cols = vec![];
let mut vals = vec![];
cols.push("lines".into());
vals.push(Value::Int {
val: match counts.get(&Counter::Lines) {
Some(c) => *c as i64,
None => 0,
},
span,
});
fn get_count(counts: &BTreeMap<Counter, usize>, counter: Counter, span: Span) -> Value {
Value::int(counts.get(&counter).copied().unwrap_or(0) as i64, span)
}
cols.push("words".into());
vals.push(Value::Int {
val: match counts.get(&Counter::Words) {
Some(c) => *c as i64,
None => 0,
},
span,
});
let record = record! {
"lines" => get_count(&counts, Counter::Lines, span),
"words" => get_count(&counts, Counter::Words, span),
"bytes" => get_count(&counts, Counter::Bytes, span),
"chars" => get_count(&counts, Counter::CodePoints, span),
"graphemes" => get_count(&counts, Counter::GraphemeClusters, span),
};
cols.push("bytes".into());
vals.push(Value::Int {
val: match counts.get(&Counter::Bytes) {
Some(c) => *c as i64,
None => 0,
},
span,
});
cols.push("chars".into());
vals.push(Value::Int {
val: match counts.get(&Counter::CodePoints) {
Some(c) => *c as i64,
None => 0,
},
span,
});
cols.push("graphemes".into());
vals.push(Value::Int {
val: match counts.get(&Counter::GraphemeClusters) {
Some(c) => *c as i64,
None => 0,
},
span,
});
Value::Record { cols, vals, span }
Value::record(record, span)
}
/// Take all the counts in `other_counts` and sum them into `accum`.

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape,
Type, Value,
};
use regex::Regex;
@ -64,7 +64,7 @@ impl Command for SubCommand {
description: "Split a string into columns by the specified separator",
example: "'a--b--c' | split column '--'",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"column1".to_string(),
"column2".to_string(),
@ -75,8 +75,7 @@ impl Command for SubCommand {
Value::test_string("b"),
Value::test_string("c"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -84,7 +83,7 @@ impl Command for SubCommand {
description: "Split a string into columns of char and remove the empty columns",
example: "'abc' | split column -c ''",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"column1".to_string(),
"column2".to_string(),
@ -95,8 +94,7 @@ impl Command for SubCommand {
Value::test_string("b"),
Value::test_string("c"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -105,16 +103,14 @@ impl Command for SubCommand {
example: "['a-b' 'c-d'] | split column -",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["column1".to_string(), "column2".to_string()],
vals: vec![Value::test_string("a"), Value::test_string("b")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["column1".to_string(), "column2".to_string()],
vals: vec![Value::test_string("c"), Value::test_string("d")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -124,16 +120,14 @@ impl Command for SubCommand {
example: r"['a - b' 'c - d'] | split column -r '\s*-\s*'",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["column1".to_string(), "column2".to_string()],
vals: vec![Value::test_string("a"), Value::test_string("b")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["column1".to_string(), "column2".to_string()],
vals: vec![Value::test_string("c"), Value::test_string("d")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -190,8 +184,7 @@ fn split_column_helper(
let positional: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
// If they didn't provide column names, make up our own
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
if positional.is_empty() {
let mut gen_columns = vec![];
for i in 0..split_result.len() {
@ -199,20 +192,14 @@ fn split_column_helper(
}
for (&k, v) in split_result.iter().zip(&gen_columns) {
cols.push(v.to_string());
vals.push(Value::string(k, head));
record.push(v, Value::string(k, head));
}
} else {
for (&k, v) in split_result.iter().zip(&positional) {
cols.push(v.into());
vals.push(Value::string(k, head));
record.push(v, Value::string(k, head));
}
}
vec![Value::Record {
cols,
vals,
span: head,
}]
vec![Value::record(record, head)]
} else {
match v.span() {
Ok(span) => vec![Value::Error {

View File

@ -3,7 +3,9 @@ use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
use nu_protocol::{
Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
@ -67,11 +69,10 @@ impl Command for SubCommand {
description: "Capitalize a column in a table",
example: "[[lang, gems]; [nu_test, 100]] | str capitalize lang",
result: Some(Value::List {
vals: vec![Value::Record {
span: Span::test_data(),
vals: vec![Value::test_record(Record {
cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("Nu_test"), Value::test_int(100)],
}],
})],
span: Span::test_data(),
}),
},

View File

@ -3,7 +3,9 @@ use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
use nu_protocol::{
Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
@ -67,11 +69,10 @@ impl Command for SubCommand {
description: "Downcase contents",
example: "[[ColA ColB]; [Test ABC]] | str downcase ColA",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string()],
vals: vec![Value::test_string("test"), Value::test_string("ABC")],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -79,11 +80,10 @@ impl Command for SubCommand {
description: "Downcase contents",
example: "[[ColA ColB]; [Test ABC]] | str downcase ColA ColB",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string()],
vals: vec![Value::test_string("test"), Value::test_string("abc")],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -3,8 +3,9 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
use nu_protocol::{
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
@ -88,21 +89,19 @@ impl Command for SubCommand {
Example {
description: "Check if input contains string in a record",
example: "{ ColA: test, ColB: 100 } | str contains 'e' ColA",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string()],
vals: vec![Value::test_bool(true), Value::test_int(100)],
span: Span::test_data(),
}),
})),
},
Example {
description: "Check if input contains string in a table",
example: " [[ColA ColB]; [test 100]] | str contains -i 'E' ColA",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string()],
vals: vec![Value::test_bool(true), Value::test_int(100)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -110,11 +109,10 @@ impl Command for SubCommand {
description: "Check if input contains string in a table",
example: " [[ColA ColB]; [test hello]] | str contains 'e' ColA ColB",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string()],
vals: vec![Value::test_bool(true), Value::test_bool(true)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
levenshtein_distance, Category, Example, PipelineData, ShellError, Signature, Span,
levenshtein_distance, Category, Example, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
@ -82,11 +82,10 @@ impl Command for SubCommand {
example: "[{a: 'nutshell' b: 'numetal'}] | str distance 'nushell' 'a' 'b'",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_int(1), Value::test_int(4)],
span: Span::test_data(),
}
})
],
span: Span::test_data(),
}),
@ -95,11 +94,10 @@ impl Command for SubCommand {
description: "Compute edit distance between strings in record and another string, using cell paths",
example: "{a: 'nutshell' b: 'numetal'} | str distance 'nushell' a b",
result: Some(
Value::Record {
Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_int(1), Value::test_int(4)],
span: Span::test_data(),
}
})
),
}]
}

View File

@ -4,8 +4,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
report_error_new, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
report_error_new, Category, Example, PipelineData, Record, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
};
struct Arguments {
@ -150,15 +150,14 @@ impl Command for SubCommand {
example:
"[[ColA ColB ColC]; [abc abc ads]] | str replace -ar 'b' 'z' ColA ColC",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::test_string("azc"),
Value::test_string("abc"),
Value::test_string("ads"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -166,15 +165,14 @@ impl Command for SubCommand {
description: "Find and replace all occurrences of find string in record using regular expression",
example:
"{ KeyA: abc, KeyB: abc, KeyC: ads } | str replace -ar 'b' 'z' KeyA KeyC",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["KeyA".to_string(), "KeyB".to_string(), "KeyC".to_string()],
vals: vec![
Value::test_string("azc"),
Value::test_string("abc"),
Value::test_string("ads"),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Find and replace contents without using the replace parameter as a regular expression",

View File

@ -1,10 +1,10 @@
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::Category;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape,
Type, Value,
};
#[derive(Clone)]
@ -180,14 +180,16 @@ fn action(input: &Value, arg: &Arguments, head: Span) -> Value {
Value::Error { .. } => input.clone(),
other => match mode {
ActionMode::Global => match other {
Value::Record { cols, vals, span } => {
let new_vals = vals.iter().map(|v| action(v, arg, head)).collect();
Value::Record { val: record, span } => {
let new_vals = record.vals.iter().map(|v| action(v, arg, head)).collect();
Value::Record {
cols: cols.to_vec(),
vals: new_vals,
span: *span,
}
Value::record(
Record {
cols: record.cols.to_vec(),
vals: new_vals,
},
*span,
)
}
Value::List { vals, span } => {
let new_vals = vals.iter().map(|v| action(v, arg, head)).collect();
@ -249,14 +251,12 @@ mod tests {
}
fn make_record(cols: Vec<&str>, vals: Vec<&str>) -> Value {
Value::Record {
cols: cols.iter().map(|x| x.to_string()).collect(),
vals: vals
.iter()
.map(|x| Value::test_string(x.to_string()))
Value::test_record(
cols.into_iter()
.zip(vals)
.map(|(col, val)| (col.to_owned(), Value::test_string(val)))
.collect(),
span: Span::test_data(),
}
)
}
fn make_list(vals: Vec<&str>) -> Value {