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

@ -4,7 +4,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, Range, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Range, Record, ShellError, Signature, Span, SyntaxShape, Type,
Value,
};
#[derive(Clone)]
@ -109,10 +110,10 @@ impl Command for BytesAt {
Example {
description: "Get the remaining characters from a starting index",
example: " { data: 0x[33 44 55 10 01 13] } | bytes at 3.. data",
result: Some(Value::test_record(
vec!["data"],
vec![Value::test_binary(vec![0x10, 0x01, 0x13])],
)),
result: Some(Value::test_record(Record {
cols: vec!["data".to_string()],
vals: vec![Value::test_binary(vec![0x10, 0x01, 0x13])],
})),
},
Example {
description: "Get the characters from the beginning until ending index",
@ -127,7 +128,7 @@ impl Command for BytesAt {
"Or the characters from the beginning until ending index inside a table",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at 1.. ColB 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::Binary {
@ -143,8 +144,7 @@ impl Command for BytesAt {
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath};
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,
};
struct Arguments {
@ -112,7 +112,7 @@ impl Command for BytesIndexOf {
description: "Returns index of pattern for specific column",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] 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_int(0),
@ -122,8 +122,7 @@ impl Command for BytesIndexOf {
},
Value::test_int(-1),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
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,
};
struct Arguments {
@ -95,8 +95,10 @@ impl Command for BytesRemove {
Example {
description: "Remove all occurrences of find binary in record field",
example: "{ data: 0x[10 AA 10 BB 10] } | bytes remove -a 0x[10] data",
result: Some(Value::test_record(vec!["data"],
vec![Value::test_binary(vec![0xAA, 0xBB])])),
result: Some(Value::test_record(Record {
cols: vec!["data".to_string()],
vals: vec![Value::test_binary(vec![0xAA, 0xBB])]
})),
},
Example {
description: "Remove occurrences of find binary from end",
@ -110,7 +112,7 @@ impl Command for BytesRemove {
description: "Remove all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] 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::Binary {
@ -125,9 +127,8 @@ impl Command for BytesRemove {
val: vec![0x17, 0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
]
})],
span: Span::test_data(),
}),
},

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
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,
};
struct Arguments {
@ -104,7 +104,7 @@ impl Command for BytesReplace {
description: "Find and replace all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] 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::Binary {
@ -120,8 +120,7 @@ impl Command for BytesReplace {
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -4,11 +4,10 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
};
use std::collections::HashMap;
use std::iter;
#[derive(Clone)]
pub struct Histogram;
@ -53,7 +52,7 @@ impl Command for Histogram {
description: "Compute a histogram for a list of numbers",
example: "[1 2 1] | histogram",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
vals: vec![
Value::test_int(1),
@ -62,9 +61,8 @@ impl Command for Histogram {
Value::test_string("66.67%"),
Value::test_string("******************************************************************"),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
vals: vec![
Value::test_int(2),
@ -73,8 +71,7 @@ impl Command for Histogram {
Value::test_string("33.33%"),
Value::test_string("*********************************"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}
),
@ -195,8 +192,8 @@ fn run_histogram(
for v in values {
match v {
// parse record, and fill valid value to actual input.
Value::Record { cols, vals, .. } => {
for (c, v) in iter::zip(cols, vals) {
Value::Record { val, .. } => {
for (c, v) in val {
if &c == col_name {
if let Ok(v) = HashableValue::from_value(v, head_span) {
inputs.push(v);
@ -272,23 +269,25 @@ fn histogram_impl(
result.push((
count, // attach count first for easily sorting.
Value::Record {
cols: result_cols.clone(),
vals: vec![
val.into_value(),
Value::Int { val: count, span },
Value::Float {
val: quantile,
span,
},
Value::String {
val: percentage,
span,
},
Value::String { val: freq, span },
],
Value::record(
Record {
cols: result_cols.clone(),
vals: vec![
val.into_value(),
Value::Int { val: count, span },
Value::Float {
val: quantile,
span,
},
Value::String {
val: percentage,
span,
},
Value::String { val: freq, span },
],
},
span,
},
),
));
}
result.sort_by(|a, b| b.0.cmp(&a.0));

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -60,31 +60,26 @@ impl Command for SubCommand {
example: "[[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::bool(false, span)],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::bool(true, span)],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::bool(false, span)],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::bool(true, span)],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::bool(true, span)],
span,
},
}),
],
span,
}),

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -63,11 +63,10 @@ impl Command for SubCommand {
description: "Convert string to decimal in table",
example: "[[num]; ['5.01']] | into decimal num",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["num".to_string()],
vals: vec![Value::test_float(5.01)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -3,7 +3,8 @@ use nu_parser::{parse_unit_value, DURATION_UNIT_GROUPS};
use nu_protocol::{
ast::{Call, CellPath, Expr},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Unit, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Unit,
Value,
};
const NS_PER_SEC: i64 = 1_000_000_000;
@ -80,46 +81,41 @@ impl Command for SubCommand {
"[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: NS_PER_SEC,
span,
}],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 2 * 60 * NS_PER_SEC,
span,
}],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 3 * 60 * 60 * NS_PER_SEC,
span,
}],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 4 * 24 * 60 * 60 * NS_PER_SEC,
span,
}],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 5 * 7 * 24 * 60 * 60 * NS_PER_SEC,
span,
}],
span,
},
}),
],
span,
}),

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -81,7 +81,7 @@ impl Command for SubCommand {
example: r#"[[device size]; ["/dev/sda1" "200"] ["/dev/loop0" "50"]] | into filesize size"#,
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["device".to_string(), "size".to_string()],
vals: vec![
Value::String {
@ -93,9 +93,8 @@ impl Command for SubCommand {
span: Span::test_data(),
},
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["device".to_string(), "size".to_string()],
vals: vec![
Value::String {
@ -107,8 +106,7 @@ impl Command for SubCommand {
span: Span::test_data(),
},
],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),

View File

@ -1,10 +1,11 @@
use chrono::{DateTime, Datelike, FixedOffset, Timelike};
use nu_protocol::format_duration_as_timeperiod;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
format_duration_as_timeperiod, record, Category, Example, IntoPipelineData, PipelineData,
Record, ShellError, Signature, Span, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
@ -50,42 +51,39 @@ impl Command for SubCommand {
Example {
description: "Convert from one row table to record",
example: "[[value]; [false]] | into record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::bool(false, span)],
span,
}),
})),
},
Example {
description: "Convert from list to record",
example: "[1 2 3] | into record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["0".to_string(), "1".to_string(), "2".to_string()],
vals: vec![
Value::Int { val: 1, span },
Value::Int { val: 2, span },
Value::Int { val: 3, span },
],
span,
}),
})),
},
Example {
description: "Convert from range to record",
example: "0..2 | into record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["0".to_string(), "1".to_string(), "2".to_string()],
vals: vec![
Value::Int { val: 0, span },
Value::Int { val: 1, span },
Value::Int { val: 2, span },
],
span,
}),
})),
},
Example {
description: "convert duration to record (weeks max)",
example: "(-500day - 4hr - 5sec) | into record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"week".into(),
"day".into(),
@ -103,22 +101,20 @@ impl Command for SubCommand {
span,
},
],
span,
}),
})),
},
Example {
description: "convert record to record",
example: "{a: 1, b: 2} | into record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::Int { val: 1, span }, Value::Int { val: 2, span }],
span,
}),
})),
},
Example {
description: "convert date to record",
example: "2020-04-12T22:10:57+02:00 | into record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"year".into(),
"month".into(),
@ -140,8 +136,7 @@ impl Command for SubCommand {
span,
},
],
span,
}),
})),
},
]
}
@ -155,34 +150,26 @@ fn into_record(
let input = input.into_value(call.head);
let input_type = input.get_type();
let res = match input {
Value::Date { val, span } => parse_date_into_record(Ok(val), span),
Value::Date { val, span } => parse_date_into_record(val, span),
Value::Duration { val, span } => parse_duration_into_record(val, span),
Value::List { mut vals, span } => match input_type {
Type::Table(..) if vals.len() == 1 => vals.pop().expect("already checked 1 item"),
_ => {
let mut cols = vec![];
let mut values = vec![];
for (idx, val) in vals.into_iter().enumerate() {
cols.push(format!("{idx}"));
values.push(val);
}
Value::Record {
cols,
vals: values,
span,
}
}
_ => Value::record(
vals.into_iter()
.enumerate()
.map(|(idx, val)| (format!("{idx}"), val))
.collect(),
span,
),
},
Value::Range { val, span } => {
let mut cols = vec![];
let mut vals = vec![];
for (idx, val) in val.into_range_iter(engine_state.ctrlc.clone())?.enumerate() {
cols.push(format!("{idx}"));
vals.push(val);
}
Value::Record { cols, vals, span }
}
Value::Record { cols, vals, span } => Value::Record { cols, vals, span },
Value::Range { val, span } => Value::record(
val.into_range_iter(engine_state.ctrlc.clone())?
.enumerate()
.map(|(idx, val)| (format!("{idx}"), val))
.collect(),
span,
),
Value::Record { val, span } => Value::Record { val, span },
Value::Error { .. } => input,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
@ -196,87 +183,50 @@ fn into_record(
Ok(res.into_pipeline_data())
}
fn parse_date_into_record(date: Result<DateTime<FixedOffset>, Value>, span: Span) -> Value {
let cols = vec![
"year".into(),
"month".into(),
"day".into(),
"hour".into(),
"minute".into(),
"second".into(),
"timezone".into(),
];
match date {
Ok(x) => {
let vals = vec![
Value::Int {
val: x.year() as i64,
span,
},
Value::Int {
val: x.month() as i64,
span,
},
Value::Int {
val: x.day() as i64,
span,
},
Value::Int {
val: x.hour() as i64,
span,
},
Value::Int {
val: x.minute() as i64,
span,
},
Value::Int {
val: x.second() as i64,
span,
},
Value::String {
val: x.offset().to_string(),
span,
},
];
Value::Record { cols, vals, span }
}
Err(e) => e,
}
fn parse_date_into_record(date: DateTime<FixedOffset>, span: Span) -> Value {
Value::record(
record! {
"year" => Value::int(date.year() as i64, span),
"month" => Value::int(date.month() as i64, span),
"day" => Value::int(date.day() as i64, span),
"hour" => Value::int(date.hour() as i64, span),
"minute" => Value::int(date.minute() as i64, span),
"second" => Value::int(date.second() as i64, span),
"timezone" => Value::string(date.offset().to_string(), span),
},
span,
)
}
fn parse_duration_into_record(duration: i64, span: Span) -> Value {
let (sign, periods) = format_duration_as_timeperiod(duration);
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for p in periods {
let num_with_unit = p.to_text().to_string();
let split = num_with_unit.split(' ').collect::<Vec<&str>>();
cols.push(match split[1] {
"ns" => "nanosecond".into(),
"µs" => "microsecond".into(),
"ms" => "millisecond".into(),
"sec" => "second".into(),
"min" => "minute".into(),
"hr" => "hour".into(),
"day" => "day".into(),
"wk" => "week".into(),
_ => "unknown".into(),
});
vals.push(Value::Int {
val: split[0].parse::<i64>().unwrap_or(0),
span,
});
record.push(
match split[1] {
"ns" => "nanosecond",
"µs" => "microsecond",
"ms" => "millisecond",
"sec" => "second",
"min" => "minute",
"hr" => "hour",
"day" => "day",
"wk" => "week",
_ => "unknown",
},
Value::int(split[0].parse().unwrap_or(0), span),
);
}
cols.push("sign".into());
vals.push(Value::String {
val: if sign == -1 { "-".into() } else { "+".into() },
span,
});
record.push(
"sign",
Value::string(if sign == -1 { "-" } else { "+" }, span),
);
Value::Record { cols, vals, span }
Value::record(record, span)
}
#[cfg(test)]

View File

@ -257,11 +257,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
val: "".to_string(),
span,
},
Value::Record {
cols: _,
vals: _,
span: _,
} => Value::Error {
Value::Record { .. } => Value::Error {
// Watch out for CantConvert's argument order
error: Box::new(ShellError::CantConvert {
to_type: "string".into(),

View File

@ -7,7 +7,6 @@ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
};
use std::iter;
use std::path::Path;
#[derive(Clone)]
@ -127,12 +126,9 @@ fn action(
format!(
"({})",
match list_value {
Value::Record {
cols: _,
vals,
span: _,
} => {
vals.iter()
Value::Record { val, .. } => {
val.vals
.iter()
.map(|rec_val| {
format!("'{}'", nu_value_to_string(rec_val.clone(), ""))
})
@ -249,14 +245,13 @@ fn nu_value_to_string(value: Value, separator: &str) -> String {
nu_utils::strip_ansi_unlikely(&val).replace('\'', "''")
}
Value::List { vals: val, .. } => val
.iter()
.map(|x| nu_value_to_string(x.clone(), ", "))
.into_iter()
.map(|x| nu_value_to_string(x, ", "))
.collect::<Vec<_>>()
.join(separator),
Value::Record { cols, vals, .. } => cols
.iter()
.zip(vals.iter())
.map(|(x, y)| format!("{}: {}", x, nu_value_to_string(y.clone(), ", ")))
Value::Record { val, .. } => val
.into_iter()
.map(|(x, y)| format!("{}: {}", x, nu_value_to_string(y, ", ")))
.collect::<Vec<_>>()
.join(separator),
Value::LazyRecord { val, .. } => match val.collect() {
@ -305,8 +300,8 @@ fn get_columns_with_sqlite_types(input: &[Value]) -> Vec<(String, String)> {
// sqlite_type
// );
if let Value::Record { cols, vals, .. } = item {
for (c, v) in iter::zip(cols, vals) {
if let Value::Record { val, .. } = item {
for (c, v) in val {
if !columns.iter().any(|(name, _)| name == c) {
columns.push((
c.to_string(),

View File

@ -3,7 +3,7 @@ use crate::database::values::definitions::{db_row::DbRow, db_table::DbTable};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
record, Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
use rusqlite::Connection;
#[derive(Clone)]
@ -43,8 +43,6 @@ impl Command for SchemaDb {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut cols = vec![];
let mut vals = vec![];
let span = call.head;
let sqlite_db = SQLiteDatabase::try_from_pipeline(input, span)?;
@ -59,58 +57,32 @@ impl Command for SchemaDb {
)
})?;
let mut table_names = vec![];
let mut table_values = vec![];
let mut tables_record = Record::new();
for table in tables {
let column_info = get_table_columns(&sqlite_db, &conn, &table, span)?;
let constraint_info = get_table_constraints(&sqlite_db, &conn, &table, span)?;
let foreign_key_info = get_table_foreign_keys(&sqlite_db, &conn, &table, span)?;
let index_info = get_table_indexes(&sqlite_db, &conn, &table, span)?;
let mut cols = vec![];
let mut vals = vec![];
cols.push("columns".into());
vals.push(Value::List {
vals: column_info,
span,
});
cols.push("constraints".into());
vals.push(Value::List {
vals: constraint_info,
span,
});
cols.push("foreign_keys".into());
vals.push(Value::List {
vals: foreign_key_info,
span,
});
cols.push("indexes".into());
vals.push(Value::List {
vals: index_info,
span,
});
table_names.push(table.name);
table_values.push(Value::Record { cols, vals, span });
tables_record.push(
table.name,
Value::record(
record! {
"columns" => Value::list(column_info, span),
"constraints" => Value::list(constraint_info, span),
"foreign_keys" => Value::list(foreign_key_info, span),
"indexes" => Value::list(index_info, span),
},
span,
),
);
}
cols.push("tables".into());
vals.push(Value::Record {
cols: table_names,
vals: table_values,
span,
});
let record = record! { "tables" => Value::record(tables_record, span) };
// TODO: add views and triggers
Ok(PipelineData::Value(
Value::Record { cols, vals, span },
None,
))
Ok(PipelineData::Value(Value::record(record, span), None))
}
}
@ -145,19 +117,14 @@ fn get_table_columns(
// a record of column name = column value
let mut column_info = vec![];
for t in columns {
let mut col_names = vec![];
let mut col_values = vec![];
let fields = t.fields();
let columns = t.columns();
for (k, v) in fields.iter().zip(columns.iter()) {
col_names.push(k.clone());
col_values.push(Value::string(v.clone(), span));
}
column_info.push(Value::Record {
cols: col_names.clone(),
vals: col_values.clone(),
column_info.push(Value::record(
t.fields()
.into_iter()
.zip(t.columns())
.map(|(k, v)| (k, Value::string(v, span)))
.collect(),
span,
});
));
}
Ok(column_info)
@ -180,19 +147,15 @@ fn get_table_constraints(
})?;
let mut constraint_info = vec![];
for constraint in constraints {
let mut con_cols = vec![];
let mut con_vals = vec![];
let fields = constraint.fields();
let columns = constraint.columns();
for (k, v) in fields.iter().zip(columns.iter()) {
con_cols.push(k.clone());
con_vals.push(Value::string(v.clone(), span));
}
constraint_info.push(Value::Record {
cols: con_cols.clone(),
vals: con_vals.clone(),
constraint_info.push(Value::record(
constraint
.fields()
.into_iter()
.zip(constraint.columns())
.map(|(k, v)| (k, Value::string(v, span)))
.collect(),
span,
});
));
}
Ok(constraint_info)
@ -215,19 +178,14 @@ fn get_table_foreign_keys(
})?;
let mut foreign_key_info = vec![];
for fk in foreign_keys {
let mut fk_cols = vec![];
let mut fk_vals = vec![];
let fields = fk.fields();
let columns = fk.columns();
for (k, v) in fields.iter().zip(columns.iter()) {
fk_cols.push(k.clone());
fk_vals.push(Value::string(v.clone(), span));
}
foreign_key_info.push(Value::Record {
cols: fk_cols.clone(),
vals: fk_vals.clone(),
foreign_key_info.push(Value::record(
fk.fields()
.into_iter()
.zip(fk.columns())
.map(|(k, v)| (k, Value::string(v, span)))
.collect(),
span,
});
));
}
Ok(foreign_key_info)
@ -250,19 +208,15 @@ fn get_table_indexes(
})?;
let mut index_info = vec![];
for index in indexes {
let mut idx_cols = vec![];
let mut idx_vals = vec![];
let fields = index.fields();
let columns = index.columns();
for (k, v) in fields.iter().zip(columns.iter()) {
idx_cols.push(k.clone());
idx_vals.push(Value::string(v.clone(), span));
}
index_info.push(Value::Record {
cols: idx_cols.clone(),
vals: idx_vals.clone(),
index_info.push(Value::record(
index
.fields()
.into_iter()
.zip(index.columns())
.map(|(k, v)| (k, Value::string(v, span)))
.collect(),
span,
});
));
}
Ok(index_info)

View File

@ -3,7 +3,7 @@ use super::definitions::{
db_index::DbIndex, db_table::DbTable,
};
use nu_protocol::{CustomValue, PipelineData, ShellError, Span, Spanned, Value};
use nu_protocol::{CustomValue, PipelineData, Record, ShellError, Span, Spanned, Value};
use rusqlite::{types::ValueRef, Connection, Row};
use serde::{Deserialize, Serialize};
use std::{
@ -415,8 +415,7 @@ fn read_entire_sqlite_db(
call_span: Span,
ctrlc: Option<Arc<AtomicBool>>,
) -> Result<Value, rusqlite::Error> {
let mut table_names: Vec<String> = Vec::new();
let mut tables: Vec<Value> = Vec::new();
let mut tables = Record::new();
let mut get_table_names =
conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")?;
@ -424,18 +423,12 @@ fn read_entire_sqlite_db(
for row in rows {
let table_name: String = row?;
table_names.push(table_name.clone());
let table_stmt = conn.prepare(&format!("select * from [{table_name}]"))?;
let rows = prepared_statement_to_nu_list(table_stmt, call_span, ctrlc.clone())?;
tables.push(rows);
tables.push(table_name, rows);
}
Ok(Value::Record {
cols: table_names,
vals: tables,
span: call_span,
})
Ok(Value::record(tables, call_span))
}
pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: Vec<String>) -> Value {
@ -446,11 +439,13 @@ pub fn convert_sqlite_row_to_nu_value(row: &Row, span: Span, column_names: Vec<S
vals.push(val);
}
Value::Record {
cols: column_names,
vals,
Value::record(
Record {
cols: column_names,
vals,
},
span,
}
)
}
pub fn convert_sqlite_value_to_nu_value(value: ValueRef, span: Span) -> Value {
@ -488,11 +483,7 @@ mod test {
let db = open_connection_in_memory().unwrap();
let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap();
let expected = Value::Record {
cols: vec![],
vals: vec![],
span: Span::test_data(),
};
let expected = Value::test_record(Record::new());
assert_eq!(converted_db, expected);
}
@ -512,14 +503,13 @@ mod test {
.unwrap();
let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap();
let expected = Value::Record {
let expected = Value::test_record(Record {
cols: vec!["person".to_string()],
vals: vec![Value::List {
vals: vec![],
span: Span::test_data(),
}],
span: Span::test_data(),
};
});
assert_eq!(converted_db, expected);
}
@ -546,16 +536,15 @@ mod test {
let converted_db = read_entire_sqlite_db(db, span, None).unwrap();
let expected = Value::Record {
let expected = Value::test_record(Record {
cols: vec!["item".to_string()],
vals: vec![Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["id".to_string(), "name".to_string()],
vals: vec![Value::Int { val: 123, span }, Value::Nothing { span }],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["id".to_string(), "name".to_string()],
vals: vec![
Value::Int { val: 456, span },
@ -564,13 +553,11 @@ mod test {
span,
},
],
span,
},
}),
],
span,
}],
span,
};
});
assert_eq!(converted_db, expected);
}

View File

@ -2,8 +2,8 @@ use chrono_tz::TZ_VARIANTS;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
Type, Value,
record, Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError,
Signature, Span, Type, Value,
};
#[derive(Clone)]
@ -40,12 +40,10 @@ impl Command for SubCommand {
Ok(TZ_VARIANTS
.iter()
.map(move |x| {
let cols = vec!["timezone".into()];
let vals = vec![Value::String {
val: x.name().to_string(),
Value::record(
record! { "timezone" => Value::string(x.name(), span) },
span,
}];
Value::Record { cols, vals, span }
)
})
.into_pipeline_data(engine_state.ctrlc.clone()))
}
@ -55,11 +53,10 @@ impl Command for SubCommand {
example: "date list-timezone | where timezone =~ Shanghai",
description: "Show timezone(s) that contains 'Shanghai'",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["timezone".into()],
vals: vec![Value::test_string("Asia/Shanghai")],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
}]

View File

@ -3,10 +3,9 @@ use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty,
Signature, Span, Value,
record, Category, Example, PipelineData, Record, ShellError, ShellError::DatetimeParseError,
ShellError::PipelineEmpty, Signature, Span, Type, Value,
};
use nu_protocol::{ShellError, Type};
#[derive(Clone)]
pub struct SubCommand;
@ -78,7 +77,7 @@ impl Command for SubCommand {
span,
},
];
Some(Value::Record { cols, vals, span })
Some(Value::test_record(Record { cols, vals }))
};
let example_result_2 = || {
@ -106,7 +105,7 @@ impl Command for SubCommand {
span,
},
];
Some(Value::Record { cols, vals, span })
Some(Value::test_record(Record { cols, vals }))
};
vec![
@ -134,37 +133,20 @@ impl Command for SubCommand {
}
}
fn parse_date_into_table(date: Result<DateTime<FixedOffset>, Value>, head: Span) -> Value {
let cols = vec![
"year".into(),
"month".into(),
"day".into(),
"hour".into(),
"minute".into(),
"second".into(),
"nanosecond".into(),
"timezone".into(),
];
match date {
Ok(x) => {
let vals = vec![
Value::int(x.year() as i64, head),
Value::int(x.month() as i64, head),
Value::int(x.day() as i64, head),
Value::int(x.hour() as i64, head),
Value::int(x.minute() as i64, head),
Value::int(x.second() as i64, head),
Value::int(x.nanosecond() as i64, head),
Value::string(x.offset().to_string(), head),
];
Value::Record {
cols,
vals,
span: head,
}
}
Err(e) => e,
}
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
Value::record(
record! {
"year" => Value::int(date.year() as i64, head),
"month" => Value::int(date.month() as i64, head),
"day" => Value::int(date.day() as i64, head),
"hour" => Value::int(date.hour() as i64, head),
"minute" => Value::int(date.minute() as i64, head),
"second" => Value::int(date.second() as i64, head),
"nanosecond" => Value::int(date.nanosecond() as i64, head),
"timezone" => Value::string(date.offset().to_string(), head),
},
head,
)
}
fn helper(val: Value, head: Span) -> Value {
@ -172,16 +154,16 @@ fn helper(val: Value, head: Span) -> Value {
Value::String {
val,
span: val_span,
} => {
let date = parse_date_from_string(&val, val_span);
parse_date_into_table(date, head)
}
} => match parse_date_from_string(&val, val_span) {
Ok(date) => parse_date_into_table(date, head),
Err(e) => e,
},
Value::Nothing { span: _ } => {
let now = Local::now();
let n = now.with_timezone(now.offset());
parse_date_into_table(Ok(n), head)
parse_date_into_table(n, head)
}
Value::Date { val, span: _ } => parse_date_into_table(Ok(val), head),
Value::Date { val, span: _ } => parse_date_into_table(val, head),
_ => Value::Error {
error: Box::new(DatetimeParseError(val.debug_value(), head)),
},

View File

@ -3,10 +3,9 @@ use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty,
Signature, Span, Value,
record, Category, Example, PipelineData, Record, ShellError, ShellError::DatetimeParseError,
ShellError::PipelineEmpty, Signature, Span, Type, Value,
};
use nu_protocol::{ShellError, Type};
#[derive(Clone)]
pub struct SubCommand;
@ -76,7 +75,7 @@ impl Command for SubCommand {
},
];
Some(Value::List {
vals: vec![Value::Record { cols, vals, span }],
vals: vec![Value::test_record(Record { cols, vals })],
span,
})
};
@ -107,7 +106,7 @@ impl Command for SubCommand {
},
];
Some(Value::List {
vals: vec![Value::Record { cols, vals, span }],
vals: vec![Value::test_record(Record { cols, vals })],
span,
})
};
@ -137,40 +136,19 @@ impl Command for SubCommand {
}
}
fn parse_date_into_table(date: Result<DateTime<FixedOffset>, Value>, head: Span) -> Value {
let cols = vec![
"year".into(),
"month".into(),
"day".into(),
"hour".into(),
"minute".into(),
"second".into(),
"nanosecond".into(),
"timezone".into(),
];
match date {
Ok(x) => {
let vals = vec![
Value::int(x.year() as i64, head),
Value::int(x.month() as i64, head),
Value::int(x.day() as i64, head),
Value::int(x.hour() as i64, head),
Value::int(x.minute() as i64, head),
Value::int(x.second() as i64, head),
Value::int(x.nanosecond() as i64, head),
Value::string(x.offset().to_string(), head),
];
Value::List {
vals: vec![Value::Record {
cols,
vals,
span: head,
}],
span: head,
}
}
Err(e) => e,
}
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
let record = record! {
"year" => Value::int(date.year() as i64, head),
"month" => Value::int(date.month() as i64, head),
"day" => Value::int(date.day() as i64, head),
"hour" => Value::int(date.hour() as i64, head),
"minute" => Value::int(date.minute() as i64, head),
"second" => Value::int(date.second() as i64, head),
"nanosecond" => Value::int(date.nanosecond() as i64, head),
"timezone" => Value::string(date.offset().to_string(), head),
};
Value::list(vec![Value::record(record, head)], head)
}
fn helper(val: Value, head: Span) -> Value {
@ -178,16 +156,16 @@ fn helper(val: Value, head: Span) -> Value {
Value::String {
val,
span: val_span,
} => {
let date = parse_date_from_string(&val, val_span);
parse_date_into_table(date, head)
}
} => match parse_date_from_string(&val, val_span) {
Ok(date) => parse_date_into_table(date, head),
Err(e) => e,
},
Value::Nothing { span: _ } => {
let now = Local::now();
let n = now.with_timezone(now.offset());
parse_date_into_table(Ok(n), head)
parse_date_into_table(n, head)
}
Value::Date { val, span: _ } => parse_date_into_table(Ok(val), head),
Value::Date { val, span: _ } => parse_date_into_table(val, head),
_ => Value::Error {
error: Box::new(DatetimeParseError(val.debug_value(), head)),
},

View File

@ -1,5 +1,5 @@
use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone};
use nu_protocol::{ShellError, Span, Value};
use nu_protocol::{record, ShellError, Span, Value};
pub(crate) fn parse_date_from_string(
input: &str,
@ -31,11 +31,6 @@ pub(crate) fn parse_date_from_string(
/// * `head` - use the call's head
/// * `show_parse_only_formats` - whether parse-only format specifiers (that can't be outputted) should be shown. Should only be used for `into datetime`, not `format date`
pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool) -> Value {
let column_names = vec![
"Specification".into(),
"Example".into(),
"Description".into(),
];
let now = Local::now();
struct FormatSpecification<'a> {
@ -258,14 +253,15 @@ pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool)
let mut records = specifications
.iter()
.map(|s| Value::Record {
cols: column_names.clone(),
vals: vec![
Value::string(s.spec, head),
Value::string(now.format(s.spec).to_string(), head),
Value::string(s.description, head),
],
span: head,
.map(|s| {
Value::record(
record! {
"Specification" => Value::string(s.spec, head),
"Example" => Value::string(now.format(s.spec).to_string(), head),
"Description" => Value::string(s.description, head),
},
head,
)
})
.collect::<Vec<Value>>();
@ -279,21 +275,16 @@ pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool)
.unwrap_or("")
.to_string();
records.push(Value::Record {
cols: column_names,
vals: vec![
Value::string("%#z", head),
Value::String {
val: example,
span: head,
},
Value::string(
"Parsing only: Same as %z but allows minutes to be missing or present.",
head,
),
],
span: head,
});
let description = "Parsing only: Same as %z but allows minutes to be missing or present.";
records.push(Value::record(
record! {
"Specification" => Value::string("%#z", head),
"Example" => Value::string(example, head),
"Description" => Value::string(description, head),
},
head,
));
}
Value::List {

View File

@ -3,8 +3,8 @@ use nu_parser::parse;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack, StateWorkingSet},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -88,14 +88,13 @@ impl Command for Ast {
};
// Create a new output record, merging the block and error
let output_record = Value::Record {
cols: vec!["block".to_string(), "error".to_string()],
vals: vec![
Value::string(block_json, *block_span),
Value::string(error_json, Span::test_data()),
],
span: pipeline.span,
};
let output_record = Value::record(
record! {
"block" => Value::string(block_json, *block_span),
"error" => Value::string(error_json, Span::test_data()),
},
pipeline.span,
);
Ok(output_record.into_pipeline_data())
} else {
let block_value = Value::String {
@ -114,11 +113,13 @@ impl Command for Ast {
},
span: pipeline.span,
};
let output_record = Value::Record {
cols: vec!["block".to_string(), "error".to_string()],
vals: vec![block_value, error_value],
span: pipeline.span,
};
let output_record = Value::record(
record! {
"block" => block_value,
"error" => error_value
},
pipeline.span,
);
Ok(output_record.into_pipeline_data())
}
}

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_expression, CallExt};
use nu_protocol::ast::{Argument, Block, Call, Expr, Expression};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
record, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -91,29 +91,15 @@ pub fn get_pipeline_elements(
let value_span_end = value_span.end as i64;
let command_name = command_name;
let rec = Value::Record {
cols: vec![
"cmd_index".to_string(),
"cmd_name".to_string(),
"type".to_string(),
"cmd_args".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(index, span),
Value::string(command_name, value_span),
Value::string(value_type.to_string(), span),
Value::List {
vals: command_args_value,
span: value_span,
},
Value::int(value_span_start, span),
Value::int(value_span_end, span),
],
span: value_span,
let record = record! {
"cmd_index" => Value::string(index, span),
"cmd_name" => Value::string(command_name, value_span),
"type" => Value::string(value_type.to_string(), span),
"cmd_args" => Value::list(command_args_value, value_span),
"span_start" => Value::int(value_span_start, span),
"span_end" => Value::int(value_span_end, span),
};
element_values.push(rec);
element_values.push(Value::record(record, value_span));
i += 1;
}
}
@ -133,24 +119,14 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
let arg_value_name_span_start = name.span.start as i64;
let arg_value_name_span_end = name.span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, name.span),
Value::string("string".to_string(), span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: name.span,
let record = record! {
"arg_type" => Value::string(arg_type, span),
"name" => Value::string(arg_value_name, name.span),
"type" => Value::string("string", span),
"span_start" => Value::int(arg_value_name_span_start, span),
"span_end" => Value::int(arg_value_name_span_end, span),
};
arg_value.push(rec);
arg_value.push(Value::record(record, name.span));
if let Some(shortcut) = short {
let arg_type = "short";
@ -158,24 +134,14 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
let arg_value_name_span_start = shortcut.span.start as i64;
let arg_value_name_span_end = shortcut.span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, shortcut.span),
Value::string("string".to_string(), span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: name.span,
let record = record! {
"arg_type" => Value::string(arg_type, span),
"name" => Value::string(arg_value_name, shortcut.span),
"type" => Value::string("string", span),
"span_start" => Value::int(arg_value_name_span_start, span),
"span_end" => Value::int(arg_value_name_span_end, span),
};
arg_value.push(rec);
arg_value.push(Value::record(record, name.span));
};
if let Some(expression) = opt_expr {
@ -188,24 +154,14 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, expression.span),
Value::string(arg_value_type, span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: expression.span,
let record = record! {
"arg_type" => Value::string(arg_type, span),
"name" => Value::string(arg_value_name, expression.span),
"type" => Value::string(arg_value_type, span),
"span_start" => Value::int(arg_value_name_span_start, span),
"span_end" => Value::int(arg_value_name_span_end, span),
};
arg_value.push(rec);
arg_value.push(Value::record(record, expression.span));
};
}
Argument::Positional(inner_expr) => {
@ -217,24 +173,14 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, inner_expr.span),
Value::string(arg_value_type, span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: inner_expr.span,
let record = record! {
"arg_type" => Value::string(arg_type, span),
"name" => Value::string(arg_value_name, inner_expr.span),
"type" => Value::string(arg_value_type, span),
"span_start" => Value::int(arg_value_name_span_start, span),
"span_end" => Value::int(arg_value_name_span_end, span),
};
arg_value.push(rec);
arg_value.push(Value::record(record, inner_expr.span));
}
Argument::Unknown(inner_expr) => {
let arg_type = "unknown";
@ -245,24 +191,14 @@ fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> V
let arg_value_name_span_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record {
cols: vec![
"arg_type".to_string(),
"name".to_string(),
"type".to_string(),
"span_start".to_string(),
"span_end".to_string(),
],
vals: vec![
Value::string(arg_type, span),
Value::string(arg_value_name, inner_expr.span),
Value::string(arg_value_type, span),
Value::int(arg_value_name_span_start, span),
Value::int(arg_value_name_span_end, span),
],
span: inner_expr.span,
let record = record! {
"arg_type" => Value::string(arg_type, span),
"name" => Value::string(arg_value_name, inner_expr.span),
"type" => Value::string(arg_value_type, span),
"span_start" => Value::int(arg_value_name_span_start, span),
"span_end" => Value::int(arg_value_name_span_end, span),
};
arg_value.push(rec);
arg_value.push(Value::record(record, inner_expr.span));
}
};
}
@ -306,10 +242,9 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
.collect::<Vec<_>>()
.join(" ")
),
Value::Record { cols, vals, .. } => format!(
Value::Record { val, .. } => format!(
"{{{}}}",
cols.iter()
.zip(vals.iter())
val.iter()
.map(|(x, y)| format!("{}: {}", x, debug_string_without_formatting(y)))
.collect::<Vec<_>>()
.join(" ")

View File

@ -198,9 +198,10 @@ mod util {
/// Try to build column names and a table grid.
pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<String>>) {
match value {
Value::Record { cols, vals, .. } => (
cols,
vec![vals
Value::Record { val: record, .. } => (
record.cols,
vec![record
.vals
.into_iter()
.map(|s| debug_string_without_formatting(&s))
.collect()],

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::{Call, Expr, Expression};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, ShellError,
Signature, Span, SyntaxShape, Type, Value,
record, Category, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata,
Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -73,37 +73,22 @@ impl Command for Metadata {
Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data())
}
None => {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
if let Some(x) = input.metadata().as_deref() {
match x {
PipelineMetadata {
data_source: DataSource::Ls,
} => {
cols.push("source".into());
vals.push(Value::string("ls", head))
}
} => record.push("source", Value::string("ls", head)),
PipelineMetadata {
data_source: DataSource::HtmlThemes,
} => {
cols.push("source".into());
vals.push(Value::string("into html --list", head))
}
} => record.push("source", Value::string("into html --list", head)),
PipelineMetadata {
data_source: DataSource::Profiling(values),
} => {
cols.push("profiling".into());
vals.push(Value::list(values.clone(), head))
}
} => record.push("profiling", Value::list(values.clone(), head)),
}
}
Ok(Value::Record {
cols,
vals,
span: head,
}
.into_pipeline_data())
Ok(Value::record(record, head).into_pipeline_data())
}
}
}
@ -129,55 +114,36 @@ fn build_metadata_record(
metadata: &Option<Box<PipelineMetadata>>,
head: Span,
) -> Value {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
if let Ok(span) = arg.span() {
cols.push("span".into());
vals.push(Value::Record {
cols: vec!["start".into(), "end".into()],
vals: vec![
Value::Int {
val: span.start as i64,
span,
record.push(
"span",
Value::record(
record! {
"start" => Value::int(span.start as i64,span),
"end" => Value::int(span.end as i64, span),
},
Value::Int {
val: span.end as i64,
span,
},
],
span: head,
});
head,
),
);
}
if let Some(x) = metadata.as_deref() {
match x {
PipelineMetadata {
data_source: DataSource::Ls,
} => {
cols.push("source".into());
vals.push(Value::string("ls", head))
}
} => record.push("source", Value::string("ls", head)),
PipelineMetadata {
data_source: DataSource::HtmlThemes,
} => {
cols.push("source".into());
vals.push(Value::string("into html --list", head))
}
} => record.push("source", Value::string("into html --list", head)),
PipelineMetadata {
data_source: DataSource::Profiling(values),
} => {
cols.push("profiling".into());
vals.push(Value::list(values.clone(), head))
}
} => record.push("profiling", Value::list(values.clone(), head)),
}
}
Value::Record {
cols,
vals,
span: head,
}
Value::record(record, 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,
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
};
#[derive(Clone)]
@ -36,21 +36,15 @@ impl Command for ViewFiles {
let mut records = vec![];
for (file, start, end) in engine_state.files() {
records.push(Value::Record {
cols: vec![
"filename".to_string(),
"start".to_string(),
"end".to_string(),
"size".to_string(),
],
vals: vec![
Value::string(file, call.head),
Value::int(*start as i64, call.head),
Value::int(*end as i64, call.head),
Value::int(*end as i64 - *start as i64, call.head),
],
span: call.head,
});
records.push(Value::record(
record! {
"filename" => Value::string(file, call.head),
"start" => Value::int(*start as i64, call.head),
"end" => Value::int(*end as i64, call.head),
"size" => Value::int(*end as i64 - *start as i64, call.head),
},
call.head,
));
}
Ok(Value::List {

View File

@ -2,7 +2,7 @@ use nu_engine::{current_dir, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -39,12 +39,12 @@ impl Command for LoadEnv {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let arg: Option<(Vec<String>, Vec<Value>)> = call.opt(engine_state, stack, 0)?;
let arg: Option<Record> = call.opt(engine_state, stack, 0)?;
let span = call.head;
match arg {
Some((cols, vals)) => {
for (env_var, rhs) in cols.into_iter().zip(vals) {
Some(record) => {
for (env_var, rhs) in record {
let env_var_ = env_var.as_str();
if ["FILE_PWD", "CURRENT_FILE", "PWD"].contains(&env_var_) {
return Err(ShellError::AutomaticEnvVarSetManually {
@ -57,8 +57,8 @@ impl Command for LoadEnv {
Ok(PipelineData::empty())
}
None => match input {
PipelineData::Value(Value::Record { cols, vals, .. }, ..) => {
for (env_var, rhs) in cols.into_iter().zip(vals) {
PipelineData::Value(Value::Record { val, .. }, ..) => {
for (env_var, rhs) in val {
let env_var_ = env_var.as_str();
if ["FILE_PWD", "CURRENT_FILE"].contains(&env_var_) {
return Err(ShellError::AutomaticEnvVarSetManually {

View File

@ -94,8 +94,8 @@ fn with_env(
if table.len() == 1 {
// single row([[X W]; [Y Z]])
match &table[0] {
Value::Record { cols, vals, .. } => {
for (k, v) in cols.iter().zip(vals.iter()) {
Value::Record { val, .. } => {
for (k, v) in val {
env.insert(k.to_string(), v.clone());
}
}
@ -122,8 +122,8 @@ fn with_env(
}
}
// when get object by `open x.json` or `from json`
Value::Record { cols, vals, .. } => {
for (k, v) in cols.iter().zip(vals) {
Value::Record { val, .. } => {
for (k, v) in val {
env.insert(k.clone(), v.clone());
}
}

View File

@ -9,7 +9,7 @@ use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, DataSource, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
PipelineMetadata, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
PipelineMetadata, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
use pathdiff::diff_paths;
@ -431,219 +431,155 @@ pub(crate) fn dir_entry_dict(
));
}
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
let mut file_type = "unknown".to_string();
cols.push("name".into());
vals.push(Value::String {
val: display_name.to_string(),
span,
});
record.push("name", Value::string(display_name, span));
if let Some(md) = metadata {
file_type = get_file_type(md, display_name, use_mime_type);
cols.push("type".into());
vals.push(Value::String {
val: file_type.clone(),
span,
});
record.push("type", Value::string(file_type.clone(), span));
} else {
cols.push("type".into());
vals.push(Value::nothing(span));
record.push("type", Value::nothing(span));
}
if long {
cols.push("target".into());
if let Some(md) = metadata {
if md.file_type().is_symlink() {
if let Ok(path_to_link) = filename.read_link() {
vals.push(Value::String {
val: path_to_link.to_string_lossy().to_string(),
span,
});
record.push(
"target",
if md.file_type().is_symlink() {
if let Ok(path_to_link) = filename.read_link() {
Value::string(path_to_link.to_string_lossy(), span)
} else {
Value::string("Could not obtain target file's path", span)
}
} else {
vals.push(Value::String {
val: "Could not obtain target file's path".to_string(),
span,
});
}
} else {
vals.push(Value::nothing(span));
}
Value::nothing(span)
},
)
}
}
if long {
if let Some(md) = metadata {
cols.push("readonly".into());
vals.push(Value::Bool {
val: md.permissions().readonly(),
span,
});
record.push("readonly", Value::bool(md.permissions().readonly(), span));
#[cfg(unix)]
{
use crate::filesystem::util::users;
use std::os::unix::fs::MetadataExt;
let mode = md.permissions().mode();
cols.push("mode".into());
vals.push(Value::String {
val: umask::Mode::from(mode).to_string(),
span,
});
record.push(
"mode",
Value::string(umask::Mode::from(mode).to_string(), span),
);
let nlinks = md.nlink();
cols.push("num_links".into());
vals.push(Value::Int {
val: nlinks as i64,
span,
});
record.push("num_links", Value::int(nlinks as i64, span));
let inode = md.ino();
cols.push("inode".into());
vals.push(Value::Int {
val: inode as i64,
span,
});
record.push("inode", Value::int(inode as i64, span));
cols.push("user".into());
if let Some(user) = users::get_user_by_uid(md.uid()) {
vals.push(Value::String {
val: user.name,
span,
});
} else {
vals.push(Value::Int {
val: md.uid() as i64,
span,
})
}
record.push(
"user",
if let Some(user) = users::get_user_by_uid(md.uid()) {
Value::string(user.name, span)
} else {
Value::int(md.uid() as i64, span)
},
);
cols.push("group".into());
if let Some(group) = users::get_group_by_gid(md.gid()) {
vals.push(Value::String {
val: group.name,
span,
});
} else {
vals.push(Value::Int {
val: md.gid() as i64,
span,
})
}
record.push(
"group",
if let Some(group) = users::get_group_by_gid(md.gid()) {
Value::string(group.name, span)
} else {
Value::int(md.gid() as i64, span)
},
);
}
}
}
cols.push("size".to_string());
if let Some(md) = metadata {
let zero_sized = file_type == "pipe"
|| file_type == "socket"
|| file_type == "char device"
|| file_type == "block device";
record.push(
"size",
if let Some(md) = metadata {
let zero_sized = file_type == "pipe"
|| file_type == "socket"
|| file_type == "char device"
|| file_type == "block device";
if md.is_dir() {
if du {
let params = DirBuilder::new(Span::new(0, 2), None, false, None, false);
let dir_size = DirInfo::new(filename, &params, None, ctrl_c).get_size();
if md.is_dir() {
if du {
let params = DirBuilder::new(Span::new(0, 2), None, false, None, false);
let dir_size = DirInfo::new(filename, &params, None, ctrl_c).get_size();
vals.push(Value::Filesize {
val: dir_size as i64,
span,
});
} else {
let dir_size: u64 = md.len();
Value::filesize(dir_size as i64, span)
} else {
let dir_size: u64 = md.len();
vals.push(Value::Filesize {
val: dir_size as i64,
span,
});
};
} else if md.is_file() {
vals.push(Value::Filesize {
val: md.len() as i64,
span,
});
} else if md.file_type().is_symlink() {
if let Ok(symlink_md) = filename.symlink_metadata() {
vals.push(Value::Filesize {
val: symlink_md.len() as i64,
span,
});
} else {
vals.push(Value::nothing(span));
}
} else {
let value = if zero_sized {
Value::Filesize { val: 0, span }
Value::filesize(dir_size as i64, span)
}
} else if md.is_file() {
Value::filesize(md.len() as i64, span)
} else if md.file_type().is_symlink() {
if let Ok(symlink_md) = filename.symlink_metadata() {
Value::filesize(symlink_md.len() as i64, span)
} else {
Value::nothing(span)
}
} else if zero_sized {
Value::filesize(0, span)
} else {
Value::nothing(span)
};
vals.push(value);
}
} else {
vals.push(Value::nothing(span));
}
}
} else {
Value::nothing(span)
},
);
if let Some(md) = metadata {
if long {
cols.push("created".to_string());
{
record.push("created", {
let mut val = Value::nothing(span);
if let Ok(c) = md.created() {
if let Some(local) = try_convert_to_local_date_time(c) {
val = Value::Date {
val: local.with_timezone(local.offset()),
span,
};
val = Value::date(local.with_timezone(local.offset()), span);
}
}
vals.push(val);
}
val
});
cols.push("accessed".to_string());
{
record.push("accessed", {
let mut val = Value::nothing(span);
if let Ok(a) = md.accessed() {
if let Some(local) = try_convert_to_local_date_time(a) {
val = Value::Date {
val: local.with_timezone(local.offset()),
span,
};
val = Value::date(local.with_timezone(local.offset()), span)
}
}
vals.push(val);
}
val
});
}
cols.push("modified".to_string());
{
record.push("modified", {
let mut val = Value::nothing(span);
if let Ok(m) = md.modified() {
if let Some(local) = try_convert_to_local_date_time(m) {
val = Value::Date {
val: local.with_timezone(local.offset()),
span,
};
val = Value::date(local.with_timezone(local.offset()), span);
}
}
vals.push(val);
}
val
})
} else {
if long {
cols.push("created".to_string());
vals.push(Value::nothing(span));
cols.push("accessed".to_string());
vals.push(Value::nothing(span));
record.push("created", Value::nothing(span));
record.push("accessed", Value::nothing(span));
}
cols.push("modified".to_string());
vals.push(Value::nothing(span));
record.push("modified", Value::nothing(span));
}
Ok(Value::Record { cols, vals, span })
Ok(Value::record(record, span))
}
// TODO: can we get away from local times in `ls`? internals might be cleaner if we worked in UTC
@ -702,14 +638,9 @@ mod windows_helper {
span: Span,
long: bool,
) -> Value {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
cols.push("name".into());
vals.push(Value::String {
val: display_name.to_string(),
span,
});
record.push("name", Value::string(display_name, span));
let find_data = match find_first_file(filename, span) {
Ok(fd) => fd,
@ -722,90 +653,71 @@ mod windows_helper {
filename.to_string_lossy()
);
log::error!("{e}");
return Value::Record { cols, vals, span };
return Value::record(record, span);
}
};
cols.push("type".into());
vals.push(Value::String {
val: get_file_type_windows_fallback(&find_data),
span,
});
record.push(
"type",
Value::string(get_file_type_windows_fallback(&find_data), span),
);
if long {
cols.push("target".into());
if is_symlink(&find_data) {
if let Ok(path_to_link) = filename.read_link() {
vals.push(Value::String {
val: path_to_link.to_string_lossy().to_string(),
span,
});
record.push(
"target",
if is_symlink(&find_data) {
if let Ok(path_to_link) = filename.read_link() {
Value::string(path_to_link.to_string_lossy(), span)
} else {
Value::string("Could not obtain target file's path", span)
}
} else {
vals.push(Value::String {
val: "Could not obtain target file's path".to_string(),
span,
});
}
} else {
vals.push(Value::nothing(span));
}
Value::nothing(span)
},
);
cols.push("readonly".into());
vals.push(Value::Bool {
val: (find_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY.0 != 0),
span,
});
record.push(
"readonly",
Value::bool(
find_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY.0 != 0,
span,
),
);
}
cols.push("size".to_string());
let file_size = (find_data.nFileSizeHigh as u64) << 32 | find_data.nFileSizeLow as u64;
vals.push(Value::Filesize {
val: file_size as i64,
span,
});
record.push("size", Value::filesize(file_size as i64, span));
if long {
cols.push("created".to_string());
{
record.push("created", {
let mut val = Value::nothing(span);
let seconds_since_unix_epoch = unix_time_from_filetime(&find_data.ftCreationTime);
if let Some(local) = unix_time_to_local_date_time(seconds_since_unix_epoch) {
val = Value::Date {
val: local.with_timezone(local.offset()),
span,
};
val = Value::date(local.with_timezone(local.offset()), span);
}
vals.push(val);
}
val
});
cols.push("accessed".to_string());
{
record.push("accessed", {
let mut val = Value::nothing(span);
let seconds_since_unix_epoch = unix_time_from_filetime(&find_data.ftLastAccessTime);
if let Some(local) = unix_time_to_local_date_time(seconds_since_unix_epoch) {
val = Value::Date {
val: local.with_timezone(local.offset()),
span,
};
val = Value::date(local.with_timezone(local.offset()), span);
}
vals.push(val);
}
val
});
}
cols.push("modified".to_string());
{
record.push("modified", {
let mut val = Value::nothing(span);
let seconds_since_unix_epoch = unix_time_from_filetime(&find_data.ftLastWriteTime);
if let Some(local) = unix_time_to_local_date_time(seconds_since_unix_epoch) {
val = Value::Date {
val: local.with_timezone(local.offset()),
span,
};
val = Value::date(local.with_timezone(local.offset()), span);
}
vals.push(val);
}
val
});
Value::Record { cols, vals, span }
Value::record(record, span)
}
fn unix_time_from_filetime(ft: &FILETIME) -> i64 {

View File

@ -131,7 +131,8 @@ fn getcol(
.into_pipeline_data(ctrlc)
.set_metadata(metadata)
}),
PipelineData::Value(Value::Record { cols, .. }, ..) => Ok(cols
PipelineData::Value(Value::Record { val, .. }, ..) => Ok(val
.cols
.into_iter()
.map(move |x| Value::String { val: x, span: head })
.into_pipeline_data(ctrlc)

View File

@ -1,7 +1,7 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call, engine::Command, engine::EngineState, engine::Stack, Category, Example,
PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -63,11 +63,10 @@ impl Command for Compact {
description: "Filter out all records where 'World' is null (Returns the table)",
example: r#"[["Hello" "World"]; [null 3]] | compact World"#,
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["Hello".into(), "World".into()],
vals: vec![Value::nothing(Span::test_data()), Value::test_int(3)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -86,29 +86,27 @@ fn default(
input.map(
move |item| match item {
Value::Record {
mut cols,
mut vals,
val: mut record,
span,
} => {
let mut idx = 0;
let mut found = false;
while idx < cols.len() {
if cols[idx] == column.item {
while idx < record.len() {
if record.cols[idx] == column.item {
found = true;
if matches!(vals[idx], Value::Nothing { .. }) {
vals[idx] = value.clone();
if matches!(record.vals[idx], Value::Nothing { .. }) {
record.vals[idx] = value.clone();
}
}
idx += 1;
}
if !found {
cols.push(column.item.clone());
vals.push(value.clone());
record.push(column.item.clone(), value.clone());
}
Value::Record { cols, vals, span }
Value::record(record, span)
}
_ => item,
},

View File

@ -3,7 +3,7 @@ use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Type, Value,
Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -66,16 +66,14 @@ impl Command for DropColumn {
example: "[[lib, extension]; [nu-lib, rs] [nu-core, rb]] | drop column",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["lib".into()],
vals: vec![Value::test_string("nu-lib")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["lib".into()],
vals: vec![Value::test_string("nu-core")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -105,15 +103,13 @@ fn dropcol(
keep_columns = get_cellpath_columns(kc, span);
for input_val in input_vals {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for path in &keep_columns {
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
cols.push(path.into_string());
vals.push(fetcher);
record.push(path.into_string(), fetcher);
}
output.push(Value::Record { cols, vals, span })
output.push(Value::record(record, span))
}
Ok(output
@ -129,15 +125,13 @@ fn dropcol(
keep_columns = get_cellpath_columns(kc, span);
for input_val in v {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for path in &keep_columns {
let fetcher = input_val.clone().follow_cell_path(&path.members, false)?;
cols.push(path.into_string());
vals.push(fetcher);
record.push(path.into_string(), fetcher);
}
output.push(Value::Record { cols, vals, span })
output.push(Value::record(record, span))
}
Ok(output
@ -145,17 +139,14 @@ fn dropcol(
.into_pipeline_data(engine_state.ctrlc.clone()))
}
PipelineData::Value(v, ..) => {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for cell_path in &keep_columns {
let result = v.clone().follow_cell_path(&cell_path.members, false)?;
cols.push(cell_path.into_string());
vals.push(result);
record.push(cell_path.into_string(), result);
}
Ok(Value::Record { cols, vals, span }.into_pipeline_data())
Ok(Value::record(record, span).into_pipeline_data())
}
x => Ok(x),
}
@ -164,7 +155,7 @@ fn dropcol(
fn get_input_cols(input: Vec<Value>) -> Vec<String> {
let rec = input.first();
match rec {
Some(Value::Record { cols, vals: _, .. }) => cols.to_vec(),
Some(Value::Record { val, .. }) => val.cols.to_vec(),
_ => vec!["".to_string()],
}
}

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -71,11 +71,10 @@ impl Command for Drop {
description: "Remove the last row in a table",
example: "[[a, b]; [1, 2] [3, 4]] | drop 1",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -1,8 +1,8 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
Type, Value,
record, Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError,
Signature, Span, Type, Value,
};
#[derive(Clone)]
@ -33,21 +33,18 @@ impl Command for Enumerate {
example: r#"[a, b, c] | enumerate "#,
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["index".into(), "item".into()],
vals: vec![Value::test_int(0), Value::test_string("a")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["index".into(), "item".into()],
vals: vec![Value::test_int(1), Value::test_string("b")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["index".into(), "item".into()],
vals: vec![Value::test_int(2), Value::test_string("c")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -68,16 +65,14 @@ impl Command for Enumerate {
Ok(input
.into_iter()
.enumerate()
.map(move |(idx, x)| Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![
Value::Int {
val: idx as i64,
span,
.map(move |(idx, x)| {
Value::record(
record! {
"index" => Value::int(idx as i64, span),
"item" => x,
},
x,
],
span,
span,
)
})
.into_pipeline_data_with_metadata(metadata, ctrlc))
}

View File

@ -3,8 +3,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Record,
ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -205,11 +205,10 @@ a variable. On the other hand, the "row condition" syntax is not supported."#
description: "Filter rows of a table according to a condition",
example: "[{a: 1} {a: 2}] | filter {|x| $x.a > 1}",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(2)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -217,11 +216,10 @@ a variable. On the other hand, the "row condition" syntax is not supported."#
description: "Filter rows of a table according to a stored condition",
example: "let cond = {|x| $x.a > 1}; [{a: 1} {a: 2}] | filter $cond",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(2)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -9,7 +9,7 @@ use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Config, Example, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -126,13 +126,13 @@ impl Command for Find {
description: "Find value in records using regex",
example: r#"[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu""#,
result: Some(Value::List {
vals: vec![Value::test_record(
vec!["version", "name"],
vec![
vals: vec![Value::test_record(Record {
cols: vec!["version".to_string(), "name".to_string()],
vals: vec![
Value::test_string("0.1.0"),
Value::test_string("nushell".to_string()),
],
)],
})],
span: Span::test_data(),
}),
},
@ -141,20 +141,20 @@ impl Command for Find {
example: r#"[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu" --invert"#,
result: Some(Value::List {
vals: vec![
Value::test_record(
vec!["version", "name"],
vec![
Value::test_record(Record {
cols: vec!["version".to_string(), "name".to_string()],
vals: vec![
Value::test_string("0.1.1"),
Value::test_string("fish".to_string()),
],
),
Value::test_record(
vec!["version", "name"],
vec![
}),
Value::test_record(Record {
cols: vec!["version".to_string(), "name".to_string()],
vals: vec![
Value::test_string("0.2.0"),
Value::test_string("zsh".to_string()),
],
),
}),
],
span: Span::test_data(),
}),
@ -191,9 +191,9 @@ impl Command for Find {
example:
"[[col1 col2 col3]; [moe larry curly] [larry curly moe]] | find moe -c [col1]",
result: Some(Value::List {
vals: vec![Value::test_record(
vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
vec![
vals: vec![Value::test_record(Record {
cols: vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
vals: vec![
Value::test_string(
"\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m"
.to_string(),
@ -201,7 +201,7 @@ impl Command for Find {
Value::test_string("larry".to_string()),
Value::test_string("curly".to_string()),
],
)],
})],
span: Span::test_data(),
}),
},
@ -267,9 +267,11 @@ fn find_with_regex(
input.filter(
move |value| match value {
Value::String { val, .. } => re.is_match(val.as_str()).unwrap_or(false) != invert,
Value::Record { vals, .. } | Value::List { vals, .. } => {
values_match_find(vals, &re, &config, invert)
Value::Record {
val: Record { vals, .. },
..
}
| Value::List { vals, .. } => values_match_find(vals, &re, &config, invert),
_ => false,
},
ctrlc,
@ -293,8 +295,7 @@ fn record_matches_regex(values: &[Value], re: &Regex, config: &Config) -> bool {
#[allow(clippy::too_many_arguments)]
fn highlight_terms_in_record_with_search_columns(
search_cols: &Vec<String>,
cols: &[String],
vals: &[Value],
record: &Record,
span: Span,
config: &Config,
terms: &[Value],
@ -302,14 +303,14 @@ fn highlight_terms_in_record_with_search_columns(
highlight_style: Style,
) -> Value {
let cols_to_search = if search_cols.is_empty() {
cols.to_vec()
&record.cols
} else {
search_cols.to_vec()
search_cols
};
let term_strs: Vec<_> = terms.iter().map(|v| v.into_string("", config)).collect();
// iterator of Ok((val_str, term_str)) pairs if the value should be highlighted, otherwise Err(val)
let try_val_highlight = vals.iter().zip(cols).map(|(val, col)| {
let try_val_highlight = record.iter().map(|(col, val)| {
let val_str = val.into_string("", config);
let predicate = cols_to_search.contains(col);
predicate
@ -337,11 +338,13 @@ fn highlight_terms_in_record_with_search_columns(
})
.map(|v| v.unwrap_or_else(|v| v));
Value::Record {
cols: cols.to_vec(),
vals: new_vals.collect(),
Value::record(
Record {
cols: record.cols.clone(),
vals: new_vals.collect(),
},
span,
}
)
}
fn contains_ignore_case(string: &str, substring: &str) -> bool {
@ -392,18 +395,15 @@ fn find_with_rest_and_highlight(
PipelineData::Value(_, _) => input
.map(
move |mut x| match &mut x {
Value::Record { cols, vals, span } => {
highlight_terms_in_record_with_search_columns(
&cols_to_search_in_map,
cols,
vals,
*span,
&config,
&terms,
string_style,
highlight_style,
)
}
Value::Record { val, span } => highlight_terms_in_record_with_search_columns(
&cols_to_search_in_map,
val,
*span,
&config,
&terms,
string_style,
highlight_style,
),
_ => x,
},
ctrlc.clone(),
@ -424,18 +424,15 @@ fn find_with_rest_and_highlight(
PipelineData::ListStream(stream, meta) => Ok(ListStream::from_stream(
stream
.map(move |mut x| match &mut x {
Value::Record { cols, vals, span } => {
highlight_terms_in_record_with_search_columns(
&cols_to_search_in_map,
cols,
vals,
*span,
&config,
&terms,
string_style,
highlight_style,
)
}
Value::Record { val, span } => highlight_terms_in_record_with_search_columns(
&cols_to_search_in_map,
val,
*span,
&config,
&terms,
string_style,
highlight_style,
),
_ => x,
})
.filter(move |value| {
@ -535,13 +532,13 @@ fn value_should_be_printed(
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term_contains_value(term, &lower_value, span),
Value::Record { cols, vals, .. } => {
record_matches_term(cols, vals, columns_to_search, filter_config, term, span)
Value::Record { val, .. } => {
record_matches_term(val, columns_to_search, filter_config, term, span)
}
Value::LazyRecord { val, .. } => match val.collect() {
Ok(val) => match val {
Value::Record { cols, vals, .. } => {
record_matches_term(&cols, &vals, columns_to_search, filter_config, term, span)
Value::Record { val, .. } => {
record_matches_term(&val, columns_to_search, filter_config, term, span)
}
_ => false,
},
@ -567,19 +564,18 @@ fn term_equals_value(term: &Value, value: &Value, span: Span) -> bool {
}
fn record_matches_term(
cols: &[String],
vals: &[Value],
record: &Record,
columns_to_search: &Vec<String>,
filter_config: &Config,
term: &Value,
span: Span,
) -> bool {
let cols_to_search = if columns_to_search.is_empty() {
cols.to_vec()
&record.cols
} else {
columns_to_search.to_vec()
columns_to_search
};
cols.iter().zip(vals).any(|(col, val)| {
record.iter().any(|(col, val)| {
if !cols_to_search.contains(col) {
return false;
}

View File

@ -4,7 +4,7 @@ use nu_protocol::ast::{Call, CellPath, PathMember};
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)]
@ -84,26 +84,50 @@ impl Command for Flatten {
example: "{ a: b, d: [ 1 2 3 4 ], e: [ 4 3 ] } | flatten d --all",
result: Some(Value::List{
vals: vec![
Value::Record{
Value::test_record(Record {
cols: vec!["a".to_string(), "d".to_string(), "e".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(1), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ],
span: Span::test_data()
},
Value::Record{
vals: vec![
Value::test_string("b"),
Value::test_int(1),
Value::List {
vals: vec![Value::test_int(4), Value::test_int(3)],
span: Span::test_data(),
},
],
}),
Value::test_record(Record {
cols: vec!["a".to_string(), "d".to_string(), "e".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(2), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ],
span: Span::test_data()
},
Value::Record{
vals: vec![
Value::test_string("b"),
Value::test_int(2),
Value::List {
vals: vec![Value::test_int(4), Value::test_int(3)],
span: Span::test_data(),
},
],
}),
Value::test_record(Record {
cols: vec!["a".to_string(), "d".to_string(), "e".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(3), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ],
span: Span::test_data()
},
Value::Record{
vals: vec![
Value::test_string("b"),
Value::test_int(3),
Value::List {
vals: vec![Value::test_int(4), Value::test_int(3)],
span: Span::test_data(),
},
],
}),
Value::test_record(Record {
cols: vec!["a".to_string(), "d".to_string(), "e".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(4), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ],
span: Span::test_data()
}
vals: vec![
Value::test_string("b"),
Value::test_int(4),
Value::List {
vals: vec![Value::test_int(4), Value::test_int(3)],
span: Span::test_data()
}
],
}),
],
span: Span::test_data(),
}),
@ -157,259 +181,208 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span, all: bool) ->
Err(e) => return vec![Value::Error { error: Box::new(e) }],
};
let res = {
if item.as_record().is_ok() {
let mut out = IndexMap::<String, Value>::new();
let mut inner_table = None;
if item.as_record().is_ok() {
let mut out = IndexMap::<String, Value>::new();
let mut inner_table = None;
let records = match item {
Value::Record {
cols,
vals,
span: _,
} => (cols, vals),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return vec![item.clone()],
other => {
return vec![Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: other.get_type().to_string(),
dst_span: _name_tag,
src_span: other.expect_span(),
}),
}];
}
};
let record = match item {
Value::Record { val, .. } => val,
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return vec![item.clone()],
other => {
return vec![Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: other.get_type().to_string(),
dst_span: _name_tag,
src_span: other.expect_span(),
}),
}];
}
};
let s = match item.span() {
Ok(x) => x,
Err(e) => return vec![Value::Error { error: Box::new(e) }],
};
let s = match item.span() {
Ok(x) => x,
Err(e) => return vec![Value::Error { error: Box::new(e) }],
};
let records_iterator = {
let cols = records.0;
let vals = records.1;
for (column_index, (column, value)) in record.iter().enumerate() {
let column_requested = columns.iter().find(|c| c.into_string() == *column);
let need_flatten = { columns.is_empty() || column_requested.is_some() };
let mut pairs = vec![];
for i in 0..cols.len() {
pairs.push((cols[i].as_str(), &vals[i]));
}
pairs
};
for (column_index, (column, value)) in records_iterator.into_iter().enumerate() {
let column_requested = columns.iter().find(|c| c.into_string() == *column);
let need_flatten = { columns.is_empty() || column_requested.is_some() };
match value {
Value::Record {
cols,
vals,
span: _,
} => {
if need_flatten {
cols.iter().enumerate().for_each(|(idx, inner_record_col)| {
if out.contains_key(inner_record_col) {
out.insert(
format!("{column}_{inner_record_col}"),
vals[idx].clone(),
);
} else {
out.insert(inner_record_col.to_string(), vals[idx].clone());
}
})
} else if out.contains_key(column) {
out.insert(format!("{column}_{column}"), value.clone());
} else {
out.insert(column.to_string(), value.clone());
}
}
Value::List { vals, span }
if all && vals.iter().all(|f| f.as_record().is_ok()) =>
{
if need_flatten && inner_table.is_some() {
return vec![Value::Error{ error: Box::new(ShellError::UnsupportedInput(
"can only flatten one inner list at a time. tried flattening more than one column with inner lists... but is flattened already".to_string(),
"value originates from here".into(),
s,
*span
))}
];
}
// it's a table (a list of record, we can flatten inner record)
let mut cs = vec![];
let mut vs = vec![];
for v in vals {
if let Ok(r) = v.as_record() {
cs.push(r.0);
vs.push(r.1)
}
}
if need_flatten {
let cols = cs.into_iter().map(|f| f.to_vec());
let vals = vs.into_iter().map(|f| f.to_vec());
inner_table = Some(TableInside::FlattenedRows {
columns: cols.collect(),
_span: &s,
values: vals.collect(),
parent_column_name: column,
parent_column_index: column_index,
});
} else if out.contains_key(column) {
out.insert(format!("{column}_{column}"), value.clone());
} else {
out.insert(column.to_string(), value.clone());
}
}
Value::List { vals: values, span } => {
if need_flatten && inner_table.is_some() {
return vec![Value::Error{ error: Box::new(ShellError::UnsupportedInput(
"can only flatten one inner list at a time. tried flattening more than one column with inner lists... but is flattened already".to_string(),
"value originates from here".into(),
s,
*span
))}
];
}
if !columns.is_empty() {
let cell_path =
column_requested.and_then(|x| match x.members.first() {
Some(PathMember::String { val, span: _, .. }) => Some(val),
_ => None,
});
if let Some(r) = cell_path {
inner_table = Some(TableInside::Entries(
r,
&s,
values.iter().collect::<Vec<_>>(),
column_index,
));
match value {
Value::Record { val, .. } => {
if need_flatten {
val.iter().for_each(|(col, val)| {
if out.contains_key(col) {
out.insert(format!("{column}_{col}"), val.clone());
} else {
out.insert(column.to_string(), value.clone());
out.insert(col.to_string(), val.clone());
}
} else {
})
} else if out.contains_key(column) {
out.insert(format!("{column}_{column}"), value.clone());
} else {
out.insert(column.to_string(), value.clone());
}
}
Value::List { vals, span } if all && vals.iter().all(|f| f.as_record().is_ok()) => {
if need_flatten && inner_table.is_some() {
return vec![Value::Error{ error: Box::new(ShellError::UnsupportedInput(
"can only flatten one inner list at a time. tried flattening more than one column with inner lists... but is flattened already".to_string(),
"value originates from here".into(),
s,
*span
))}
];
}
// it's a table (a list of record, we can flatten inner record)
let mut records = vec![];
for v in vals {
if let Ok(r) = v.as_record() {
records.push(r)
}
}
if need_flatten {
let cols = records.iter().map(|r| r.cols.clone());
let vals = records.iter().map(|r| r.vals.clone());
inner_table = Some(TableInside::FlattenedRows {
columns: cols.collect(),
_span: &s,
values: vals.collect(),
parent_column_name: column,
parent_column_index: column_index,
});
} else if out.contains_key(column) {
out.insert(format!("{column}_{column}"), value.clone());
} else {
out.insert(column.to_string(), value.clone());
}
}
Value::List { vals: values, span } => {
if need_flatten && inner_table.is_some() {
return vec![Value::Error{ error: Box::new(ShellError::UnsupportedInput(
"can only flatten one inner list at a time. tried flattening more than one column with inner lists... but is flattened already".to_string(),
"value originates from here".into(),
s,
*span
))}
];
}
if !columns.is_empty() {
let cell_path = column_requested.and_then(|x| match x.members.first() {
Some(PathMember::String { val, span: _, .. }) => Some(val),
_ => None,
});
if let Some(r) = cell_path {
inner_table = Some(TableInside::Entries(
column,
r,
&s,
values.iter().collect::<Vec<_>>(),
column_index,
));
} else {
out.insert(column.to_string(), value.clone());
}
} else {
inner_table = Some(TableInside::Entries(
column,
&s,
values.iter().collect::<Vec<_>>(),
column_index,
));
}
_ => {
out.insert(column.to_string(), value.clone());
}
}
_ => {
out.insert(column.to_string(), value.clone());
}
}
}
let mut expanded = vec![];
match inner_table {
Some(TableInside::Entries(column, _, entries, parent_column_index)) => {
for entry in entries {
let base = out.clone();
let (mut record_cols, mut record_vals) = (vec![], vec![]);
let mut index = 0;
for (col, val) in base.into_iter() {
// meet the flattened column, push them to result record first
// this can avoid output column order changed.
if index == parent_column_index {
record_cols.push(column.to_string());
record_vals.push(entry.clone());
}
record_cols.push(col);
record_vals.push(val);
index += 1;
}
// the flattened column may be the last column in the original table.
let mut expanded = vec![];
match inner_table {
Some(TableInside::Entries(column, _, entries, parent_column_index)) => {
for entry in entries {
let base = out.clone();
let mut record = Record::new();
let mut index = 0;
for (col, val) in base.into_iter() {
// meet the flattened column, push them to result record first
// this can avoid output column order changed.
if index == parent_column_index {
record_cols.push(column.to_string());
record_vals.push(entry.clone());
record.push(column, entry.clone());
}
let record = Value::Record {
cols: record_cols,
vals: record_vals,
span: tag,
};
expanded.push(record);
record.push(col, val);
index += 1;
}
// the flattened column may be the last column in the original table.
if index == parent_column_index {
record.push(column, entry.clone());
}
expanded.push(Value::record(record, tag));
}
Some(TableInside::FlattenedRows {
columns,
_span,
values,
parent_column_name,
parent_column_index,
}) => {
for (inner_cols, inner_vals) in columns.into_iter().zip(values) {
let base = out.clone();
let (mut record_cols, mut record_vals) = (vec![], vec![]);
let mut index = 0;
}
Some(TableInside::FlattenedRows {
columns,
_span,
values,
parent_column_name,
parent_column_index,
}) => {
for (inner_cols, inner_vals) in columns.into_iter().zip(values) {
let base = out.clone();
let mut record = Record::new();
let mut index = 0;
for (base_col, base_val) in base.into_iter() {
// meet the flattened column, push them to result record first
// this can avoid output column order changed.
if index == parent_column_index {
for (col, val) in inner_cols.iter().zip(inner_vals.iter()) {
if record_cols.contains(col) {
record_cols.push(format!("{parent_column_name}_{col}"));
} else {
record_cols.push(col.to_string());
}
record_vals.push(val.clone());
}
}
record_cols.push(base_col);
record_vals.push(base_val);
index += 1;
}
// the flattened column may be the last column in the original table.
for (base_col, base_val) in base.into_iter() {
// meet the flattened column, push them to result record first
// this can avoid output column order changed.
if index == parent_column_index {
for (col, val) in inner_cols.iter().zip(inner_vals.iter()) {
if record_cols.contains(col) {
record_cols.push(format!("{parent_column_name}_{col}"));
if record.cols.contains(col) {
record.push(format!("{parent_column_name}_{col}"), val.clone());
} else {
record_cols.push(col.to_string());
}
record_vals.push(val.clone());
record.push(col, val.clone());
};
}
}
let record = Value::Record {
cols: record_cols,
vals: record_vals,
span: tag,
};
expanded.push(record);
record.push(base_col, base_val);
index += 1;
}
}
None => {
let record = Value::Record {
cols: out.keys().map(|f| f.to_string()).collect::<Vec<_>>(),
vals: out.values().cloned().collect(),
span: tag,
};
expanded.push(record);
// the flattened column may be the last column in the original table.
if index == parent_column_index {
for (col, val) in inner_cols.iter().zip(inner_vals.iter()) {
if record.cols.contains(col) {
record.push(format!("{parent_column_name}_{col}"), val.clone());
} else {
record.push(col, val.clone());
}
}
}
expanded.push(Value::record(record, tag));
}
}
expanded
} else if item.as_list().is_ok() {
if let Value::List { vals, span: _ } = item {
vals.to_vec()
} else {
vec![]
None => {
expanded.push(Value::record(out.into_iter().collect(), tag));
}
} else {
vec![item.clone()]
}
};
res
expanded
} else if item.as_list().is_ok() {
if let Value::List { vals, span: _ } = item {
vals.to_vec()
} else {
vec![]
}
} else {
vec![item.clone()]
}
}
#[cfg(test)]

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
use indexmap::IndexMap;
@ -68,7 +68,7 @@ impl Command for GroupBy {
Example {
description: "Group using a block which is evaluated against each input value",
example: "[foo.txt bar.csv baz.txt] | group-by { path parse | get extension }",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["txt".to_string(), "csv".to_string()],
vals: vec![
Value::List {
@ -83,14 +83,13 @@ impl Command for GroupBy {
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
})),
},
Example {
description: "You can also group by raw values by leaving out the argument",
example: "['1' '3' '1' '3' '2' '1' '1'] | group-by",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["1".to_string(), "3".to_string(), "2".to_string()],
vals: vec![
Value::List {
@ -111,8 +110,7 @@ impl Command for GroupBy {
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
})),
},
]
}
@ -177,15 +175,13 @@ pub fn group_cell_path(
group.push(value);
}
let mut cols = vec![];
let mut vals = vec![];
for (k, v) in groups {
cols.push(k.to_string());
vals.push(Value::List { vals: v, span });
}
Ok(Value::Record { cols, vals, span })
Ok(Value::record(
groups
.into_iter()
.map(|(k, v)| (k, Value::list(v, span)))
.collect(),
span,
))
}
pub fn group_no_grouper(values: Vec<Value>, span: Span) -> Result<Value, ShellError> {
@ -197,15 +193,13 @@ pub fn group_no_grouper(values: Vec<Value>, span: Span) -> Result<Value, ShellEr
group.push(value);
}
let mut cols = vec![];
let mut vals = vec![];
for (k, v) in groups {
cols.push(k.to_string());
vals.push(Value::List { vals: v, span });
}
Ok(Value::Record { cols, vals, span })
Ok(Value::record(
groups
.into_iter()
.map(|(k, v)| (k, Value::list(v, span)))
.collect(),
span,
))
}
// TODO: refactor this, it's a bit of a mess
@ -285,15 +279,13 @@ fn group_closure(
group.push(value);
}
let mut cols = vec![];
let mut vals = vec![];
for (k, v) in groups {
cols.push(k.to_string());
vals.push(Value::List { vals: v, span });
}
Ok(Value::Record { cols, vals, span })
Ok(Value::record(
groups
.into_iter()
.map(|(k, v)| (k, Value::list(v, span)))
.collect(),
span,
))
}
#[cfg(test)]

View File

@ -1,8 +1,8 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
Value,
Category, Config, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Type, Value,
};
#[derive(Clone)]
@ -37,15 +37,14 @@ impl Command for Headers {
description: "Sets the column names for a table created by `split column`",
example: r#""a b c|1 2 3" | split row "|" | split column " " | headers"#,
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: columns.clone(),
vals: vec![
Value::test_string("1"),
Value::test_string("2"),
Value::test_string("3"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -54,24 +53,22 @@ impl Command for Headers {
example: r#""a b c|1 2 3|1 2 3 4" | split row "|" | split column " " | headers"#,
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: columns.clone(),
vals: vec![
Value::test_string("1"),
Value::test_string("2"),
Value::test_string("3"),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: columns,
vals: vec![
Value::test_string("1"),
Value::test_string("2"),
Value::test_string("3"),
],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -102,20 +99,17 @@ fn replace_headers(
new_headers: &[String],
) -> Result<Value, ShellError> {
match value {
Value::Record { cols, vals, span } => {
let (cols, vals) = cols
.into_iter()
.zip(vals)
Value::Record { val, span } => Ok(Value::record(
val.into_iter()
.filter_map(|(col, val)| {
old_headers
.iter()
.position(|c| c == &col)
.map(|i| (new_headers[i].clone(), val))
})
.unzip();
Ok(Value::Record { cols, vals, span })
}
.collect(),
span,
)),
Value::List { vals, span } => {
let vals = vals
.into_iter()
@ -148,8 +142,8 @@ fn extract_headers(
config: &Config,
) -> Result<(Vec<String>, Vec<String>), ShellError> {
match value {
Value::Record { cols, vals, .. } => {
for v in vals {
Value::Record { val: record, .. } => {
for v in &record.vals {
if !is_valid_header(v) {
return Err(ShellError::TypeMismatch {
err_message: "needs compatible type: Null, String, Bool, Float, Int"
@ -159,8 +153,9 @@ fn extract_headers(
}
}
let old_headers = cols.to_vec();
let new_headers = vals
let old_headers = record.cols.clone();
let new_headers = record
.vals
.iter()
.enumerate()
.map(|(idx, value)| {

View File

@ -3,7 +3,7 @@ use nu_protocol::ast::{Call, CellPath, PathMember};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Type, Value,
Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -60,51 +60,56 @@ impl Command for Insert {
vec![Example {
description: "Insert a new entry into a single record",
example: "{'name': 'nu', 'stars': 5} | insert alias 'Nushell'",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["name".into(), "stars".into(), "alias".into()],
vals: vec![
Value::test_string("nu"),
Value::test_int(5),
Value::test_string("Nushell"),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Insert a new column into a table, populating all rows",
example: "[[project, lang]; ['Nushell', 'Rust']] | insert type 'shell'",
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "lang".into(), "type".into()],
vals: vec![Value::test_string("Nushell"), Value::test_string("Rust"), Value::test_string("shell")], span: Span::test_data()}], span: Span::test_data()}),
result: Some(Value::List {
vals: vec![Value::test_record(Record {
cols: vec!["project".into(), "lang".into(), "type".into()],
vals: vec![Value::test_string("Nushell"), Value::test_string("Rust"), Value::test_string("shell")],
})],
span: Span::test_data(),
}),
},
Example {
description: "Insert a column with values equal to their row index, plus the value of 'foo' in each row",
example: "[[foo]; [7] [8] [9]] | enumerate | insert bar {|e| $e.item.foo + $e.index } | flatten",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["index".into(), "foo".into(), "bar".into()],
vals: vec![
Value::test_int(0),
Value::test_int(7),
Value::test_int(7),
],
span: Span::test_data(),
}, Value::Record {
cols: vec!["index".into(),"foo".into(), "bar".into()],
vals: vec![
Value::test_int(1),
Value::test_int(8),
Value::test_int(9),
],
span: Span::test_data(),
}, Value::Record {
cols: vec!["index".into(), "foo".into(), "bar".into()],
vals: vec![
Value::test_int(2),
Value::test_int(9),
Value::test_int(11),
],
span: Span::test_data(),
}],
vals: vec![
Value::test_record(Record {
cols: vec!["index".into(), "foo".into(), "bar".into()],
vals: vec![
Value::test_int(0),
Value::test_int(7),
Value::test_int(7),
],
}),
Value::test_record(Record {
cols: vec!["index".into(),"foo".into(), "bar".into()],
vals: vec![
Value::test_int(1),
Value::test_int(8),
Value::test_int(9),
],
}),
Value::test_record(Record {
cols: vec!["index".into(), "foo".into(), "bar".into()],
vals: vec![
Value::test_int(2),
Value::test_int(9),
Value::test_int(11),
],
}),
],
span: Span::test_data(),
}),
}]

View File

@ -94,9 +94,8 @@ impl Command for Items {
};
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(Value::Record { cols, vals, .. }, ..) => Ok(cols
PipelineData::Value(Value::Record { val, .. }, ..) => Ok(val
.into_iter()
.zip(vals)
.map_while(run_for_each_item)
.into_pipeline_data(ctrlc)),
// Errors

View File

@ -2,7 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Config, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Config, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape,
Type, Value,
};
use std::cmp::max;
use std::collections::{HashMap, HashSet};
@ -22,8 +23,6 @@ enum IncludeInner {
Yes,
}
type RowEntries<'a> = Vec<(&'a Vec<String>, &'a Vec<Value>)>;
const EMPTY_COL_NAMES: &Vec<String> = &vec![];
impl Command for Join {
@ -112,7 +111,7 @@ impl Command for Join {
description: "Join two tables",
example: "[{a: 1 b: 2}] | join [{a: 1 c: 3}] a",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["a".into(), "b".into(), "c".into()],
vals: vec![
Value::Int {
@ -128,8 +127,7 @@ impl Command for Join {
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
}]
@ -265,8 +263,8 @@ fn join_rows(
result: &mut Vec<Value>,
this: &Vec<Value>,
this_join_key: &str,
other: HashMap<String, RowEntries>,
other_keys: &Vec<String>,
other: HashMap<String, Vec<&Record>>,
other_keys: &[String],
shared_join_key: Option<&str>,
join_type: &JoinType,
include_inner: IncludeInner,
@ -276,71 +274,60 @@ fn join_rows(
) {
for this_row in this {
if let Value::Record {
cols: this_cols,
vals: this_vals,
..
val: this_record, ..
} = this_row
{
if let Some(this_valkey) = this_row.get_data_by_key(this_join_key) {
if let Some(other_rows) = other.get(&this_valkey.into_string(sep, config)) {
if matches!(include_inner, IncludeInner::Yes) {
for (other_cols, other_vals) in other_rows {
for other_record in other_rows {
// `other` table contains rows matching `this` row on the join column
let (res_cols, res_vals) = match join_type {
let record = match join_type {
JoinType::Inner | JoinType::Right => merge_records(
(other_cols, other_vals), // `other` (lookup) is the left input table
(this_cols, this_vals),
other_record, // `other` (lookup) is the left input table
this_record,
shared_join_key,
),
JoinType::Left => merge_records(
(this_cols, this_vals), // `this` is the left input table
(other_cols, other_vals),
this_record, // `this` is the left input table
other_record,
shared_join_key,
),
_ => panic!("not implemented"),
};
result.push(Value::Record {
cols: res_cols,
vals: res_vals,
span,
})
result.push(Value::record(record, span))
}
}
} else if !matches!(join_type, JoinType::Inner) {
// `other` table did not contain any rows matching
// `this` row on the join column; emit a single joined
// row with null values for columns not present,
let other_vals = other_keys
let other_record = other_keys
.iter()
.map(|key| {
if Some(key.as_ref()) == shared_join_key {
let val = if Some(key.as_ref()) == shared_join_key {
this_row
.get_data_by_key(key)
.unwrap_or_else(|| Value::nothing(span))
} else {
Value::nothing(span)
}
};
(key.clone(), val)
})
.collect();
let (res_cols, res_vals) = match join_type {
JoinType::Inner | JoinType::Right => merge_records(
(other_keys, &other_vals),
(this_cols, this_vals),
shared_join_key,
),
JoinType::Left => merge_records(
(this_cols, this_vals),
(other_keys, &other_vals),
shared_join_key,
),
let record = match join_type {
JoinType::Inner | JoinType::Right => {
merge_records(&other_record, this_record, shared_join_key)
}
JoinType::Left => {
merge_records(this_record, &other_record, shared_join_key)
}
_ => panic!("not implemented"),
};
result.push(Value::Record {
cols: res_cols,
vals: res_vals,
span,
})
result.push(Value::record(record, span))
}
} // else { a row is missing a value for the join column }
};
@ -353,7 +340,7 @@ fn column_names(table: &[Value]) -> &Vec<String> {
table
.iter()
.find_map(|val| match val {
Value::Record { cols, .. } => Some(cols),
Value::Record { val, .. } => Some(&val.cols),
_ => None,
})
.unwrap_or(EMPTY_COL_NAMES)
@ -367,13 +354,13 @@ fn lookup_table<'a>(
sep: &str,
cap: usize,
config: &Config,
) -> HashMap<String, RowEntries<'a>> {
let mut map = HashMap::<String, RowEntries>::with_capacity(cap);
) -> HashMap<String, Vec<&'a Record>> {
let mut map = HashMap::<String, Vec<&'a Record>>::with_capacity(cap);
for row in rows {
if let Value::Record { cols, vals, .. } = row {
if let Value::Record { val: record, .. } = row {
if let Some(val) = &row.get_data_by_key(on) {
let valkey = val.into_string(sep, config);
map.entry(valkey).or_default().push((cols, vals));
map.entry(valkey).or_default().push(record);
}
};
}
@ -383,31 +370,27 @@ fn lookup_table<'a>(
// Merge `left` and `right` records, renaming keys in `right` where they clash
// with keys in `left`. If `shared_key` is supplied then it is the name of a key
// that should not be renamed (its values are guaranteed to be equal).
fn merge_records(
left: (&Vec<String>, &Vec<Value>),
right: (&Vec<String>, &Vec<Value>),
shared_key: Option<&str>,
) -> (Vec<String>, Vec<Value>) {
let ((l_keys, l_vals), (r_keys, r_vals)) = (left, right);
let cap = max(l_keys.len(), r_keys.len());
fn merge_records(left: &Record, right: &Record, shared_key: Option<&str>) -> Record {
let cap = max(left.len(), right.len());
let mut seen = HashSet::with_capacity(cap);
let (mut res_keys, mut res_vals) = (Vec::with_capacity(cap), Vec::with_capacity(cap));
for (k, v) in l_keys.iter().zip(l_vals) {
res_keys.push(k.clone());
res_vals.push(v.clone());
let mut record = Record::with_capacity(cap);
for (k, v) in left {
record.push(k.clone(), v.clone());
seen.insert(k);
}
for (k, v) in r_keys.iter().zip(r_vals) {
for (k, v) in right {
let k_seen = seen.contains(k);
let k_shared = shared_key == Some(k);
// Do not output shared join key twice
if !(k_seen && k_shared) {
res_keys.push(if k_seen { format!("{}_", k) } else { k.clone() });
res_vals.push(v.clone());
record.push(
if k_seen { format!("{}_", k) } else { k.clone() },
v.clone(),
);
}
}
(res_keys, res_vals)
record
}
#[cfg(test)]

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Record,
ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -48,18 +48,18 @@ repeating this process with row 1, and so on."#
description: "Add an 'index' column to the input table",
result: Some(Value::List {
vals: vec![
Value::test_record(
vec!["name", "index"],
vec![Value::test_string("a"), Value::test_int(1)],
),
Value::test_record(
vec!["name", "index"],
vec![Value::test_string("b"), Value::test_int(2)],
),
Value::test_record(
vec!["name", "index"],
vec![Value::test_string("c"), Value::test_int(3)],
),
Value::test_record(Record {
cols: vec!["name".to_string(), "index".to_string()],
vals: vec![Value::test_string("a"), Value::test_int(1)],
}),
Value::test_record(Record {
cols: vec!["name".to_string(), "index".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(2)],
}),
Value::test_record(Record {
cols: vec!["name".to_string(), "index".to_string()],
vals: vec![Value::test_string("c"), Value::test_int(3)],
}),
],
span: Span::test_data(),
}),
@ -67,20 +67,19 @@ repeating this process with row 1, and so on."#
Example {
example: "{a: 1, b: 2} | merge {c: 3}",
description: "Merge two records",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string(), "c".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
span: Span::test_data(),
}),
})),
},
Example {
example: "[{columnA: A0 columnB: B0}] | merge [{columnA: 'A0*'}]",
description: "Merge two tables, overwriting overlapping columns",
result: Some(Value::List {
vals: vec![Value::test_record(
vec!["columnA", "columnB"],
vec![Value::test_string("A0*"), Value::test_string("B0")],
)],
vals: vec![Value::test_record(Record {
cols: vec!["columnA".to_string(), "columnB".to_string()],
vals: vec![Value::test_string("A0*"), Value::test_string("B0")],
})],
span: Span::test_data(),
}),
},
@ -112,24 +111,12 @@ repeating this process with row 1, and so on."#
input
.into_iter()
.map(move |inp| match (inp.as_record(), table_iter.next()) {
(Ok((inp_cols, inp_vals)), Some(to_merge)) => {
match to_merge.as_record() {
Ok((to_merge_cols, to_merge_vals)) => {
let (cols, vals) = do_merge(
(inp_cols.to_vec(), inp_vals.to_vec()),
(to_merge_cols.to_vec(), to_merge_vals.to_vec()),
);
Value::Record {
cols,
vals,
span: call.head,
}
}
Err(error) => Value::Error {
error: Box::new(error),
},
}
}
(Ok(inp), Some(to_merge)) => match to_merge.as_record() {
Ok(to_merge) => Value::record(do_merge(inp, to_merge), call.head),
Err(error) => Value::Error {
error: Box::new(error),
},
},
(_, None) => inp,
(Err(error), _) => Value::Error {
error: Box::new(error),
@ -144,31 +131,9 @@ repeating this process with row 1, and so on."#
}
// record
(
PipelineData::Value(
Value::Record {
cols: inp_cols,
vals: inp_vals,
..
},
..,
),
Value::Record {
cols: to_merge_cols,
vals: to_merge_vals,
..
},
) => {
let (cols, vals) = do_merge(
(inp_cols.to_vec(), inp_vals.to_vec()),
(to_merge_cols.to_vec(), to_merge_vals.to_vec()),
);
Ok(Value::Record {
cols,
vals,
span: call.head,
}
.into_pipeline_data())
}
PipelineData::Value(Value::Record { val: inp, .. }, ..),
Value::Record { val: to_merge, .. },
) => Ok(Value::record(do_merge(inp, &to_merge), call.head).into_pipeline_data()),
(PipelineData::Value(val, ..), ..) => {
// Only point the "value originates here" arrow at the merge value
// if it was generated from a block. Otherwise, point at the pipeline value. -Leon 2022-10-27
@ -194,27 +159,22 @@ repeating this process with row 1, and so on."#
}
}
fn do_merge(
input_record: (Vec<String>, Vec<Value>),
to_merge_record: (Vec<String>, Vec<Value>),
) -> (Vec<String>, Vec<Value>) {
let (mut result_cols, mut result_vals) = input_record;
let (to_merge_cols, to_merge_vals) = to_merge_record;
fn do_merge(input_record: &Record, to_merge_record: &Record) -> Record {
let mut result = input_record.clone();
for (col, val) in to_merge_cols.into_iter().zip(to_merge_vals) {
let pos = result_cols.iter().position(|c| c == &col);
for (col, val) in to_merge_record {
let pos = result.cols.iter().position(|c| c == col);
// if find, replace existing data, else, push new data.
match pos {
Some(index) => {
result_vals[index] = val;
result.vals[index] = val.clone();
}
None => {
result_cols.push(col);
result_vals.push(val);
result.push(col, val.clone());
}
}
}
(result_cols, result_vals)
result
}
#[cfg(test)]

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Spanned, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Record,
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
#[derive(Clone, Debug)]
@ -54,18 +54,18 @@ impl Command for Move {
result:
Some(Value::List {
vals: vec![
Value::test_record(
vec!["index", "name", "value"],
vec![Value::test_int(1), Value::test_string("foo"), Value::test_string("a")],
),
Value::test_record(
vec!["index", "name", "value"],
vec![Value::test_int(2), Value::test_string("bar"), Value::test_string("b")],
),
Value::test_record(
vec!["index", "name", "value"],
vec![Value::test_int(3), Value::test_string("baz"), Value::test_string("c")],
),
Value::test_record(Record {
cols: vec!["index".to_string(), "name".to_string(), "value".to_string()],
vals: vec![Value::test_int(1), Value::test_string("foo"), Value::test_string("a")],
}),
Value::test_record(Record {
cols: vec!["index".to_string(), "name".to_string(), "value".to_string()],
vals: vec![Value::test_int(2), Value::test_string("bar"), Value::test_string("b")],
}),
Value::test_record(Record {
cols: vec!["index".to_string(), "name".to_string(), "value".to_string()],
vals: vec![Value::test_int(3), Value::test_string("baz"), Value::test_string("c")],
}),
],
span: Span::test_data(),
})
@ -76,18 +76,18 @@ impl Command for Move {
result:
Some(Value::List {
vals: vec![
Value::test_record(
vec!["index", "value", "name"],
vec![Value::test_int(1), Value::test_string("a"), Value::test_string("foo")],
),
Value::test_record(
vec!["index", "value", "name"],
vec![Value::test_int(2), Value::test_string("b"), Value::test_string("bar")],
),
Value::test_record(
vec!["index", "value", "name"],
vec![Value::test_int(3), Value::test_string("c"), Value::test_string("baz")],
),
Value::test_record(Record {
cols: vec!["index".to_string(), "value".to_string(), "name".to_string()],
vals: vec![Value::test_int(1), Value::test_string("a"), Value::test_string("foo")],
}),
Value::test_record(Record {
cols: vec!["index".to_string(), "value".to_string(), "name".to_string()],
vals: vec![Value::test_int(2), Value::test_string("b"), Value::test_string("bar")],
}),
Value::test_record(Record {
cols: vec!["index".to_string(), "value".to_string(), "name".to_string()],
vals: vec![Value::test_int(3), Value::test_string("c"), Value::test_string("baz")],
}),
],
span: Span::test_data(),
})
@ -95,10 +95,10 @@ impl Command for Move {
Example {
example: "{ name: foo, value: a, index: 1 } | move name --before index",
description: "Move columns of a record",
result: Some(Value::test_record(
vec!["value", "name", "index"],
vec![Value::test_string("a"), Value::test_string("foo"), Value::test_int(1)],
))
result: Some(Value::test_record(Record {
cols: vec!["value".to_string(), "name".to_string(), "index".to_string()],
vals: vec![Value::test_string("a"), Value::test_string("foo"), Value::test_int(1)],
}))
},
]
}
@ -150,18 +150,14 @@ impl Command for Move {
match input {
PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. } => {
let res = input.into_iter().map(move |x| match x.as_record() {
Ok((inp_cols, inp_vals)) => match move_record_columns(
inp_cols,
inp_vals,
&columns,
&before_or_after,
call.head,
) {
Ok(val) => val,
Err(error) => Value::Error {
error: Box::new(error),
},
},
Ok(record) => {
match move_record_columns(record, &columns, &before_or_after, call.head) {
Ok(val) => val,
Err(error) => Value::Error {
error: Box::new(error),
},
}
}
Err(error) => Value::Error {
error: Box::new(error),
},
@ -173,21 +169,12 @@ impl Command for Move {
Ok(res.into_pipeline_data(ctrlc))
}
}
PipelineData::Value(
Value::Record {
cols: inp_cols,
vals: inp_vals,
..
},
..,
) => Ok(move_record_columns(
&inp_cols,
&inp_vals,
&columns,
&before_or_after,
call.head,
)?
.into_pipeline_data()),
PipelineData::Value(Value::Record { val, .. }, ..) => {
Ok(
move_record_columns(&val, &columns, &before_or_after, call.head)?
.into_pipeline_data(),
)
}
_ => Err(ShellError::PipelineMismatch {
exp_input_type: "record or table".to_string(),
dst_span: call.head,
@ -199,8 +186,7 @@ impl Command for Move {
// Move columns within a record
fn move_record_columns(
inp_cols: &[String],
inp_vals: &[Value],
record: &Record,
columns: &[Value],
before_or_after: &Spanned<BeforeOrAfter>,
span: Span,
@ -210,7 +196,7 @@ fn move_record_columns(
// Check if before/after column exist
match &before_or_after.item {
BeforeOrAfter::After(after) => {
if !inp_cols.contains(after) {
if !record.cols.contains(after) {
return Err(ShellError::GenericError(
"Cannot move columns".to_string(),
"column does not exist".to_string(),
@ -221,7 +207,7 @@ fn move_record_columns(
}
}
BeforeOrAfter::Before(before) => {
if !inp_cols.contains(before) {
if !record.cols.contains(before) {
return Err(ShellError::GenericError(
"Cannot move columns".to_string(),
"column does not exist".to_string(),
@ -237,7 +223,11 @@ fn move_record_columns(
for column in columns.iter() {
let column_str = column.as_string()?;
if let Some(idx) = inp_cols.iter().position(|inp_col| &column_str == inp_col) {
if let Some(idx) = record
.cols
.iter()
.position(|inp_col| &column_str == inp_col)
{
column_idx.push(idx);
} else {
return Err(ShellError::GenericError(
@ -250,19 +240,16 @@ fn move_record_columns(
}
}
let mut out_cols: Vec<String> = Vec::with_capacity(inp_cols.len());
let mut out_vals: Vec<Value> = Vec::with_capacity(inp_vals.len());
let mut out = Record::with_capacity(record.len());
for (i, (inp_col, inp_val)) in inp_cols.iter().zip(inp_vals).enumerate() {
for (i, (inp_col, inp_val)) in record.iter().enumerate() {
match &before_or_after.item {
BeforeOrAfter::After(after) if after == inp_col => {
out_cols.push(inp_col.into());
out_vals.push(inp_val.clone());
out.push(inp_col.clone(), inp_val.clone());
for idx in column_idx.iter() {
if let (Some(col), Some(val)) = (inp_cols.get(*idx), inp_vals.get(*idx)) {
out_cols.push(col.into());
out_vals.push(val.clone());
if let (Some(col), Some(val)) = (record.cols.get(*idx), record.vals.get(*idx)) {
out.push(col.clone(), val.clone());
} else {
return Err(ShellError::NushellFailedSpanned {
msg: "Error indexing input columns".to_string(),
@ -274,9 +261,8 @@ fn move_record_columns(
}
BeforeOrAfter::Before(before) if before == inp_col => {
for idx in column_idx.iter() {
if let (Some(col), Some(val)) = (inp_cols.get(*idx), inp_vals.get(*idx)) {
out_cols.push(col.into());
out_vals.push(val.clone());
if let (Some(col), Some(val)) = (record.cols.get(*idx), record.vals.get(*idx)) {
out.push(col.clone(), val.clone());
} else {
return Err(ShellError::NushellFailedSpanned {
msg: "Error indexing input columns".to_string(),
@ -286,23 +272,17 @@ fn move_record_columns(
}
}
out_cols.push(inp_col.into());
out_vals.push(inp_val.clone());
out.push(inp_col.clone(), inp_val.clone());
}
_ => {
if !column_idx.contains(&i) {
out_cols.push(inp_col.into());
out_vals.push(inp_val.clone());
out.push(inp_col.clone(), inp_val.clone());
}
}
}
}
Ok(Value::Record {
cols: out_cols,
vals: out_vals,
span,
})
Ok(Value::record(out, span))
}
#[cfg(test)]

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -63,35 +63,31 @@ impl Command for Reject {
description: "Reject a column in a table",
example: "[[a, b]; [1, 2]] | reject a",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["b".to_string()],
vals: vec![Value::test_int(2)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
Example {
description: "Reject the specified field in a record",
example: "{a: 1, b: 2} | reject a",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["b".into()],
vals: vec![Value::test_int(2)],
span: Span::test_data(),
}),
})),
},
Example {
description: "Reject a nested field in a record",
example: "{a: {b: 3, c: 5}} | reject a.b",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".into()],
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["c".into()],
vals: vec![Value::test_int(5)],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
})],
})),
},
]
}

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block_with_early_return, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -56,11 +56,10 @@ impl Command for Rename {
description: "Rename a column",
example: "[[a, b]; [1, 2]] | rename my_column",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["my_column".to_string(), "b".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -68,11 +67,10 @@ impl Command for Rename {
description: "Rename many columns",
example: "[[a, b, c]; [1, 2, 3]] | rename eggs ham bacon",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["eggs".to_string(), "ham".to_string(), "bacon".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -80,31 +78,28 @@ impl Command for Rename {
description: "Rename a specific column",
example: "[[a, b, c]; [1, 2, 3]] | rename -c [a ham]",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ham".to_string(), "b".to_string(), "c".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
Example {
description: "Rename the fields of a record",
example: "{a: 1 b: 2} | rename x y",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["x".to_string(), "y".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
}),
})),
},
Example {
description: "Rename fields based on a given closure",
example: "{abc: 1, bbc: 2} | rename -b {str replace -a 'b' 'z'}",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["azc".to_string(), "zzc".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
}),
})),
},
]
}
@ -162,14 +157,13 @@ fn rename(
.map(
move |item| match item {
Value::Record {
mut cols,
vals,
val: mut record,
span,
} => {
if let Some((engine_state, block, mut stack, env_vars, env_hidden)) =
block_info.clone()
{
for c in &mut cols {
for c in &mut record.cols {
stack.with_env(&env_vars, &env_hidden);
if let Some(var) = block.signature.get_positional(0) {
@ -197,7 +191,7 @@ fn rename(
match &specified_column {
Some(c) => {
// check if the specified column to be renamed exists
if !cols.contains(&c[0]) {
if !record.cols.contains(&c[0]) {
return Value::Error {
error: Box::new(ShellError::UnsupportedInput(
format!(
@ -212,26 +206,26 @@ fn rename(
)),
};
}
for (idx, val) in cols.iter_mut().enumerate() {
for (idx, val) in record.cols.iter_mut().enumerate() {
if *val == c[0] {
cols[idx] = c[1].to_string();
record.cols[idx] = c[1].to_string();
break;
}
}
}
None => {
for (idx, val) in columns.iter().enumerate() {
if idx >= cols.len() {
if idx >= record.len() {
// skip extra new columns names if we already reached the final column
break;
}
cols[idx] = val.clone();
record.cols[idx] = val.clone();
}
}
}
}
Value::Record { cols, vals, span }
Value::record(record, span)
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => item.clone(),

View File

@ -1,8 +1,8 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, Type, Value,
};
#[derive(Clone)]
@ -53,8 +53,14 @@ impl Command for Reverse {
description: "Reverse a table",
result: Some(Value::List {
vals: vec![
Value::test_record(vec!["a"], vec![Value::test_int(2)]),
Value::test_record(vec!["a"], vec![Value::test_int(1)]),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(2)],
}),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(1)],
}),
],
span: Span::test_data(),
}),

View File

@ -3,7 +3,7 @@ use nu_protocol::ast::{Call, CellPath, PathMember};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
PipelineIterator, ShellError, Signature, Span, SyntaxShape, Type, Value,
PipelineIterator, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use std::collections::HashSet;
@ -152,14 +152,20 @@ produce a table, a list will produce a list, and a record will produce a record.
description: "Select a column in a table",
example: "[{a: a b: b}] | select a",
result: Some(Value::List {
vals: vec![Value::test_record(vec!["a"], vec![Value::test_string("a")])],
vals: vec![Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_string("a")]
})],
span: Span::test_data(),
}),
},
Example {
description: "Select a field in a record",
example: "{a: a b: b} | select a",
result: Some(Value::test_record(vec!["a"], vec![Value::test_string("a")])),
result: Some(Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_string("a")]
})),
},
Example {
description: "Select just the `name` column",
@ -256,14 +262,12 @@ fn select(
let mut columns_with_value = Vec::new();
for input_val in input_vals {
if !columns.is_empty() {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) {
Ok(fetcher) => {
cols.push(path.into_string().replace('.', "_"));
vals.push(fetcher);
record.push(path.into_string().replace('.', "_"), fetcher);
if !columns_with_value.contains(&path) {
columns_with_value.push(path);
}
@ -274,7 +278,7 @@ fn select(
}
}
output.push(Value::Record { cols, vals, span })
output.push(Value::record(record, span))
} else {
output.push(input_val)
}
@ -290,23 +294,17 @@ fn select(
for x in stream {
if !columns.is_empty() {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) {
Ok(value) => {
cols.push(path.into_string().replace('.', "_"));
vals.push(value);
record.push(path.into_string().replace('.', "_"), value);
}
Err(e) => return Err(e),
}
}
values.push(Value::Record {
cols,
vals,
span: call_span,
});
values.push(Value::record(record, call_span));
} else {
values.push(x);
}
@ -318,27 +316,21 @@ fn select(
}
PipelineData::Value(v, metadata, ..) => {
if !columns.is_empty() {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for cell_path in columns {
// FIXME: remove clone
match v.clone().follow_cell_path(&cell_path.members, false) {
Ok(result) => {
cols.push(cell_path.into_string().replace('.', "_"));
vals.push(result);
record.push(cell_path.into_string().replace('.', "_"), result);
}
Err(e) => return Err(e),
}
}
Ok(Value::Record {
cols,
vals,
span: call_span,
}
.into_pipeline_data()
.set_metadata(metadata))
Ok(Value::record(record, call_span)
.into_pipeline_data()
.set_metadata(metadata))
} else {
Ok(v.into_pipeline_data().set_metadata(metadata))
}

View File

@ -4,8 +4,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Record,
ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -55,11 +55,10 @@ impl Command for Skip {
description: "Skip two rows of a table",
example: "[[editions]; [2015] [2018] [2021]] | skip 2",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["editions".to_owned()],
vals: vec![Value::test_int(2021)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -62,8 +62,14 @@ impl Command for SkipUntil {
example: "[{a: -2} {a: 0} {a: 2} {a: -1}] | skip until {|x| $x.a > 0 }",
result: Some(Value::List {
vals: vec![
Value::test_record(vec!["a"], vec![Value::test_int(2)]),
Value::test_record(vec!["a"], vec![Value::test_int(-1)]),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(2)],
}),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(-1)],
}),
],
span: Span::test_data(),
}),

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -62,9 +62,18 @@ impl Command for SkipWhile {
example: "[{a: -2} {a: 0} {a: 2} {a: -1}] | skip while {|x| $x.a < 0 }",
result: Some(Value::List {
vals: vec![
Value::test_record(vec!["a"], vec![Value::test_int(0)]),
Value::test_record(vec!["a"], vec![Value::test_int(2)]),
Value::test_record(vec!["a"], vec![Value::test_int(-1)]),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(0)],
}),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(2)],
}),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(-1)],
}),
],
span: Span::test_data(),
}),

View File

@ -2,8 +2,8 @@ use alphanumeric_sort::compare_str;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Type, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Record,
ShellError, Signature, Span, Type, Value,
};
use std::cmp::Ordering;
@ -113,20 +113,18 @@ impl Command for Sort {
Example {
description: "Sort record by key (case-insensitive)",
example: "{b: 3, a: 4} | sort",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_int(4), Value::test_int(3)],
span: Span::test_data(),
}),
})),
},
Example {
description: "Sort record by value",
example: "{b: 4, a: 3, c:1} | sort -v",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["c".to_string(), "a".to_string(), "b".to_string()],
vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(4)],
span: Span::test_data(),
}),
})),
},
]
}
@ -145,17 +143,9 @@ impl Command for Sort {
match input {
// Records have two sorting methods, toggled by presence or absence of -v
PipelineData::Value(Value::Record { cols, vals, span }, ..) => {
PipelineData::Value(Value::Record { val, span }, ..) => {
let sort_by_value = call.has_flag("values");
let record = sort_record(
cols,
vals,
span,
sort_by_value,
reverse,
insensitive,
natural,
);
let record = sort_record(val, span, sort_by_value, reverse, insensitive, natural);
Ok(record.into_pipeline_data())
}
// Other values are sorted here
@ -185,15 +175,14 @@ impl Command for Sort {
}
fn sort_record(
cols: Vec<String>,
vals: Vec<Value>,
record: Record,
rec_span: Span,
sort_by_value: bool,
reverse: bool,
insensitive: bool,
natural: bool,
) -> Value {
let mut input_pairs: Vec<(String, Value)> = cols.into_iter().zip(vals).collect();
let mut input_pairs: Vec<(String, Value)> = record.into_iter().collect();
input_pairs.sort_by(|a, b| {
// Extract the data (if sort_by_value) or the column names for comparison
let left_res = if sort_by_value {
@ -248,21 +237,11 @@ fn sort_record(
}
});
let mut new_cols = Vec::with_capacity(input_pairs.len());
let mut new_vals = Vec::with_capacity(input_pairs.len());
for (col, val) in input_pairs {
new_cols.push(col);
new_vals.push(val)
}
if reverse {
new_cols.reverse();
new_vals.reverse();
}
Value::Record {
cols: new_cols,
vals: new_vals,
span: rec_span,
input_pairs.reverse();
}
Value::record(input_pairs.into_iter().collect(), rec_span)
}
pub fn sort(
@ -272,12 +251,8 @@ pub fn sort(
natural: bool,
) -> Result<(), ShellError> {
match vec.first() {
Some(Value::Record {
cols,
vals: _input_vals,
..
}) => {
let columns = cols.clone();
Some(Value::Record { val, .. }) => {
let columns = val.cols.clone();
vec.sort_by(|a, b| process(a, b, &columns, span, insensitive, natural));
}
_ => {

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -60,18 +60,18 @@ impl Command for SortBy {
example: "[[fruit count]; [apple 9] [pear 3] [orange 7]] | sort-by fruit -r",
result: Some(Value::List {
vals: vec![
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("pear"), Value::test_int(3)],
),
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("orange"), Value::test_int(7)],
),
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("apple"), Value::test_int(9)],
),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("pear"), Value::test_int(3)],
}),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("orange"), Value::test_int(7)],
}),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("apple"), Value::test_int(9)],
}),
],
span: Span::test_data(),
}),

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -48,13 +48,13 @@ impl Command for SplitBy {
{ name: 'storm', lang: 'rs', 'year': '2021' }
]
} | split-by lang"#,
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["rb".to_string(), "rs".to_string()],
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["2019".to_string()],
vals: vec![Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"name".to_string(),
"lang".to_string(),
@ -65,17 +65,15 @@ impl Command for SplitBy {
Value::test_string("rb"),
Value::test_string("2019"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["2019".to_string(), "2021".to_string()],
vals: vec![
Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"name".to_string(),
"lang".to_string(),
@ -86,12 +84,11 @@ impl Command for SplitBy {
Value::test_string("rs"),
Value::test_string("2019"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
},
Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"name".to_string(),
"lang".to_string(),
@ -102,16 +99,13 @@ impl Command for SplitBy {
Value::test_string("rs"),
Value::test_string("2021"),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
},
],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
})),
}]
}
}
@ -203,15 +197,13 @@ fn data_group(
group.push(value);
}
let mut cols = vec![];
let mut vals = vec![];
for (k, v) in groups {
cols.push(k.to_string());
vals.push(Value::List { vals: v, span });
}
Ok(Value::Record { cols, vals, span })
Ok(Value::record(
groups
.into_iter()
.map(|(k, v)| (k, Value::list(v, span)))
.collect(),
span,
))
}
#[allow(clippy::type_complexity)]
@ -222,32 +214,17 @@ pub fn data_split(
) -> Result<PipelineData, ShellError> {
let mut splits = indexmap::IndexMap::new();
let mut cols = vec![];
let mut vals = vec![];
match value {
PipelineData::Value(
Value::Record {
cols,
vals: grouped_rows,
span,
},
_,
) => {
for (idx, list) in grouped_rows.iter().enumerate() {
PipelineData::Value(Value::Record { val: grouped, span }, _) => {
for (idx, list) in grouped.vals.iter().enumerate() {
match data_group(list, splitter, span) {
Ok(grouped) => {
if let Value::Record {
vals: li,
cols: sub_cols,
..
} = grouped
{
for (inner_idx, subset) in li.iter().enumerate() {
Ok(grouped_vals) => {
if let Value::Record { val: sub, .. } = grouped_vals {
for (inner_idx, subset) in sub.vals.iter().enumerate() {
let s: &mut IndexMap<String, Value> =
splits.entry(sub_cols[inner_idx].clone()).or_default();
splits.entry(sub.cols[inner_idx].clone()).or_default();
s.insert(cols[idx].clone(), subset.clone());
s.insert(grouped.cols[idx].clone(), subset.clone());
}
}
}
@ -266,28 +243,12 @@ pub fn data_split(
}
}
for (k, rows) in splits {
cols.push(k.to_string());
let record = splits
.into_iter()
.map(|(k, rows)| (k, Value::record(rows.into_iter().collect(), span)))
.collect();
let mut sub_cols = vec![];
let mut sub_vals = vec![];
for (k, v) in rows {
sub_cols.push(k);
sub_vals.push(v);
}
vals.push(Value::Record {
cols: sub_cols,
vals: sub_vals,
span,
});
}
Ok(PipelineData::Value(
Value::Record { cols, vals, span },
None,
))
Ok(PipelineData::Value(Value::record(record, span), None))
}
#[cfg(test)]

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -125,8 +125,14 @@ impl Command for Take {
example: "[[editions]; [2015] [2018] [2021]] | take 2",
result: Some(Value::List {
vals: vec![
Value::test_record(vec!["editions"], vec![Value::test_int(2015)]),
Value::test_record(vec!["editions"], vec![Value::test_int(2018)]),
Value::test_record(Record {
cols: vec!["editions".to_string()],
vals: vec![Value::test_int(2015)],
}),
Value::test_record(Record {
cols: vec!["editions".to_string()],
vals: vec![Value::test_int(2018)],
}),
],
span: Span::test_data(),
}),

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -58,8 +58,14 @@ impl Command for TakeUntil {
example: "[{a: -1} {a: -2} {a: 9} {a: 1}] | take until {|x| $x.a > 0 }",
result: Some(Value::List {
vals: vec![
Value::test_record(vec!["a"], vec![Value::test_int(-1)]),
Value::test_record(vec!["a"], vec![Value::test_int(-2)]),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(-1)],
}),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(-2)],
}),
],
span: Span::test_data(),
}),

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{Closure, Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -58,8 +58,14 @@ impl Command for TakeWhile {
example: "[{a: -1} {a: -2} {a: 9} {a: 1}] | take while {|x| $x.a < 0 }",
result: Some(Value::List {
vals: vec![
Value::test_record(vec!["a"], vec![Value::test_int(-1)]),
Value::test_record(vec!["a"], vec![Value::test_int(-2)]),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(-1)],
}),
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(-2)],
}),
],
span: Span::test_data(),
}),

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Span, Spanned, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -90,16 +90,14 @@ impl Command for Transpose {
example: "[[c1 c2]; [1 2]] | transpose",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("c1"), Value::test_int(1)],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("c2"), Value::test_int(2)],
span,
},
}),
],
span,
}),
@ -109,16 +107,14 @@ impl Command for Transpose {
example: "[[c1 c2]; [1 2]] | transpose key val",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["key".to_string(), "val".to_string()],
vals: vec![Value::test_string("c1"), Value::test_int(1)],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["key".to_string(), "val".to_string()],
vals: vec![Value::test_string("c2"), Value::test_int(2)],
span,
},
}),
],
span,
}),
@ -129,16 +125,14 @@ impl Command for Transpose {
example: "[[c1 c2]; [1 2]] | transpose -i val",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["val".to_string()],
vals: vec![Value::test_int(1)],
span,
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["val".to_string()],
vals: vec![Value::test_int(2)],
span,
},
}),
],
span,
}),
@ -146,11 +140,10 @@ impl Command for Transpose {
Example {
description: "Transfer back to record with -d flag",
example: "{c1: 1, c2: 2} | transpose | transpose -i -r -d",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["c1".to_string(), "c2".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2)],
span,
}),
})),
},
]
}
@ -248,24 +241,26 @@ pub fn transpose(
.into_iter()
.map(move |desc| {
let mut column_num: usize = 0;
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
if !args.ignore_titles && !args.header_row {
cols.push(headers[column_num].clone());
vals.push(Value::string(desc.clone(), name));
record.push(
headers[column_num].clone(),
Value::string(desc.clone(), name),
);
column_num += 1
}
for i in input.clone() {
match &i.get_data_by_key(&desc) {
Some(x) => {
if args.keep_all && cols.contains(&headers[column_num]) {
let index = cols
if args.keep_all && record.cols.contains(&headers[column_num]) {
let index = record
.cols
.iter()
.position(|y| y == &headers[column_num])
.expect("value is contained.");
let new_val = match &vals[index] {
let new_val = match &record.vals[index] {
Value::List { vals, span } => {
let mut vals = vals.clone();
vals.push(x.clone());
@ -279,32 +274,31 @@ pub fn transpose(
span: v.expect_span(),
},
};
cols.remove(index);
vals.remove(index);
record.cols.remove(index);
record.vals.remove(index);
cols.push(headers[column_num].clone());
vals.push(new_val);
} else if args.keep_last && cols.contains(&headers[column_num]) {
let index = cols
record.push(headers[column_num].clone(), new_val);
} else if args.keep_last && record.cols.contains(&headers[column_num]) {
let index = record
.cols
.iter()
.position(|y| y == &headers[column_num])
.expect("value is contained.");
cols.remove(index);
vals.remove(index);
cols.push(headers[column_num].clone());
vals.push(x.clone());
} else if !cols.contains(&headers[column_num]) {
cols.push(headers[column_num].clone());
vals.push(x.clone());
record.cols.remove(index);
record.vals.remove(index);
record.push(headers[column_num].clone(), x.clone());
} else if !record.cols.contains(&headers[column_num]) {
record.push(headers[column_num].clone(), x.clone());
}
}
_ => {
if args.keep_all && cols.contains(&headers[column_num]) {
let index = cols
if args.keep_all && record.cols.contains(&headers[column_num]) {
let index = record
.cols
.iter()
.position(|y| y == &headers[column_num])
.expect("value is contained.");
let new_val = match &vals[index] {
let new_val = match &record.vals[index] {
Value::List { vals, span } => {
let mut vals = vals.clone();
vals.push(Value::nothing(name));
@ -318,34 +312,28 @@ pub fn transpose(
span: v.expect_span(),
},
};
cols.remove(index);
vals.remove(index);
record.cols.remove(index);
record.vals.remove(index);
cols.push(headers[column_num].clone());
vals.push(new_val);
} else if args.keep_last && cols.contains(&headers[column_num]) {
let index = cols
record.push(headers[column_num].clone(), new_val);
} else if args.keep_last && record.cols.contains(&headers[column_num]) {
let index = record
.cols
.iter()
.position(|y| y == &headers[column_num])
.expect("value is contained.");
cols.remove(index);
vals.remove(index);
cols.push(headers[column_num].clone());
vals.push(Value::nothing(name));
} else if !cols.contains(&headers[column_num]) {
cols.push(headers[column_num].clone());
vals.push(Value::nothing(name));
record.cols.remove(index);
record.vals.remove(index);
record.push(headers[column_num].clone(), Value::nothing(name));
} else if !record.cols.contains(&headers[column_num]) {
record.push(headers[column_num].clone(), Value::nothing(name));
}
}
}
column_num += 1;
}
Value::Record {
cols,
vals,
span: name,
}
Value::record(record, name)
})
.collect::<Vec<Value>>();
if result_data.len() == 1 && args.as_record {

View File

@ -3,8 +3,8 @@ use itertools::Itertools;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, PipelineMetadata, ShellError, Signature,
Span, Type, Value,
record, Category, Example, IntoPipelineData, PipelineData, PipelineMetadata, Record,
ShellError, Signature, Span, Type, Value,
};
use std::collections::hash_map::IntoIter;
use std::collections::HashMap;
@ -122,16 +122,14 @@ impl Command for Uniq {
example: "[1 2 2] | uniq -c",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["value".to_string(), "count".to_string()],
vals: vec![Value::test_int(1), Value::test_int(1)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["value".to_string(), "count".to_string()],
vals: vec![Value::test_int(2), Value::test_int(2)],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -193,32 +191,25 @@ fn clone_to_lowercase(value: &Value) -> Value {
span: *span,
},
Value::List { vals: vec, span } => Value::List {
vals: vec
.clone()
.into_iter()
.map(|v| clone_to_lowercase(&v))
.collect(),
span: *span,
},
Value::Record { cols, vals, span } => Value::Record {
cols: cols.clone(),
vals: vals
.clone()
.into_iter()
.map(|v| clone_to_lowercase(&v))
.collect(),
vals: vec.iter().map(clone_to_lowercase).collect(),
span: *span,
},
Value::Record { val: record, span } => Value::record(
Record {
cols: record.cols.clone(),
vals: record.vals.iter().map(clone_to_lowercase).collect(),
},
*span,
),
other => other.clone(),
}
}
fn sort_attributes(val: Value) -> Value {
match val {
Value::Record { cols, vals, span } => {
let sorted = cols
Value::Record { val, span } => {
let sorted = val
.into_iter()
.zip(vals)
.sorted_by(|a, b| a.0.cmp(&b.0))
.collect_vec();
@ -228,11 +219,13 @@ fn sort_attributes(val: Value) -> Value {
.map(|a| sort_attributes(a.1))
.collect_vec();
Value::Record {
cols: sorted_cols,
vals: sorted_vals,
Value::record(
Record {
cols: sorted_cols,
vals: sorted_vals,
},
span,
}
)
}
Value::List { vals, span } => Value::List {
vals: vals.into_iter().map(sort_attributes).collect_vec(),
@ -250,10 +243,14 @@ fn generate_key(item: &ValueCounter) -> Result<String, ShellError> {
fn generate_results_with_count(head: Span, uniq_values: Vec<ValueCounter>) -> Vec<Value> {
uniq_values
.into_iter()
.map(|item| Value::Record {
cols: vec!["value".to_string(), "count".to_string()],
vals: vec![item.val, Value::int(item.count, head)],
span: head,
.map(|item| {
Value::record(
record! {
"value" => item.val,
"count" => Value::int(item.count, head),
},
head,
)
})
.collect()
}

View File

@ -4,7 +4,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)]
@ -94,18 +94,18 @@ impl Command for UniqBy {
example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit",
result: Some(Value::List {
vals: vec![
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("apple"), Value::test_int(9)],
),
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("pear"), Value::test_int(3)],
),
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("orange"), Value::test_int(7)],
),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("apple"), Value::test_int(9)],
}),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("pear"), Value::test_int(3)],
}),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("orange"), Value::test_int(7)],
}),
],
span: Span::test_data(),
}),
@ -115,8 +115,7 @@ impl Command for UniqBy {
fn validate(vec: Vec<Value>, columns: &Vec<String>, span: Span) -> Result<(), ShellError> {
if let Some(Value::Record {
cols,
vals: _input_vals,
val: record,
span: val_span,
}) = vec.first()
{
@ -131,7 +130,7 @@ fn validate(vec: Vec<Value>, columns: &Vec<String>, span: Span) -> Result<(), Sh
));
}
if let Some(nonexistent) = nonexistent_column(columns.clone(), cols.to_vec()) {
if let Some(nonexistent) = nonexistent_column(columns.clone(), record.cols.clone()) {
return Err(ShellError::CantFindColumn {
col_name: nonexistent,
span,

View File

@ -3,7 +3,7 @@ use nu_protocol::ast::{Call, CellPath, PathMember};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Type, Value,
Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -57,33 +57,43 @@ impl Command for Update {
Example {
description: "Update a column value",
example: "{'name': 'nu', 'stars': 5} | update name 'Nushell'",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["name".into(), "stars".into()],
vals: vec![Value::test_string("Nushell"), Value::test_int(5)],
span: Span::test_data(),
}),
})),
},
Example {
description: "Use in closure form for more involved updating logic",
example: "[[count fruit]; [1 'apple']] | enumerate | update item.count {|e| ($e.item.fruit | str length) + $e.index } | get item",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["count".into(), "fruit".into()],
vals: vec![Value::test_int(5), Value::test_string("apple")],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
Example {
description: "Alter each value in the 'authors' column to use a single string instead of a list",
example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|row| $row.authors | str join ','}",
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}),
result: Some(Value::List {
vals: vec![Value::test_record(Record {
cols: vec!["project".into(), "authors".into()],
vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")],
})],
span: Span::test_data(),
}),
},
Example {
description: "You can also use a simple command to update 'authors' to a single string",
example: "[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|| str join ','}",
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}),
result: Some(Value::List {
vals: vec![Value::test_record(Record {
cols: vec!["project".into(), "authors".into()],
vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")],
})],
span: Span::test_data(),
}),
}
]
}

View File

@ -3,7 +3,7 @@ use nu_protocol::ast::{Call, CellPath, PathMember};
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Type, Value,
Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -60,26 +60,45 @@ impl Command for Upsert {
vec![Example {
description: "Update a record's value",
example: "{'name': 'nu', 'stars': 5} | upsert name 'Nushell'",
result: Some(Value::Record { cols: vec!["name".into(), "stars".into()], vals: vec![Value::test_string("Nushell"), Value::test_int(5)], span: Span::test_data()}),
result: Some(Value::test_record(Record {
cols: vec!["name".into(), "stars".into()],
vals: vec![Value::test_string("Nushell"), Value::test_int(5)],
})),
},
Example {
description: "Update each row of a table",
example: "[[name lang]; [Nushell ''] [Reedline '']] | upsert lang 'Rust'",
result: Some(Value::List { vals: vec![
Value::Record { cols: vec!["name".into(), "lang".into()], vals: vec![Value::test_string("Nushell"), Value::test_string("Rust")], span: Span::test_data()},
Value::Record { cols: vec!["name".into(), "lang".into()], vals: vec![Value::test_string("Reedline"), Value::test_string("Rust")], span: Span::test_data()}
], span: Span::test_data()}),
result: Some(Value::List {
vals: vec![
Value::test_record(Record {
cols: vec!["name".into(), "lang".into()],
vals: vec![Value::test_string("Nushell"), Value::test_string("Rust")],
}),
Value::test_record(Record {
cols: vec!["name".into(), "lang".into()],
vals: vec![Value::test_string("Reedline"), Value::test_string("Rust")],
}),
],
span: Span::test_data(),
}),
},
Example {
description: "Insert a new entry into a single record",
example: "{'name': 'nu', 'stars': 5} | upsert language 'Rust'",
result: Some(Value::Record { cols: vec!["name".into(), "stars".into(), "language".into()], vals: vec![Value::test_string("nu"), Value::test_int(5), Value::test_string("Rust")], span: Span::test_data()}),
result: Some(Value::test_record(Record {
cols: vec!["name".into(), "stars".into(), "language".into()],
vals: vec![Value::test_string("nu"), Value::test_int(5), Value::test_string("Rust")],
})),
}, Example {
description: "Use in closure form for more involved updating logic",
example: "[[count fruit]; [1 'apple']] | enumerate | upsert item.count {|e| ($e.item.fruit | str length) + $e.index } | get item",
result: Some(Value::List { vals: vec![
Value::Record { cols: vec!["count".into(), "fruit".into()], vals: vec![Value::test_int(5), Value::test_string("apple")], span: Span::test_data()}],
span: Span::test_data()}),
result: Some(Value::List {
vals: vec![Value::test_record(Record {
cols: vec!["count".into(), "fruit".into()],
vals: vec![Value::test_int(5), Value::test_string("apple")],
})],
span: Span::test_data(),
}),
},
Example {
description: "Upsert an int into a list, updating an existing value based on the index",

View File

@ -110,8 +110,8 @@ pub fn get_values<'a>(
for item in input {
match item {
Value::Record { cols, vals, .. } => {
for (k, v) in cols.iter().zip(vals.iter()) {
Value::Record { val, .. } => {
for (k, v) in val {
if let Some(vec) = output.get_mut(k) {
vec.push(v.clone());
} else {
@ -172,8 +172,8 @@ fn values(
Err(err) => Err(err),
}
}
PipelineData::Value(Value::Record { vals, .. }, ..) => {
Ok(vals.into_pipeline_data(ctrlc).set_metadata(metadata))
PipelineData::Value(Value::Record { val, .. }, ..) => {
Ok(val.vals.into_pipeline_data(ctrlc).set_metadata(metadata))
}
// Propagate errors
PipelineData::Value(Value::Error { error }, ..) => Err(*error),

View File

@ -2,8 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Type, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Record,
ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -114,11 +114,10 @@ not supported."#
description: "Filter rows of a table according to a condition",
example: "[{a: 1} {a: 2}] | where a > 1",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(2)],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Type, Value,
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -47,27 +47,18 @@ impl Command for Wrap {
| PipelineData::Value(Value::List { .. }, ..)
| PipelineData::ListStream { .. } => Ok(input
.into_iter()
.map(move |x| Value::Record {
cols: vec![name.clone()],
vals: vec![x],
span,
})
.map(move |x| Value::record(record! { name.clone() => x }, span))
.into_pipeline_data(engine_state.ctrlc.clone())
.set_metadata(metadata)),
PipelineData::ExternalStream { .. } => Ok(Value::Record {
cols: vec![name],
vals: vec![input.into_value(call.head)],
PipelineData::ExternalStream { .. } => Ok(Value::record(
record! { name => input.into_value(call.head) },
span,
}
.into_pipeline_data()
.set_metadata(metadata)),
PipelineData::Value(input, ..) => Ok(Value::Record {
cols: vec![name],
vals: vec![input],
span,
}
)
.into_pipeline_data()
.set_metadata(metadata)),
PipelineData::Value(input, ..) => Ok(Value::record(record! { name => input }, span)
.into_pipeline_data()
.set_metadata(metadata)),
}
}
@ -78,21 +69,18 @@ impl Command for Wrap {
example: "[1 2 3] | wrap num",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["num".into()],
vals: vec![Value::test_int(1)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["num".into()],
vals: vec![Value::test_int(2)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["num".into()],
vals: vec![Value::test_int(3)],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -102,21 +90,18 @@ impl Command for Wrap {
example: "1..3 | wrap num",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["num".into()],
vals: vec![Value::test_int(1)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["num".into()],
vals: vec![Value::test_int(2)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["num".into()],
vals: vec![Value::test_int(3)],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),

View File

@ -4,7 +4,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)]
@ -82,14 +82,13 @@ impl Command for FromCsv {
description: "Convert comma-separated data to a table",
example: "\"ColA,ColB\n1,2\" | from csv",
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_int(1),
Value::test_int(2),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
})
},

View File

@ -1,5 +1,5 @@
use csv::{ReaderBuilder, Trim};
use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Value};
use nu_protocol::{IntoPipelineData, PipelineData, Record, ShellError, Span, Value};
fn from_delimited_string_to_value(
DelimitedReaderConfig {
@ -56,11 +56,13 @@ fn from_delimited_string_to_value(
});
}
}
rows.push(Value::Record {
cols: headers.clone(),
vals: output_row,
rows.push(Value::record(
Record {
cols: headers.clone(),
vals: output_row,
},
span,
});
));
}
Ok(Value::List { vals: rows, span })

View File

@ -1,8 +1,8 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Type, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Record,
ShellError, Signature, Span, Type, Value,
};
#[derive(Clone)]
@ -29,16 +29,15 @@ impl Command for FromJson {
Example {
example: r#"'{ "a": 1 }' | from json"#,
description: "Converts json formatted string to table",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(1)],
span: Span::test_data(),
}),
})),
},
Example {
example: r#"'{ "a": 1, "b": [1, 2] }' | from json"#,
description: "Converts json formatted string to table",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![
Value::test_int(1),
@ -47,8 +46,7 @@ impl Command for FromJson {
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
})),
},
]
}
@ -107,17 +105,12 @@ fn convert_nujson_to_value(value: &nu_json::Value, span: Span) -> Value {
nu_json::Value::F64(f) => Value::Float { val: *f, span },
nu_json::Value::I64(i) => Value::Int { val: *i, span },
nu_json::Value::Null => Value::Nothing { span },
nu_json::Value::Object(k) => {
let mut cols = vec![];
let mut vals = vec![];
for item in k {
cols.push(item.0.clone());
vals.push(convert_nujson_to_value(item.1, span));
}
Value::Record { cols, vals, span }
}
nu_json::Value::Object(k) => Value::record(
k.iter()
.map(|(k, v)| (k.clone(), convert_nujson_to_value(v, span)))
.collect(),
span,
),
nu_json::Value::U64(u) => {
if *u > i64::MAX as u64 {
Value::Error {

View File

@ -1,8 +1,8 @@
use nu_protocol::ast::{Call, Expr, Expression, PipelineElement};
use nu_protocol::engine::{Command, EngineState, Stack, StateWorkingSet};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Range, ShellError, Signature, Span, Type,
Unit, Value,
Category, Example, IntoPipelineData, PipelineData, Range, Record, ShellError, Signature, Span,
Type, Unit, Value,
};
#[derive(Clone)]
pub struct FromNuon;
@ -27,16 +27,15 @@ impl Command for FromNuon {
Example {
example: "'{ a:1 }' | from nuon",
description: "Converts nuon formatted string to table",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(1)],
span: Span::test_data(),
}),
})),
},
Example {
example: "'{ a:1, b: [1, 2] }' | from nuon",
description: "Converts nuon formatted string to table",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![
Value::test_int(1),
@ -45,8 +44,7 @@ impl Command for FromNuon {
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
})),
},
]
}
@ -323,8 +321,7 @@ fn convert_to_value(
})
}
Expr::Record(key_vals) => {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
for (key, val) in key_vals {
let key_str = match key.expr {
@ -341,11 +338,10 @@ fn convert_to_value(
let value = convert_to_value(val, span, original_text)?;
cols.push(key_str);
vals.push(value);
record.push(key_str, value);
}
Ok(Value::Record { cols, vals, span })
Ok(Value::record(record, span))
}
Expr::RowCondition(..) => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
@ -409,11 +405,13 @@ fn convert_to_value(
));
}
output.push(Value::Record {
cols: cols.clone(),
vals,
output.push(Value::record(
Record {
cols: cols.clone(),
vals,
},
span,
});
));
}
Ok(Value::List { vals: output, span })

View File

@ -133,41 +133,29 @@ fn from_ods(
sheet_names.retain(|e| sel_sheets.contains(e));
}
for sheet_name in &sheet_names {
for sheet_name in sheet_names {
let mut sheet_output = vec![];
if let Some(Ok(current_sheet)) = ods.worksheet_range(sheet_name) {
if let Some(Ok(current_sheet)) = ods.worksheet_range(&sheet_name) {
for row in current_sheet.rows() {
let mut row_output = IndexMap::new();
for (i, cell) in row.iter().enumerate() {
let value = match cell {
DataType::Empty => Value::nothing(head),
DataType::String(s) => Value::string(s, head),
DataType::Float(f) => Value::float(*f, head),
DataType::Int(i) => Value::int(*i, head),
DataType::Bool(b) => Value::bool(*b, head),
_ => Value::nothing(head),
};
let record = row
.iter()
.enumerate()
.map(|(i, cell)| {
let value = match cell {
DataType::Empty => Value::nothing(head),
DataType::String(s) => Value::string(s, head),
DataType::Float(f) => Value::float(*f, head),
DataType::Int(i) => Value::int(*i, head),
DataType::Bool(b) => Value::bool(*b, head),
_ => Value::nothing(head),
};
row_output.insert(format!("column{i}"), value);
}
(format!("column{i}"), value)
})
.collect();
let (cols, vals) =
row_output
.into_iter()
.fold((vec![], vec![]), |mut acc, (k, v)| {
acc.0.push(k);
acc.1.push(v);
acc
});
let record = Value::Record {
cols,
vals,
span: head,
};
sheet_output.push(record);
sheet_output.push(Value::record(record, head));
}
dict.insert(
@ -187,19 +175,10 @@ fn from_ods(
}
}
let (cols, vals) = dict.into_iter().fold((vec![], vec![]), |mut acc, (k, v)| {
acc.0.push(k.clone());
acc.1.push(v);
acc
});
let record = Value::Record {
cols,
vals,
span: head,
};
Ok(PipelineData::Value(record, None))
Ok(PipelineData::Value(
Value::record(dict.into_iter().collect(), head),
None,
))
}
#[cfg(test)]

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -44,13 +44,32 @@ impl Command for FromSsv {
example: r#"'FOO BAR
1 2' | from ssv"#,
description: "Converts ssv formatted string to table",
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["FOO".to_string(), "BAR".to_string()], vals: vec![Value::test_string("1"), Value::test_string("2")], span: Span::test_data() }], span: Span::test_data() }),
result: Some(Value::List {
vals: vec![Value::test_record(Record {
cols: vec!["FOO".to_string(), "BAR".to_string()],
vals: vec![Value::test_string("1"), Value::test_string("2")],
})],
span: Span::test_data(),
}),
}, Example {
example: r#"'FOO BAR
1 2' | from ssv -n"#,
description: "Converts ssv formatted string to table but not treating the first row as column names",
result: Some(
Value::List { vals: vec![Value::Record { cols: vec!["column1".to_string(), "column2".to_string()], vals: vec![Value::test_string("FOO"), Value::test_string("BAR")], span: Span::test_data() }, Value::Record { cols: vec!["column1".to_string(), "column2".to_string()], vals: vec![Value::test_string("1"), Value::test_string("2")], span: Span::test_data() }], span: Span::test_data() }),
Value::List {
vals: vec![
Value::test_record(Record {
cols: vec!["column1".to_string(), "column2".to_string()],
vals: vec![Value::test_string("FOO"), Value::test_string("BAR")],
}),
Value::test_record(Record {
cols: vec!["column1".to_string(), "column2".to_string()],
vals: vec![Value::test_string("1"), Value::test_string("2")],
}),
],
span: Span::test_data(),
}
),
}]
}
@ -251,19 +270,13 @@ fn from_ssv_string_to_value(
span: Span,
) -> Value {
let rows = string_to_table(s, noheaders, aligned_columns, split_at)
.iter()
.into_iter()
.map(|row| {
let mut dict = IndexMap::new();
for (col, entry) in row {
dict.insert(
col.to_string(),
Value::String {
val: entry.to_string(),
span,
},
);
dict.insert(col, Value::string(entry, span));
}
Value::from(Spanned { item: dict, span })
Value::record(dict.into_iter().collect(), span)
})
.collect();

View File

@ -1,7 +1,8 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span, Type,
Value,
};
#[derive(Clone)]
@ -27,17 +28,16 @@ impl Command for FromToml {
Example {
example: "'a = 1' | from toml",
description: "Converts toml formatted string to record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(1)],
span: Span::test_data(),
}),
})),
},
Example {
example: "'a = 1
b = [1, 2]' | from toml",
description: "Converts toml formatted string to record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![
Value::test_int(1),
@ -46,8 +46,7 @@ b = [1, 2]' | from toml",
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
})),
},
]
}
@ -79,17 +78,12 @@ fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value {
toml::Value::Boolean(b) => Value::Bool { val: *b, span },
toml::Value::Float(f) => Value::Float { val: *f, span },
toml::Value::Integer(i) => Value::Int { val: *i, span },
toml::Value::Table(k) => {
let mut cols = vec![];
let mut vals = vec![];
for item in k {
cols.push(item.0.clone());
vals.push(convert_toml_to_value(item.1, span));
}
Value::Record { cols, vals, span }
}
toml::Value::Table(k) => Value::record(
k.iter()
.map(|(k, v)| (k.clone(), convert_toml_to_value(v, span)))
.collect(),
span,
),
toml::Value::String(s) => Value::String {
val: s.clone(),
span,

View File

@ -4,7 +4,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)]
@ -76,14 +76,13 @@ impl Command for FromTsv {
description: "Convert tab-separated data to a table",
example: "\"ColA\tColB\n1\t2\" | from tsv",
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_int(1),
Value::test_int(2),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
})
},

View File

@ -132,41 +132,29 @@ fn from_xlsx(
sheet_names.retain(|e| sel_sheets.contains(e));
}
for sheet_name in &sheet_names {
for sheet_name in sheet_names {
let mut sheet_output = vec![];
if let Some(Ok(current_sheet)) = xlsx.worksheet_range(sheet_name) {
if let Some(Ok(current_sheet)) = xlsx.worksheet_range(&sheet_name) {
for row in current_sheet.rows() {
let mut row_output = IndexMap::new();
for (i, cell) in row.iter().enumerate() {
let value = match cell {
DataType::Empty => Value::nothing(head),
DataType::String(s) => Value::string(s, head),
DataType::Float(f) => Value::float(*f, head),
DataType::Int(i) => Value::int(*i, head),
DataType::Bool(b) => Value::bool(*b, head),
_ => Value::nothing(head),
};
let record = row
.iter()
.enumerate()
.map(|(i, cell)| {
let value = match cell {
DataType::Empty => Value::nothing(head),
DataType::String(s) => Value::string(s, head),
DataType::Float(f) => Value::float(*f, head),
DataType::Int(i) => Value::int(*i, head),
DataType::Bool(b) => Value::bool(*b, head),
_ => Value::nothing(head),
};
row_output.insert(format!("column{i}"), value);
}
(format!("column{i}"), value)
})
.collect();
let (cols, vals) =
row_output
.into_iter()
.fold((vec![], vec![]), |mut acc, (k, v)| {
acc.0.push(k);
acc.1.push(v);
acc
});
let record = Value::Record {
cols,
vals,
span: head,
};
sheet_output.push(record);
sheet_output.push(Value::record(record, head));
}
dict.insert(
@ -186,19 +174,10 @@ fn from_xlsx(
}
}
let (cols, vals) = dict.into_iter().fold((vec![], vec![]), |mut acc, (k, v)| {
acc.0.push(k.clone());
acc.1.push(v);
acc
});
let record = Value::Record {
cols,
vals,
span: head,
};
Ok(PipelineData::Value(record, None))
Ok(PipelineData::Value(
Value::record(dict.into_iter().collect(), head),
None,
))
}
#[cfg(test)]

View File

@ -3,7 +3,7 @@ use indexmap::map::IndexMap;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, Type,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span, Type,
Value,
};
use roxmltree::NodeType;
@ -69,38 +69,46 @@ string. This way content of every tag is always a table and is easier to parse"#
<remember>Event</remember>
</note>' | from xml"#,
description: "Converts xml formatted string to record",
result: Some(Value::test_record(
vec![COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME],
vec![
result: Some(Value::test_record(Record {
cols: vec![
COLUMN_TAG_NAME.to_string(),
COLUMN_ATTRS_NAME.to_string(),
COLUMN_CONTENT_NAME.to_string(),
],
vals: vec![
Value::test_string("note"),
Value::test_record(Vec::<&str>::new(), vec![]),
Value::test_record(Record::new()),
Value::list(
vec![Value::test_record(
vec![COLUMN_TAG_NAME, COLUMN_ATTRS_NAME, COLUMN_CONTENT_NAME],
vec![
vec![Value::test_record(Record {
cols: vec![
COLUMN_TAG_NAME.to_string(),
COLUMN_ATTRS_NAME.to_string(),
COLUMN_CONTENT_NAME.to_string(),
],
vals: vec![
Value::test_string("remember"),
Value::test_record(Vec::<&str>::new(), vec![]),
Value::test_record(Record::new()),
Value::list(
vec![Value::test_record(
vec![
COLUMN_TAG_NAME,
COLUMN_ATTRS_NAME,
COLUMN_CONTENT_NAME,
vec![Value::test_record(Record {
cols: vec![
COLUMN_TAG_NAME.to_string(),
COLUMN_ATTRS_NAME.to_string(),
COLUMN_CONTENT_NAME.to_string(),
],
vec![
vals: vec![
Value::test_nothing(),
Value::test_nothing(),
Value::test_string("Event"),
],
)],
})],
Span::test_data(),
),
],
)],
})],
Span::test_data(),
),
],
)),
})),
}]
}
}
@ -116,20 +124,7 @@ fn from_attributes_to_value(attributes: &[roxmltree::Attribute], info: &ParsingI
for a in attributes {
collected.insert(String::from(a.name()), Value::string(a.value(), info.span));
}
let (cols, vals) = collected
.into_iter()
.fold((vec![], vec![]), |mut acc, (k, v)| {
acc.0.push(k);
acc.1.push(v);
acc
});
Value::Record {
cols,
vals,
span: info.span,
}
Value::record(collected.into_iter().collect(), info.span)
}
fn element_to_value(n: &roxmltree::Node, info: &ParsingInfo) -> Value {
@ -151,7 +146,7 @@ fn element_to_value(n: &roxmltree::Node, info: &ParsingInfo) -> Value {
node.insert(String::from(COLUMN_ATTRS_NAME), attributes);
node.insert(String::from(COLUMN_CONTENT_NAME), content);
Value::from(Spanned { item: node, span })
Value::record(node.into_iter().collect(), span)
}
fn text_to_value(n: &roxmltree::Node, info: &ParsingInfo) -> Option<Value> {
@ -168,9 +163,7 @@ fn text_to_value(n: &roxmltree::Node, info: &ParsingInfo) -> Option<Value> {
node.insert(String::from(COLUMN_ATTRS_NAME), Value::nothing(span));
node.insert(String::from(COLUMN_CONTENT_NAME), content);
let result = Value::from(Spanned { item: node, span });
Some(result)
Some(Value::record(node.into_iter().collect(), span))
}
}
@ -188,9 +181,7 @@ fn comment_to_value(n: &roxmltree::Node, info: &ParsingInfo) -> Option<Value> {
node.insert(String::from(COLUMN_ATTRS_NAME), Value::nothing(span));
node.insert(String::from(COLUMN_CONTENT_NAME), content);
let result = Value::from(Spanned { item: node, span });
Some(result)
Some(Value::record(node.into_iter().collect(), span))
} else {
None
}
@ -213,9 +204,7 @@ fn processing_instruction_to_value(n: &roxmltree::Node, info: &ParsingInfo) -> O
node.insert(String::from(COLUMN_ATTRS_NAME), Value::nothing(span));
node.insert(String::from(COLUMN_CONTENT_NAME), content);
let result = Value::from(Spanned { item: node, span });
Some(result)
Some(Value::record(node.into_iter().collect(), span))
} else {
None
}
@ -332,20 +321,18 @@ mod tests {
use indexmap::indexmap;
use indexmap::IndexMap;
use nu_protocol::{Spanned, Value};
fn string(input: impl Into<String>) -> Value {
Value::test_string(input)
}
fn attributes(entries: IndexMap<&str, &str>) -> Value {
Value::from(Spanned {
item: entries
Value::test_record(
entries
.into_iter()
.map(|(k, v)| (k.into(), string(v)))
.collect::<IndexMap<String, Value>>(),
span: Span::test_data(),
})
.collect(),
)
}
fn table(list: &[Value]) -> Value {
@ -360,24 +347,28 @@ mod tests {
attrs: IndexMap<&str, &str>,
content: &[Value],
) -> Value {
Value::from(Spanned {
item: indexmap! {
COLUMN_TAG_NAME.into() => string(tag),
COLUMN_ATTRS_NAME.into() => attributes(attrs),
COLUMN_CONTENT_NAME.into() => table(content),
},
span: Span::test_data(),
Value::test_record(Record {
cols: vec![
COLUMN_TAG_NAME.into(),
COLUMN_ATTRS_NAME.into(),
COLUMN_CONTENT_NAME.into(),
],
vals: vec![string(tag), attributes(attrs), table(content)],
})
}
fn content_string(value: impl Into<String>) -> Value {
Value::from(Spanned {
item: indexmap! {
COLUMN_TAG_NAME.into() => Value::nothing(Span::test_data()),
COLUMN_ATTRS_NAME.into() => Value::nothing(Span::test_data()),
COLUMN_CONTENT_NAME.into() => string(value),
},
span: Span::test_data(),
Value::test_record(Record {
cols: vec![
COLUMN_TAG_NAME.into(),
COLUMN_ATTRS_NAME.into(),
COLUMN_CONTENT_NAME.into(),
],
vals: vec![
Value::nothing(Span::test_data()),
Value::nothing(Span::test_data()),
string(value),
],
})
}

View File

@ -3,7 +3,7 @@ use itertools::Itertools;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, Type,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span, Type,
Value,
};
use serde::de::Deserialize;
@ -112,11 +112,8 @@ fn convert_yaml_value_to_nu_value(
}
}
serde_yaml::Value::Mapping(t) => {
let mut collected = Spanned {
// Using an IndexMap ensures consistent ordering
item: IndexMap::new(),
span,
};
// Using an IndexMap ensures consistent ordering
let mut collected = IndexMap::new();
for (k, v) in t {
// A ShellError that we re-use multiple times in the Mapping scenario
@ -128,19 +125,19 @@ fn convert_yaml_value_to_nu_value(
);
match (k, v) {
(serde_yaml::Value::Number(k), _) => {
collected.item.insert(
collected.insert(
k.to_string(),
convert_yaml_value_to_nu_value(v, span, val_span)?,
);
}
(serde_yaml::Value::Bool(k), _) => {
collected.item.insert(
collected.insert(
k.to_string(),
convert_yaml_value_to_nu_value(v, span, val_span)?,
);
}
(serde_yaml::Value::String(k), _) => {
collected.item.insert(
collected.insert(
k.clone(),
convert_yaml_value_to_nu_value(v, span, val_span)?,
);
@ -173,7 +170,7 @@ fn convert_yaml_value_to_nu_value(
}
}
Value::from(collected)
Value::record(collected.into_iter().collect(), span)
}
serde_yaml::Value::Tagged(t) => {
let tag = &t.tag;
@ -238,30 +235,27 @@ pub fn get_examples() -> Vec<Example<'static>> {
Example {
example: "'a: 1' | from yaml",
description: "Converts yaml formatted string to table",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(1)],
span: Span::test_data(),
}),
})),
},
Example {
example: "'[ a: 1, b: [1, 2] ]' | from yaml",
description: "Converts yaml formatted string to table",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["a".to_string()],
vals: vec![Value::test_int(1)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["b".to_string()],
vals: vec![Value::List {
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
}],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -294,20 +288,18 @@ mod test {
TestCase {
description: "Double Curly Braces With Quotes",
input: r#"value: "{{ something }}""#,
expected: Ok(Value::Record {
expected: Ok(Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::test_string("{{ something }}")],
span: Span::test_data(),
}),
})),
},
TestCase {
description: "Double Curly Braces Without Quotes",
input: r#"value: {{ something }}"#,
expected: Ok(Value::Record {
expected: Ok(Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::test_string("{{ something }}")],
span: Span::test_data(),
}),
})),
},
];
let config = Config::default();
@ -359,16 +351,14 @@ mod test {
let expected: Result<Value, ShellError> = Ok(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_string("b"), Value::test_string("c")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_string("g"), Value::test_string("h")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
});
@ -388,15 +378,15 @@ mod test {
let actual_record = actual_vals[jj].as_record().unwrap();
let expected_record = expected_vals[jj].as_record().unwrap();
let actual_columns = actual_record.0;
let expected_columns = expected_record.0;
let actual_columns = &actual_record.cols;
let expected_columns = &expected_record.cols;
assert_eq!(
expected_columns, actual_columns,
"record {jj}, iteration {ii}"
);
let actual_vals = actual_record.1;
let expected_vals = expected_record.1;
let actual_vals = &actual_record.vals;
let expected_vals = &expected_record.vals;
assert_eq!(expected_vals, actual_vals, "record {jj}, iteration {ii}")
}
}
@ -412,43 +402,38 @@ mod test {
let test_cases: Vec<TestCase> = vec![
TestCase {
input: "Key: !Value ${TEST}-Test-role",
expected: Ok(Value::Record {
expected: Ok(Value::test_record(Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!Value ${TEST}-Test-role")],
span: Span::test_data(),
}),
})),
},
TestCase {
input: "Key: !Value test-${TEST}",
expected: Ok(Value::Record {
expected: Ok(Value::test_record(Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!Value test-${TEST}")],
span: Span::test_data(),
}),
})),
},
TestCase {
input: "Key: !Value",
expected: Ok(Value::Record {
expected: Ok(Value::test_record(Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!Value")],
span: Span::test_data(),
}),
})),
},
TestCase {
input: "Key: !True",
expected: Ok(Value::Record {
expected: Ok(Value::test_record(Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!True")],
span: Span::test_data(),
}),
})),
},
TestCase {
input: "Key: !123",
expected: Ok(Value::Record {
expected: Ok(Value::test_record(Record {
cols: vec!["Key".to_string()],
vals: vec![Value::test_string("!123")],
span: Span::test_data(),
}),
})),
},
];

View File

@ -1,6 +1,6 @@
use csv::{Writer, WriterBuilder};
use nu_cmd_base::formats::to::delimited::merge_descriptors;
use nu_protocol::{Config, IntoPipelineData, PipelineData, ShellError, Span, Value};
use nu_protocol::{Config, IntoPipelineData, PipelineData, Record, ShellError, Span, Value};
use std::collections::VecDeque;
use std::error::Error;
@ -11,9 +11,7 @@ fn from_value_to_delimited_string(
head: Span,
) -> Result<String, ShellError> {
match value {
Value::Record { cols, vals, span } => {
record_to_delimited(cols, vals, *span, separator, config, head)
}
Value::Record { val, span } => record_to_delimited(val, *span, separator, config, head),
Value::List { vals, span } => table_to_delimited(vals, *span, separator, config, head),
// Propagate errors by explicitly matching them before the final case.
Value::Error { error } => Err(*error.clone()),
@ -22,8 +20,7 @@ fn from_value_to_delimited_string(
}
fn record_to_delimited(
cols: &[String],
vals: &[Value],
record: &Record,
span: Span,
separator: char,
config: &Config,
@ -35,7 +32,7 @@ fn record_to_delimited(
let mut fields: VecDeque<String> = VecDeque::new();
let mut values: VecDeque<String> = VecDeque::new();
for (k, v) in cols.iter().zip(vals.iter()) {
for (k, v) in record {
fields.push_back(k.clone());
values.push_back(to_string_tagged_value(v, config, head, span)?);

View File

@ -134,9 +134,9 @@ pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
Value::Binary { val, .. } => {
nu_json::Value::Array(val.iter().map(|x| nu_json::Value::U64(*x as u64)).collect())
}
Value::Record { cols, vals, .. } => {
Value::Record { val, .. } => {
let mut m = nu_json::Map::new();
for (k, v) in cols.iter().zip(vals) {
for (k, v) in val {
m.insert(k.clone(), value_to_json_value(v)?);
}
nu_json::Value::Object(m)

View File

@ -105,10 +105,7 @@ fn to_md(
}
fn fragment(input: Value, pretty: bool, config: &Config) -> String {
let headers = match input {
Value::Record { ref cols, .. } => cols.to_owned(),
_ => vec![],
};
let headers = input.columns();
let mut out = String::new();
if headers.len() == 1 {
@ -207,9 +204,12 @@ pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineD
let mut lists = IndexMap::new();
let mut single_list = false;
for val in values {
if let Value::Record { ref cols, .. } = val {
if let Value::Record {
val: ref record, ..
} = val
{
lists
.entry(cols.concat())
.entry(record.cols.concat())
.and_modify(|v: &mut Vec<Value>| v.push(val.clone()))
.or_insert_with(|| vec![val.clone()]);
} else {
@ -329,7 +329,7 @@ fn get_padded_string(text: String, desired_length: usize, padding_character: cha
#[cfg(test)]
mod tests {
use super::*;
use nu_protocol::{Config, IntoPipelineData, Span, Value};
use nu_protocol::{Config, IntoPipelineData, Record, Span, Value};
fn one(string: &str) -> String {
string
@ -351,44 +351,40 @@ mod tests {
#[test]
fn render_h1() {
let value = Value::Record {
let value = Value::test_record(Record {
cols: vec!["H1".to_string()],
vals: vec![Value::test_string("Ecuador")],
span: Span::test_data(),
};
});
assert_eq!(fragment(value, false, &Config::default()), "# Ecuador\n");
}
#[test]
fn render_h2() {
let value = Value::Record {
let value = Value::test_record(Record {
cols: vec!["H2".to_string()],
vals: vec![Value::test_string("Ecuador")],
span: Span::test_data(),
};
});
assert_eq!(fragment(value, false, &Config::default()), "## Ecuador\n");
}
#[test]
fn render_h3() {
let value = Value::Record {
let value = Value::test_record(Record {
cols: vec!["H3".to_string()],
vals: vec![Value::test_string("Ecuador")],
span: Span::test_data(),
};
});
assert_eq!(fragment(value, false, &Config::default()), "### Ecuador\n");
}
#[test]
fn render_blockquote() {
let value = Value::Record {
let value = Value::test_record(Record {
cols: vec!["BLOCKQUOTE".to_string()],
vals: vec![Value::test_string("Ecuador")],
span: Span::test_data(),
};
});
assert_eq!(fragment(value, false, &Config::default()), "> Ecuador\n");
}
@ -397,21 +393,18 @@ mod tests {
fn render_table() {
let value = Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["country".to_string()],
vals: vec![Value::test_string("Ecuador")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["country".to_string()],
vals: vec![Value::test_string("New Zealand")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["country".to_string()],
vals: vec![Value::test_string("USA")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
};

View File

@ -209,8 +209,8 @@ pub fn value_to_string(
for val in vals {
let mut row = vec![];
if let Value::Record { vals, .. } = val {
for val in vals {
if let Value::Record { val, .. } = val {
for val in &val.vals {
row.push(value_to_string_without_quotes(
val,
span,
@ -259,9 +259,9 @@ pub fn value_to_string(
},
value_to_string(&val.to, span, depth + 1, indent)?
)),
Value::Record { cols, vals, .. } => {
Value::Record { val, .. } => {
let mut collection = vec![];
for (col, val) in cols.iter().zip(vals) {
for (col, val) in val {
collection.push(if needs_quotes(col) {
format!(
"{idt_po}\"{}\": {}",

View File

@ -132,14 +132,13 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
}
Value::String { val, .. } => val,
Value::List { vals: val, .. } => val
.iter()
.map(|x| local_into_string(x.clone(), ", ", config))
.into_iter()
.map(|x| local_into_string(x, ", ", config))
.collect::<Vec<_>>()
.join(separator),
Value::Record { cols, vals, .. } => cols
.iter()
.zip(vals.iter())
.map(|(x, y)| format!("{}: {}", x, local_into_string(y.clone(), ", ", config)))
Value::Record { val, .. } => val
.into_iter()
.map(|(x, y)| format!("{}: {}", x, local_into_string(y, ", ", config)))
.collect::<Vec<_>>()
.join(separator),
Value::LazyRecord { val, .. } => match val.collect() {

View File

@ -54,9 +54,9 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
Value::Range { .. } => toml::Value::String("<Range>".to_string()),
Value::Float { val, .. } => toml::Value::Float(*val),
Value::String { val, .. } => toml::Value::String(val.clone()),
Value::Record { cols, vals, .. } => {
Value::Record { val, .. } => {
let mut m = toml::map::Map::new();
for (k, v) in cols.iter().zip(vals.iter()) {
for (k, v) in val {
m.insert(k.clone(), helper(engine_state, v)?);
}
toml::Value::Table(m)
@ -172,7 +172,6 @@ fn to_toml(
#[cfg(test)]
mod tests {
use super::*;
use nu_protocol::Spanned;
#[test]
fn test_examples() {
@ -201,10 +200,7 @@ mod tests {
);
let tv = value_to_toml_value(
&engine_state,
&Value::from(Spanned {
item: m,
span: Span::test_data(),
}),
&Value::record(m.into_iter().collect(), Span::test_data()),
Span::test_data(),
)
.expect("Expected Ok from valid TOML dictionary");

View File

@ -4,8 +4,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Spanned, SyntaxShape, Type, Value,
};
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use std::io::Cursor;
@ -208,9 +208,9 @@ fn to_tag_like<W: Write>(
// Allow tag to have no attributes or content for short hand input
// alternatives like {tag: a attributes: {} content: []}, {tag: a attribbutes: null
// content: null}, {tag: a}. See to_xml_entry for more
let (attr_cols, attr_values) = match attrs {
Value::Record { cols, vals, .. } => (cols, vals),
Value::Nothing { .. } => (Vec::new(), Vec::new()),
let attrs = match attrs {
Value::Record { val, .. } => val,
Value::Nothing { .. } => Record::new(),
_ => {
return Err(ShellError::CantConvert {
to_type: "XML".into(),
@ -234,15 +234,7 @@ fn to_tag_like<W: Write>(
}
};
to_tag(
entry_span,
tag,
tag_span,
attr_cols,
attr_values,
content,
writer,
)
to_tag(entry_span, tag, tag_span, attrs, content, writer)
}
}
@ -305,8 +297,7 @@ fn to_tag<W: Write>(
entry_span: Span,
tag: String,
tag_span: Span,
attr_cols: Vec<String>,
attr_vals: Vec<Value>,
attrs: Record,
children: Vec<Value>,
writer: &mut quick_xml::Writer<W>,
) -> Result<(), ShellError> {
@ -322,7 +313,7 @@ fn to_tag<W: Write>(
});
}
let attributes = parse_attributes(attr_cols, attr_vals)?;
let attributes = parse_attributes(attrs)?;
let mut open_tag_event = BytesStart::new(tag.clone());
add_attributes(&mut open_tag_event, &attributes);
@ -350,12 +341,9 @@ fn to_tag<W: Write>(
})
}
fn parse_attributes(
cols: Vec<String>,
vals: Vec<Value>,
) -> Result<IndexMap<String, String>, ShellError> {
fn parse_attributes(attrs: Record) -> Result<IndexMap<String, String>, ShellError> {
let mut h = IndexMap::new();
for (k, v) in cols.into_iter().zip(vals) {
for (k, v) in attrs {
if let Value::String { val, .. } = v {
h.insert(k, val);
} else {

View File

@ -53,9 +53,9 @@ pub fn value_to_yaml_value(v: &Value) -> Result<serde_yaml::Value, ShellError> {
Value::Range { .. } => serde_yaml::Value::Null,
Value::Float { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)),
Value::String { val, .. } => serde_yaml::Value::String(val.clone()),
Value::Record { cols, vals, .. } => {
Value::Record { val, .. } => {
let mut m = serde_yaml::Mapping::new();
for (k, v) in cols.iter().zip(vals.iter()) {
for (k, v) in val {
m.insert(
serde_yaml::Value::String(k.clone()),
value_to_yaml_value(v)?,

View File

@ -359,18 +359,7 @@ fn add_month_to_table(
day_number += 1;
}
let cols: Vec<String> = indexmap.keys().map(|f| f.to_string()).collect();
let mut vals: Vec<Value> = Vec::new();
for c in &cols {
if let Some(x) = indexmap.get(c) {
vals.push(x.to_owned())
}
}
calendar_vec_deque.push_back(Value::Record {
cols,
vals,
span: tag,
})
calendar_vec_deque.push_back(Value::record(indexmap.into_iter().collect(), tag))
}
Ok(())

View File

@ -148,8 +148,8 @@ pub fn highlight_search_in_table(
let mut matches = vec![];
for record in table {
let (cols, mut vals, record_span) = if let Value::Record { cols, vals, span } = record {
(cols, vals, span)
let (mut record, record_span) = if let Value::Record { val, span } = record {
(val, span)
} else {
return Err(ShellError::NushellFailedSpanned {
msg: "Expected record".to_string(),
@ -158,7 +158,7 @@ pub fn highlight_search_in_table(
});
};
let has_match = cols.iter().zip(vals.iter_mut()).try_fold(
let has_match = record.iter_mut().try_fold(
false,
|acc: bool, (col, val)| -> Result<bool, ShellError> {
if !searched_cols.contains(&col.as_str()) {
@ -186,11 +186,7 @@ pub fn highlight_search_in_table(
)?;
if has_match {
matches.push(Value::Record {
cols,
vals,
span: record_span,
});
matches.push(Value::record(record, record_span));
}
}

View File

@ -4,8 +4,8 @@ use nu_engine::{get_full_help, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
span, Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, Spanned, SyntaxShape, Type, Value,
record, span, Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
use std::borrow::Borrow;
@ -128,9 +128,6 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
let mut found_cmds_vec = Vec::new();
for (_, decl_id) in commands {
let mut cols = vec![];
let mut vals = vec![];
let decl = engine_state.get_decl(decl_id);
let sig = decl.signature().update_from_command(decl.borrow());
@ -138,79 +135,46 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
let usage = sig.usage;
let search_terms = sig.search_terms;
cols.push("name".into());
vals.push(Value::String { val: key, span });
cols.push("category".into());
vals.push(Value::string(sig.category.to_string(), span));
cols.push("command_type".into());
vals.push(Value::String {
val: format!("{:?}", decl.command_type()).to_lowercase(),
span,
});
cols.push("usage".into());
vals.push(Value::String { val: usage, span });
cols.push("params".into());
let command_type = format!("{:?}", decl.command_type()).to_lowercase();
// Build table of parameters
let param_table = {
let mut vals = vec![];
for required_param in &sig.required_positional {
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(&required_param.name, span),
Value::string(required_param.shape.to_string(), span),
Value::bool(true, span),
Value::string(&required_param.desc, span),
],
vals.push(Value::record(
record! {
"name" => Value::string(&required_param.name, span),
"type" => Value::string(required_param.shape.to_string(), span),
"required" => Value::bool(true, span),
"description" => Value::string(&required_param.desc, span),
},
span,
});
));
}
for optional_param in &sig.optional_positional {
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(&optional_param.name, span),
Value::string(optional_param.shape.to_string(), span),
Value::bool(false, span),
Value::string(&optional_param.desc, span),
],
vals.push(Value::record(
record! {
"name" => Value::string(&optional_param.name, span),
"type" => Value::string(optional_param.shape.to_string(), span),
"required" => Value::bool(false, span),
"description" => Value::string(&optional_param.desc, span),
},
span,
});
));
}
if let Some(rest_positional) = &sig.rest_positional {
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(format!("...{}", rest_positional.name), span),
Value::string(rest_positional.shape.to_string(), span),
Value::bool(false, span),
Value::string(&rest_positional.desc, span),
],
vals.push(Value::record(
record! {
"name" => Value::string(format!("...{}", rest_positional.name), span),
"type" => Value::string(rest_positional.shape.to_string(), span),
"required" => Value::bool(false, span),
"description" => Value::string(&rest_positional.desc, span),
},
span,
});
));
}
for named_param in &sig.named {
@ -224,68 +188,54 @@ fn build_help_commands(engine_state: &EngineState, span: Span) -> Vec<Value> {
format!("--{}", named_param.long)
};
vals.push(Value::Record {
cols: vec![
"name".to_string(),
"type".to_string(),
"required".to_string(),
"description".to_string(),
],
vals: vec![
Value::string(name, span),
Value::string(
if let Some(arg) = &named_param.arg {
arg.to_string()
} else {
"switch".to_string()
},
span,
),
Value::bool(named_param.required, span),
Value::string(&named_param.desc, span),
],
let typ = if let Some(arg) = &named_param.arg {
arg.to_string()
} else {
"switch".to_string()
};
vals.push(Value::record(
record! {
"name" => Value::string(name, span),
"type" => Value::string(typ, span),
"required" => Value::bool(named_param.required, span),
"description" => Value::string(&named_param.desc, span),
},
span,
});
));
}
Value::List { vals, span }
};
vals.push(param_table);
cols.push("input_output".into());
// Build the signature input/output table
let input_output_table = {
let mut vals = vec![];
for (input_type, output_type) in sig.input_output_types {
vals.push(Value::Record {
cols: vec!["input".to_string(), "output".to_string()],
vals: vec![
Value::String {
val: input_type.to_string(),
span,
},
Value::String {
val: output_type.to_string(),
span,
},
],
vals.push(Value::record(
record! {
"input" => Value::string(input_type.to_string(), span),
"output" => Value::string(output_type.to_string(), span),
},
span,
});
));
}
Value::List { vals, span }
};
vals.push(input_output_table);
cols.push("search_terms".into());
vals.push(Value::String {
val: search_terms.join(", "),
span,
});
let record = record! {
"name" => Value::string(key, span),
"category" => Value::string(sig.category.to_string(), span),
"command_type" => Value::string(command_type, span),
"usage" => Value::string(usage, span),
"params" => param_table,
"input_output" => input_output_table,
"search_terms" => Value::string(search_terms.join(", "), span),
};
found_cmds_vec.push(Value::Record { cols, vals, span });
found_cmds_vec.push(Value::record(record, span));
}
found_cmds_vec

View File

@ -1,7 +1,8 @@
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type, Value,
record, Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type,
Value,
};
#[derive(Clone)]
@ -35,23 +36,16 @@ impl Command for HelpOperators {
let mut recs = vec![];
for op in op_info {
let mut cols = vec![];
let mut vals = vec![];
cols.push("type".into());
vals.push(Value::string(op.op_type, head));
cols.push("operator".into());
vals.push(Value::string(op.operator, head));
cols.push("name".into());
vals.push(Value::string(op.name, head));
cols.push("description".into());
vals.push(Value::string(op.description, head));
cols.push("precedence".into());
vals.push(Value::int(op.precedence, head));
recs.push(Value::Record {
cols,
vals,
span: head,
})
recs.push(Value::record(
record! {
"type" => Value::string(op.op_type, head),
"operator" => Value::string(op.operator, head),
"name" => Value::string(op.name, head),
"description" => Value::string(op.description, head),
"precedence" => Value::int(op.precedence, head),
},
head,
));
}
Ok(recs

View File

@ -14,12 +14,8 @@ pub fn eval_env_change_hook(
) -> Result<(), ShellError> {
if let Some(hook) = env_change_hook {
match hook {
Value::Record {
cols: env_names,
vals: hook_values,
..
} => {
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
Value::Record { val, .. } => {
for (env_name, hook_value) in &val {
let before = engine_state
.previous_env_vars
.get(env_name)

View File

@ -2,7 +2,9 @@ use crate::math::reducers::{reducer_for, Reduce};
use crate::math::utils::run_with_function;
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::{
Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
@ -50,11 +52,10 @@ impl Command for SubCommand {
Example {
description: "Find the maxima of the columns of a table",
example: "[{a: 1 b: 3} {a: 2 b: -1}] | math max",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_int(2), Value::test_int(3)],
span: Span::test_data(),
}),
})),
},
]
}

View File

@ -4,7 +4,9 @@ use crate::math::avg::average;
use crate::math::utils::run_with_function;
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::{
Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
@ -54,11 +56,10 @@ impl Command for SubCommand {
Example {
description: "Compute the medians of the columns of a table",
example: "[{a: 1 b: 3} {a: 2 b: -1} {a: -3 b: 5}] | math median",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_int(1), Value::test_int(3)],
span: Span::test_data(),
}),
})),
},
]
}

View File

@ -2,7 +2,9 @@ use crate::math::reducers::{reducer_for, Reduce};
use crate::math::utils::run_with_function;
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::{
Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
#[derive(Clone)]
pub struct SubCommand;
@ -50,11 +52,10 @@ impl Command for SubCommand {
Example {
description: "Compute the minima of the columns of a table",
example: "[{a: 1 b: 3} {a: 2 b: -1}] | math min",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::test_int(1), Value::test_int(-1)],
span: Span::test_data(),
}),
})),
},
Example {
description: "Find the minimum of a list of arbitrary values (Warning: Weird)",

View File

@ -1,7 +1,9 @@
use crate::math::utils::run_with_function;
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::{
Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
use std::cmp::Ordering;
use std::collections::HashMap;
@ -88,7 +90,7 @@ impl Command for SubCommand {
Example {
description: "Compute the mode(s) of the columns of a table",
example: "[{a: 1 b: 3} {a: 2 b: -1} {a: 1 b: 5}] | math mode",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![
Value::List {
@ -100,8 +102,7 @@ impl Command for SubCommand {
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
})),
},
]
}

View File

@ -1,6 +1,6 @@
use indexmap::map::IndexMap;
use nu_protocol::ast::Call;
use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Spanned, Value};
use nu_protocol::{IntoPipelineData, PipelineData, Record, ShellError, Span, Value};
pub fn run_with_function(
call: &Call,
@ -26,8 +26,8 @@ fn helper_for_tables(
let mut column_values = IndexMap::new();
for val in values {
match val {
Value::Record { cols, vals, .. } => {
for (key, value) in cols.iter().zip(vals.iter()) {
Value::Record { val, .. } => {
for (key, value) in val {
column_values
.entry(key.clone())
.and_modify(|v: &mut Vec<Value>| v.push(value.clone()))
@ -57,10 +57,7 @@ fn helper_for_tables(
));
}
Ok(Value::from(Spanned {
item: column_totals,
span: name,
}))
Ok(Value::record(column_totals.into_iter().collect(), name))
}
pub fn calculate(
@ -83,15 +80,20 @@ pub fn calculate(
),
_ => mf(vals, span, name),
},
PipelineData::Value(Value::Record { vals, cols, span }, ..) => {
let new_vals: Result<Vec<Value>, ShellError> =
vals.into_iter().map(|val| mf(&[val], span, name)).collect();
PipelineData::Value(Value::Record { val: record, span }, ..) => {
let new_vals: Result<Vec<Value>, ShellError> = record
.vals
.into_iter()
.map(|val| mf(&[val], span, name))
.collect();
match new_vals {
Ok(vec) => Ok(Value::Record {
cols,
vals: vec,
Ok(vec) => Ok(Value::record(
Record {
cols: record.cols,
vals: vec,
},
span,
}),
)),
Err(err) => Err(err),
}
}

View File

@ -5,7 +5,7 @@ use base64::{alphabet, Engine};
use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{
BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Value,
record, BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Value,
};
use ureq::{Error, ErrorKind, Request, Response};
@ -183,12 +183,12 @@ pub fn send_request(
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
}
Value::Record { cols, vals, .. } if body_type == BodyType::Form => {
let mut data: Vec<(String, String)> = Vec::with_capacity(cols.len());
Value::Record { val, .. } if body_type == BodyType::Form => {
let mut data: Vec<(String, String)> = Vec::with_capacity(val.len());
for (col, val) in cols.iter().zip(vals.iter()) {
for (col, val) in val {
let val_string = val.as_string()?;
data.push((col.clone(), val_string))
data.push((col, val_string))
}
let request_fn = move || {
@ -296,8 +296,8 @@ pub fn request_add_custom_headers(
let mut custom_headers: HashMap<String, Value> = HashMap::new();
match &headers {
Value::Record { cols, vals, .. } => {
for (k, v) in cols.iter().zip(vals.iter()) {
Value::Record { val, .. } => {
for (k, v) in val {
custom_headers.insert(k.to_string(), v.clone());
}
}
@ -306,8 +306,8 @@ pub fn request_add_custom_headers(
if table.len() == 1 {
// single row([key1 key2]; [val1 val2])
match &table[0] {
Value::Record { cols, vals, .. } => {
for (k, v) in cols.iter().zip(vals.iter()) {
Value::Record { val, .. } => {
for (k, v) in val {
custom_headers.insert(k.to_string(), v.clone());
}
}
@ -498,25 +498,19 @@ fn request_handle_response_content(
Err(_) => Value::nothing(span),
};
let headers = Value::Record {
cols: vec!["request".to_string(), "response".to_string()],
vals: vec![request_headers_value, response_headers_value],
span,
let headers = record! {
"request" => request_headers_value,
"response" => response_headers_value,
};
let full_response = Value::Record {
cols: vec![
"headers".to_string(),
"body".to_string(),
"status".to_string(),
],
vals: vec![
headers,
consume_response_body(resp)?.into_value(span),
Value::int(response_status as i64, span),
],
let full_response = Value::record(
record! {
"headers" => Value::record(headers, span),
"body" => consume_response_body(resp)?.into_value(span),
"status" => Value::int(response_status as i64, span),
},
span,
};
);
Ok(full_response.into_pipeline_data())
} else {
@ -597,15 +591,14 @@ fn extract_response_headers(response: &Response) -> Headers {
}
fn headers_to_nu(headers: &Headers, span: Span) -> Result<PipelineData, ShellError> {
let cols = vec!["name".to_string(), "value".to_string()];
let mut vals = Vec::with_capacity(headers.len());
for (name, values) in headers {
let is_duplicate = vals.iter().any(|val| {
if let Value::Record { vals, .. } = val {
if let Value::Record { val, .. } = val {
if let Some(Value::String {
val: header_name, ..
}) = vals.get(0)
}) = val.vals.get(0)
{
return name == header_name;
}
@ -616,11 +609,11 @@ fn headers_to_nu(headers: &Headers, span: Span) -> Result<PipelineData, ShellErr
// A single header can hold multiple values
// This interface is why we needed to check if we've already parsed this header name.
for str_value in values {
let header = vec![
Value::string(name, span),
Value::string(str_value.to_string(), span),
];
vals.push(Value::record(cols.clone(), header, span));
let record = record! {
"name" => Value::string(name, span),
"value" => Value::string(str_value, span),
};
vals.push(Value::record(record, span));
}
}
}

View File

@ -65,13 +65,9 @@ fn to_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
let output: Result<String, ShellError> = input
.into_iter()
.map(move |value| match value {
Value::Record {
ref cols,
ref vals,
span,
} => {
Value::Record { ref val, span } => {
let mut row_vec = vec![];
for (k, v) in cols.iter().zip(vals.iter()) {
for (k, v) in val {
match v.as_string() {
Ok(s) => {
row_vec.push((k.clone(), s.to_string()));

View File

@ -92,16 +92,11 @@ impl Command for SubCommand {
let output: Result<String, ShellError> = input
.into_iter()
.map(move |value| match value {
Value::Record {
ref cols,
ref vals,
span,
} => {
let url_components = cols
.iter()
.zip(vals.iter())
Value::Record { val, span } => {
let url_components = val
.into_iter()
.try_fold(UrlComponents::new(), |url, (k, v)| {
url.add_component(k.clone(), v.clone(), span)
url.add_component(k, v, span)
});
url_components?.to_url(span)
@ -176,14 +171,9 @@ impl UrlComponents {
if key == "params" {
return match value {
Value::Record {
ref cols,
ref vals,
span,
} => {
let mut qs = cols
Value::Record { ref val, span } => {
let mut qs = val
.iter()
.zip(vals.iter())
.map(|(k, v)| match v.as_string() {
Ok(val) => Ok(format!("{k}={val}")),
Err(err) => Err(err),

View File

@ -2,7 +2,8 @@ use super::url;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
record, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape,
Type, Value,
};
use url::Url;
@ -55,7 +56,7 @@ impl Command for SubCommand {
vec![Example {
description: "Parses a url",
example: "'http://user123:pass567@www.example.com:8081/foo/bar?param1=section&p2=&f[name]=vldc#hello' | url parse",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"scheme".to_string(),
"username".to_string(),
@ -76,18 +77,16 @@ impl Command for SubCommand {
Value::test_string("/foo/bar"),
Value::test_string("param1=section&p2=&f[name]=vldc"),
Value::test_string("hello"),
Value::Record {
Value::test_record(Record {
cols: vec!["param1".to_string(), "p2".to_string(), "f[name]".to_string()],
vals: vec![
Value::test_string("section"),
Value::test_string(""),
Value::test_string("vldc"),
],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
})),
}]
}
}
@ -106,80 +105,31 @@ fn parse(value: Value, head: Span, engine_state: &EngineState) -> Result<Pipelin
match result_url {
Ok(url) => {
let cols = vec![
String::from("scheme"),
String::from("username"),
String::from("password"),
String::from("host"),
String::from("port"),
String::from("path"),
String::from("query"),
String::from("fragment"),
String::from("params"),
];
let mut vals: Vec<Value> = vec![
Value::String {
val: String::from(url.scheme()),
span: head,
},
Value::String {
val: String::from(url.username()),
span: head,
},
Value::String {
val: String::from(url.password().unwrap_or("")),
span: head,
},
Value::String {
val: String::from(url.host_str().unwrap_or("")),
span: head,
},
Value::String {
val: url
.port()
.map(|p| p.to_string())
.unwrap_or_else(|| "".into()),
span: head,
},
Value::String {
val: String::from(url.path()),
span: head,
},
Value::String {
val: String::from(url.query().unwrap_or("")),
span: head,
},
Value::String {
val: String::from(url.fragment().unwrap_or("")),
span: head,
},
];
let params =
serde_urlencoded::from_str::<Vec<(String, String)>>(url.query().unwrap_or(""));
match params {
Ok(result) => {
let (param_cols, param_vals) = result
let params = result
.into_iter()
.map(|(k, v)| (k, Value::String { val: v, span: head }))
.unzip();
.map(|(k, v)| (k, Value::string(v, head)))
.collect();
vals.push(Value::Record {
cols: param_cols,
vals: param_vals,
span: head,
});
let port = url.port().map(|p| p.to_string()).unwrap_or_default();
Ok(PipelineData::Value(
Value::Record {
cols,
vals,
span: head,
},
None,
))
let record = record! {
"scheme" => Value::string(url.scheme(), head),
"username" => Value::string(url.username(), head),
"password" => Value::string(url.password().unwrap_or(""), head),
"host" => Value::string(url.host_str().unwrap_or(""), head),
"port" => Value::string(port, head),
"path" => Value::string(url.path(), head),
"query" => Value::string(url.query().unwrap_or(""), head),
"fragment" => Value::string(url.fragment().unwrap_or(""), head),
"params" => Value::record(params, head),
};
Ok(PipelineData::Value(Value::record(record, head), None))
}
_ => Err(ShellError::UnsupportedInput(
"String not compatible with url-encoding".to_string(),
"value originates from here".into(),

View File

@ -5,7 +5,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
};
@ -150,7 +150,7 @@ the output of 'path parse' and 'path split' subcommands."#
fn handle_value(v: Value, args: &Arguments, head: Span) -> Value {
match v {
Value::String { ref val, .. } => join_single(Path::new(val), head, args),
Value::Record { cols, vals, span } => join_record(&cols, &vals, head, span, args),
Value::Record { val, span } => join_record(&val, head, span, args),
Value::List { vals, span } => join_list(&vals, head, span, args),
_ => super::handle_invalid_values(v, head),
@ -177,7 +177,7 @@ fn join_list(parts: &[Value], head: Span, span: Span, args: &Arguments) -> Value
Ok(vals) => {
let vals = vals
.iter()
.map(|(k, v)| join_record(k, v, head, span, args))
.map(|r| join_record(r, head, span, args))
.collect();
Value::List { vals, span }
@ -194,8 +194,8 @@ fn join_list(parts: &[Value], head: Span, span: Span, args: &Arguments) -> Value
}
}
fn join_record(cols: &[String], vals: &[Value], head: Span, span: Span, args: &Arguments) -> Value {
match merge_record(cols, vals, head, span) {
fn join_record(record: &Record, head: Span, span: Span, args: &Arguments) -> Value {
match merge_record(record, head, span) {
Ok(p) => join_single(p.as_path(), head, args),
Err(error) => Value::Error {
error: Box::new(error),
@ -203,13 +203,8 @@ fn join_record(cols: &[String], vals: &[Value], head: Span, span: Span, args: &A
}
}
fn merge_record(
cols: &[String],
vals: &[Value],
head: Span,
span: Span,
) -> Result<PathBuf, ShellError> {
for key in cols {
fn merge_record(record: &Record, head: Span, span: Span) -> Result<PathBuf, ShellError> {
for key in &record.cols {
if !super::ALLOWED_COLUMNS.contains(&key.as_str()) {
let allowed_cols = super::ALLOWED_COLUMNS.join(", ");
return Err(ShellError::UnsupportedInput(
@ -223,7 +218,13 @@ fn merge_record(
}
}
let entries: HashMap<&str, &Value> = cols.iter().map(String::as_str).zip(vals).collect();
let entries: HashMap<&str, &Value> = record
.cols
.iter()
.map(String::as_str)
.zip(&record.vals)
.collect();
let mut result = PathBuf::new();
#[cfg(windows)]

View File

@ -1,11 +1,10 @@
use std::path::Path;
use indexmap::IndexMap;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{
engine::Command, Category, Example, PipelineData, ShellError, Signature, Span, Spanned,
engine::Command, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value,
};
@ -77,7 +76,7 @@ On Windows, an extra 'prefix' column is added."#
Example {
description: "Parse a single path",
example: r"'C:\Users\viking\spam.txt' | path parse",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"prefix".into(),
"parent".into(),
@ -90,8 +89,7 @@ On Windows, an extra 'prefix' column is added."#
Value::test_string("spam"),
Value::test_string("txt"),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Replace a complex extension",
@ -101,7 +99,7 @@ On Windows, an extra 'prefix' column is added."#
Example {
description: "Ignore the extension",
example: r"'C:\Users\viking.d' | path parse -e ''",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"prefix".into(),
"parent".into(),
@ -114,14 +112,13 @@ On Windows, an extra 'prefix' column is added."#
Value::test_string("viking.d"),
Value::test_string(""),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Parse all paths in a list",
example: r"[ C:\Users\viking.d C:\Users\spam.txt ] | path parse",
result: Some(Value::test_list(vec![
Value::Record {
Value::test_record(Record {
cols: vec![
"prefix".into(),
"parent".into(),
@ -134,9 +131,8 @@ On Windows, an extra 'prefix' column is added."#
Value::test_string("viking"),
Value::test_string("d"),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec![
"prefix".into(),
"parent".into(),
@ -149,8 +145,7 @@ On Windows, an extra 'prefix' column is added."#
Value::test_string("spam"),
Value::test_string("txt"),
],
span: Span::test_data(),
},
}),
])),
},
]
@ -162,15 +157,14 @@ On Windows, an extra 'prefix' column is added."#
Example {
description: "Parse a path",
example: r"'/home/viking/spam.txt' | path parse",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["parent".into(), "stem".into(), "extension".into()],
vals: vec![
Value::test_string("/home/viking"),
Value::test_string("spam"),
Value::test_string("txt"),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Replace a complex extension",
@ -180,38 +174,35 @@ On Windows, an extra 'prefix' column is added."#
Example {
description: "Ignore the extension",
example: r"'/etc/conf.d' | path parse -e ''",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["parent".into(), "stem".into(), "extension".into()],
vals: vec![
Value::test_string("/etc"),
Value::test_string("conf.d"),
Value::test_string(""),
],
span: Span::test_data(),
}),
})),
},
Example {
description: "Parse all paths in a list",
example: r"[ /home/viking.d /home/spam.txt ] | path parse",
result: Some(Value::test_list(vec![
Value::Record {
Value::test_record(Record {
cols: vec!["parent".into(), "stem".into(), "extension".into()],
vals: vec![
Value::test_string("/home"),
Value::test_string("viking"),
Value::test_string("d"),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["parent".into(), "stem".into(), "extension".into()],
vals: vec![
Value::test_string("/home"),
Value::test_string("spam"),
Value::test_string("txt"),
],
span: Span::test_data(),
},
}),
])),
},
]
@ -219,7 +210,7 @@ On Windows, an extra 'prefix' column is added."#
}
fn parse(path: &Path, span: Span, args: &Arguments) -> Value {
let mut map: IndexMap<String, Value> = IndexMap::new();
let mut record = Record::new();
#[cfg(windows)]
{
@ -231,7 +222,7 @@ fn parse(path: &Path, span: Span, args: &Arguments) -> Value {
}
_ => "".into(),
};
map.insert("prefix".into(), Value::string(prefix, span));
record.push("prefix", Value::string(prefix, span));
}
let parent = path
@ -239,7 +230,7 @@ fn parse(path: &Path, span: Span, args: &Arguments) -> Value {
.unwrap_or_else(|| "".as_ref())
.to_string_lossy();
map.insert("parent".into(), Value::string(parent, span));
record.push("parent", Value::string(parent, span));
let basename = path
.file_name()
@ -254,14 +245,11 @@ fn parse(path: &Path, span: Span, args: &Arguments) -> Value {
let ext_with_dot = [".", extension].concat();
if basename.ends_with(&ext_with_dot) && !extension.is_empty() {
let stem = basename.trim_end_matches(&ext_with_dot);
map.insert("stem".into(), Value::string(stem, span));
map.insert(
"extension".into(),
Value::string(extension, *extension_span),
);
record.push("stem", Value::string(stem, span));
record.push("extension", Value::string(extension, *extension_span));
} else {
map.insert("stem".into(), Value::string(basename, span));
map.insert("extension".into(), Value::string("", span));
record.push("stem", Value::string(basename, span));
record.push("extension", Value::string("", span));
}
}
None => {
@ -274,12 +262,12 @@ fn parse(path: &Path, span: Span, args: &Arguments) -> Value {
.unwrap_or_else(|| "".as_ref())
.to_string_lossy();
map.insert("stem".into(), Value::string(stem, span));
map.insert("extension".into(), Value::string(extension, span));
record.push("stem", Value::string(stem, span));
record.push("extension", Value::string(extension, span));
}
}
Value::from(Spanned { item: map, span })
Value::record(record, span)
}
#[cfg(test)]

View File

@ -1,6 +1,7 @@
use nu_ansi_term::*;
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,
@ -763,7 +764,7 @@ Operating system commands:
attr: None,
};
// Iterate and populate NuStyle with real values
for (k, v) in record.0.iter().zip(record.1) {
for (k, v) in record {
match k.as_str() {
"fg" => nu_style.fg = Some(v.as_string()?),
"bg" => nu_style.bg = Some(v.as_string()?),
@ -799,17 +800,7 @@ fn generate_ansi_code_list(
.iter()
.enumerate()
.map(move |(i, ansi_code)| {
let cols = if use_ansi_coloring {
vec![
"name".into(),
"preview".into(),
"short name".into(),
"code".into(),
]
} else {
vec!["name".into(), "short name".into(), "code".into()]
};
let name: Value = Value::string(String::from(ansi_code.long_name), call_span);
let name = Value::string(ansi_code.long_name, call_span);
let short_name = Value::string(ansi_code.short_name.unwrap_or(""), call_span);
// The first 102 items in the ansi array are colors
let preview = if i < 389 {
@ -817,18 +808,24 @@ fn generate_ansi_code_list(
} else {
Value::string("\u{1b}[0m", call_span)
};
let code_string = String::from(&ansi_code.code.replace('\u{1b}', "\\e"));
let code = Value::string(code_string, call_span);
let vals = if use_ansi_coloring {
vec![name, preview, short_name, code]
let code = Value::string(ansi_code.code.replace('\u{1b}', "\\e"), call_span);
let record = if use_ansi_coloring {
record! {
"name" => name,
"preview" => preview,
"short name" => short_name,
"code" => code,
}
} else {
vec![name, short_name, code]
record! {
"name" => name,
"short name" => short_name,
"code" => code,
}
};
Value::Record {
cols,
vals,
span: call_span,
}
Value::record(record, call_span)
})
.into_pipeline_data(engine_state.ctrlc.clone()));
}

View File

@ -1,6 +1,6 @@
use filesize::file_real_size_fast;
use nu_glob::Pattern;
use nu_protocol::{ShellError, Span, Value};
use nu_protocol::{record, ShellError, Span, Value};
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@ -185,30 +185,6 @@ impl DirInfo {
impl From<DirInfo> for Value {
fn from(d: DirInfo) -> Self {
let mut cols = vec![];
let mut vals = vec![];
cols.push("path".into());
vals.push(Value::string(d.path.display().to_string(), d.tag));
cols.push("apparent".into());
vals.push(Value::Filesize {
val: d.size as i64,
span: d.tag,
});
cols.push("physical".into());
vals.push(Value::Filesize {
val: d.blocks as i64,
span: d.tag,
});
cols.push("directories".into());
vals.push(value_from_vec(d.dirs, d.tag));
cols.push("files".into());
vals.push(value_from_vec(d.files, d.tag));
// if !d.errors.is_empty() {
// let v = d
// .errors
@ -223,51 +199,34 @@ impl From<DirInfo> for Value {
// })
// }
Value::Record {
cols,
vals,
span: d.tag,
}
Value::record(
record! {
"path" => Value::string(d.path.display().to_string(), d.tag),
"apparent" => Value::filesize(d.size as i64, d.tag),
"physical" => Value::filesize(d.blocks as i64, d.tag),
"directories" => value_from_vec(d.dirs, d.tag),
"files" => value_from_vec(d.files, d.tag)
},
d.tag,
)
}
}
impl From<FileInfo> for Value {
fn from(f: FileInfo) -> Self {
let mut cols = vec![];
let mut vals = vec![];
cols.push("path".into());
vals.push(Value::string(f.path.display().to_string(), f.tag));
cols.push("apparent".into());
vals.push(Value::Filesize {
val: f.size as i64,
span: f.tag,
});
cols.push("physical".into());
vals.push(Value::Filesize {
val: match f.blocks {
Some(b) => b as i64,
None => 0i64,
},
span: f.tag,
});
cols.push("directories".into());
vals.push(Value::nothing(Span::unknown()));
cols.push("files".into());
vals.push(Value::nothing(Span::unknown()));
// cols.push("errors".into());
// vals.push(Value::nothing(Span::unknown()));
Value::Record {
cols,
vals,
span: f.tag,
}
Value::record(
record! {
"path" => Value::string(f.path.display().to_string(), f.tag),
"apparent" => Value::filesize(f.size as i64, f.tag),
"physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
"directories" => Value::nothing(Span::unknown()),
"files" => Value::nothing(Span::unknown()),
},
f.tag,
)
}
}

Some files were not shown because too many files have changed in this diff Show More