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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
195 changed files with 4211 additions and 6245 deletions

View File

@ -114,13 +114,14 @@ fn eval_benchmarks(c: &mut Criterion) {
// generate a new table data with `row_cnt` rows, `col_cnt` columns. // generate a new table data with `row_cnt` rows, `col_cnt` columns.
fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value { fn encoding_test_data(row_cnt: usize, col_cnt: usize) -> Value {
let columns: Vec<String> = (0..col_cnt).map(|x| format!("col_{x}")).collect(); let record = Value::test_record(
let vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect(); (0..col_cnt)
.map(|x| (format!("col_{x}"), Value::test_int(x as i64)))
.collect(),
);
Value::List { Value::List {
vals: (0..row_cnt) vals: vec![record; row_cnt],
.map(|_| Value::test_record(columns.clone(), vals.clone()))
.collect(),
span: Span::test_data(), span: Span::test_data(),
} }
} }

View File

@ -1,8 +1,8 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError, record, Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData,
Signature, Span, Type, Value, ShellError, Signature, Span, Type, Value,
}; };
use reedline::{ use reedline::{
FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery, FileBackedHistory, History as ReedlineHistory, HistoryItem, SearchDirection, SearchQuery,
@ -95,20 +95,15 @@ impl Command for History {
.ok() .ok()
}) })
.map(move |entries| { .map(move |entries| {
entries entries.into_iter().enumerate().map(move |(idx, entry)| {
.into_iter() Value::record(
.enumerate() record! {
.map(move |(idx, entry)| Value::Record { "command" => Value::string(entry.command_line, head),
cols: vec!["command".to_string(), "index".to_string()], "index" => Value::int(idx as i64, head),
vals: vec![ },
Value::String { head,
val: entry.command_line, )
span: head, })
},
Value::int(idx as i64, head),
],
span: head,
})
}) })
.ok_or(ShellError::FileNotFound(head))? .ok_or(ShellError::FileNotFound(head))?
.into_pipeline_data(ctrlc)), .into_pipeline_data(ctrlc)),
@ -217,48 +212,30 @@ fn create_history_record(idx: usize, entry: HistoryItem, long: bool, head: Span)
let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head); let exit_status_value = Value::int(entry.exit_status.unwrap_or(0), head);
let index_value = Value::int(idx as i64, head); let index_value = Value::int(idx as i64, head);
if long { if long {
Value::Record { Value::record(
cols: vec![ record! {
"item_id".into(), "item_id" => item_id_value,
"start_timestamp".into(), "start_timestamp" => start_timestamp_value,
"command".to_string(), "command" => command_value,
"session_id".into(), "session_id" => session_id_value,
"hostname".into(), "hostname" => hostname_value,
"cwd".into(), "cwd" => cwd_value,
"duration".into(), "duration" => duration_value,
"exit_status".into(), "exit_status" => exit_status_value,
"idx".to_string(), "idx" => index_value,
], },
vals: vec![ head,
item_id_value, )
start_timestamp_value,
command_value,
session_id_value,
hostname_value,
cwd_value,
duration_value,
exit_status_value,
index_value,
],
span: head,
}
} else { } else {
Value::Record { Value::record(
cols: vec![ record! {
"start_timestamp".into(), "start_timestamp" => start_timestamp_value,
"command".to_string(), "command" => command_value,
"cwd".into(), "cwd" => cwd_value,
"duration".into(), "duration" => duration_value,
"exit_status".into(), "exit_status" => exit_status_value,
], },
vals: vec![ head,
start_timestamp_value, )
command_value,
cwd_value,
duration_value,
exit_status_value,
],
span: head,
}
} }
} }

View File

@ -1,7 +1,7 @@
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
}; };
use reedline::get_reedline_default_keybindings; use reedline::get_reedline_default_keybindings;
@ -41,36 +41,15 @@ impl Command for KeybindingsDefault {
let records = get_reedline_default_keybindings() let records = get_reedline_default_keybindings()
.into_iter() .into_iter()
.map(|(mode, modifier, code, event)| { .map(|(mode, modifier, code, event)| {
let mode = Value::String { Value::record(
val: mode, record! {
span: call.head, "mode" => Value::string(mode, call.head),
}; "modifier" => Value::string(modifier, call.head),
"code" => Value::string(code, call.head),
let modifier = Value::String { "event" => Value::string(event, call.head),
val: modifier, },
span: call.head, call.head,
}; )
let code = Value::String {
val: code,
span: call.head,
};
let event = Value::String {
val: event,
span: call.head,
};
Value::Record {
cols: vec![
"mode".to_string(),
"modifier".to_string(),
"code".to_string(),
"event".to_string(),
],
vals: vec![mode, modifier, code, event],
span: call.head,
}
}) })
.collect(); .collect();

View File

@ -1,7 +1,8 @@
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value, record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
Value,
}; };
use reedline::{ use reedline::{
get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes, get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes,
@ -96,15 +97,13 @@ fn get_records(entry_type: &str, span: Span) -> Vec<Value> {
} }
fn convert_to_record(edit: &str, entry_type: &str, span: Span) -> Value { fn convert_to_record(edit: &str, entry_type: &str, span: Span) -> Value {
let entry_type = Value::string(entry_type, span); Value::record(
record! {
let name = Value::string(edit, span); "type" => Value::string(entry_type, span),
"name" => Value::string(edit, span),
Value::Record { },
cols: vec!["type".to_string(), "name".to_string()],
vals: vec![entry_type, name],
span, span,
} )
} }
// Helper to sort a vec and return a vec // Helper to sort a vec and return a vec

View File

@ -3,7 +3,8 @@ use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value, record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type,
Value,
}; };
use std::io::{stdout, Write}; use std::io::{stdout, Write};
@ -78,9 +79,8 @@ pub fn print_events(engine_state: &EngineState) -> Result<Value, ShellError> {
let v = print_events_helper(event)?; let v = print_events_helper(event)?;
// Print out the record // Print out the record
let o = match v { let o = match v {
Value::Record { cols, vals, .. } => cols Value::Record { val, .. } => val
.iter() .iter()
.zip(vals.iter())
.map(|(x, y)| format!("{}: {}", x, y.into_string("", config))) .map(|(x, y)| format!("{}: {}", x, y.into_string("", config)))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "), .join(", "),
@ -111,54 +111,29 @@ fn print_events_helper(event: Event) -> Result<Value, ShellError> {
{ {
match code { match code {
KeyCode::Char(c) => { KeyCode::Char(c) => {
let record = Value::Record { let record = record! {
cols: vec![ "char" => Value::string(format!("{c}"), Span::unknown()),
"char".into(), "code" => Value::string(format!("{:#08x}", u32::from(c)), Span::unknown()),
"code".into(), "modifier" => Value::string(format!("{modifiers:?}"), Span::unknown()),
"modifier".into(), "flags" => Value::string(format!("{modifiers:#08b}"), Span::unknown()),
"flags".into(), "kind" => Value::string(format!("{kind:?}"), Span::unknown()),
"kind".into(), "state" => Value::string(format!("{state:?}"), Span::unknown()),
"state".into(),
],
vals: vec![
Value::string(format!("{c}"), Span::unknown()),
Value::string(format!("{:#08x}", u32::from(c)), Span::unknown()),
Value::string(format!("{modifiers:?}"), Span::unknown()),
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
Value::string(format!("{kind:?}"), Span::unknown()),
Value::string(format!("{state:?}"), Span::unknown()),
],
span: Span::unknown(),
}; };
Ok(record) Ok(Value::record(record, Span::unknown()))
} }
_ => { _ => {
let record = Value::Record { let record = record! {
cols: vec![ "code" => Value::string(format!("{code:?}"), Span::unknown()),
"code".into(), "modifier" => Value::string(format!("{modifiers:?}"), Span::unknown()),
"modifier".into(), "flags" => Value::string(format!("{modifiers:#08b}"), Span::unknown()),
"flags".into(), "kind" => Value::string(format!("{kind:?}"), Span::unknown()),
"kind".into(), "state" => Value::string(format!("{state:?}"), Span::unknown()),
"state".into(),
],
vals: vec![
Value::string(format!("{code:?}"), Span::unknown()),
Value::string(format!("{modifiers:?}"), Span::unknown()),
Value::string(format!("{modifiers:#08b}"), Span::unknown()),
Value::string(format!("{kind:?}"), Span::unknown()),
Value::string(format!("{state:?}"), Span::unknown()),
],
span: Span::unknown(),
}; };
Ok(record) Ok(Value::record(record, Span::unknown()))
} }
} }
} else { } else {
let record = Value::Record { let record = record! { "event" => Value::string(format!("{event:?}"), Span::unknown()) };
cols: vec!["event".into()], Ok(Value::record(record, Span::unknown()))
vals: vec![Value::string(format!("{event:?}"), Span::unknown())],
span: Span::unknown(),
};
Ok(record)
} }
} }

View File

@ -454,7 +454,7 @@ pub fn map_value_completions<'a>(
} }
// Match for record values // Match for record values
if let Ok((cols, vals)) = x.as_record() { if let Ok(record) = x.as_record() {
let mut suggestion = Suggestion { let mut suggestion = Suggestion {
value: String::from(""), // Initialize with empty string value: String::from(""), // Initialize with empty string
description: None, description: None,
@ -467,7 +467,7 @@ pub fn map_value_completions<'a>(
}; };
// Iterate the cols looking for `value` and `description` // Iterate the cols looking for `value` and `description`
cols.iter().zip(vals).for_each(|it| { record.iter().for_each(|it| {
// Match `value` column // Match `value` column
if it.0 == "value" { if it.0 == "value" {
// Convert the value to string // Convert the value to string

View File

@ -235,13 +235,9 @@ fn nested_suggestions(
let value = recursive_value(val, sublevels); let value = recursive_value(val, sublevels);
match value { match value {
Value::Record { Value::Record { val, .. } => {
cols,
vals: _,
span: _,
} => {
// Add all the columns as completion // Add all the columns as completion
for item in cols { for item in val.cols {
output.push(Suggestion { output.push(Suggestion {
value: item, value: item,
description: None, description: None,
@ -289,12 +285,8 @@ fn recursive_value(val: Value, sublevels: Vec<Vec<u8>>) -> Value {
// Go to next sublevel // Go to next sublevel
if let Some(next_sublevel) = sublevels.clone().into_iter().next() { if let Some(next_sublevel) = sublevels.clone().into_iter().next() {
match val { match val {
Value::Record { Value::Record { val, .. } => {
cols, for item in val {
vals,
span: _,
} => {
for item in cols.into_iter().zip(vals) {
// Check if index matches with sublevel // Check if index matches with sublevel
if item.0.as_bytes().to_vec() == next_sublevel { if item.0.as_bytes().to_vec() == next_sublevel {
// If matches try to fetch recursively the next // If matches try to fetch recursively the next

View File

@ -7,7 +7,8 @@ use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
create_menus, create_menus,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, ShellError, Span, Value, extract_value, Config, ParsedKeybinding, ParsedMenu, PipelineData, Record, ShellError, Span,
Value,
}; };
use reedline::{ use reedline::{
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
@ -130,8 +131,8 @@ fn add_menu(
stack: &Stack, stack: &Stack,
config: &Config, config: &Config,
) -> Result<Reedline, ShellError> { ) -> Result<Reedline, ShellError> {
if let Value::Record { cols, vals, span } = &menu.menu_type { if let Value::Record { val, span } = &menu.menu_type {
let layout = extract_value("layout", cols, vals, *span)?.into_string("", config); let layout = extract_value("layout", val, *span)?.into_string("", config);
match layout.as_str() { match layout.as_str() {
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config), "columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
@ -154,8 +155,8 @@ fn add_menu(
macro_rules! add_style { macro_rules! add_style {
// first arm match add!(1,2), add!(2,3) etc // first arm match add!(1,2), add!(2,3) etc
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => { ($name:expr, $record: expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
$menu = match extract_value($name, $cols, $vals, *$span) { $menu = match extract_value($name, $record, *$span) {
Ok(text) => { Ok(text) => {
let style = match text { let style = match text {
Value::String { val, .. } => lookup_ansi_color_style(&val), Value::String { val, .. } => lookup_ansi_color_style(&val),
@ -180,8 +181,8 @@ pub(crate) fn add_columnar_menu(
let name = menu.name.into_string("", config); let name = menu.name.into_string("", config);
let mut columnar_menu = ColumnarMenu::default().with_name(&name); let mut columnar_menu = ColumnarMenu::default().with_name(&name);
if let Value::Record { cols, vals, span } = &menu.menu_type { if let Value::Record { val, span } = &menu.menu_type {
columnar_menu = match extract_value("columns", cols, vals, *span) { columnar_menu = match extract_value("columns", val, *span) {
Ok(columns) => { Ok(columns) => {
let columns = columns.as_int()?; let columns = columns.as_int()?;
columnar_menu.with_columns(columns as u16) columnar_menu.with_columns(columns as u16)
@ -189,7 +190,7 @@ pub(crate) fn add_columnar_menu(
Err(_) => columnar_menu, Err(_) => columnar_menu,
}; };
columnar_menu = match extract_value("col_width", cols, vals, *span) { columnar_menu = match extract_value("col_width", val, *span) {
Ok(col_width) => { Ok(col_width) => {
let col_width = col_width.as_int()?; let col_width = col_width.as_int()?;
columnar_menu.with_column_width(Some(col_width as usize)) columnar_menu.with_column_width(Some(col_width as usize))
@ -197,7 +198,7 @@ pub(crate) fn add_columnar_menu(
Err(_) => columnar_menu.with_column_width(None), Err(_) => columnar_menu.with_column_width(None),
}; };
columnar_menu = match extract_value("col_padding", cols, vals, *span) { columnar_menu = match extract_value("col_padding", val, *span) {
Ok(col_padding) => { Ok(col_padding) => {
let col_padding = col_padding.as_int()?; let col_padding = col_padding.as_int()?;
columnar_menu.with_column_padding(col_padding as usize) columnar_menu.with_column_padding(col_padding as usize)
@ -206,11 +207,10 @@ pub(crate) fn add_columnar_menu(
}; };
} }
if let Value::Record { cols, vals, span } = &menu.style { if let Value::Record { val, span } = &menu.style {
add_style!( add_style!(
"text", "text",
cols, val,
vals,
span, span,
config, config,
columnar_menu, columnar_menu,
@ -218,8 +218,7 @@ pub(crate) fn add_columnar_menu(
); );
add_style!( add_style!(
"selected_text", "selected_text",
cols, val,
vals,
span, span,
config, config,
columnar_menu, columnar_menu,
@ -227,8 +226,7 @@ pub(crate) fn add_columnar_menu(
); );
add_style!( add_style!(
"description_text", "description_text",
cols, val,
vals,
span, span,
config, config,
columnar_menu, columnar_menu,
@ -282,8 +280,8 @@ pub(crate) fn add_list_menu(
let name = menu.name.into_string("", config); let name = menu.name.into_string("", config);
let mut list_menu = ListMenu::default().with_name(&name); let mut list_menu = ListMenu::default().with_name(&name);
if let Value::Record { cols, vals, span } = &menu.menu_type { if let Value::Record { val, span } = &menu.menu_type {
list_menu = match extract_value("page_size", cols, vals, *span) { list_menu = match extract_value("page_size", val, *span) {
Ok(page_size) => { Ok(page_size) => {
let page_size = page_size.as_int()?; let page_size = page_size.as_int()?;
list_menu.with_page_size(page_size as usize) list_menu.with_page_size(page_size as usize)
@ -292,11 +290,10 @@ pub(crate) fn add_list_menu(
}; };
} }
if let Value::Record { cols, vals, span } = &menu.style { if let Value::Record { val, span } = &menu.style {
add_style!( add_style!(
"text", "text",
cols, val,
vals,
span, span,
config, config,
list_menu, list_menu,
@ -304,8 +301,7 @@ pub(crate) fn add_list_menu(
); );
add_style!( add_style!(
"selected_text", "selected_text",
cols, val,
vals,
span, span,
config, config,
list_menu, list_menu,
@ -313,8 +309,7 @@ pub(crate) fn add_list_menu(
); );
add_style!( add_style!(
"description_text", "description_text",
cols, val,
vals,
span, span,
config, config,
list_menu, list_menu,
@ -368,8 +363,8 @@ pub(crate) fn add_description_menu(
let name = menu.name.into_string("", config); let name = menu.name.into_string("", config);
let mut description_menu = DescriptionMenu::default().with_name(&name); let mut description_menu = DescriptionMenu::default().with_name(&name);
if let Value::Record { cols, vals, span } = &menu.menu_type { if let Value::Record { val, span } = &menu.menu_type {
description_menu = match extract_value("columns", cols, vals, *span) { description_menu = match extract_value("columns", val, *span) {
Ok(columns) => { Ok(columns) => {
let columns = columns.as_int()?; let columns = columns.as_int()?;
description_menu.with_columns(columns as u16) description_menu.with_columns(columns as u16)
@ -377,7 +372,7 @@ pub(crate) fn add_description_menu(
Err(_) => description_menu, Err(_) => description_menu,
}; };
description_menu = match extract_value("col_width", cols, vals, *span) { description_menu = match extract_value("col_width", val, *span) {
Ok(col_width) => { Ok(col_width) => {
let col_width = col_width.as_int()?; let col_width = col_width.as_int()?;
description_menu.with_column_width(Some(col_width as usize)) description_menu.with_column_width(Some(col_width as usize))
@ -385,7 +380,7 @@ pub(crate) fn add_description_menu(
Err(_) => description_menu.with_column_width(None), Err(_) => description_menu.with_column_width(None),
}; };
description_menu = match extract_value("col_padding", cols, vals, *span) { description_menu = match extract_value("col_padding", val, *span) {
Ok(col_padding) => { Ok(col_padding) => {
let col_padding = col_padding.as_int()?; let col_padding = col_padding.as_int()?;
description_menu.with_column_padding(col_padding as usize) description_menu.with_column_padding(col_padding as usize)
@ -393,7 +388,7 @@ pub(crate) fn add_description_menu(
Err(_) => description_menu, Err(_) => description_menu,
}; };
description_menu = match extract_value("selection_rows", cols, vals, *span) { description_menu = match extract_value("selection_rows", val, *span) {
Ok(selection_rows) => { Ok(selection_rows) => {
let selection_rows = selection_rows.as_int()?; let selection_rows = selection_rows.as_int()?;
description_menu.with_selection_rows(selection_rows as u16) description_menu.with_selection_rows(selection_rows as u16)
@ -401,7 +396,7 @@ pub(crate) fn add_description_menu(
Err(_) => description_menu, Err(_) => description_menu,
}; };
description_menu = match extract_value("description_rows", cols, vals, *span) { description_menu = match extract_value("description_rows", val, *span) {
Ok(description_rows) => { Ok(description_rows) => {
let description_rows = description_rows.as_int()?; let description_rows = description_rows.as_int()?;
description_menu.with_description_rows(description_rows as usize) description_menu.with_description_rows(description_rows as usize)
@ -410,11 +405,10 @@ pub(crate) fn add_description_menu(
}; };
} }
if let Value::Record { cols, vals, span } = &menu.style { if let Value::Record { val, span } = &menu.style {
add_style!( add_style!(
"text", "text",
cols, val,
vals,
span, span,
config, config,
description_menu, description_menu,
@ -422,8 +416,7 @@ pub(crate) fn add_description_menu(
); );
add_style!( add_style!(
"selected_text", "selected_text",
cols, val,
vals,
span, span,
config, config,
description_menu, description_menu,
@ -431,8 +424,7 @@ pub(crate) fn add_description_menu(
); );
add_style!( add_style!(
"description_text", "description_text",
cols, val,
vals,
span, span,
config, config,
description_menu, description_menu,
@ -722,68 +714,60 @@ enum EventType<'config> {
} }
impl<'config> EventType<'config> { impl<'config> EventType<'config> {
fn try_from_columns( fn try_from_record(record: &'config Record, span: Span) -> Result<Self, ShellError> {
cols: &'config [String], extract_value("send", record, span)
vals: &'config [Value],
span: Span,
) -> Result<Self, ShellError> {
extract_value("send", cols, vals, span)
.map(Self::Send) .map(Self::Send)
.or_else(|_| extract_value("edit", cols, vals, span).map(Self::Edit)) .or_else(|_| extract_value("edit", record, span).map(Self::Edit))
.or_else(|_| extract_value("until", cols, vals, span).map(Self::Until)) .or_else(|_| extract_value("until", record, span).map(Self::Until))
.map_err(|_| ShellError::MissingConfigValue("send, edit or until".to_string(), span)) .map_err(|_| ShellError::MissingConfigValue("send, edit or until".to_string(), span))
} }
} }
fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>, ShellError> { fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>, ShellError> {
match value { match value {
Value::Record { cols, vals, span } => { Value::Record { val: record, span } => match EventType::try_from_record(record, *span)? {
match EventType::try_from_columns(cols, vals, *span)? { EventType::Send(value) => event_from_record(
EventType::Send(value) => event_from_record( value.into_string("", config).to_lowercase().as_str(),
record,
config,
*span,
)
.map(Some),
EventType::Edit(value) => {
let edit = edit_from_record(
value.into_string("", config).to_lowercase().as_str(), value.into_string("", config).to_lowercase().as_str(),
cols, record,
vals,
config, config,
*span, *span,
) )?;
.map(Some), Ok(Some(ReedlineEvent::Edit(vec![edit])))
EventType::Edit(value) => {
let edit = edit_from_record(
value.into_string("", config).to_lowercase().as_str(),
cols,
vals,
config,
*span,
)?;
Ok(Some(ReedlineEvent::Edit(vec![edit])))
}
EventType::Until(value) => match value {
Value::List { vals, .. } => {
let events = vals
.iter()
.map(|value| match parse_event(value, config) {
Ok(inner) => match inner {
None => Err(ShellError::UnsupportedConfigValue(
"List containing valid events".to_string(),
"Nothing value (null)".to_string(),
value.span()?,
)),
Some(event) => Ok(event),
},
Err(e) => Err(e),
})
.collect::<Result<Vec<ReedlineEvent>, ShellError>>()?;
Ok(Some(ReedlineEvent::UntilFound(events)))
}
v => Err(ShellError::UnsupportedConfigValue(
"list of events".to_string(),
v.into_abbreviated_string(config),
v.span()?,
)),
},
} }
} EventType::Until(value) => match value {
Value::List { vals, .. } => {
let events = vals
.iter()
.map(|value| match parse_event(value, config) {
Ok(inner) => match inner {
None => Err(ShellError::UnsupportedConfigValue(
"List containing valid events".to_string(),
"Nothing value (null)".to_string(),
value.span()?,
)),
Some(event) => Ok(event),
},
Err(e) => Err(e),
})
.collect::<Result<Vec<ReedlineEvent>, ShellError>>()?;
Ok(Some(ReedlineEvent::UntilFound(events)))
}
v => Err(ShellError::UnsupportedConfigValue(
"list of events".to_string(),
v.into_abbreviated_string(config),
v.span()?,
)),
},
},
Value::List { vals, .. } => { Value::List { vals, .. } => {
let events = vals let events = vals
.iter() .iter()
@ -813,8 +797,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
fn event_from_record( fn event_from_record(
name: &str, name: &str,
cols: &[String], record: &Record,
vals: &[Value],
config: &Config, config: &Config,
span: Span, span: Span,
) -> Result<ReedlineEvent, ShellError> { ) -> Result<ReedlineEvent, ShellError> {
@ -848,11 +831,11 @@ fn event_from_record(
"menupageprevious" => ReedlineEvent::MenuPagePrevious, "menupageprevious" => ReedlineEvent::MenuPagePrevious,
"openeditor" => ReedlineEvent::OpenEditor, "openeditor" => ReedlineEvent::OpenEditor,
"menu" => { "menu" => {
let menu = extract_value("name", cols, vals, span)?; let menu = extract_value("name", record, span)?;
ReedlineEvent::Menu(menu.into_string("", config)) ReedlineEvent::Menu(menu.into_string("", config))
} }
"executehostcommand" => { "executehostcommand" => {
let cmd = extract_value("cmd", cols, vals, span)?; let cmd = extract_value("cmd", record, span)?;
ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config)) ReedlineEvent::ExecuteHostCommand(cmd.into_string("", config))
} }
v => { v => {
@ -869,8 +852,7 @@ fn event_from_record(
fn edit_from_record( fn edit_from_record(
name: &str, name: &str,
cols: &[String], record: &Record,
vals: &[Value],
config: &Config, config: &Config,
span: Span, span: Span,
) -> Result<EditCommand, ShellError> { ) -> Result<EditCommand, ShellError> {
@ -889,16 +871,16 @@ fn edit_from_record(
"movewordrightstart" => EditCommand::MoveWordRightStart, "movewordrightstart" => EditCommand::MoveWordRightStart,
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart, "movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
"movetoposition" => { "movetoposition" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
EditCommand::MoveToPosition(value.as_int()? as usize) EditCommand::MoveToPosition(value.as_int()? as usize)
} }
"insertchar" => { "insertchar" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::InsertChar(char) EditCommand::InsertChar(char)
} }
"insertstring" => { "insertstring" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
EditCommand::InsertString(value.into_string("", config)) EditCommand::InsertString(value.into_string("", config))
} }
"insertnewline" => EditCommand::InsertNewline, "insertnewline" => EditCommand::InsertNewline,
@ -930,42 +912,42 @@ fn edit_from_record(
"undo" => EditCommand::Undo, "undo" => EditCommand::Undo,
"redo" => EditCommand::Redo, "redo" => EditCommand::Redo,
"cutrightuntil" => { "cutrightuntil" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::CutRightUntil(char) EditCommand::CutRightUntil(char)
} }
"cutrightbefore" => { "cutrightbefore" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::CutRightBefore(char) EditCommand::CutRightBefore(char)
} }
"moverightuntil" => { "moverightuntil" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::MoveRightUntil(char) EditCommand::MoveRightUntil(char)
} }
"moverightbefore" => { "moverightbefore" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::MoveRightBefore(char) EditCommand::MoveRightBefore(char)
} }
"cutleftuntil" => { "cutleftuntil" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::CutLeftUntil(char) EditCommand::CutLeftUntil(char)
} }
"cutleftbefore" => { "cutleftbefore" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::CutLeftBefore(char) EditCommand::CutLeftBefore(char)
} }
"moveleftuntil" => { "moveleftuntil" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::MoveLeftUntil(char) EditCommand::MoveLeftUntil(char)
} }
"moveleftbefore" => { "moveleftbefore" => {
let value = extract_value("value", cols, vals, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
EditCommand::MoveLeftBefore(char) EditCommand::MoveLeftBefore(char)
} }
@ -999,16 +981,13 @@ mod test {
fn test_send_event() { fn test_send_event() {
let cols = vec!["send".to_string()]; let cols = vec!["send".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::test_string("Enter")];
let event = Record { vals, cols };
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, span).unwrap(); let b = EventType::try_from_record(&event, span).unwrap();
assert!(matches!(b, EventType::Send(_))); assert!(matches!(b, EventType::Send(_)));
let event = Value::Record { let event = Value::test_record(event);
vals,
cols,
span: Span::test_data(),
};
let config = Config::default(); let config = Config::default();
let parsed_event = parse_event(&event, &config).unwrap(); let parsed_event = parse_event(&event, &config).unwrap();
@ -1019,16 +998,13 @@ mod test {
fn test_edit_event() { fn test_edit_event() {
let cols = vec!["edit".to_string()]; let cols = vec!["edit".to_string()];
let vals = vec![Value::test_string("Clear")]; let vals = vec![Value::test_string("Clear")];
let event = Record { vals, cols };
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, span).unwrap(); let b = EventType::try_from_record(&event, span).unwrap();
assert!(matches!(b, EventType::Edit(_))); assert!(matches!(b, EventType::Edit(_)));
let event = Value::Record { let event = Value::test_record(event);
vals,
cols,
span: Span::test_data(),
};
let config = Config::default(); let config = Config::default();
let parsed_event = parse_event(&event, &config).unwrap(); let parsed_event = parse_event(&event, &config).unwrap();
@ -1045,16 +1021,13 @@ mod test {
Value::test_string("Menu"), Value::test_string("Menu"),
Value::test_string("history_menu"), Value::test_string("history_menu"),
]; ];
let event = Record { vals, cols };
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, span).unwrap(); let b = EventType::try_from_record(&event, span).unwrap();
assert!(matches!(b, EventType::Send(_))); assert!(matches!(b, EventType::Send(_)));
let event = Value::Record { let event = Value::test_record(event);
vals,
cols,
span: Span::test_data(),
};
let config = Config::default(); let config = Config::default();
let parsed_event = parse_event(&event, &config).unwrap(); let parsed_event = parse_event(&event, &config).unwrap();
@ -1073,21 +1046,13 @@ mod test {
Value::test_string("history_menu"), Value::test_string("history_menu"),
]; ];
let menu_event = Value::Record { let menu_event = Value::test_record(Record { cols, vals });
cols,
vals,
span: Span::test_data(),
};
// Enter event // Enter event
let cols = vec!["send".to_string()]; let cols = vec!["send".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::test_string("Enter")];
let enter_event = Value::Record { let enter_event = Value::test_record(Record { cols, vals });
cols,
vals,
span: Span::test_data(),
};
// Until event // Until event
let cols = vec!["until".to_string()]; let cols = vec!["until".to_string()];
@ -1095,16 +1060,13 @@ mod test {
vals: vec![menu_event, enter_event], vals: vec![menu_event, enter_event],
span: Span::test_data(), span: Span::test_data(),
}]; }];
let event = Record { cols, vals };
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, span).unwrap(); let b = EventType::try_from_record(&event, span).unwrap();
assert!(matches!(b, EventType::Until(_))); assert!(matches!(b, EventType::Until(_)));
let event = Value::Record { let event = Value::test_record(event);
cols,
vals,
span: Span::test_data(),
};
let config = Config::default(); let config = Config::default();
let parsed_event = parse_event(&event, &config).unwrap(); let parsed_event = parse_event(&event, &config).unwrap();
@ -1126,21 +1088,13 @@ mod test {
Value::test_string("history_menu"), Value::test_string("history_menu"),
]; ];
let menu_event = Value::Record { let menu_event = Value::test_record(Record { cols, vals });
cols,
vals,
span: Span::test_data(),
};
// Enter event // Enter event
let cols = vec!["send".to_string()]; let cols = vec!["send".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::test_string("Enter")];
let enter_event = Value::Record { let enter_event = Value::test_record(Record { cols, vals });
cols,
vals,
span: Span::test_data(),
};
// Multiple event // Multiple event
let event = Value::List { let event = Value::List {
@ -1163,9 +1117,10 @@ mod test {
fn test_error() { fn test_error() {
let cols = vec!["not_exist".to_string()]; let cols = vec!["not_exist".to_string()];
let vals = vec![Value::test_string("Enter")]; let vals = vec![Value::test_string("Enter")];
let event = Record { cols, vals };
let span = Span::test_data(); let span = Span::test_data();
let b = EventType::try_from_columns(&cols, &vals, span); let b = EventType::try_from_record(&event, span);
assert!(matches!(b, Err(ShellError::MissingConfigValue(_, _)))); assert!(matches!(b, Err(ShellError::MissingConfigValue(_, _))));
} }
} }

View File

@ -6,7 +6,7 @@ pub fn merge_descriptors(values: &[Value]) -> Vec<String> {
let mut seen: IndexSet<String> = indexset! {}; let mut seen: IndexSet<String> = indexset! {};
for value in values { for value in values {
let data_descriptors = match value { let data_descriptors = match value {
Value::Record { cols, .. } => cols.to_owned(), Value::Record { val, .. } => val.cols.clone(),
_ => vec!["".to_string()], _ => vec!["".to_string()],
}; };
for desc in data_descriptors { for desc in data_descriptors {

View File

@ -1,7 +1,7 @@
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value, record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value,
}; };
use crate::dataframe::values::NuDataFrame; use crate::dataframe::values::NuDataFrame;
@ -55,27 +55,14 @@ impl Command for ListDF {
NuDataFrame::try_from_value(value).ok().map(|df| (name, df)) NuDataFrame::try_from_value(value).ok().map(|df| (name, df))
}) })
.map(|(name, df)| { .map(|(name, df)| {
let name = Value::String { Value::record(
val: name, record! {
span: call.head, "name" => Value::string(name, call.head),
}; "columns" => Value::int(df.as_ref().width() as i64, call.head),
"rows" => Value::int(df.as_ref().height() as i64, call.head),
let columns = Value::int(df.as_ref().width() as i64, call.head); },
call.head,
let rows = Value::int(df.as_ref().height() as i64, call.head); )
let cols = vec![
"name".to_string(),
"columns".to_string(),
"rows".to_string(),
];
let vals = vec![name, columns, rows];
Value::Record {
cols,
vals,
span: call.head,
}
}) })
.collect::<Vec<Value>>(); .collect::<Vec<Value>>();

View File

@ -2,7 +2,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use crate::dataframe::values::NuExpression; use crate::dataframe::values::NuExpression;
@ -40,21 +40,18 @@ impl Command for ToNu {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
let cols = vec!["index".into(), "a".into(), "b".into()]; let cols = vec!["index".into(), "a".into(), "b".into()];
let rec_1 = Value::Record { let rec_1 = Value::test_record(Record {
cols: cols.clone(), cols: cols.clone(),
vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)], vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
span: Span::test_data(), });
}; let rec_2 = Value::test_record(Record {
let rec_2 = Value::Record {
cols: cols.clone(), cols: cols.clone(),
vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(4)], vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(4)],
span: Span::test_data(), });
}; let rec_3 = Value::test_record(Record {
let rec_3 = Value::Record {
cols, cols,
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)], vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)],
span: Span::test_data(), });
};
vec![ vec![
Example { Example {
@ -76,11 +73,10 @@ impl Command for ToNu {
Example { Example {
description: "Convert a col expression into a nushell value", description: "Convert a col expression into a nushell value",
example: "dfr col a | dfr into-nu", example: "dfr col a | dfr into-nu",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: vec!["expr".into(), "value".into()], cols: vec!["expr".into(), "value".into()],
vals: vec![Value::test_string("column"), Value::test_string("a")], vals: vec![Value::test_string("column"), Value::test_string("a")],
span: Span::test_data(), })),
}),
}, },
] ]
} }

View File

@ -4,7 +4,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -41,20 +41,18 @@ impl Command for ExprAlias {
let cols = vec!["expr".into(), "value".into()]; let cols = vec!["expr".into(), "value".into()];
let expr = Value::test_string("column"); let expr = Value::test_string("column");
let value = Value::test_string("a"); let value = Value::test_string("a");
let expr = Value::Record { let expr = Value::test_record(Record {
cols, cols,
vals: vec![expr, value], vals: vec![expr, value],
span: Span::test_data(), });
};
let cols = vec!["expr".into(), "alias".into()]; let cols = vec!["expr".into(), "alias".into()];
let value = Value::test_string("new_a"); let value = Value::test_string("new_a");
let record = Value::Record { let record = Value::test_record(Record {
cols, cols,
vals: vec![expr, value], vals: vec![expr, value],
span: Span::test_data(), });
};
Some(record) Some(record)
}, },

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
}; };
use polars::prelude::col; use polars::prelude::col;
@ -34,11 +34,10 @@ impl Command for ExprCol {
vec![Example { vec![Example {
description: "Creates a named column expression and converts it to a nu object", description: "Creates a named column expression and converts it to a nu object",
example: "dfr col a | dfr into-nu", example: "dfr col a | dfr into-nu",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: vec!["expr".into(), "value".into()], cols: vec!["expr".into(), "value".into()],
vals: vec![Value::test_string("column"), Value::test_string("a")], vals: vec![Value::test_string("column"), Value::test_string("a")],
span: Span::test_data(), })),
}),
}] }]
} }

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -33,11 +33,10 @@ impl Command for ExprLit {
vec![Example { vec![Example {
description: "Created a literal expression and converts it to a nu object", description: "Created a literal expression and converts it to a nu object",
example: "dfr lit 2 | dfr into-nu", example: "dfr lit 2 | dfr into-nu",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: vec!["expr".into(), "value".into()], cols: vec!["expr".into(), "value".into()],
vals: vec![Value::test_string("literal"), Value::test_string("2")], vals: vec![Value::test_string("literal"), Value::test_string("2")],
span: Span::test_data(), })),
}),
}] }]
} }

View File

@ -2,7 +2,7 @@ use super::{DataFrameValue, NuDataFrame};
use chrono::{DateTime, FixedOffset, NaiveDateTime}; use chrono::{DateTime, FixedOffset, NaiveDateTime};
use indexmap::map::{Entry, IndexMap}; use indexmap::map::{Entry, IndexMap};
use nu_protocol::{ShellError, Span, Value}; use nu_protocol::{Record, ShellError, Span, Value};
use polars::chunked_array::builder::AnonymousOwnedListBuilder; use polars::chunked_array::builder::AnonymousOwnedListBuilder;
use polars::chunked_array::object::builder::ObjectChunkedBuilder; use polars::chunked_array::object::builder::ObjectChunkedBuilder;
use polars::chunked_array::ChunkedArray; use polars::chunked_array::ChunkedArray;
@ -128,36 +128,21 @@ pub fn create_column(
// Adds a separator to the vector of values using the column names from the // Adds a separator to the vector of values using the column names from the
// dataframe to create the Values Row // dataframe to create the Values Row
pub fn add_separator(values: &mut Vec<Value>, df: &DataFrame, span: Span) { pub fn add_separator(values: &mut Vec<Value>, df: &DataFrame, span: Span) {
let mut cols = vec![]; let mut record = Record::new();
let mut vals = vec![];
cols.push("index".to_string()); record.push("index", Value::string("...", span));
vals.push(Value::String {
val: "...".into(),
span,
});
for name in df.get_column_names() { for name in df.get_column_names() {
cols.push(name.to_string()); record.push(name, Value::string("...", span))
vals.push(Value::String {
val: "...".into(),
span,
})
} }
let extra_record = Value::Record { cols, vals, span }; values.push(Value::record(record, span));
values.push(extra_record);
} }
// Inserting the values found in a Value::List // Inserting the values found in a Value::List or Value::Record
pub fn insert_record( pub fn insert_record(column_values: &mut ColumnMap, record: Record) -> Result<(), ShellError> {
column_values: &mut ColumnMap, for (col, value) in record {
cols: &[String], insert_value(value, col, column_values)?;
values: &[Value],
) -> Result<(), ShellError> {
for (col, value) in cols.iter().zip(values.iter()) {
insert_value(value.clone(), col.clone(), column_values)?;
} }
Ok(()) Ok(())

View File

@ -7,7 +7,7 @@ pub use conversion::{Column, ColumnMap};
pub use operations::Axis; pub use operations::Axis;
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
use nu_protocol::{did_you_mean, PipelineData, ShellError, Span, Value}; use nu_protocol::{did_you_mean, PipelineData, Record, ShellError, Span, Value};
use polars::prelude::{DataFrame, DataType, IntoLazy, LazyFrame, PolarsObject, Series}; use polars::prelude::{DataFrame, DataType, IntoLazy, LazyFrame, PolarsObject, Series};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt::Display, hash::Hasher}; use std::{cmp::Ordering, fmt::Display, hash::Hasher};
@ -162,10 +162,10 @@ impl NuDataFrame {
.map(|i| format!("{i}")) .map(|i| format!("{i}"))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
conversion::insert_record(&mut column_values, &cols, &vals)? conversion::insert_record(&mut column_values, Record { cols, vals })?
} }
Value::Record { cols, vals, .. } => { Value::Record { val: record, .. } => {
conversion::insert_record(&mut column_values, &cols, &vals)? conversion::insert_record(&mut column_values, record)?
} }
_ => { _ => {
let key = "0".to_string(); let key = "0".to_string();
@ -427,25 +427,15 @@ impl NuDataFrame {
let values = (0..size) let values = (0..size)
.map(|i| { .map(|i| {
let mut cols = vec![]; let mut record = Record::new();
let mut vals = vec![];
cols.push("index".into()); record.push("index", Value::int((i + from_row) as i64, span));
vals.push(Value::Int {
val: (i + from_row) as i64,
span,
});
for (name, col) in &mut iterators { for (name, col) in &mut iterators {
cols.push(name.clone()); record.push(name.clone(), col.next().unwrap_or(Value::nothing(span)));
match col.next() {
Some(v) => vals.push(v),
None => vals.push(Value::Nothing { span }),
};
} }
Value::Record { cols, vals, span } Value::record(record, span)
}) })
.collect::<Vec<Value>>(); .collect::<Vec<Value>>();

View File

@ -1,6 +1,6 @@
mod custom_value; mod custom_value;
use nu_protocol::{PipelineData, ShellError, Span, Value}; use nu_protocol::{record, PipelineData, ShellError, Span, Value};
use polars::prelude::{col, AggExpr, Expr, Literal}; use polars::prelude::{col, AggExpr, Expr, Literal};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
@ -165,103 +165,59 @@ impl ExtractedExpr {
} }
pub fn expr_to_value(expr: &Expr, span: Span) -> Value { pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
let cols = vec!["expr".to_string(), "value".to_string()];
match expr { match expr {
Expr::Alias(expr, alias) => { Expr::Alias(expr, alias) => Value::record(
let expr = expr_to_value(expr.as_ref(), span); record! {
let alias = Value::String { "expr" => expr_to_value(expr.as_ref(), span),
val: alias.as_ref().into(), "alias" => Value::string(alias.as_ref(), span),
span, },
}; span,
),
let cols = vec!["expr".into(), "alias".into()]; Expr::Column(name) => Value::record(
record! {
Value::Record { "expr" => Value::string("column", span),
cols, "value" => Value::string(name.to_string(), span),
vals: vec![expr, alias], },
span, span,
} ),
}
Expr::Column(name) => {
let expr_type = Value::String {
val: "column".to_string(),
span,
};
let value = Value::String {
val: name.to_string(),
span,
};
let vals = vec![expr_type, value];
Value::Record { cols, vals, span }
}
Expr::Columns(columns) => { Expr::Columns(columns) => {
let expr_type = Value::String { let value = columns.iter().map(|col| Value::string(col, span)).collect();
val: "columns".into(), Value::record(
record! {
"expr" => Value::string("columns", span),
"value" => Value::list(value, span),
},
span, span,
}; )
let value = Value::List {
vals: columns
.iter()
.map(|col| Value::String {
val: col.clone(),
span,
})
.collect(),
span,
};
let vals = vec![expr_type, value];
Value::Record { cols, vals, span }
}
Expr::Literal(literal) => {
let expr_type = Value::String {
val: "literal".into(),
span,
};
let value = Value::String {
val: format!("{literal:?}"),
span,
};
let vals = vec![expr_type, value];
Value::Record { cols, vals, span }
}
Expr::BinaryExpr { left, op, right } => {
let left_val = expr_to_value(left, span);
let right_val = expr_to_value(right, span);
let operator = Value::String {
val: format!("{op:?}"),
span,
};
let cols = vec!["left".into(), "op".into(), "right".into()];
Value::Record {
cols,
vals: vec![left_val, operator, right_val],
span,
}
} }
Expr::Literal(literal) => Value::record(
record! {
"expr" => Value::string("literal", span),
"value" => Value::string(format!("{literal:?}"), span),
},
span,
),
Expr::BinaryExpr { left, op, right } => Value::record(
record! {
"left" => expr_to_value(left, span),
"op" => Value::string(format!("{op:?}"), span),
"right" => expr_to_value(right, span),
},
span,
),
Expr::Ternary { Expr::Ternary {
predicate, predicate,
truthy, truthy,
falsy, falsy,
} => { } => Value::record(
let predicate = expr_to_value(predicate.as_ref(), span); record! {
let truthy = expr_to_value(truthy.as_ref(), span); "predicate" => expr_to_value(predicate.as_ref(), span),
let falsy = expr_to_value(falsy.as_ref(), span); "truthy" => expr_to_value(truthy.as_ref(), span),
"falsy" => expr_to_value(falsy.as_ref(), span),
let cols = vec!["predicate".into(), "truthy".into(), "falsy".into()]; },
span,
Value::Record { ),
cols,
vals: vec![predicate, truthy, falsy],
span,
}
}
Expr::Agg(agg_expr) => { Expr::Agg(agg_expr) => {
let value = match agg_expr { let value = match agg_expr {
AggExpr::Min { input: expr, .. } AggExpr::Min { input: expr, .. }
@ -281,88 +237,37 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
expr, expr,
quantile, quantile,
interpol, interpol,
} => { } => Value::record(
let expr = expr_to_value(expr.as_ref(), span); record! {
let quantile = expr_to_value(quantile.as_ref(), span); "expr" => expr_to_value(expr.as_ref(), span),
let interpol = Value::String { "quantile" => expr_to_value(quantile.as_ref(), span),
val: format!("{interpol:?}"), "interpol" => Value::string(format!("{interpol:?}"), span),
span, },
}; span,
),
let cols = vec!["expr".into(), "quantile".into(), "interpol".into()];
Value::Record {
cols,
vals: vec![expr, quantile, interpol],
span,
}
}
}; };
let expr_type = Value::String { Value::record(
val: "agg".into(), record! {
"expr" => Value::string("agg", span),
"value" => value,
},
span, span,
}; )
let vals = vec![expr_type, value];
Value::Record { cols, vals, span }
}
Expr::Count => {
let expr = Value::String {
val: "count".into(),
span,
};
let cols = vec!["expr".into()];
Value::Record {
cols,
vals: vec![expr],
span,
}
} }
Expr::Count => Value::record(record! { "expr" => Value::string("count", span) }, span),
Expr::Wildcard => { Expr::Wildcard => {
let expr = Value::String { Value::record(record! { "expr" => Value::string("wildcard", span) }, span)
val: "wildcard".into(),
span,
};
let cols = vec!["expr".into()];
Value::Record {
cols,
vals: vec![expr],
span,
}
}
Expr::Explode(expr) => {
let expr = expr_to_value(expr.as_ref(), span);
let cols = vec!["expr".into()];
Value::Record {
cols,
vals: vec![expr],
span,
}
}
Expr::KeepName(expr) => {
let expr = expr_to_value(expr.as_ref(), span);
let cols = vec!["expr".into()];
Value::Record {
cols,
vals: vec![expr],
span,
}
}
Expr::Nth(i) => {
let expr = Value::int(*i, span);
let cols = vec!["expr".into()];
Value::Record {
cols,
vals: vec![expr],
span,
}
} }
Expr::Explode(expr) => Value::record(
record! { "expr" => expr_to_value(expr.as_ref(), span) },
span,
),
Expr::KeepName(expr) => Value::record(
record! { "expr" => expr_to_value(expr.as_ref(), span) },
span,
),
Expr::Nth(i) => Value::record(record! { "expr" => Value::int(*i, span) }, span),
Expr::DtypeColumn(dtypes) => { Expr::DtypeColumn(dtypes) => {
let vals = dtypes let vals = dtypes
.iter() .iter()
@ -374,109 +279,72 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
Value::List { vals, span } Value::List { vals, span }
} }
Expr::Sort { expr, options } => { Expr::Sort { expr, options } => Value::record(
let expr = expr_to_value(expr.as_ref(), span); record! {
let options = Value::String { "expr" => expr_to_value(expr.as_ref(), span),
val: format!("{options:?}"), "options" => Value::string(format!("{options:?}"), span),
span, },
}; span,
let cols = vec!["expr".into(), "options".into()]; ),
Value::Record {
cols,
vals: vec![expr, options],
span,
}
}
Expr::Cast { Expr::Cast {
expr, expr,
data_type, data_type,
strict, strict,
} => { } => Value::record(
let expr = expr_to_value(expr.as_ref(), span); record! {
let dtype = Value::String { "expr" => expr_to_value(expr.as_ref(), span),
val: format!("{data_type:?}"), "dtype" => Value::string(format!("{data_type:?}"), span),
span, "strict" => Value::bool(*strict, span),
}; },
let strict = Value::Bool { val: *strict, span }; span,
),
let cols = vec!["expr".into(), "dtype".into(), "strict".into()]; Expr::Take { expr, idx } => Value::record(
record! {
Value::Record { "expr" => expr_to_value(expr.as_ref(), span),
cols, "idx" => expr_to_value(idx.as_ref(), span),
vals: vec![expr, dtype, strict], },
span, span,
} ),
}
Expr::Take { expr, idx } => {
let expr = expr_to_value(expr.as_ref(), span);
let idx = expr_to_value(idx.as_ref(), span);
let cols = vec!["expr".into(), "idx".into()];
Value::Record {
cols,
vals: vec![expr, idx],
span,
}
}
Expr::SortBy { Expr::SortBy {
expr, expr,
by, by,
descending, descending,
} => { } => {
let expr = expr_to_value(expr.as_ref(), span);
let by: Vec<Value> = by.iter().map(|b| expr_to_value(b, span)).collect(); let by: Vec<Value> = by.iter().map(|b| expr_to_value(b, span)).collect();
let by = Value::List { vals: by, span };
let descending: Vec<Value> = descending let descending: Vec<Value> = descending
.iter() .iter()
.map(|r| Value::Bool { val: *r, span }) .map(|r| Value::Bool { val: *r, span })
.collect(); .collect();
let descending = Value::List {
vals: descending,
span,
};
let cols = vec!["expr".into(), "by".into(), "descending".into()]; Value::record(
record! {
Value::Record { "expr" => expr_to_value(expr.as_ref(), span),
cols, "by" => Value::list(by, span),
vals: vec![expr, by, descending], "descending" => Value::list(descending, span),
},
span, span,
} )
}
Expr::Filter { input, by } => {
let input = expr_to_value(input.as_ref(), span);
let by = expr_to_value(by.as_ref(), span);
let cols = vec!["input".into(), "by".into()];
Value::Record {
cols,
vals: vec![input, by],
span,
}
} }
Expr::Filter { input, by } => Value::record(
record! {
"input" => expr_to_value(input.as_ref(), span),
"by" => expr_to_value(by.as_ref(), span),
},
span,
),
Expr::Slice { Expr::Slice {
input, input,
offset, offset,
length, length,
} => { } => Value::record(
let input = expr_to_value(input.as_ref(), span); record! {
let offset = expr_to_value(offset.as_ref(), span); "input" => expr_to_value(input.as_ref(), span),
let length = expr_to_value(length.as_ref(), span); "offset" => expr_to_value(offset.as_ref(), span),
"length" => expr_to_value(length.as_ref(), span),
let cols = vec!["input".into(), "offset".into(), "length".into()]; },
span,
Value::Record { ),
cols,
vals: vec![input, offset, length],
span,
}
}
Expr::Exclude(expr, excluded) => { Expr::Exclude(expr, excluded) => {
let expr = expr_to_value(expr.as_ref(), span);
let excluded = excluded let excluded = excluded
.iter() .iter()
.map(|e| Value::String { .map(|e| Value::String {
@ -484,34 +352,22 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
span, span,
}) })
.collect(); .collect();
let excluded = Value::List {
vals: excluded,
span,
};
let cols = vec!["expr".into(), "excluded".into()]; Value::record(
record! {
Value::Record { "expr" => expr_to_value(expr.as_ref(), span),
cols, "excluded" => Value::list(excluded, span),
vals: vec![expr, excluded], },
span, span,
} )
}
Expr::RenameAlias { expr, function } => {
let expr = expr_to_value(expr.as_ref(), span);
let function = Value::String {
val: format!("{function:?}"),
span,
};
let cols = vec!["expr".into(), "function".into()];
Value::Record {
cols,
vals: vec![expr, function],
span,
}
} }
Expr::RenameAlias { expr, function } => Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"function" => Value::string(format!("{function:?}"), span),
},
span,
),
Expr::AnonymousFunction { Expr::AnonymousFunction {
input, input,
function, function,
@ -519,33 +375,15 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
options, options,
} => { } => {
let input: Vec<Value> = input.iter().map(|e| expr_to_value(e, span)).collect(); let input: Vec<Value> = input.iter().map(|e| expr_to_value(e, span)).collect();
let input = Value::List { vals: input, span }; Value::record(
record! {
let function = Value::String { "input" => Value::list(input, span),
val: format!("{function:?}"), "function" => Value::string(format!("{function:?}"), span),
"output_type" => Value::string(format!("{output_type:?}"), span),
"options" => Value::string(format!("{options:?}"), span),
},
span, span,
}; )
let output_type = Value::String {
val: format!("{output_type:?}"),
span,
};
let options = Value::String {
val: format!("{options:?}"),
span,
};
let cols = vec![
"input".into(),
"function".into(),
"output_type".into(),
"options".into(),
];
Value::Record {
cols,
vals: vec![input, function, output_type, options],
span,
}
} }
Expr::Function { Expr::Function {
input, input,
@ -553,79 +391,47 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
options, options,
} => { } => {
let input: Vec<Value> = input.iter().map(|e| expr_to_value(e, span)).collect(); let input: Vec<Value> = input.iter().map(|e| expr_to_value(e, span)).collect();
let input = Value::List { vals: input, span }; Value::record(
record! {
let function = Value::String { "input" => Value::list(input, span),
val: format!("{function:?}"), "function" => Value::string(format!("{function:?}"), span),
"options" => Value::string(format!("{options:?}"), span),
},
span, span,
}; )
let options = Value::String {
val: format!("{options:?}"),
span,
};
let cols = vec!["input".into(), "function".into(), "options".into()];
Value::Record {
cols,
vals: vec![input, function, options],
span,
}
}
Expr::Cache { input, id } => {
let input = expr_to_value(input.as_ref(), span);
let id = Value::String {
val: format!("{id:?}"),
span,
};
let cols = vec!["input".into(), "id".into()];
Value::Record {
cols,
vals: vec![input, id],
span,
}
} }
Expr::Cache { input, id } => Value::record(
record! {
"input" => expr_to_value(input.as_ref(), span),
"id" => Value::string(format!("{id:?}"), span),
},
span,
),
Expr::Window { Expr::Window {
function, function,
partition_by, partition_by,
order_by, order_by,
options, options,
} => { } => {
let function = expr_to_value(function, span);
let partition_by: Vec<Value> = partition_by let partition_by: Vec<Value> = partition_by
.iter() .iter()
.map(|e| expr_to_value(e, span)) .map(|e| expr_to_value(e, span))
.collect(); .collect();
let partition_by = Value::List {
vals: partition_by,
span,
};
let order_by = order_by let order_by = order_by
.as_ref() .as_ref()
.map(|e| expr_to_value(e.as_ref(), span)) .map(|e| expr_to_value(e.as_ref(), span))
.unwrap_or_else(|| Value::nothing(span)); .unwrap_or_else(|| Value::nothing(span));
let options = Value::String { Value::record(
val: format!("{options:?}"), record! {
"function" => expr_to_value(function, span),
"partition_by" => Value::list(partition_by, span),
"order_by" => order_by,
"options" => Value::string(format!("{options:?}"), span),
},
span, span,
}; )
let cols = vec![
"function".into(),
"partition_by".into(),
"order_by".into(),
"options".into(),
];
Value::Record {
cols,
vals: vec![function, partition_by, order_by, options],
span,
}
} }
} }
} }

View File

@ -1,5 +1,5 @@
use super::NuLazyFrame; use super::NuLazyFrame;
use nu_protocol::{CustomValue, ShellError, Span, Value}; use nu_protocol::{record, CustomValue, ShellError, Span, Value};
// CustomValue implementation for NuDataFrame // CustomValue implementation for NuDataFrame
impl CustomValue for NuLazyFrame { impl CustomValue for NuLazyFrame {
@ -29,22 +29,18 @@ impl CustomValue for NuLazyFrame {
} }
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> { fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
let cols = vec!["plan".into(), "optimized_plan".into()]; let optimized_plan = self
let vals = vec![ .as_ref()
Value::String { .describe_optimized_plan()
val: self.as_ref().describe_plan(), .unwrap_or_else(|_| "<NOT AVAILABLE>".to_string());
span,
},
Value::String {
val: self
.as_ref()
.describe_optimized_plan()
.unwrap_or_else(|_| "<NOT AVAILABLE>".to_string()),
span,
},
];
Ok(Value::Record { cols, vals, span }) Ok(Value::record(
record! {
"plan" => Value::string(self.as_ref().describe_plan(), span),
"optimized_plan" => Value::string(optimized_plan, span),
},
span,
))
} }
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {

View File

@ -1,5 +1,5 @@
use super::NuLazyGroupBy; use super::NuLazyGroupBy;
use nu_protocol::{CustomValue, ShellError, Span, Value}; use nu_protocol::{record, CustomValue, ShellError, Span, Value};
// CustomValue implementation for NuDataFrame // CustomValue implementation for NuDataFrame
impl CustomValue for NuLazyGroupBy { impl CustomValue for NuLazyGroupBy {
@ -29,13 +29,12 @@ impl CustomValue for NuLazyGroupBy {
} }
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> { fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
let cols = vec!["LazyGroupBy".into()]; Ok(Value::record(
let vals = vec![Value::String { record! {
val: "apply aggregation to complete execution plan".into(), "LazyGroupBy" => Value::string("apply aggregation to complete execution plan", span)
},
span, span,
}]; ))
Ok(Value::Record { cols, vals, span })
} }
fn as_any(&self) -> &dyn std::any::Any { fn as_any(&self) -> &dyn std::any::Any {

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value, record, Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -32,7 +32,7 @@ impl Command for Fmt {
vec![Example { vec![Example {
description: "Get a record containing multiple formats for the number 42", description: "Get a record containing multiple formats for the number 42",
example: "42 | fmt", example: "42 | fmt",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: vec![ cols: vec![
"binary".into(), "binary".into(),
"debug".into(), "debug".into(),
@ -53,8 +53,7 @@ impl Command for Fmt {
Value::test_string("4.2E1"), Value::test_string("4.2E1"),
Value::test_string("0x2A"), Value::test_string("0x2A"),
], ],
span: Span::test_data(), })),
}),
}] }]
} }
@ -99,71 +98,35 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
} }
fn fmt_it(num: i64, span: Span) -> Value { fn fmt_it(num: i64, span: Span) -> Value {
let mut cols = vec![]; Value::record(
let mut vals = vec![]; record! {
"binary" => Value::string(format!("{num:#b}"), span),
cols.push("binary".into()); "debug" => Value::string(format!("{num:#?}"), span),
vals.push(Value::string(format!("{num:#b}"), span)); "display" => Value::string(format!("{num}"), span),
"lowerexp" => Value::string(format!("{num:#e}"), span),
cols.push("debug".into()); "lowerhex" => Value::string(format!("{num:#x}"), span),
vals.push(Value::string(format!("{num:#?}"), span)); "octal" => Value::string(format!("{num:#o}"), span),
"upperexp" => Value::string(format!("{num:#E}"), span),
cols.push("display".into()); "upperhex" => Value::string(format!("{num:#X}"), span),
vals.push(Value::string(format!("{num}"), span)); },
span,
cols.push("lowerexp".into()); )
vals.push(Value::string(format!("{num:#e}"), span));
cols.push("lowerhex".into());
vals.push(Value::string(format!("{num:#x}"), span));
cols.push("octal".into());
vals.push(Value::string(format!("{num:#o}"), span));
// cols.push("pointer".into());
// vals.push(Value::string(format!("{:#p}", &num), span));
cols.push("upperexp".into());
vals.push(Value::string(format!("{num:#E}"), span));
cols.push("upperhex".into());
vals.push(Value::string(format!("{num:#X}"), span));
Value::Record { cols, vals, span }
} }
fn fmt_it_64(num: f64, span: Span) -> Value { fn fmt_it_64(num: f64, span: Span) -> Value {
let mut cols = vec![]; Value::record(
let mut vals = vec![]; record! {
"binary" => Value::string(format!("{:b}", num.to_bits()), span),
cols.push("binary".into()); "debug" => Value::string(format!("{num:#?}"), span),
vals.push(Value::string(format!("{:b}", num.to_bits()), span)); "display" => Value::string(format!("{num}"), span),
"lowerexp" => Value::string(format!("{num:#e}"), span),
cols.push("debug".into()); "lowerhex" => Value::string(format!("{:0x}", num.to_bits()), span),
vals.push(Value::string(format!("{num:#?}"), span)); "octal" => Value::string(format!("{:0o}", num.to_bits()), span),
"upperexp" => Value::string(format!("{num:#E}"), span),
cols.push("display".into()); "upperhex" => Value::string(format!("{:0X}", num.to_bits()), span),
vals.push(Value::string(format!("{num}"), span)); },
span,
cols.push("lowerexp".into()); )
vals.push(Value::string(format!("{num:#e}"), span));
cols.push("lowerhex".into());
vals.push(Value::string(format!("{:0x}", num.to_bits()), span));
cols.push("octal".into());
vals.push(Value::string(format!("{:0o}", num.to_bits()), span));
// cols.push("pointer".into());
// vals.push(Value::string(format!("{:#p}", &num), span));
cols.push("upperexp".into());
vals.push(Value::string(format!("{num:#E}"), span));
cols.push("upperhex".into());
vals.push(Value::string(format!("{:0X}", num.to_bits()), span));
Value::Record { cols, vals, span }
} }
#[cfg(test)] #[cfg(test)]

View File

@ -56,37 +56,24 @@ fn horizontal_rotate_value(
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
match value { match value {
Value::Record { Value::Record {
mut cols, val: mut record,
mut vals,
span, span,
} => { } => {
let rotations = by.map(|n| n % vals.len()).unwrap_or(1); let rotations = by.map(|n| n % record.len()).unwrap_or(1);
let columns = if cells_only {
cols
} else {
let columns = cols.as_mut_slice();
if !cells_only {
match direction { match direction {
HorizontalDirection::Right => columns.rotate_right(rotations), HorizontalDirection::Right => record.cols.rotate_right(rotations),
HorizontalDirection::Left => columns.rotate_left(rotations), HorizontalDirection::Left => record.cols.rotate_left(rotations),
} }
columns.to_owned()
}; };
let values = vals.as_mut_slice();
match direction { match direction {
HorizontalDirection::Right => values.rotate_right(rotations), HorizontalDirection::Right => record.vals.rotate_right(rotations),
HorizontalDirection::Left => values.rotate_left(rotations), HorizontalDirection::Left => record.vals.rotate_left(rotations),
} }
Ok(Value::Record { Ok(Value::record(record, span))
cols: columns,
vals: values.to_owned(),
span,
})
} }
Value::List { vals, span } => { Value::List { vals, span } => {
let values = vals let values = vals

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Type, Value, SyntaxShape, Type, Value,
}; };
use super::{vertical_rotate_value, VerticalDirection}; use super::{vertical_rotate_value, VerticalDirection};
@ -39,21 +39,18 @@ impl Command for RollDown {
example: "[[a b]; [1 2] [3 4] [5 6]] | roll down", example: "[[a b]; [1 2] [3 4] [5 6]] | roll down",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![Value::test_int(5), Value::test_int(6)], vals: vec![Value::test_int(5), Value::test_int(6)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![Value::test_int(1), Value::test_int(2)], vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: columns, cols: columns,
vals: vec![Value::test_int(3), Value::test_int(4)], vals: vec![Value::test_int(3), Value::test_int(4)],
span: Span::test_data(), }),
},
], ],
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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Type, Value, SyntaxShape, Type, Value,
}; };
use super::{horizontal_rotate_value, HorizontalDirection}; use super::{horizontal_rotate_value, HorizontalDirection};
@ -51,27 +51,24 @@ impl Command for RollLeft {
Example { Example {
description: "Rolls columns of a record to the left", description: "Rolls columns of a record to the left",
example: "{a:1 b:2 c:3} | roll left", example: "{a:1 b:2 c:3} | roll left",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: rotated_columns.clone(), cols: rotated_columns.clone(),
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)], vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)],
span: Span::test_data(), })),
}),
}, },
Example { Example {
description: "Rolls columns of a table to the left", description: "Rolls columns of a table to the left",
example: "[[a b c]; [1 2 3] [4 5 6]] | roll left", example: "[[a b c]; [1 2 3] [4 5 6]] | roll left",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: rotated_columns.clone(), cols: rotated_columns.clone(),
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)], vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: rotated_columns, cols: rotated_columns,
vals: vec![Value::test_int(5), Value::test_int(6), Value::test_int(4)], vals: vec![Value::test_int(5), Value::test_int(6), Value::test_int(4)],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -81,16 +78,14 @@ impl Command for RollLeft {
example: "[[a b c]; [1 2 3] [4 5 6]] | roll left --cells-only", example: "[[a b c]; [1 2 3] [4 5 6]] | roll left --cells-only",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)], vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(1)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: columns, cols: columns,
vals: vec![Value::test_int(5), Value::test_int(6), Value::test_int(4)], vals: vec![Value::test_int(5), Value::test_int(6), Value::test_int(4)],
span: Span::test_data(), }),
},
], ],
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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Type, Value, SyntaxShape, Type, Value,
}; };
use super::{horizontal_rotate_value, HorizontalDirection}; use super::{horizontal_rotate_value, HorizontalDirection};
@ -51,27 +51,24 @@ impl Command for RollRight {
Example { Example {
description: "Rolls columns of a record to the right", description: "Rolls columns of a record to the right",
example: "{a:1 b:2 c:3} | roll right", example: "{a:1 b:2 c:3} | roll right",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: rotated_columns.clone(), cols: rotated_columns.clone(),
vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)], vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)],
span: Span::test_data(), })),
}),
}, },
Example { Example {
description: "Rolls columns to the right", description: "Rolls columns to the right",
example: "[[a b c]; [1 2 3] [4 5 6]] | roll right", example: "[[a b c]; [1 2 3] [4 5 6]] | roll right",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: rotated_columns.clone(), cols: rotated_columns.clone(),
vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)], vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: rotated_columns, cols: rotated_columns,
vals: vec![Value::test_int(6), Value::test_int(4), Value::test_int(5)], vals: vec![Value::test_int(6), Value::test_int(4), Value::test_int(5)],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -81,16 +78,14 @@ impl Command for RollRight {
example: "[[a b c]; [1 2 3] [4 5 6]] | roll right --cells-only", example: "[[a b c]; [1 2 3] [4 5 6]] | roll right --cells-only",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)], vals: vec![Value::test_int(3), Value::test_int(1), Value::test_int(2)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: columns, cols: columns,
vals: vec![Value::test_int(6), Value::test_int(4), Value::test_int(5)], vals: vec![Value::test_int(6), Value::test_int(4), Value::test_int(5)],
span: Span::test_data(), }),
},
], ],
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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Type, Value, SyntaxShape, Type, Value,
}; };
use super::{vertical_rotate_value, VerticalDirection}; use super::{vertical_rotate_value, VerticalDirection};
@ -39,21 +39,18 @@ impl Command for RollUp {
example: "[[a b]; [1 2] [3 4] [5 6]] | roll up", example: "[[a b]; [1 2] [3 4] [5 6]] | roll up",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![Value::test_int(3), Value::test_int(4)], vals: vec![Value::test_int(3), Value::test_int(4)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![Value::test_int(5), Value::test_int(6)], vals: vec![Value::test_int(5), Value::test_int(6)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: columns, cols: columns,
vals: vec![Value::test_int(1), Value::test_int(2)], vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Type, Value, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -40,16 +40,14 @@ impl Command for Rotate {
example: "{a:1, b:2} | rotate", example: "{a:1, b:2} | rotate",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()], cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_int(1), Value::test_string("a")], vals: vec![Value::test_int(1), Value::test_string("a")],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["column0".to_string(), "column1".to_string()], cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_int(2), Value::test_string("b")], vals: vec![Value::test_int(2), Value::test_string("b")],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -59,7 +57,7 @@ impl Command for Rotate {
example: "[[a b]; [1 2] [3 4] [5 6]] | rotate", example: "[[a b]; [1 2] [3 4] [5 6]] | rotate",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec![ cols: vec![
"column0".to_string(), "column0".to_string(),
"column1".to_string(), "column1".to_string(),
@ -72,9 +70,8 @@ impl Command for Rotate {
Value::test_int(1), Value::test_int(1),
Value::test_string("a"), Value::test_string("a"),
], ],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec![ cols: vec![
"column0".to_string(), "column0".to_string(),
"column1".to_string(), "column1".to_string(),
@ -87,8 +84,7 @@ impl Command for Rotate {
Value::test_int(2), Value::test_int(2),
Value::test_string("b"), Value::test_string("b"),
], ],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -98,16 +94,14 @@ impl Command for Rotate {
example: "[[a b]; [1 2]] | rotate col_a col_b", example: "[[a b]; [1 2]] | rotate col_a col_b",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["col_a".to_string(), "col_b".to_string()], cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_int(1), Value::test_string("a")], vals: vec![Value::test_int(1), Value::test_string("a")],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["col_a".to_string(), "col_b".to_string()], cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_int(2), Value::test_string("b")], vals: vec![Value::test_int(2), Value::test_string("b")],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -117,16 +111,14 @@ impl Command for Rotate {
example: "[[a b]; [1 2]] | rotate --ccw", example: "[[a b]; [1 2]] | rotate --ccw",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()], cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(2)], vals: vec![Value::test_string("b"), Value::test_int(2)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["column0".to_string(), "column1".to_string()], cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("a"), Value::test_int(1)], vals: vec![Value::test_string("a"), Value::test_int(1)],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -136,7 +128,7 @@ impl Command for Rotate {
example: "[[a b]; [1 2] [3 4] [5 6]] | rotate --ccw", example: "[[a b]; [1 2] [3 4] [5 6]] | rotate --ccw",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec![ cols: vec![
"column0".to_string(), "column0".to_string(),
"column1".to_string(), "column1".to_string(),
@ -149,9 +141,8 @@ impl Command for Rotate {
Value::test_int(4), Value::test_int(4),
Value::test_int(6), Value::test_int(6),
], ],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec![ cols: vec![
"column0".to_string(), "column0".to_string(),
"column1".to_string(), "column1".to_string(),
@ -164,8 +155,7 @@ impl Command for Rotate {
Value::test_int(3), Value::test_int(3),
Value::test_int(5), Value::test_int(5),
], ],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -175,16 +165,14 @@ impl Command for Rotate {
example: "[[a b]; [1 2]] | rotate --ccw col_a col_b", example: "[[a b]; [1 2]] | rotate --ccw col_a col_b",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["col_a".to_string(), "col_b".to_string()], cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(2)], vals: vec![Value::test_string("b"), Value::test_int(2)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["col_a".to_string(), "col_b".to_string()], cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_string("a"), Value::test_int(1)], vals: vec![Value::test_string("a"), Value::test_int(1)],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -226,9 +214,9 @@ pub fn rotate(
if !values.is_empty() { if !values.is_empty() {
for val in values.into_iter() { for val in values.into_iter() {
match val { match val {
Value::Record { cols, vals, .. } => { Value::Record { val: record, .. } => {
old_column_names = cols; old_column_names = record.cols;
for v in vals { for v in record.vals {
new_values.push(v) new_values.push(v)
} }
} }
@ -286,11 +274,13 @@ pub fn rotate(
if not_a_record { if not_a_record {
return Ok(Value::List { return Ok(Value::List {
vals: vec![Value::Record { vals: vec![Value::record(
cols: new_column_names, Record {
vals: new_values, cols: new_column_names,
span: call.head, vals: new_values,
}], },
call.head,
)],
span: call.head, span: call.head,
} }
.into_pipeline_data() .into_pipeline_data()
@ -333,11 +323,13 @@ pub fn rotate(
} }
res.to_vec() res.to_vec()
}; };
final_values.push(Value::Record { final_values.push(Value::record(
cols: new_column_names.clone(), Record {
vals: new_vals, cols: new_column_names.clone(),
span: call.head, vals: new_vals,
}) },
call.head,
))
} }
Ok(Value::List { Ok(Value::List {

View File

@ -3,7 +3,7 @@ use nu_protocol::ast::{Block, Call};
use nu_protocol::engine::{Closure, Command, EngineState, Stack}; use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
PipelineIterator, ShellError, Signature, Span, SyntaxShape, Type, Value, PipelineIterator, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use std::collections::HashSet; use std::collections::HashSet;
use std::iter::FromIterator; use std::iter::FromIterator;
@ -52,7 +52,7 @@ impl Command for UpdateCells {
} }
}"#, }"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
cols: vec![ cols: vec![
"2021-04-16".into(), "2021-04-16".into(),
"2021-06-10".into(), "2021-06-10".into(),
@ -71,8 +71,7 @@ impl Command for UpdateCells {
Value::test_string(""), Value::test_string(""),
Value::test_string(""), Value::test_string(""),
], ],
span: Span::test_data(), })],
}],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
@ -89,7 +88,7 @@ impl Command for UpdateCells {
} }
}"#, }"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
cols: vec![ cols: vec![
"2021-04-16".into(), "2021-04-16".into(),
"2021-06-10".into(), "2021-06-10".into(),
@ -108,8 +107,7 @@ impl Command for UpdateCells {
Value::test_string(""), Value::test_string(""),
Value::test_string(""), Value::test_string(""),
], ],
span: Span::test_data(), })],
}],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
@ -194,26 +192,26 @@ impl Iterator for UpdateCellIterator {
} }
match val { match val {
Value::Record { vals, cols, span } => Some(Value::Record { Value::Record { val, span } => Some(Value::record(
vals: cols val.into_iter()
.iter()
.zip(vals)
.map(|(col, val)| match &self.columns { .map(|(col, val)| match &self.columns {
Some(cols) if !cols.contains(col) => val, Some(cols) if !cols.contains(&col) => (col, val),
_ => process_cell( _ => (
val, col,
&self.engine_state, process_cell(
&mut self.stack, val,
&self.block, &self.engine_state,
self.redirect_stdout, &mut self.stack,
self.redirect_stderr, &self.block,
span, self.redirect_stdout,
self.redirect_stderr,
span,
),
), ),
}) })
.collect(), .collect(),
cols,
span, span,
}), )),
val => Some(process_cell( val => Some(process_cell(
val, val,
&self.engine_state, &self.engine_state,

View File

@ -1,6 +1,8 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; 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)] #[derive(Clone)]
pub struct FromUrl; pub struct FromUrl;
@ -35,7 +37,7 @@ impl Command for FromUrl {
vec![Example { vec![Example {
example: "'bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter' | from url", example: "'bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter' | from url",
description: "Convert url encoded string into a record", description: "Convert url encoded string into a record",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: vec![ cols: vec![
"bread".to_string(), "bread".to_string(),
"cheese".to_string(), "cheese".to_string(),
@ -48,8 +50,7 @@ impl Command for FromUrl {
Value::test_string("ham"), Value::test_string("ham"),
Value::test_string("butter"), Value::test_string("butter"),
], ],
span: Span::test_data(), })),
}),
}] }]
} }
} }
@ -61,21 +62,12 @@ fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError>
match result { match result {
Ok(result) => { Ok(result) => {
let mut cols = vec![]; let record = result
let mut vals = vec![]; .into_iter()
for (k, v) in result { .map(|(k, v)| (k, Value::string(v, head)))
cols.push(k); .collect();
vals.push(Value::String { val: v, span: head })
}
Ok(PipelineData::Value( Ok(PipelineData::Value(Value::record(record, head), metadata))
Value::Record {
cols,
vals,
span: head,
},
metadata,
))
} }
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
"String not compatible with URL encoding".to_string(), "String not compatible with URL encoding".to_string(),

View File

@ -4,8 +4,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Config, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, record, Category, Config, DataSource, Example, IntoPipelineData, PipelineData,
ShellError, Signature, Spanned, SyntaxShape, Type, Value, PipelineMetadata, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
}; };
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -258,62 +258,34 @@ fn to_html(
// If asset doesn't work, make sure to return the default theme // If asset doesn't work, make sure to return the default theme
let html_themes = get_html_themes("228_themes.json").unwrap_or_default(); let html_themes = get_html_themes("228_themes.json").unwrap_or_default();
let cols = vec![
"name".into(),
"black".into(),
"red".into(),
"green".into(),
"yellow".into(),
"blue".into(),
"purple".into(),
"cyan".into(),
"white".into(),
"brightBlack".into(),
"brightRed".into(),
"brightGreen".into(),
"brightYellow".into(),
"brightBlue".into(),
"brightPurple".into(),
"brightCyan".into(),
"brightWhite".into(),
"background".into(),
"foreground".into(),
];
let result: Vec<Value> = html_themes let result: Vec<Value> = html_themes
.themes .themes
.into_iter() .into_iter()
.map(|n| { .map(|n| {
let vals = vec![ Value::record(
n.name, record! {
n.black, "name" => Value::string(n.name, head),
n.red, "black" => Value::string(n.black, head),
n.green, "red" => Value::string(n.red, head),
n.yellow, "green" => Value::string(n.green, head),
n.blue, "yellow" => Value::string(n.yellow, head),
n.purple, "blue" => Value::string(n.blue, head),
n.cyan, "purple" => Value::string(n.purple, head),
n.white, "cyan" => Value::string(n.cyan, head),
n.brightBlack, "white" => Value::string(n.white, head),
n.brightRed, "brightBlack" => Value::string(n.brightBlack, head),
n.brightGreen, "brightRed" => Value::string(n.brightRed, head),
n.brightYellow, "brightGreen" => Value::string(n.brightGreen, head),
n.brightBlue, "brightYellow" => Value::string(n.brightYellow, head),
n.brightPurple, "brightBlue" => Value::string(n.brightBlue, head),
n.brightCyan, "brightPurple" => Value::string(n.brightPurple, head),
n.brightWhite, "brightCyan" => Value::string(n.brightCyan, head),
n.background, "brightWhite" => Value::string(n.brightWhite, head),
n.foreground, "background" => Value::string(n.background, head),
] "foreground" => Value::string(n.foreground, head),
.into_iter() },
.map(|val| Value::String { val, span: head }) head,
.collect(); )
Value::Record {
cols: cols.clone(),
vals,
span: head,
}
}) })
.collect(); .collect();
return Ok(Value::List { return Ok(Value::List {

View File

@ -2,7 +2,7 @@ use inflector::cases::camelcase::to_camel_case;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use super::operate; use super::operate;
@ -74,11 +74,10 @@ impl Command for SubCommand {
description: "convert a column from a table to camelCase", description: "convert a column from a table to camelCase",
example: r#"[[lang, gems]; [nu_test, 100]] | str camel-case lang"#, example: r#"[[lang, gems]; [nu_test, 100]] | str camel-case lang"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
span: Span::test_data(),
cols: vec!["lang".to_string(), "gems".to_string()], cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("nuTest"), Value::test_int(100)], vals: vec![Value::test_string("nuTest"), Value::test_int(100)],
}], })],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },

View File

@ -2,7 +2,7 @@ use inflector::cases::kebabcase::to_kebab_case;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use super::operate; use super::operate;
@ -74,11 +74,10 @@ impl Command for SubCommand {
description: "convert a column from a table to kebab-case", description: "convert a column from a table to kebab-case",
example: r#"[[lang, gems]; [nuTest, 100]] | str kebab-case lang"#, example: r#"[[lang, gems]; [nuTest, 100]] | str kebab-case lang"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
span: Span::test_data(),
cols: vec!["lang".to_string(), "gems".to_string()], cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("nu-test"), Value::test_int(100)], vals: vec![Value::test_string("nu-test"), Value::test_int(100)],
}], })],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },

View File

@ -2,7 +2,7 @@ use inflector::cases::pascalcase::to_pascal_case;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use super::operate; use super::operate;
@ -74,11 +74,10 @@ impl Command for SubCommand {
description: "convert a column from a table to PascalCase", description: "convert a column from a table to PascalCase",
example: r#"[[lang, gems]; [nu_test, 100]] | str pascal-case lang"#, example: r#"[[lang, gems]; [nu_test, 100]] | str pascal-case lang"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
span: Span::test_data(),
cols: vec!["lang".to_string(), "gems".to_string()], cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("NuTest"), Value::test_int(100)], vals: vec![Value::test_string("NuTest"), Value::test_int(100)],
}], })],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },

View File

@ -2,7 +2,7 @@ use inflector::cases::screamingsnakecase::to_screaming_snake_case;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use super::operate; use super::operate;
@ -74,11 +74,10 @@ impl Command for SubCommand {
description: "convert a column from a table to SCREAMING_SNAKE_CASE", description: "convert a column from a table to SCREAMING_SNAKE_CASE",
example: r#"[[lang, gems]; [nu_test, 100]] | str screaming-snake-case lang"#, example: r#"[[lang, gems]; [nu_test, 100]] | str screaming-snake-case lang"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
span: Span::test_data(),
cols: vec!["lang".to_string(), "gems".to_string()], cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("NU_TEST"), Value::test_int(100)], vals: vec![Value::test_string("NU_TEST"), Value::test_int(100)],
}], })],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },

View File

@ -2,7 +2,7 @@ use inflector::cases::snakecase::to_snake_case;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use super::operate; use super::operate;
@ -73,11 +73,10 @@ impl Command for SubCommand {
description: "convert a column from a table to snake_case", description: "convert a column from a table to snake_case",
example: r#"[[lang, gems]; [nuTest, 100]] | str snake-case lang"#, example: r#"[[lang, gems]; [nuTest, 100]] | str snake-case lang"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
span: Span::test_data(),
cols: vec!["lang".to_string(), "gems".to_string()], cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("nu_test"), Value::test_int(100)], vals: vec![Value::test_string("nu_test"), Value::test_int(100)],
}], })],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },

View File

@ -2,7 +2,7 @@ use inflector::cases::titlecase::to_title_case;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use super::operate; use super::operate;
@ -69,11 +69,10 @@ impl Command for SubCommand {
description: "convert a column from a table to Title Case", description: "convert a column from a table to Title Case",
example: r#"[[title, count]; ['nu test', 100]] | str title-case title"#, example: r#"[[title, count]; ['nu test', 100]] | str title-case title"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
span: Span::test_data(),
cols: vec!["title".to_string(), "count".to_string()], cols: vec!["title".to_string(), "count".to_string()],
vals: vec![Value::test_string("Nu Test"), Value::test_int(100)], vals: vec![Value::test_string("Nu Test"), Value::test_int(100)],
}], })],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ use crate::{
parse_nustyle, NuStyle, parse_nustyle, NuStyle,
}; };
use nu_ansi_term::Style; use nu_ansi_term::Style;
use nu_protocol::Value; use nu_protocol::{Record, Value};
use std::collections::HashMap; use std::collections::HashMap;
pub fn lookup_ansi_color_style(s: &str) -> Style { pub fn lookup_ansi_color_style(s: &str) -> Style {
@ -32,7 +32,7 @@ pub fn get_color_map(colors: &HashMap<String, Value>) -> HashMap<String, Style>
fn parse_map_entry(hm: &mut HashMap<String, Style>, key: &str, value: &Value) { fn parse_map_entry(hm: &mut HashMap<String, Style>, key: &str, value: &Value) {
let value = match value { let value = match value {
Value::String { val, .. } => Some(lookup_ansi_color_style(val)), Value::String { val, .. } => Some(lookup_ansi_color_style(val)),
Value::Record { cols, vals, .. } => get_style_from_value(cols, vals).map(parse_nustyle), Value::Record { val, .. } => get_style_from_value(val).map(parse_nustyle),
_ => None, _ => None,
}; };
if let Some(value) = value { if let Some(value) = value {
@ -40,10 +40,10 @@ fn parse_map_entry(hm: &mut HashMap<String, Style>, key: &str, value: &Value) {
} }
} }
fn get_style_from_value(cols: &[String], vals: &[Value]) -> Option<NuStyle> { fn get_style_from_value(record: &Record) -> Option<NuStyle> {
let mut was_set = false; let mut was_set = false;
let mut style = NuStyle::from(Style::default()); let mut style = NuStyle::from(Style::default());
for (col, val) in cols.iter().zip(vals) { for (col, val) in record {
match col.as_str() { match col.as_str() {
"bg" => { "bg" => {
if let Value::String { val, .. } = val { if let Value::String { val, .. } = val {
@ -120,48 +120,54 @@ mod tests {
#[test] #[test]
fn test_get_style_from_value() { fn test_get_style_from_value() {
// Test case 1: all values are valid // Test case 1: all values are valid
let cols = vec!["bg".to_string(), "fg".to_string(), "attr".to_string()]; let record = Record {
let vals = vec![ cols: vec!["bg".to_string(), "fg".to_string(), "attr".to_string()],
Value::String { vals: vec![
val: "red".to_string(), Value::String {
span: Span::unknown(), val: "red".to_string(),
}, span: Span::unknown(),
Value::String { },
val: "blue".to_string(), Value::String {
span: Span::unknown(), val: "blue".to_string(),
}, span: Span::unknown(),
Value::String { },
val: "bold".to_string(), Value::String {
span: Span::unknown(), val: "bold".to_string(),
}, span: Span::unknown(),
]; },
],
};
let expected_style = NuStyle { let expected_style = NuStyle {
bg: Some("red".to_string()), bg: Some("red".to_string()),
fg: Some("blue".to_string()), fg: Some("blue".to_string()),
attr: Some("bold".to_string()), attr: Some("bold".to_string()),
}; };
assert_eq!(get_style_from_value(&cols, &vals), Some(expected_style)); assert_eq!(get_style_from_value(&record), Some(expected_style));
// Test case 2: no values are valid // Test case 2: no values are valid
let cols = vec!["invalid".to_string()]; let record = Record {
let vals = vec![Value::nothing(Span::unknown())]; cols: vec!["invalid".to_string()],
assert_eq!(get_style_from_value(&cols, &vals), None); vals: vec![Value::nothing(Span::unknown())],
};
assert_eq!(get_style_from_value(&record), None);
// Test case 3: some values are valid // Test case 3: some values are valid
let cols = vec!["bg".to_string(), "invalid".to_string()]; let record = Record {
let vals = vec![ cols: vec!["bg".to_string(), "invalid".to_string()],
Value::String { vals: vec![
val: "green".to_string(), Value::String {
span: Span::unknown(), val: "green".to_string(),
}, span: Span::unknown(),
Value::nothing(Span::unknown()), },
]; Value::nothing(Span::unknown()),
],
};
let expected_style = NuStyle { let expected_style = NuStyle {
bg: Some("green".to_string()), bg: Some("green".to_string()),
fg: None, fg: None,
attr: None, attr: None,
}; };
assert_eq!(get_style_from_value(&cols, &vals), Some(expected_style)); assert_eq!(get_style_from_value(&record), Some(expected_style));
} }
#[test] #[test]

View File

@ -100,8 +100,8 @@ pub fn color_record_to_nustyle(value: &Value) -> Style {
let mut bg = None; let mut bg = None;
let mut attr = None; let mut attr = None;
let v = value.as_record(); let v = value.as_record();
if let Ok((cols, inner_vals)) = v { if let Ok(record) = v {
for (k, v) in cols.iter().zip(inner_vals) { for (k, v) in record {
// Because config already type-checked the color_config records, this doesn't bother giving errors // Because config already type-checked the color_config records, this doesn't bother giving errors
// if there are unrecognised keys or bad values. // if there are unrecognised keys or bad values.
if let Ok(v) = v.as_string() { if let Ok(v) = v.as_string() {

View File

@ -4,7 +4,8 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, 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)] #[derive(Clone)]
@ -109,10 +110,10 @@ impl Command for BytesAt {
Example { Example {
description: "Get the remaining characters from a starting index", description: "Get the remaining characters from a starting index",
example: " { data: 0x[33 44 55 10 01 13] } | bytes at 3.. data", example: " { data: 0x[33 44 55 10 01 13] } | bytes at 3.. data",
result: Some(Value::test_record( result: Some(Value::test_record(Record {
vec!["data"], cols: vec!["data".to_string()],
vec![Value::test_binary(vec![0x10, 0x01, 0x13])], vals: vec![Value::test_binary(vec![0x10, 0x01, 0x13])],
)), })),
}, },
Example { Example {
description: "Get the characters from the beginning until ending index", 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", "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"#, 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 { 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()], cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![ vals: vec![
Value::Binary { Value::Binary {
@ -143,8 +144,7 @@ impl Command for BytesAt {
span: Span::test_data(), span: Span::test_data(),
}, },
], ],
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::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
struct Arguments { struct Arguments {
@ -112,7 +112,7 @@ impl Command for BytesIndexOf {
description: "Returns index of pattern for specific column", 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"#, 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 { 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()], cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![ vals: vec![
Value::test_int(0), Value::test_int(0),
@ -122,8 +122,7 @@ impl Command for BytesIndexOf {
}, },
Value::test_int(-1), Value::test_int(-1),
], ],
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::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape,
Value, Type, Value,
}; };
struct Arguments { struct Arguments {
@ -95,8 +95,10 @@ impl Command for BytesRemove {
Example { Example {
description: "Remove all occurrences of find binary in record field", description: "Remove all occurrences of find binary in record field",
example: "{ data: 0x[10 AA 10 BB 10] } | bytes remove -a 0x[10] data", example: "{ data: 0x[10 AA 10 BB 10] } | bytes remove -a 0x[10] data",
result: Some(Value::test_record(vec!["data"], result: Some(Value::test_record(Record {
vec![Value::test_binary(vec![0xAA, 0xBB])])), cols: vec!["data".to_string()],
vals: vec![Value::test_binary(vec![0xAA, 0xBB])]
})),
}, },
Example { Example {
description: "Remove occurrences of find binary from end", 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", 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", 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 { 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()], cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![ vals: vec![
Value::Binary { Value::Binary {
@ -125,9 +127,8 @@ impl Command for BytesRemove {
val: vec![0x17, 0x18, 0x19], val: vec![0x17, 0x18, 0x19],
span: Span::test_data(), span: Span::test_data(),
}, },
], ]
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::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape,
Value, Type, Value,
}; };
struct Arguments { struct Arguments {
@ -104,7 +104,7 @@ impl Command for BytesReplace {
description: "Find and replace all occurrences of find binary in table", 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", 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 { 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()], cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![ vals: vec![
Value::Binary { Value::Binary {
@ -120,8 +120,7 @@ impl Command for BytesReplace {
span: Span::test_data(), span: Span::test_data(),
}, },
], ],
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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value, Spanned, SyntaxShape, Type, Value,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use std::iter;
#[derive(Clone)] #[derive(Clone)]
pub struct Histogram; pub struct Histogram;
@ -53,7 +52,7 @@ impl Command for Histogram {
description: "Compute a histogram for a list of numbers", description: "Compute a histogram for a list of numbers",
example: "[1 2 1] | histogram", example: "[1 2 1] | histogram",
result: Some(Value::List { 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()], cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
vals: vec![ vals: vec![
Value::test_int(1), Value::test_int(1),
@ -62,9 +61,8 @@ impl Command for Histogram {
Value::test_string("66.67%"), Value::test_string("66.67%"),
Value::test_string("******************************************************************"), Value::test_string("******************************************************************"),
], ],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()], cols: vec!["value".to_string(), "count".to_string(), "quantile".to_string(), "percentage".to_string(), "frequency".to_string()],
vals: vec![ vals: vec![
Value::test_int(2), Value::test_int(2),
@ -73,8 +71,7 @@ impl Command for Histogram {
Value::test_string("33.33%"), Value::test_string("33.33%"),
Value::test_string("*********************************"), Value::test_string("*********************************"),
], ],
span: Span::test_data(), })],
}],
span: Span::test_data(), span: Span::test_data(),
} }
), ),
@ -195,8 +192,8 @@ fn run_histogram(
for v in values { for v in values {
match v { match v {
// parse record, and fill valid value to actual input. // parse record, and fill valid value to actual input.
Value::Record { cols, vals, .. } => { Value::Record { val, .. } => {
for (c, v) in iter::zip(cols, vals) { for (c, v) in val {
if &c == col_name { if &c == col_name {
if let Ok(v) = HashableValue::from_value(v, head_span) { if let Ok(v) = HashableValue::from_value(v, head_span) {
inputs.push(v); inputs.push(v);
@ -272,23 +269,25 @@ fn histogram_impl(
result.push(( result.push((
count, // attach count first for easily sorting. count, // attach count first for easily sorting.
Value::Record { Value::record(
cols: result_cols.clone(), Record {
vals: vec![ cols: result_cols.clone(),
val.into_value(), vals: vec![
Value::Int { val: count, span }, val.into_value(),
Value::Float { Value::Int { val: count, span },
val: quantile, Value::Float {
span, val: quantile,
}, span,
Value::String { },
val: percentage, Value::String {
span, val: percentage,
}, span,
Value::String { val: freq, span }, },
], Value::String { val: freq, span },
],
},
span, span,
}, ),
)); ));
} }
result.sort_by(|a, b| b.0.cmp(&a.0)); result.sort_by(|a, b| b.0.cmp(&a.0));

View File

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

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, 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)] #[derive(Clone)]
@ -63,11 +63,10 @@ impl Command for SubCommand {
description: "Convert string to decimal in table", description: "Convert string to decimal in table",
example: "[[num]; ['5.01']] | into decimal num", example: "[[num]; ['5.01']] | into decimal num",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
cols: vec!["num".to_string()], cols: vec!["num".to_string()],
vals: vec![Value::test_float(5.01)], vals: vec![Value::test_float(5.01)],
span: Span::test_data(), })],
}],
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::{ use nu_protocol::{
ast::{Call, CellPath, Expr}, ast::{Call, CellPath, Expr},
engine::{Command, EngineState, Stack}, 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; 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", "[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["value".to_string()], cols: vec!["value".to_string()],
vals: vec![Value::Duration { vals: vec![Value::Duration {
val: NS_PER_SEC, val: NS_PER_SEC,
span, span,
}], }],
span, }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["value".to_string()], cols: vec!["value".to_string()],
vals: vec![Value::Duration { vals: vec![Value::Duration {
val: 2 * 60 * NS_PER_SEC, val: 2 * 60 * NS_PER_SEC,
span, span,
}], }],
span, }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["value".to_string()], cols: vec!["value".to_string()],
vals: vec![Value::Duration { vals: vec![Value::Duration {
val: 3 * 60 * 60 * NS_PER_SEC, val: 3 * 60 * 60 * NS_PER_SEC,
span, span,
}], }],
span, }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["value".to_string()], cols: vec!["value".to_string()],
vals: vec![Value::Duration { vals: vec![Value::Duration {
val: 4 * 24 * 60 * 60 * NS_PER_SEC, val: 4 * 24 * 60 * 60 * NS_PER_SEC,
span, span,
}], }],
span, }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["value".to_string()], cols: vec!["value".to_string()],
vals: vec![Value::Duration { vals: vec![Value::Duration {
val: 5 * 7 * 24 * 60 * 60 * NS_PER_SEC, val: 5 * 7 * 24 * 60 * 60 * NS_PER_SEC,
span, span,
}], }],
span, }),
},
], ],
span, span,
}), }),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@ use chrono_tz::TZ_VARIANTS;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, record, Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError,
Type, Value, Signature, Span, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -40,12 +40,10 @@ impl Command for SubCommand {
Ok(TZ_VARIANTS Ok(TZ_VARIANTS
.iter() .iter()
.map(move |x| { .map(move |x| {
let cols = vec!["timezone".into()]; Value::record(
let vals = vec![Value::String { record! { "timezone" => Value::string(x.name(), span) },
val: x.name().to_string(),
span, span,
}]; )
Value::Record { cols, vals, span }
}) })
.into_pipeline_data(engine_state.ctrlc.clone())) .into_pipeline_data(engine_state.ctrlc.clone()))
} }
@ -55,11 +53,10 @@ impl Command for SubCommand {
example: "date list-timezone | where timezone =~ Shanghai", example: "date list-timezone | where timezone =~ Shanghai",
description: "Show timezone(s) that contains 'Shanghai'", description: "Show timezone(s) that contains 'Shanghai'",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
cols: vec!["timezone".into()], cols: vec!["timezone".into()],
vals: vec![Value::test_string("Asia/Shanghai")], vals: vec![Value::test_string("Asia/Shanghai")],
span: Span::test_data(), })],
}],
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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty, record, Category, Example, PipelineData, Record, ShellError, ShellError::DatetimeParseError,
Signature, Span, Value, ShellError::PipelineEmpty, Signature, Span, Type, Value,
}; };
use nu_protocol::{ShellError, Type};
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -78,7 +77,7 @@ impl Command for SubCommand {
span, span,
}, },
]; ];
Some(Value::Record { cols, vals, span }) Some(Value::test_record(Record { cols, vals }))
}; };
let example_result_2 = || { let example_result_2 = || {
@ -106,7 +105,7 @@ impl Command for SubCommand {
span, span,
}, },
]; ];
Some(Value::Record { cols, vals, span }) Some(Value::test_record(Record { cols, vals }))
}; };
vec![ vec![
@ -134,37 +133,20 @@ impl Command for SubCommand {
} }
} }
fn parse_date_into_table(date: Result<DateTime<FixedOffset>, Value>, head: Span) -> Value { fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
let cols = vec![ Value::record(
"year".into(), record! {
"month".into(), "year" => Value::int(date.year() as i64, head),
"day".into(), "month" => Value::int(date.month() as i64, head),
"hour".into(), "day" => Value::int(date.day() as i64, head),
"minute".into(), "hour" => Value::int(date.hour() as i64, head),
"second".into(), "minute" => Value::int(date.minute() as i64, head),
"nanosecond".into(), "second" => Value::int(date.second() as i64, head),
"timezone".into(), "nanosecond" => Value::int(date.nanosecond() as i64, head),
]; "timezone" => Value::string(date.offset().to_string(), head),
match date { },
Ok(x) => { head,
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 helper(val: Value, head: Span) -> Value { fn helper(val: Value, head: Span) -> Value {
@ -172,16 +154,16 @@ fn helper(val: Value, head: Span) -> Value {
Value::String { Value::String {
val, val,
span: val_span, span: val_span,
} => { } => match parse_date_from_string(&val, val_span) {
let date = parse_date_from_string(&val, val_span); Ok(date) => parse_date_into_table(date, head),
parse_date_into_table(date, head) Err(e) => e,
} },
Value::Nothing { span: _ } => { Value::Nothing { span: _ } => {
let now = Local::now(); let now = Local::now();
let n = now.with_timezone(now.offset()); 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 { _ => Value::Error {
error: Box::new(DatetimeParseError(val.debug_value(), head)), 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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty, record, Category, Example, PipelineData, Record, ShellError, ShellError::DatetimeParseError,
Signature, Span, Value, ShellError::PipelineEmpty, Signature, Span, Type, Value,
}; };
use nu_protocol::{ShellError, Type};
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -76,7 +75,7 @@ impl Command for SubCommand {
}, },
]; ];
Some(Value::List { Some(Value::List {
vals: vec![Value::Record { cols, vals, span }], vals: vec![Value::test_record(Record { cols, vals })],
span, span,
}) })
}; };
@ -107,7 +106,7 @@ impl Command for SubCommand {
}, },
]; ];
Some(Value::List { Some(Value::List {
vals: vec![Value::Record { cols, vals, span }], vals: vec![Value::test_record(Record { cols, vals })],
span, span,
}) })
}; };
@ -137,40 +136,19 @@ impl Command for SubCommand {
} }
} }
fn parse_date_into_table(date: Result<DateTime<FixedOffset>, Value>, head: Span) -> Value { fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
let cols = vec![ let record = record! {
"year".into(), "year" => Value::int(date.year() as i64, head),
"month".into(), "month" => Value::int(date.month() as i64, head),
"day".into(), "day" => Value::int(date.day() as i64, head),
"hour".into(), "hour" => Value::int(date.hour() as i64, head),
"minute".into(), "minute" => Value::int(date.minute() as i64, head),
"second".into(), "second" => Value::int(date.second() as i64, head),
"nanosecond".into(), "nanosecond" => Value::int(date.nanosecond() as i64, head),
"timezone".into(), "timezone" => Value::string(date.offset().to_string(), head),
]; };
match date {
Ok(x) => { Value::list(vec![Value::record(record, head)], head)
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 helper(val: Value, head: Span) -> Value { fn helper(val: Value, head: Span) -> Value {
@ -178,16 +156,16 @@ fn helper(val: Value, head: Span) -> Value {
Value::String { Value::String {
val, val,
span: val_span, span: val_span,
} => { } => match parse_date_from_string(&val, val_span) {
let date = parse_date_from_string(&val, val_span); Ok(date) => parse_date_into_table(date, head),
parse_date_into_table(date, head) Err(e) => e,
} },
Value::Nothing { span: _ } => { Value::Nothing { span: _ } => {
let now = Local::now(); let now = Local::now();
let n = now.with_timezone(now.offset()); 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 { _ => Value::Error {
error: Box::new(DatetimeParseError(val.debug_value(), head)), error: Box::new(DatetimeParseError(val.debug_value(), head)),
}, },

View File

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

View File

@ -3,8 +3,8 @@ use nu_parser::parse;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack, StateWorkingSet}, engine::{Command, EngineState, Stack, StateWorkingSet},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value, Spanned, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -88,14 +88,13 @@ impl Command for Ast {
}; };
// Create a new output record, merging the block and error // Create a new output record, merging the block and error
let output_record = Value::Record { let output_record = Value::record(
cols: vec!["block".to_string(), "error".to_string()], record! {
vals: vec![ "block" => Value::string(block_json, *block_span),
Value::string(block_json, *block_span), "error" => Value::string(error_json, Span::test_data()),
Value::string(error_json, Span::test_data()), },
], pipeline.span,
span: pipeline.span, );
};
Ok(output_record.into_pipeline_data()) Ok(output_record.into_pipeline_data())
} else { } else {
let block_value = Value::String { let block_value = Value::String {
@ -114,11 +113,13 @@ impl Command for Ast {
}, },
span: pipeline.span, span: pipeline.span,
}; };
let output_record = Value::Record { let output_record = Value::record(
cols: vec!["block".to_string(), "error".to_string()], record! {
vals: vec![block_value, error_value], "block" => block_value,
span: pipeline.span, "error" => error_value
}; },
pipeline.span,
);
Ok(output_record.into_pipeline_data()) 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::ast::{Argument, Block, Call, Expr, Expression};
use nu_protocol::engine::{Closure, Command, EngineState, Stack}; use nu_protocol::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, record, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature,
SyntaxShape, Type, Value, Span, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -91,29 +91,15 @@ pub fn get_pipeline_elements(
let value_span_end = value_span.end as i64; let value_span_end = value_span.end as i64;
let command_name = command_name; let command_name = command_name;
let rec = Value::Record { let record = record! {
cols: vec![ "cmd_index" => Value::string(index, span),
"cmd_index".to_string(), "cmd_name" => Value::string(command_name, value_span),
"cmd_name".to_string(), "type" => Value::string(value_type.to_string(), span),
"type".to_string(), "cmd_args" => Value::list(command_args_value, value_span),
"cmd_args".to_string(), "span_start" => Value::int(value_span_start, span),
"span_start".to_string(), "span_end" => Value::int(value_span_end, span),
"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,
}; };
element_values.push(rec); element_values.push(Value::record(record, value_span));
i += 1; 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_start = name.span.start as i64;
let arg_value_name_span_end = name.span.end as i64; let arg_value_name_span_end = name.span.end as i64;
let rec = Value::Record { let record = record! {
cols: vec![ "arg_type" => Value::string(arg_type, span),
"arg_type".to_string(), "name" => Value::string(arg_value_name, name.span),
"name".to_string(), "type" => Value::string("string", span),
"type".to_string(), "span_start" => Value::int(arg_value_name_span_start, span),
"span_start".to_string(), "span_end" => Value::int(arg_value_name_span_end, span),
"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,
}; };
arg_value.push(rec); arg_value.push(Value::record(record, name.span));
if let Some(shortcut) = short { if let Some(shortcut) = short {
let arg_type = "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_start = shortcut.span.start as i64;
let arg_value_name_span_end = shortcut.span.end as i64; let arg_value_name_span_end = shortcut.span.end as i64;
let rec = Value::Record { let record = record! {
cols: vec![ "arg_type" => Value::string(arg_type, span),
"arg_type".to_string(), "name" => Value::string(arg_value_name, shortcut.span),
"name".to_string(), "type" => Value::string("string", span),
"type".to_string(), "span_start" => Value::int(arg_value_name_span_start, span),
"span_start".to_string(), "span_end" => Value::int(arg_value_name_span_end, span),
"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,
}; };
arg_value.push(rec); arg_value.push(Value::record(record, name.span));
}; };
if let Some(expression) = opt_expr { 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_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64; let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record { let record = record! {
cols: vec![ "arg_type" => Value::string(arg_type, span),
"arg_type".to_string(), "name" => Value::string(arg_value_name, expression.span),
"name".to_string(), "type" => Value::string(arg_value_type, span),
"type".to_string(), "span_start" => Value::int(arg_value_name_span_start, span),
"span_start".to_string(), "span_end" => Value::int(arg_value_name_span_end, span),
"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,
}; };
arg_value.push(rec); arg_value.push(Value::record(record, expression.span));
}; };
} }
Argument::Positional(inner_expr) => { 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_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64; let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record { let record = record! {
cols: vec![ "arg_type" => Value::string(arg_type, span),
"arg_type".to_string(), "name" => Value::string(arg_value_name, inner_expr.span),
"name".to_string(), "type" => Value::string(arg_value_type, span),
"type".to_string(), "span_start" => Value::int(arg_value_name_span_start, span),
"span_start".to_string(), "span_end" => Value::int(arg_value_name_span_end, span),
"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,
}; };
arg_value.push(rec); arg_value.push(Value::record(record, inner_expr.span));
} }
Argument::Unknown(inner_expr) => { Argument::Unknown(inner_expr) => {
let arg_type = "unknown"; 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_start = evaled_span.start as i64;
let arg_value_name_span_end = evaled_span.end as i64; let arg_value_name_span_end = evaled_span.end as i64;
let rec = Value::Record { let record = record! {
cols: vec![ "arg_type" => Value::string(arg_type, span),
"arg_type".to_string(), "name" => Value::string(arg_value_name, inner_expr.span),
"name".to_string(), "type" => Value::string(arg_value_type, span),
"type".to_string(), "span_start" => Value::int(arg_value_name_span_start, span),
"span_start".to_string(), "span_end" => Value::int(arg_value_name_span_end, span),
"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,
}; };
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<_>>() .collect::<Vec<_>>()
.join(" ") .join(" ")
), ),
Value::Record { cols, vals, .. } => format!( Value::Record { val, .. } => format!(
"{{{}}}", "{{{}}}",
cols.iter() val.iter()
.zip(vals.iter())
.map(|(x, y)| format!("{}: {}", x, debug_string_without_formatting(y))) .map(|(x, y)| format!("{}: {}", x, debug_string_without_formatting(y)))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join(" ")

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -36,21 +36,15 @@ impl Command for ViewFiles {
let mut records = vec![]; let mut records = vec![];
for (file, start, end) in engine_state.files() { for (file, start, end) in engine_state.files() {
records.push(Value::Record { records.push(Value::record(
cols: vec![ record! {
"filename".to_string(), "filename" => Value::string(file, call.head),
"start".to_string(), "start" => Value::int(*start as i64, call.head),
"end".to_string(), "end" => Value::int(*end as i64, call.head),
"size".to_string(), "size" => Value::int(*end as i64 - *start as i64, call.head),
], },
vals: vec![ call.head,
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,
});
} }
Ok(Value::List { Ok(Value::List {

View File

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

View File

@ -94,8 +94,8 @@ fn with_env(
if table.len() == 1 { if table.len() == 1 {
// single row([[X W]; [Y Z]]) // single row([[X W]; [Y Z]])
match &table[0] { match &table[0] {
Value::Record { cols, vals, .. } => { Value::Record { val, .. } => {
for (k, v) in cols.iter().zip(vals.iter()) { for (k, v) in val {
env.insert(k.to_string(), v.clone()); env.insert(k.to_string(), v.clone());
} }
} }
@ -122,8 +122,8 @@ fn with_env(
} }
} }
// when get object by `open x.json` or `from json` // when get object by `open x.json` or `from json`
Value::Record { cols, vals, .. } => { Value::Record { val, .. } => {
for (k, v) in cols.iter().zip(vals) { for (k, v) in val {
env.insert(k.clone(), v.clone()); 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::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, DataSource, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, 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; use pathdiff::diff_paths;
@ -431,219 +431,155 @@ pub(crate) fn dir_entry_dict(
)); ));
} }
let mut cols = vec![]; let mut record = Record::new();
let mut vals = vec![];
let mut file_type = "unknown".to_string(); let mut file_type = "unknown".to_string();
cols.push("name".into()); record.push("name", Value::string(display_name, span));
vals.push(Value::String {
val: display_name.to_string(),
span,
});
if let Some(md) = metadata { if let Some(md) = metadata {
file_type = get_file_type(md, display_name, use_mime_type); file_type = get_file_type(md, display_name, use_mime_type);
cols.push("type".into()); record.push("type", Value::string(file_type.clone(), span));
vals.push(Value::String {
val: file_type.clone(),
span,
});
} else { } else {
cols.push("type".into()); record.push("type", Value::nothing(span));
vals.push(Value::nothing(span));
} }
if long { if long {
cols.push("target".into());
if let Some(md) = metadata { if let Some(md) = metadata {
if md.file_type().is_symlink() { record.push(
if let Ok(path_to_link) = filename.read_link() { "target",
vals.push(Value::String { if md.file_type().is_symlink() {
val: path_to_link.to_string_lossy().to_string(), if let Ok(path_to_link) = filename.read_link() {
span, Value::string(path_to_link.to_string_lossy(), span)
}); } else {
Value::string("Could not obtain target file's path", span)
}
} else { } else {
vals.push(Value::String { Value::nothing(span)
val: "Could not obtain target file's path".to_string(), },
span, )
});
}
} else {
vals.push(Value::nothing(span));
}
} }
} }
if long { if long {
if let Some(md) = metadata { if let Some(md) = metadata {
cols.push("readonly".into()); record.push("readonly", Value::bool(md.permissions().readonly(), span));
vals.push(Value::Bool {
val: md.permissions().readonly(),
span,
});
#[cfg(unix)] #[cfg(unix)]
{ {
use crate::filesystem::util::users; use crate::filesystem::util::users;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
let mode = md.permissions().mode(); let mode = md.permissions().mode();
cols.push("mode".into()); record.push(
vals.push(Value::String { "mode",
val: umask::Mode::from(mode).to_string(), Value::string(umask::Mode::from(mode).to_string(), span),
span, );
});
let nlinks = md.nlink(); let nlinks = md.nlink();
cols.push("num_links".into()); record.push("num_links", Value::int(nlinks as i64, span));
vals.push(Value::Int {
val: nlinks as i64,
span,
});
let inode = md.ino(); let inode = md.ino();
cols.push("inode".into()); record.push("inode", Value::int(inode as i64, span));
vals.push(Value::Int {
val: inode as i64,
span,
});
cols.push("user".into()); record.push(
if let Some(user) = users::get_user_by_uid(md.uid()) { "user",
vals.push(Value::String { if let Some(user) = users::get_user_by_uid(md.uid()) {
val: user.name, Value::string(user.name, span)
span, } else {
}); Value::int(md.uid() as i64, span)
} else { },
vals.push(Value::Int { );
val: md.uid() as i64,
span,
})
}
cols.push("group".into()); record.push(
if let Some(group) = users::get_group_by_gid(md.gid()) { "group",
vals.push(Value::String { if let Some(group) = users::get_group_by_gid(md.gid()) {
val: group.name, Value::string(group.name, span)
span, } else {
}); Value::int(md.gid() as i64, span)
} else { },
vals.push(Value::Int { );
val: md.gid() as i64,
span,
})
}
} }
} }
} }
cols.push("size".to_string()); record.push(
if let Some(md) = metadata { "size",
let zero_sized = file_type == "pipe" if let Some(md) = metadata {
|| file_type == "socket" let zero_sized = file_type == "pipe"
|| file_type == "char device" || file_type == "socket"
|| file_type == "block device"; || file_type == "char device"
|| file_type == "block device";
if md.is_dir() { if md.is_dir() {
if du { if du {
let params = DirBuilder::new(Span::new(0, 2), None, false, None, false); let params = DirBuilder::new(Span::new(0, 2), None, false, None, false);
let dir_size = DirInfo::new(filename, &params, None, ctrl_c).get_size(); let dir_size = DirInfo::new(filename, &params, None, ctrl_c).get_size();
vals.push(Value::Filesize { Value::filesize(dir_size as i64, span)
val: dir_size as i64, } else {
span, let dir_size: u64 = md.len();
});
} else {
let dir_size: u64 = md.len();
vals.push(Value::Filesize { Value::filesize(dir_size as i64, span)
val: dir_size as i64, }
span, } else if md.is_file() {
}); Value::filesize(md.len() as i64, span)
}; } else if md.file_type().is_symlink() {
} else if md.is_file() { if let Ok(symlink_md) = filename.symlink_metadata() {
vals.push(Value::Filesize { Value::filesize(symlink_md.len() as i64, span)
val: md.len() as i64, } else {
span, Value::nothing(span)
}); }
} else if md.file_type().is_symlink() { } else if zero_sized {
if let Ok(symlink_md) = filename.symlink_metadata() { Value::filesize(0, span)
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 }
} else { } else {
Value::nothing(span) Value::nothing(span)
}; }
vals.push(value); } else {
} Value::nothing(span)
} else { },
vals.push(Value::nothing(span)); );
}
if let Some(md) = metadata { if let Some(md) = metadata {
if long { if long {
cols.push("created".to_string()); record.push("created", {
{
let mut val = Value::nothing(span); let mut val = Value::nothing(span);
if let Ok(c) = md.created() { if let Ok(c) = md.created() {
if let Some(local) = try_convert_to_local_date_time(c) { if let Some(local) = try_convert_to_local_date_time(c) {
val = Value::Date { val = Value::date(local.with_timezone(local.offset()), span);
val: 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 mut val = Value::nothing(span);
if let Ok(a) = md.accessed() { if let Ok(a) = md.accessed() {
if let Some(local) = try_convert_to_local_date_time(a) { if let Some(local) = try_convert_to_local_date_time(a) {
val = Value::Date { val = Value::date(local.with_timezone(local.offset()), span)
val: 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 mut val = Value::nothing(span);
if let Ok(m) = md.modified() { if let Ok(m) = md.modified() {
if let Some(local) = try_convert_to_local_date_time(m) { if let Some(local) = try_convert_to_local_date_time(m) {
val = Value::Date { val = Value::date(local.with_timezone(local.offset()), span);
val: local.with_timezone(local.offset()),
span,
};
} }
} }
vals.push(val); val
} })
} else { } else {
if long { if long {
cols.push("created".to_string()); record.push("created", Value::nothing(span));
vals.push(Value::nothing(span)); record.push("accessed", Value::nothing(span));
cols.push("accessed".to_string());
vals.push(Value::nothing(span));
} }
cols.push("modified".to_string()); record.push("modified", Value::nothing(span));
vals.push(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 // 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, span: Span,
long: bool, long: bool,
) -> Value { ) -> Value {
let mut cols = vec![]; let mut record = Record::new();
let mut vals = vec![];
cols.push("name".into()); record.push("name", Value::string(display_name, span));
vals.push(Value::String {
val: display_name.to_string(),
span,
});
let find_data = match find_first_file(filename, span) { let find_data = match find_first_file(filename, span) {
Ok(fd) => fd, Ok(fd) => fd,
@ -722,90 +653,71 @@ mod windows_helper {
filename.to_string_lossy() filename.to_string_lossy()
); );
log::error!("{e}"); log::error!("{e}");
return Value::Record { cols, vals, span }; return Value::record(record, span);
} }
}; };
cols.push("type".into()); record.push(
vals.push(Value::String { "type",
val: get_file_type_windows_fallback(&find_data), Value::string(get_file_type_windows_fallback(&find_data), span),
span, );
});
if long { if long {
cols.push("target".into()); record.push(
if is_symlink(&find_data) { "target",
if let Ok(path_to_link) = filename.read_link() { if is_symlink(&find_data) {
vals.push(Value::String { if let Ok(path_to_link) = filename.read_link() {
val: path_to_link.to_string_lossy().to_string(), Value::string(path_to_link.to_string_lossy(), span)
span, } else {
}); Value::string("Could not obtain target file's path", span)
}
} else { } else {
vals.push(Value::String { Value::nothing(span)
val: "Could not obtain target file's path".to_string(), },
span, );
});
}
} else {
vals.push(Value::nothing(span));
}
cols.push("readonly".into()); record.push(
vals.push(Value::Bool { "readonly",
val: (find_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY.0 != 0), Value::bool(
span, 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; let file_size = (find_data.nFileSizeHigh as u64) << 32 | find_data.nFileSizeLow as u64;
vals.push(Value::Filesize { record.push("size", Value::filesize(file_size as i64, span));
val: file_size as i64,
span,
});
if long { if long {
cols.push("created".to_string()); record.push("created", {
{
let mut val = Value::nothing(span); let mut val = Value::nothing(span);
let seconds_since_unix_epoch = unix_time_from_filetime(&find_data.ftCreationTime); 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) { if let Some(local) = unix_time_to_local_date_time(seconds_since_unix_epoch) {
val = Value::Date { val = Value::date(local.with_timezone(local.offset()), span);
val: 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 mut val = Value::nothing(span);
let seconds_since_unix_epoch = unix_time_from_filetime(&find_data.ftLastAccessTime); 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) { if let Some(local) = unix_time_to_local_date_time(seconds_since_unix_epoch) {
val = Value::Date { val = Value::date(local.with_timezone(local.offset()), span);
val: 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 mut val = Value::nothing(span);
let seconds_since_unix_epoch = unix_time_from_filetime(&find_data.ftLastWriteTime); 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) { if let Some(local) = unix_time_to_local_date_time(seconds_since_unix_epoch) {
val = Value::Date { val = Value::date(local.with_timezone(local.offset()), span);
val: 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 { fn unix_time_from_filetime(ft: &FILETIME) -> i64 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Config, Example, IntoInterruptiblePipelineData, IntoPipelineData, ListStream, Category, Config, Example, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -126,13 +126,13 @@ impl Command for Find {
description: "Find value in records using regex", 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""#, example: r#"[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu""#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::test_record( vals: vec![Value::test_record(Record {
vec!["version", "name"], cols: vec!["version".to_string(), "name".to_string()],
vec![ vals: vec![
Value::test_string("0.1.0"), Value::test_string("0.1.0"),
Value::test_string("nushell".to_string()), Value::test_string("nushell".to_string()),
], ],
)], })],
span: Span::test_data(), 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"#, example: r#"[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu" --invert"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::test_record( Value::test_record(Record {
vec!["version", "name"], cols: vec!["version".to_string(), "name".to_string()],
vec![ vals: vec![
Value::test_string("0.1.1"), Value::test_string("0.1.1"),
Value::test_string("fish".to_string()), Value::test_string("fish".to_string()),
], ],
), }),
Value::test_record( Value::test_record(Record {
vec!["version", "name"], cols: vec!["version".to_string(), "name".to_string()],
vec![ vals: vec![
Value::test_string("0.2.0"), Value::test_string("0.2.0"),
Value::test_string("zsh".to_string()), Value::test_string("zsh".to_string()),
], ],
), }),
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -191,9 +191,9 @@ impl Command for Find {
example: example:
"[[col1 col2 col3]; [moe larry curly] [larry curly moe]] | find moe -c [col1]", "[[col1 col2 col3]; [moe larry curly] [larry curly moe]] | find moe -c [col1]",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::test_record( vals: vec![Value::test_record(Record {
vec!["col1".to_string(), "col2".to_string(), "col3".to_string()], cols: vec!["col1".to_string(), "col2".to_string(), "col3".to_string()],
vec![ vals: vec![
Value::test_string( Value::test_string(
"\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m" "\u{1b}[37m\u{1b}[0m\u{1b}[41;37mmoe\u{1b}[0m\u{1b}[37m\u{1b}[0m"
.to_string(), .to_string(),
@ -201,7 +201,7 @@ impl Command for Find {
Value::test_string("larry".to_string()), Value::test_string("larry".to_string()),
Value::test_string("curly".to_string()), Value::test_string("curly".to_string()),
], ],
)], })],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
@ -267,9 +267,11 @@ fn find_with_regex(
input.filter( input.filter(
move |value| match value { move |value| match value {
Value::String { val, .. } => re.is_match(val.as_str()).unwrap_or(false) != invert, Value::String { val, .. } => re.is_match(val.as_str()).unwrap_or(false) != invert,
Value::Record { vals, .. } | Value::List { vals, .. } => { Value::Record {
values_match_find(vals, &re, &config, invert) val: Record { vals, .. },
..
} }
| Value::List { vals, .. } => values_match_find(vals, &re, &config, invert),
_ => false, _ => false,
}, },
ctrlc, ctrlc,
@ -293,8 +295,7 @@ fn record_matches_regex(values: &[Value], re: &Regex, config: &Config) -> bool {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn highlight_terms_in_record_with_search_columns( fn highlight_terms_in_record_with_search_columns(
search_cols: &Vec<String>, search_cols: &Vec<String>,
cols: &[String], record: &Record,
vals: &[Value],
span: Span, span: Span,
config: &Config, config: &Config,
terms: &[Value], terms: &[Value],
@ -302,14 +303,14 @@ fn highlight_terms_in_record_with_search_columns(
highlight_style: Style, highlight_style: Style,
) -> Value { ) -> Value {
let cols_to_search = if search_cols.is_empty() { let cols_to_search = if search_cols.is_empty() {
cols.to_vec() &record.cols
} else { } else {
search_cols.to_vec() search_cols
}; };
let term_strs: Vec<_> = terms.iter().map(|v| v.into_string("", config)).collect(); 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) // 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 val_str = val.into_string("", config);
let predicate = cols_to_search.contains(col); let predicate = cols_to_search.contains(col);
predicate predicate
@ -337,11 +338,13 @@ fn highlight_terms_in_record_with_search_columns(
}) })
.map(|v| v.unwrap_or_else(|v| v)); .map(|v| v.unwrap_or_else(|v| v));
Value::Record { Value::record(
cols: cols.to_vec(), Record {
vals: new_vals.collect(), cols: record.cols.clone(),
vals: new_vals.collect(),
},
span, span,
} )
} }
fn contains_ignore_case(string: &str, substring: &str) -> bool { fn contains_ignore_case(string: &str, substring: &str) -> bool {
@ -392,18 +395,15 @@ fn find_with_rest_and_highlight(
PipelineData::Value(_, _) => input PipelineData::Value(_, _) => input
.map( .map(
move |mut x| match &mut x { move |mut x| match &mut x {
Value::Record { cols, vals, span } => { Value::Record { val, span } => highlight_terms_in_record_with_search_columns(
highlight_terms_in_record_with_search_columns( &cols_to_search_in_map,
&cols_to_search_in_map, val,
cols, *span,
vals, &config,
*span, &terms,
&config, string_style,
&terms, highlight_style,
string_style, ),
highlight_style,
)
}
_ => x, _ => x,
}, },
ctrlc.clone(), ctrlc.clone(),
@ -424,18 +424,15 @@ fn find_with_rest_and_highlight(
PipelineData::ListStream(stream, meta) => Ok(ListStream::from_stream( PipelineData::ListStream(stream, meta) => Ok(ListStream::from_stream(
stream stream
.map(move |mut x| match &mut x { .map(move |mut x| match &mut x {
Value::Record { cols, vals, span } => { Value::Record { val, span } => highlight_terms_in_record_with_search_columns(
highlight_terms_in_record_with_search_columns( &cols_to_search_in_map,
&cols_to_search_in_map, val,
cols, *span,
vals, &config,
*span, &terms,
&config, string_style,
&terms, highlight_style,
string_style, ),
highlight_style,
)
}
_ => x, _ => x,
}) })
.filter(move |value| { .filter(move |value| {
@ -535,13 +532,13 @@ fn value_should_be_printed(
| Value::List { .. } | Value::List { .. }
| Value::CellPath { .. } | Value::CellPath { .. }
| Value::CustomValue { .. } => term_contains_value(term, &lower_value, span), | Value::CustomValue { .. } => term_contains_value(term, &lower_value, span),
Value::Record { cols, vals, .. } => { Value::Record { val, .. } => {
record_matches_term(cols, vals, columns_to_search, filter_config, term, span) record_matches_term(val, columns_to_search, filter_config, term, span)
} }
Value::LazyRecord { val, .. } => match val.collect() { Value::LazyRecord { val, .. } => match val.collect() {
Ok(val) => match val { Ok(val) => match val {
Value::Record { cols, vals, .. } => { Value::Record { val, .. } => {
record_matches_term(&cols, &vals, columns_to_search, filter_config, term, span) record_matches_term(&val, columns_to_search, filter_config, term, span)
} }
_ => false, _ => false,
}, },
@ -567,19 +564,18 @@ fn term_equals_value(term: &Value, value: &Value, span: Span) -> bool {
} }
fn record_matches_term( fn record_matches_term(
cols: &[String], record: &Record,
vals: &[Value],
columns_to_search: &Vec<String>, columns_to_search: &Vec<String>,
filter_config: &Config, filter_config: &Config,
term: &Value, term: &Value,
span: Span, span: Span,
) -> bool { ) -> bool {
let cols_to_search = if columns_to_search.is_empty() { let cols_to_search = if columns_to_search.is_empty() {
cols.to_vec() &record.cols
} else { } 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) { if !cols_to_search.contains(col) {
return false; 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::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -84,26 +84,50 @@ impl Command for Flatten {
example: "{ a: b, d: [ 1 2 3 4 ], e: [ 4 3 ] } | flatten d --all", example: "{ a: b, d: [ 1 2 3 4 ], e: [ 4 3 ] } | flatten d --all",
result: Some(Value::List{ result: Some(Value::List{
vals: vec![ vals: vec![
Value::Record{ Value::test_record(Record {
cols: vec!["a".to_string(), "d".to_string(), "e".to_string()], 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()} ], vals: vec![
span: Span::test_data() Value::test_string("b"),
}, Value::test_int(1),
Value::Record{ 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()], 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()} ], vals: vec![
span: Span::test_data() Value::test_string("b"),
}, Value::test_int(2),
Value::Record{ 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()], 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()} ], vals: vec![
span: Span::test_data() Value::test_string("b"),
}, Value::test_int(3),
Value::Record{ 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()], 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()} ], vals: vec![
span: Span::test_data() 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(), 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) }], Err(e) => return vec![Value::Error { error: Box::new(e) }],
}; };
let res = { if item.as_record().is_ok() {
if item.as_record().is_ok() { let mut out = IndexMap::<String, Value>::new();
let mut out = IndexMap::<String, Value>::new(); let mut inner_table = None;
let mut inner_table = None;
let records = match item { let record = match item {
Value::Record { Value::Record { val, .. } => val,
cols, // Propagate errors by explicitly matching them before the final case.
vals, Value::Error { .. } => return vec![item.clone()],
span: _, other => {
} => (cols, vals), return vec![Value::Error {
// Propagate errors by explicitly matching them before the final case. error: Box::new(ShellError::OnlySupportsThisInputType {
Value::Error { .. } => return vec![item.clone()], exp_input_type: "record".into(),
other => { wrong_type: other.get_type().to_string(),
return vec![Value::Error { dst_span: _name_tag,
error: Box::new(ShellError::OnlySupportsThisInputType { src_span: other.expect_span(),
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() { let s = match item.span() {
Ok(x) => x, Ok(x) => x,
Err(e) => return vec![Value::Error { error: Box::new(e) }], Err(e) => return vec![Value::Error { error: Box::new(e) }],
}; };
let records_iterator = { for (column_index, (column, value)) in record.iter().enumerate() {
let cols = records.0; let column_requested = columns.iter().find(|c| c.into_string() == *column);
let vals = records.1; let need_flatten = { columns.is_empty() || column_requested.is_some() };
let mut pairs = vec![]; match value {
for i in 0..cols.len() { Value::Record { val, .. } => {
pairs.push((cols[i].as_str(), &vals[i])); if need_flatten {
} val.iter().for_each(|(col, val)| {
pairs if out.contains_key(col) {
}; out.insert(format!("{column}_{col}"), val.clone());
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,
));
} else { } 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( inner_table = Some(TableInside::Entries(
column, r,
&s, &s,
values.iter().collect::<Vec<_>>(), values.iter().collect::<Vec<_>>(),
column_index, 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![]; let mut expanded = vec![];
match inner_table { match inner_table {
Some(TableInside::Entries(column, _, entries, parent_column_index)) => { Some(TableInside::Entries(column, _, entries, parent_column_index)) => {
for entry in entries { for entry in entries {
let base = out.clone(); let base = out.clone();
let (mut record_cols, mut record_vals) = (vec![], vec![]); let mut record = Record::new();
let mut index = 0; let mut index = 0;
for (col, val) in base.into_iter() { for (col, val) in base.into_iter() {
// meet the flattened column, push them to result record first // meet the flattened column, push them to result record first
// this can avoid output column order changed. // 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.
if index == parent_column_index { if index == parent_column_index {
record_cols.push(column.to_string()); record.push(column, entry.clone());
record_vals.push(entry.clone());
} }
let record = Value::Record { record.push(col, val);
cols: record_cols, index += 1;
vals: record_vals,
span: tag,
};
expanded.push(record);
} }
// 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, Some(TableInside::FlattenedRows {
_span, columns,
values, _span,
parent_column_name, values,
parent_column_index, parent_column_name,
}) => { parent_column_index,
for (inner_cols, inner_vals) in columns.into_iter().zip(values) { }) => {
let base = out.clone(); for (inner_cols, inner_vals) in columns.into_iter().zip(values) {
let (mut record_cols, mut record_vals) = (vec![], vec![]); let base = out.clone();
let mut index = 0; let mut record = Record::new();
let mut index = 0;
for (base_col, base_val) in base.into_iter() { for (base_col, base_val) in base.into_iter() {
// meet the flattened column, push them to result record first // meet the flattened column, push them to result record first
// this can avoid output column order changed. // 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.
if index == parent_column_index { if index == parent_column_index {
for (col, val) in inner_cols.iter().zip(inner_vals.iter()) { for (col, val) in inner_cols.iter().zip(inner_vals.iter()) {
if record_cols.contains(col) { if record.cols.contains(col) {
record_cols.push(format!("{parent_column_name}_{col}")); record.push(format!("{parent_column_name}_{col}"), val.clone());
} else { } else {
record_cols.push(col.to_string()); record.push(col, val.clone());
} };
record_vals.push(val.clone());
} }
} }
let record = Value::Record {
cols: record_cols, record.push(base_col, base_val);
vals: record_vals, index += 1;
span: tag,
};
expanded.push(record);
} }
}
None => { // the flattened column may be the last column in the original table.
let record = Value::Record { if index == parent_column_index {
cols: out.keys().map(|f| f.to_string()).collect::<Vec<_>>(), for (col, val) in inner_cols.iter().zip(inner_vals.iter()) {
vals: out.values().cloned().collect(), if record.cols.contains(col) {
span: tag, record.push(format!("{parent_column_name}_{col}"), val.clone());
}; } else {
expanded.push(record); record.push(col, val.clone());
}
}
}
expanded.push(Value::record(record, tag));
} }
} }
expanded None => {
} else if item.as_list().is_ok() { expanded.push(Value::record(out.into_iter().collect(), tag));
if let Value::List { vals, span: _ } = item {
vals.to_vec()
} else {
vec![]
} }
} else {
vec![item.clone()]
} }
}; expanded
res } 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)] #[cfg(test)]

View File

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

View File

@ -1,8 +1,8 @@
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Category, Config, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
Value, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -37,15 +37,14 @@ impl Command for Headers {
description: "Sets the column names for a table created by `split column`", 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"#, example: r#""a b c|1 2 3" | split row "|" | split column " " | headers"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![ vals: vec![
Value::test_string("1"), Value::test_string("1"),
Value::test_string("2"), Value::test_string("2"),
Value::test_string("3"), Value::test_string("3"),
], ],
span: Span::test_data(), })],
}],
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"#, example: r#""a b c|1 2 3|1 2 3 4" | split row "|" | split column " " | headers"#,
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: columns.clone(), cols: columns.clone(),
vals: vec![ vals: vec![
Value::test_string("1"), Value::test_string("1"),
Value::test_string("2"), Value::test_string("2"),
Value::test_string("3"), Value::test_string("3"),
], ],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: columns, cols: columns,
vals: vec![ vals: vec![
Value::test_string("1"), Value::test_string("1"),
Value::test_string("2"), Value::test_string("2"),
Value::test_string("3"), Value::test_string("3"),
], ],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -102,20 +99,17 @@ fn replace_headers(
new_headers: &[String], new_headers: &[String],
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
match value { match value {
Value::Record { cols, vals, span } => { Value::Record { val, span } => Ok(Value::record(
let (cols, vals) = cols val.into_iter()
.into_iter()
.zip(vals)
.filter_map(|(col, val)| { .filter_map(|(col, val)| {
old_headers old_headers
.iter() .iter()
.position(|c| c == &col) .position(|c| c == &col)
.map(|i| (new_headers[i].clone(), val)) .map(|i| (new_headers[i].clone(), val))
}) })
.unzip(); .collect(),
span,
Ok(Value::Record { cols, vals, span }) )),
}
Value::List { vals, span } => { Value::List { vals, span } => {
let vals = vals let vals = vals
.into_iter() .into_iter()
@ -148,8 +142,8 @@ fn extract_headers(
config: &Config, config: &Config,
) -> Result<(Vec<String>, Vec<String>), ShellError> { ) -> Result<(Vec<String>, Vec<String>), ShellError> {
match value { match value {
Value::Record { cols, vals, .. } => { Value::Record { val: record, .. } => {
for v in vals { for v in &record.vals {
if !is_valid_header(v) { if !is_valid_header(v) {
return Err(ShellError::TypeMismatch { return Err(ShellError::TypeMismatch {
err_message: "needs compatible type: Null, String, Bool, Float, Int" err_message: "needs compatible type: Null, String, Bool, Float, Int"
@ -159,8 +153,9 @@ fn extract_headers(
} }
} }
let old_headers = cols.to_vec(); let old_headers = record.cols.clone();
let new_headers = vals let new_headers = record
.vals
.iter() .iter()
.enumerate() .enumerate()
.map(|(idx, value)| { .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::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Type, Value, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -60,51 +60,56 @@ impl Command for Insert {
vec![Example { vec![Example {
description: "Insert a new entry into a single record", description: "Insert a new entry into a single record",
example: "{'name': 'nu', 'stars': 5} | insert alias 'Nushell'", 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()], cols: vec!["name".into(), "stars".into(), "alias".into()],
vals: vec![ vals: vec![
Value::test_string("nu"), Value::test_string("nu"),
Value::test_int(5), Value::test_int(5),
Value::test_string("Nushell"), Value::test_string("Nushell"),
], ],
span: Span::test_data(), })),
}),
}, },
Example { Example {
description: "Insert a new column into a table, populating all rows", description: "Insert a new column into a table, populating all rows",
example: "[[project, lang]; ['Nushell', 'Rust']] | insert type 'shell'", example: "[[project, lang]; ['Nushell', 'Rust']] | insert type 'shell'",
result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "lang".into(), "type".into()], result: Some(Value::List {
vals: vec![Value::test_string("Nushell"), Value::test_string("Rust"), Value::test_string("shell")], span: Span::test_data()}], span: Span::test_data()}), 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 { Example {
description: "Insert a column with values equal to their row index, plus the value of 'foo' in each row", 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", example: "[[foo]; [7] [8] [9]] | enumerate | insert bar {|e| $e.item.foo + $e.index } | flatten",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![
cols: vec!["index".into(), "foo".into(), "bar".into()], Value::test_record(Record {
vals: vec![ cols: vec!["index".into(), "foo".into(), "bar".into()],
Value::test_int(0), vals: vec![
Value::test_int(7), Value::test_int(0),
Value::test_int(7), Value::test_int(7),
], Value::test_int(7),
span: Span::test_data(), ],
}, Value::Record { }),
cols: vec!["index".into(),"foo".into(), "bar".into()], Value::test_record(Record {
vals: vec![ cols: vec!["index".into(),"foo".into(), "bar".into()],
Value::test_int(1), vals: vec![
Value::test_int(8), Value::test_int(1),
Value::test_int(9), Value::test_int(8),
], Value::test_int(9),
span: Span::test_data(), ],
}, Value::Record { }),
cols: vec!["index".into(), "foo".into(), "bar".into()], Value::test_record(Record {
vals: vec![ cols: vec!["index".into(), "foo".into(), "bar".into()],
Value::test_int(2), vals: vec![
Value::test_int(9), Value::test_int(2),
Value::test_int(11), Value::test_int(9),
], Value::test_int(11),
span: Span::test_data(), ],
}], }),
],
span: Span::test_data(), span: Span::test_data(),
}), }),
}] }]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError, Signature,
Spanned, SyntaxShape, Type, Value, Span, Spanned, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -90,16 +90,14 @@ impl Command for Transpose {
example: "[[c1 c2]; [1 2]] | transpose", example: "[[c1 c2]; [1 2]] | transpose",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()], cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("c1"), Value::test_int(1)], vals: vec![Value::test_string("c1"), Value::test_int(1)],
span, }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["column0".to_string(), "column1".to_string()], cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("c2"), Value::test_int(2)], vals: vec![Value::test_string("c2"), Value::test_int(2)],
span, }),
},
], ],
span, span,
}), }),
@ -109,16 +107,14 @@ impl Command for Transpose {
example: "[[c1 c2]; [1 2]] | transpose key val", example: "[[c1 c2]; [1 2]] | transpose key val",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["key".to_string(), "val".to_string()], cols: vec!["key".to_string(), "val".to_string()],
vals: vec![Value::test_string("c1"), Value::test_int(1)], vals: vec![Value::test_string("c1"), Value::test_int(1)],
span, }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["key".to_string(), "val".to_string()], cols: vec!["key".to_string(), "val".to_string()],
vals: vec![Value::test_string("c2"), Value::test_int(2)], vals: vec![Value::test_string("c2"), Value::test_int(2)],
span, }),
},
], ],
span, span,
}), }),
@ -129,16 +125,14 @@ impl Command for Transpose {
example: "[[c1 c2]; [1 2]] | transpose -i val", example: "[[c1 c2]; [1 2]] | transpose -i val",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["val".to_string()], cols: vec!["val".to_string()],
vals: vec![Value::test_int(1)], vals: vec![Value::test_int(1)],
span, }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["val".to_string()], cols: vec!["val".to_string()],
vals: vec![Value::test_int(2)], vals: vec![Value::test_int(2)],
span, }),
},
], ],
span, span,
}), }),
@ -146,11 +140,10 @@ impl Command for Transpose {
Example { Example {
description: "Transfer back to record with -d flag", description: "Transfer back to record with -d flag",
example: "{c1: 1, c2: 2} | transpose | transpose -i -r -d", 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()], cols: vec!["c1".to_string(), "c2".to_string()],
vals: vec![Value::test_int(1), Value::test_int(2)], vals: vec![Value::test_int(1), Value::test_int(2)],
span, })),
}),
}, },
] ]
} }
@ -248,24 +241,26 @@ pub fn transpose(
.into_iter() .into_iter()
.map(move |desc| { .map(move |desc| {
let mut column_num: usize = 0; let mut column_num: usize = 0;
let mut cols = vec![]; let mut record = Record::new();
let mut vals = vec![];
if !args.ignore_titles && !args.header_row { if !args.ignore_titles && !args.header_row {
cols.push(headers[column_num].clone()); record.push(
vals.push(Value::string(desc.clone(), name)); headers[column_num].clone(),
Value::string(desc.clone(), name),
);
column_num += 1 column_num += 1
} }
for i in input.clone() { for i in input.clone() {
match &i.get_data_by_key(&desc) { match &i.get_data_by_key(&desc) {
Some(x) => { Some(x) => {
if args.keep_all && cols.contains(&headers[column_num]) { if args.keep_all && record.cols.contains(&headers[column_num]) {
let index = cols let index = record
.cols
.iter() .iter()
.position(|y| y == &headers[column_num]) .position(|y| y == &headers[column_num])
.expect("value is contained."); .expect("value is contained.");
let new_val = match &vals[index] { let new_val = match &record.vals[index] {
Value::List { vals, span } => { Value::List { vals, span } => {
let mut vals = vals.clone(); let mut vals = vals.clone();
vals.push(x.clone()); vals.push(x.clone());
@ -279,32 +274,31 @@ pub fn transpose(
span: v.expect_span(), span: v.expect_span(),
}, },
}; };
cols.remove(index); record.cols.remove(index);
vals.remove(index); record.vals.remove(index);
cols.push(headers[column_num].clone()); record.push(headers[column_num].clone(), new_val);
vals.push(new_val); } else if args.keep_last && record.cols.contains(&headers[column_num]) {
} else if args.keep_last && cols.contains(&headers[column_num]) { let index = record
let index = cols .cols
.iter() .iter()
.position(|y| y == &headers[column_num]) .position(|y| y == &headers[column_num])
.expect("value is contained."); .expect("value is contained.");
cols.remove(index); record.cols.remove(index);
vals.remove(index); record.vals.remove(index);
cols.push(headers[column_num].clone()); record.push(headers[column_num].clone(), x.clone());
vals.push(x.clone()); } else if !record.cols.contains(&headers[column_num]) {
} else if !cols.contains(&headers[column_num]) { record.push(headers[column_num].clone(), x.clone());
cols.push(headers[column_num].clone());
vals.push(x.clone());
} }
} }
_ => { _ => {
if args.keep_all && cols.contains(&headers[column_num]) { if args.keep_all && record.cols.contains(&headers[column_num]) {
let index = cols let index = record
.cols
.iter() .iter()
.position(|y| y == &headers[column_num]) .position(|y| y == &headers[column_num])
.expect("value is contained."); .expect("value is contained.");
let new_val = match &vals[index] { let new_val = match &record.vals[index] {
Value::List { vals, span } => { Value::List { vals, span } => {
let mut vals = vals.clone(); let mut vals = vals.clone();
vals.push(Value::nothing(name)); vals.push(Value::nothing(name));
@ -318,34 +312,28 @@ pub fn transpose(
span: v.expect_span(), span: v.expect_span(),
}, },
}; };
cols.remove(index); record.cols.remove(index);
vals.remove(index); record.vals.remove(index);
cols.push(headers[column_num].clone()); record.push(headers[column_num].clone(), new_val);
vals.push(new_val); } else if args.keep_last && record.cols.contains(&headers[column_num]) {
} else if args.keep_last && cols.contains(&headers[column_num]) { let index = record
let index = cols .cols
.iter() .iter()
.position(|y| y == &headers[column_num]) .position(|y| y == &headers[column_num])
.expect("value is contained."); .expect("value is contained.");
cols.remove(index); record.cols.remove(index);
vals.remove(index); record.vals.remove(index);
cols.push(headers[column_num].clone()); record.push(headers[column_num].clone(), Value::nothing(name));
vals.push(Value::nothing(name)); } else if !record.cols.contains(&headers[column_num]) {
} else if !cols.contains(&headers[column_num]) { record.push(headers[column_num].clone(), Value::nothing(name));
cols.push(headers[column_num].clone());
vals.push(Value::nothing(name));
} }
} }
} }
column_num += 1; column_num += 1;
} }
Value::Record { Value::record(record, name)
cols,
vals,
span: name,
}
}) })
.collect::<Vec<Value>>(); .collect::<Vec<Value>>();
if result_data.len() == 1 && args.as_record { 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::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, PipelineMetadata, ShellError, Signature, record, Category, Example, IntoPipelineData, PipelineData, PipelineMetadata, Record,
Span, Type, Value, ShellError, Signature, Span, Type, Value,
}; };
use std::collections::hash_map::IntoIter; use std::collections::hash_map::IntoIter;
use std::collections::HashMap; use std::collections::HashMap;
@ -122,16 +122,14 @@ impl Command for Uniq {
example: "[1 2 2] | uniq -c", example: "[1 2 2] | uniq -c",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::Record { Value::test_record(Record {
cols: vec!["value".to_string(), "count".to_string()], cols: vec!["value".to_string(), "count".to_string()],
vals: vec![Value::test_int(1), Value::test_int(1)], vals: vec![Value::test_int(1), Value::test_int(1)],
span: Span::test_data(), }),
}, Value::test_record(Record {
Value::Record {
cols: vec!["value".to_string(), "count".to_string()], cols: vec!["value".to_string(), "count".to_string()],
vals: vec![Value::test_int(2), Value::test_int(2)], vals: vec![Value::test_int(2), Value::test_int(2)],
span: Span::test_data(), }),
},
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -193,32 +191,25 @@ fn clone_to_lowercase(value: &Value) -> Value {
span: *span, span: *span,
}, },
Value::List { vals: vec, span } => Value::List { Value::List { vals: vec, span } => Value::List {
vals: vec vals: vec.iter().map(clone_to_lowercase).collect(),
.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(),
span: *span, 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(), other => other.clone(),
} }
} }
fn sort_attributes(val: Value) -> Value { fn sort_attributes(val: Value) -> Value {
match val { match val {
Value::Record { cols, vals, span } => { Value::Record { val, span } => {
let sorted = cols let sorted = val
.into_iter() .into_iter()
.zip(vals)
.sorted_by(|a, b| a.0.cmp(&b.0)) .sorted_by(|a, b| a.0.cmp(&b.0))
.collect_vec(); .collect_vec();
@ -228,11 +219,13 @@ fn sort_attributes(val: Value) -> Value {
.map(|a| sort_attributes(a.1)) .map(|a| sort_attributes(a.1))
.collect_vec(); .collect_vec();
Value::Record { Value::record(
cols: sorted_cols, Record {
vals: sorted_vals, cols: sorted_cols,
vals: sorted_vals,
},
span, span,
} )
} }
Value::List { vals, span } => Value::List { Value::List { vals, span } => Value::List {
vals: vals.into_iter().map(sort_attributes).collect_vec(), 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> { fn generate_results_with_count(head: Span, uniq_values: Vec<ValueCounter>) -> Vec<Value> {
uniq_values uniq_values
.into_iter() .into_iter()
.map(|item| Value::Record { .map(|item| {
cols: vec!["value".to_string(), "count".to_string()], Value::record(
vals: vec![item.val, Value::int(item.count, head)], record! {
span: head, "value" => item.val,
"count" => Value::int(item.count, head),
},
head,
)
}) })
.collect() .collect()
} }

View File

@ -4,7 +4,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -94,18 +94,18 @@ impl Command for UniqBy {
example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit", example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit",
result: Some(Value::List { result: Some(Value::List {
vals: vec![ vals: vec![
Value::test_record( Value::test_record(Record {
vec!["fruit", "count"], cols: vec!["fruit".to_string(), "count".to_string()],
vec![Value::test_string("apple"), Value::test_int(9)], vals: vec![Value::test_string("apple"), Value::test_int(9)],
), }),
Value::test_record( Value::test_record(Record {
vec!["fruit", "count"], cols: vec!["fruit".to_string(), "count".to_string()],
vec![Value::test_string("pear"), Value::test_int(3)], vals: vec![Value::test_string("pear"), Value::test_int(3)],
), }),
Value::test_record( Value::test_record(Record {
vec!["fruit", "count"], cols: vec!["fruit".to_string(), "count".to_string()],
vec![Value::test_string("orange"), Value::test_int(7)], vals: vec![Value::test_string("orange"), Value::test_int(7)],
), }),
], ],
span: Span::test_data(), span: Span::test_data(),
}), }),
@ -115,8 +115,7 @@ impl Command for UniqBy {
fn validate(vec: Vec<Value>, columns: &Vec<String>, span: Span) -> Result<(), ShellError> { fn validate(vec: Vec<Value>, columns: &Vec<String>, span: Span) -> Result<(), ShellError> {
if let Some(Value::Record { if let Some(Value::Record {
cols, val: record,
vals: _input_vals,
span: val_span, span: val_span,
}) = vec.first() }) = 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 { return Err(ShellError::CantFindColumn {
col_name: nonexistent, col_name: nonexistent,
span, 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::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Type, Value, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -57,33 +57,43 @@ impl Command for Update {
Example { Example {
description: "Update a column value", description: "Update a column value",
example: "{'name': 'nu', 'stars': 5} | update name 'Nushell'", example: "{'name': 'nu', 'stars': 5} | update name 'Nushell'",
result: Some(Value::Record { result: Some(Value::test_record(Record {
cols: vec!["name".into(), "stars".into()], cols: vec!["name".into(), "stars".into()],
vals: vec![Value::test_string("Nushell"), Value::test_int(5)], vals: vec![Value::test_string("Nushell"), Value::test_int(5)],
span: Span::test_data(), })),
}),
}, },
Example { Example {
description: "Use in closure form for more involved updating logic", 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", example: "[[count fruit]; [1 'apple']] | enumerate | update item.count {|e| ($e.item.fruit | str length) + $e.index } | get item",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::Record { vals: vec![Value::test_record(Record {
cols: vec!["count".into(), "fruit".into()], cols: vec!["count".into(), "fruit".into()],
vals: vec![Value::test_int(5), Value::test_string("apple")], vals: vec![Value::test_int(5), Value::test_string("apple")],
span: Span::test_data(), })],
}],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
Example { Example {
description: "Alter each value in the 'authors' column to use a single string instead of a list", 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 ','}", 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 { Example {
description: "You can also use a simple command to update 'authors' to a single string", 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 ','}", 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::engine::{Closure, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Category, Example, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Span, SyntaxShape, Type, Value, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -60,26 +60,45 @@ impl Command for Upsert {
vec![Example { vec![Example {
description: "Update a record's value", description: "Update a record's value",
example: "{'name': 'nu', 'stars': 5} | upsert name 'Nushell'", 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 { Example {
description: "Update each row of a table", description: "Update each row of a table",
example: "[[name lang]; [Nushell ''] [Reedline '']] | upsert lang 'Rust'", example: "[[name lang]; [Nushell ''] [Reedline '']] | upsert lang 'Rust'",
result: Some(Value::List { vals: vec![ result: Some(Value::List {
Value::Record { cols: vec!["name".into(), "lang".into()], vals: vec![Value::test_string("Nushell"), Value::test_string("Rust")], span: Span::test_data()}, vals: vec![
Value::Record { cols: vec!["name".into(), "lang".into()], vals: vec![Value::test_string("Reedline"), Value::test_string("Rust")], span: Span::test_data()} Value::test_record(Record {
], span: Span::test_data()}), 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 { Example {
description: "Insert a new entry into a single record", description: "Insert a new entry into a single record",
example: "{'name': 'nu', 'stars': 5} | upsert language 'Rust'", 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 { }, Example {
description: "Use in closure form for more involved updating logic", 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", 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![ result: Some(Value::List {
Value::Record { cols: vec!["count".into(), "fruit".into()], vals: vec![Value::test_int(5), Value::test_string("apple")], span: Span::test_data()}], vals: vec![Value::test_record(Record {
span: Span::test_data()}), cols: vec!["count".into(), "fruit".into()],
vals: vec![Value::test_int(5), Value::test_string("apple")],
})],
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Upsert an int into a list, updating an existing value based on the index", description: "Upsert an int into a list, updating an existing value based on the index",

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