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.
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 vals: Vec<Value> = (0..col_cnt as i64).map(Value::test_int).collect();
let record = Value::test_record(
(0..col_cnt)
.map(|x| (format!("col_{x}"), Value::test_int(x as i64)))
.collect(),
);
Value::List {
vals: (0..row_cnt)
.map(|_| Value::test_record(columns.clone(), vals.clone()))
.collect(),
vals: vec![record; row_cnt],
span: Span::test_data(),
}
}

View File

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

View File

@ -1,7 +1,7 @@
use nu_protocol::{
ast::Call,
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;
@ -41,36 +41,15 @@ impl Command for KeybindingsDefault {
let records = get_reedline_default_keybindings()
.into_iter()
.map(|(mode, modifier, code, event)| {
let mode = Value::String {
val: mode,
span: call.head,
};
let modifier = Value::String {
val: modifier,
span: 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,
}
Value::record(
record! {
"mode" => Value::string(mode, call.head),
"modifier" => Value::string(modifier, call.head),
"code" => Value::string(code, call.head),
"event" => Value::string(event, call.head),
},
call.head,
)
})
.collect();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
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;
@ -34,11 +34,10 @@ impl Command for ExprCol {
vec![Example {
description: "Creates a named column expression and converts it to a nu object",
example: "dfr col a | dfr into-nu",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["expr".into(), "value".into()],
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::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -33,11 +33,10 @@ impl Command for ExprLit {
vec![Example {
description: "Created a literal expression and converts it to a nu object",
example: "dfr lit 2 | dfr into-nu",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec!["expr".into(), "value".into()],
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 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::object::builder::ObjectChunkedBuilder;
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
// dataframe to create the Values Row
pub fn add_separator(values: &mut Vec<Value>, df: &DataFrame, span: Span) {
let mut cols = vec![];
let mut vals = vec![];
let mut record = Record::new();
cols.push("index".to_string());
vals.push(Value::String {
val: "...".into(),
span,
});
record.push("index", Value::string("...", span));
for name in df.get_column_names() {
cols.push(name.to_string());
vals.push(Value::String {
val: "...".into(),
span,
})
record.push(name, Value::string("...", span))
}
let extra_record = Value::Record { cols, vals, span };
values.push(extra_record);
values.push(Value::record(record, span));
}
// Inserting the values found in a Value::List
pub fn insert_record(
column_values: &mut ColumnMap,
cols: &[String],
values: &[Value],
) -> Result<(), ShellError> {
for (col, value) in cols.iter().zip(values.iter()) {
insert_value(value.clone(), col.clone(), column_values)?;
// Inserting the values found in a Value::List or Value::Record
pub fn insert_record(column_values: &mut ColumnMap, record: Record) -> Result<(), ShellError> {
for (col, value) in record {
insert_value(value, col, column_values)?;
}
Ok(())

View File

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

View File

@ -1,6 +1,6 @@
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 serde::{Deserialize, Deserializer, Serialize, Serializer};
@ -165,103 +165,59 @@ impl ExtractedExpr {
}
pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
let cols = vec!["expr".to_string(), "value".to_string()];
match expr {
Expr::Alias(expr, alias) => {
let expr = expr_to_value(expr.as_ref(), span);
let alias = Value::String {
val: alias.as_ref().into(),
span,
};
Expr::Alias(expr, alias) => Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"alias" => Value::string(alias.as_ref(), span),
},
span,
),
let cols = vec!["expr".into(), "alias".into()];
Value::Record {
cols,
vals: vec![expr, alias],
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::Column(name) => Value::record(
record! {
"expr" => Value::string("column", span),
"value" => Value::string(name.to_string(), span),
},
span,
),
Expr::Columns(columns) => {
let expr_type = Value::String {
val: "columns".into(),
let value = columns.iter().map(|col| Value::string(col, span)).collect();
Value::record(
record! {
"expr" => Value::string("columns", span),
"value" => Value::list(value, 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 {
predicate,
truthy,
falsy,
} => {
let predicate = expr_to_value(predicate.as_ref(), span);
let truthy = expr_to_value(truthy.as_ref(), span);
let falsy = expr_to_value(falsy.as_ref(), span);
let cols = vec!["predicate".into(), "truthy".into(), "falsy".into()];
Value::Record {
cols,
vals: vec![predicate, truthy, falsy],
span,
}
}
} => Value::record(
record! {
"predicate" => expr_to_value(predicate.as_ref(), span),
"truthy" => expr_to_value(truthy.as_ref(), span),
"falsy" => expr_to_value(falsy.as_ref(), span),
},
span,
),
Expr::Agg(agg_expr) => {
let value = match agg_expr {
AggExpr::Min { input: expr, .. }
@ -281,88 +237,37 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
expr,
quantile,
interpol,
} => {
let expr = expr_to_value(expr.as_ref(), span);
let quantile = expr_to_value(quantile.as_ref(), span);
let interpol = Value::String {
val: format!("{interpol:?}"),
span,
};
let cols = vec!["expr".into(), "quantile".into(), "interpol".into()];
Value::Record {
cols,
vals: vec![expr, quantile, interpol],
span,
}
}
} => Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"quantile" => expr_to_value(quantile.as_ref(), span),
"interpol" => Value::string(format!("{interpol:?}"), span),
},
span,
),
};
let expr_type = Value::String {
val: "agg".into(),
Value::record(
record! {
"expr" => Value::string("agg", span),
"value" => value,
},
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 => {
let expr = Value::String {
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,
}
Value::record(record! { "expr" => Value::string("wildcard", span) }, 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) => {
let vals = dtypes
.iter()
@ -374,109 +279,72 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
Value::List { vals, span }
}
Expr::Sort { expr, options } => {
let expr = expr_to_value(expr.as_ref(), span);
let options = Value::String {
val: format!("{options:?}"),
span,
};
let cols = vec!["expr".into(), "options".into()];
Value::Record {
cols,
vals: vec![expr, options],
span,
}
}
Expr::Sort { expr, options } => Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"options" => Value::string(format!("{options:?}"), span),
},
span,
),
Expr::Cast {
expr,
data_type,
strict,
} => {
let expr = expr_to_value(expr.as_ref(), span);
let dtype = Value::String {
val: format!("{data_type:?}"),
span,
};
let strict = Value::Bool { val: *strict, span };
let cols = vec!["expr".into(), "dtype".into(), "strict".into()];
Value::Record {
cols,
vals: vec![expr, dtype, strict],
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,
}
}
} => Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"dtype" => Value::string(format!("{data_type:?}"), span),
"strict" => Value::bool(*strict, span),
},
span,
),
Expr::Take { expr, idx } => Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"idx" => expr_to_value(idx.as_ref(), span),
},
span,
),
Expr::SortBy {
expr,
by,
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 = Value::List { vals: by, span };
let descending: Vec<Value> = descending
.iter()
.map(|r| Value::Bool { val: *r, span })
.collect();
let descending = Value::List {
vals: descending,
span,
};
let cols = vec!["expr".into(), "by".into(), "descending".into()];
Value::Record {
cols,
vals: vec![expr, by, descending],
Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"by" => Value::list(by, span),
"descending" => Value::list(descending, 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 {
input,
offset,
length,
} => {
let input = expr_to_value(input.as_ref(), span);
let offset = expr_to_value(offset.as_ref(), span);
let length = expr_to_value(length.as_ref(), span);
let cols = vec!["input".into(), "offset".into(), "length".into()];
Value::Record {
cols,
vals: vec![input, offset, length],
span,
}
}
} => Value::record(
record! {
"input" => expr_to_value(input.as_ref(), span),
"offset" => expr_to_value(offset.as_ref(), span),
"length" => expr_to_value(length.as_ref(), span),
},
span,
),
Expr::Exclude(expr, excluded) => {
let expr = expr_to_value(expr.as_ref(), span);
let excluded = excluded
.iter()
.map(|e| Value::String {
@ -484,34 +352,22 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
span,
})
.collect();
let excluded = Value::List {
vals: excluded,
span,
};
let cols = vec!["expr".into(), "excluded".into()];
Value::Record {
cols,
vals: vec![expr, excluded],
Value::record(
record! {
"expr" => expr_to_value(expr.as_ref(), span),
"excluded" => Value::list(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 {
input,
function,
@ -519,33 +375,15 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
options,
} => {
let input: Vec<Value> = input.iter().map(|e| expr_to_value(e, span)).collect();
let input = Value::List { vals: input, span };
let function = Value::String {
val: format!("{function:?}"),
Value::record(
record! {
"input" => Value::list(input, span),
"function" => Value::string(format!("{function:?}"), span),
"output_type" => Value::string(format!("{output_type:?}"), span),
"options" => Value::string(format!("{options:?}"), 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 {
input,
@ -553,79 +391,47 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Value {
options,
} => {
let input: Vec<Value> = input.iter().map(|e| expr_to_value(e, span)).collect();
let input = Value::List { vals: input, span };
let function = Value::String {
val: format!("{function:?}"),
Value::record(
record! {
"input" => Value::list(input, span),
"function" => Value::string(format!("{function:?}"), span),
"options" => Value::string(format!("{options:?}"), 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 {
function,
partition_by,
order_by,
options,
} => {
let function = expr_to_value(function, span);
let partition_by: Vec<Value> = partition_by
.iter()
.map(|e| expr_to_value(e, span))
.collect();
let partition_by = Value::List {
vals: partition_by,
span,
};
let order_by = order_by
.as_ref()
.map(|e| expr_to_value(e.as_ref(), span))
.unwrap_or_else(|| Value::nothing(span));
let options = Value::String {
val: format!("{options:?}"),
Value::record(
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,
};
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 nu_protocol::{CustomValue, ShellError, Span, Value};
use nu_protocol::{record, CustomValue, ShellError, Span, Value};
// CustomValue implementation for NuDataFrame
impl CustomValue for NuLazyFrame {
@ -29,22 +29,18 @@ impl CustomValue for NuLazyFrame {
}
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
let cols = vec!["plan".into(), "optimized_plan".into()];
let vals = vec![
Value::String {
val: self.as_ref().describe_plan(),
span,
},
Value::String {
val: self
.as_ref()
.describe_optimized_plan()
.unwrap_or_else(|_| "<NOT AVAILABLE>".to_string()),
span,
},
];
let optimized_plan = self
.as_ref()
.describe_optimized_plan()
.unwrap_or_else(|_| "<NOT AVAILABLE>".to_string());
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 {

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
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",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: columns.clone(),
vals: vec![Value::test_int(5), Value::test_int(6)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: columns.clone(),
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: columns,
vals: vec![Value::test_int(3), Value::test_int(4)],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),

View File

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

View File

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

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
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",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: columns.clone(),
vals: vec![Value::test_int(3), Value::test_int(4)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: columns.clone(),
vals: vec![Value::test_int(5), Value::test_int(6)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: columns,
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Type, Value,
Category, Example, IntoPipelineData, PipelineData, Record, ShellError, Signature, Span,
SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -40,16 +40,14 @@ impl Command for Rotate {
example: "{a:1, b:2} | rotate",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_int(1), Value::test_string("a")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_int(2), Value::test_string("b")],
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",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec![
"column0".to_string(),
"column1".to_string(),
@ -72,9 +70,8 @@ impl Command for Rotate {
Value::test_int(1),
Value::test_string("a"),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec![
"column0".to_string(),
"column1".to_string(),
@ -87,8 +84,7 @@ impl Command for Rotate {
Value::test_int(2),
Value::test_string("b"),
],
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",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_int(1), Value::test_string("a")],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_int(2), Value::test_string("b")],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -117,16 +111,14 @@ impl Command for Rotate {
example: "[[a b]; [1 2]] | rotate --ccw",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(2)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["column0".to_string(), "column1".to_string()],
vals: vec![Value::test_string("a"), Value::test_int(1)],
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",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec![
"column0".to_string(),
"column1".to_string(),
@ -149,9 +141,8 @@ impl Command for Rotate {
Value::test_int(4),
Value::test_int(6),
],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec![
"column0".to_string(),
"column1".to_string(),
@ -164,8 +155,7 @@ impl Command for Rotate {
Value::test_int(3),
Value::test_int(5),
],
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",
result: Some(Value::List {
vals: vec![
Value::Record {
Value::test_record(Record {
cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_string("b"), Value::test_int(2)],
span: Span::test_data(),
},
Value::Record {
}),
Value::test_record(Record {
cols: vec!["col_a".to_string(), "col_b".to_string()],
vals: vec![Value::test_string("a"), Value::test_int(1)],
span: Span::test_data(),
},
}),
],
span: Span::test_data(),
}),
@ -226,9 +214,9 @@ pub fn rotate(
if !values.is_empty() {
for val in values.into_iter() {
match val {
Value::Record { cols, vals, .. } => {
old_column_names = cols;
for v in vals {
Value::Record { val: record, .. } => {
old_column_names = record.cols;
for v in record.vals {
new_values.push(v)
}
}
@ -286,11 +274,13 @@ pub fn rotate(
if not_a_record {
return Ok(Value::List {
vals: vec![Value::Record {
cols: new_column_names,
vals: new_values,
span: call.head,
}],
vals: vec![Value::record(
Record {
cols: new_column_names,
vals: new_values,
},
call.head,
)],
span: call.head,
}
.into_pipeline_data()
@ -333,11 +323,13 @@ pub fn rotate(
}
res.to_vec()
};
final_values.push(Value::Record {
cols: new_column_names.clone(),
vals: new_vals,
span: call.head,
})
final_values.push(Value::record(
Record {
cols: new_column_names.clone(),
vals: new_vals,
},
call.head,
))
}
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::{
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::iter::FromIterator;
@ -52,7 +52,7 @@ impl Command for UpdateCells {
}
}"#,
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"2021-04-16".into(),
"2021-06-10".into(),
@ -71,8 +71,7 @@ impl Command for UpdateCells {
Value::test_string(""),
Value::test_string(""),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -89,7 +88,7 @@ impl Command for UpdateCells {
}
}"#,
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec![
"2021-04-16".into(),
"2021-06-10".into(),
@ -108,8 +107,7 @@ impl Command for UpdateCells {
Value::test_string(""),
Value::test_string(""),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},
@ -194,26 +192,26 @@ impl Iterator for UpdateCellIterator {
}
match val {
Value::Record { vals, cols, span } => Some(Value::Record {
vals: cols
.iter()
.zip(vals)
Value::Record { val, span } => Some(Value::record(
val.into_iter()
.map(|(col, val)| match &self.columns {
Some(cols) if !cols.contains(col) => val,
_ => process_cell(
val,
&self.engine_state,
&mut self.stack,
&self.block,
self.redirect_stdout,
self.redirect_stderr,
span,
Some(cols) if !cols.contains(&col) => (col, val),
_ => (
col,
process_cell(
val,
&self.engine_state,
&mut self.stack,
&self.block,
self.redirect_stdout,
self.redirect_stderr,
span,
),
),
})
.collect(),
cols,
span,
}),
)),
val => Some(process_cell(
val,
&self.engine_state,

View File

@ -1,6 +1,8 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Span, Type, Value};
use nu_protocol::{
Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
#[derive(Clone)]
pub struct FromUrl;
@ -35,7 +37,7 @@ impl Command for FromUrl {
vec![Example {
example: "'bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter' | from url",
description: "Convert url encoded string into a record",
result: Some(Value::Record {
result: Some(Value::test_record(Record {
cols: vec![
"bread".to_string(),
"cheese".to_string(),
@ -48,8 +50,7 @@ impl Command for FromUrl {
Value::test_string("ham"),
Value::test_string("butter"),
],
span: Span::test_data(),
}),
})),
}]
}
}
@ -61,21 +62,12 @@ fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError>
match result {
Ok(result) => {
let mut cols = vec![];
let mut vals = vec![];
for (k, v) in result {
cols.push(k);
vals.push(Value::String { val: v, span: head })
}
let record = result
.into_iter()
.map(|(k, v)| (k, Value::string(v, head)))
.collect();
Ok(PipelineData::Value(
Value::Record {
cols,
vals,
span: head,
},
metadata,
))
Ok(PipelineData::Value(Value::record(record, head), metadata))
}
_ => Err(ShellError::UnsupportedInput(
"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::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Config, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata,
ShellError, Signature, Spanned, SyntaxShape, Type, Value,
record, Category, Config, DataSource, Example, IntoPipelineData, PipelineData,
PipelineMetadata, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
};
use rust_embed::RustEmbed;
use serde::{Deserialize, Serialize};
@ -258,62 +258,34 @@ fn to_html(
// 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 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
.themes
.into_iter()
.map(|n| {
let vals = vec![
n.name,
n.black,
n.red,
n.green,
n.yellow,
n.blue,
n.purple,
n.cyan,
n.white,
n.brightBlack,
n.brightRed,
n.brightGreen,
n.brightYellow,
n.brightBlue,
n.brightPurple,
n.brightCyan,
n.brightWhite,
n.background,
n.foreground,
]
.into_iter()
.map(|val| Value::String { val, span: head })
.collect();
Value::Record {
cols: cols.clone(),
vals,
span: head,
}
Value::record(
record! {
"name" => Value::string(n.name, head),
"black" => Value::string(n.black, head),
"red" => Value::string(n.red, head),
"green" => Value::string(n.green, head),
"yellow" => Value::string(n.yellow, head),
"blue" => Value::string(n.blue, head),
"purple" => Value::string(n.purple, head),
"cyan" => Value::string(n.cyan, head),
"white" => Value::string(n.white, head),
"brightBlack" => Value::string(n.brightBlack, head),
"brightRed" => Value::string(n.brightRed, head),
"brightGreen" => Value::string(n.brightGreen, head),
"brightYellow" => Value::string(n.brightYellow, head),
"brightBlue" => Value::string(n.brightBlue, head),
"brightPurple" => Value::string(n.brightPurple, head),
"brightCyan" => Value::string(n.brightCyan, head),
"brightWhite" => Value::string(n.brightWhite, head),
"background" => Value::string(n.background, head),
"foreground" => Value::string(n.foreground, head),
},
head,
)
})
.collect();
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::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use super::operate;
@ -74,11 +74,10 @@ impl Command for SubCommand {
description: "convert a column from a table to camelCase",
example: r#"[[lang, gems]; [nu_test, 100]] | str camel-case lang"#,
result: Some(Value::List {
vals: vec![Value::Record {
span: Span::test_data(),
vals: vec![Value::test_record(Record {
cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("nuTest"), Value::test_int(100)],
}],
})],
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::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use super::operate;
@ -74,11 +74,10 @@ impl Command for SubCommand {
description: "convert a column from a table to kebab-case",
example: r#"[[lang, gems]; [nuTest, 100]] | str kebab-case lang"#,
result: Some(Value::List {
vals: vec![Value::Record {
span: Span::test_data(),
vals: vec![Value::test_record(Record {
cols: vec!["lang".to_string(), "gems".to_string()],
vals: vec![Value::test_string("nu-test"), Value::test_int(100)],
}],
})],
span: Span::test_data(),
}),
},

View File

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

View File

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

View File

@ -2,7 +2,7 @@ use inflector::cases::titlecase::to_title_case;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use super::operate;
@ -69,11 +69,10 @@ impl Command for SubCommand {
description: "convert a column from a table to Title Case",
example: r#"[[title, count]; ['nu test', 100]] | str title-case title"#,
result: Some(Value::List {
vals: vec![Value::Record {
span: Span::test_data(),
vals: vec![Value::test_record(Record {
cols: vec!["title".to_string(), "count".to_string()],
vals: vec![Value::test_string("Nu Test"), Value::test_int(100)],
}],
})],
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::engine::{Block, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
record, Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type,
Value,
};
#[derive(Clone)]
@ -91,11 +92,13 @@ impl Command for For {
stack.add_var(
var_id,
if numbered {
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![Value::int(idx as i64, head), x],
span: head,
}
Value::record(
record! {
"index" => Value::int(idx as i64, head),
"item" => x,
},
head,
)
} else {
x
},
@ -135,11 +138,13 @@ impl Command for For {
stack.add_var(
var_id,
if numbered {
Value::Record {
cols: vec!["index".into(), "item".into()],
vals: vec![Value::int(idx as i64, head), x],
span: head,
}
Value::record(
record! {
"index" => Value::int(idx as i64, head),
"item" => x,
},
head,
)
} else {
x
},

View File

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

View File

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

View File

@ -3,7 +3,7 @@ use crate::{
parse_nustyle, NuStyle,
};
use nu_ansi_term::Style;
use nu_protocol::Value;
use nu_protocol::{Record, Value};
use std::collections::HashMap;
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) {
let value = match value {
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,
};
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 style = NuStyle::from(Style::default());
for (col, val) in cols.iter().zip(vals) {
for (col, val) in record {
match col.as_str() {
"bg" => {
if let Value::String { val, .. } = val {
@ -120,48 +120,54 @@ mod tests {
#[test]
fn test_get_style_from_value() {
// Test case 1: all values are valid
let cols = vec!["bg".to_string(), "fg".to_string(), "attr".to_string()];
let vals = vec![
Value::String {
val: "red".to_string(),
span: Span::unknown(),
},
Value::String {
val: "blue".to_string(),
span: Span::unknown(),
},
Value::String {
val: "bold".to_string(),
span: Span::unknown(),
},
];
let record = Record {
cols: vec!["bg".to_string(), "fg".to_string(), "attr".to_string()],
vals: vec![
Value::String {
val: "red".to_string(),
span: Span::unknown(),
},
Value::String {
val: "blue".to_string(),
span: Span::unknown(),
},
Value::String {
val: "bold".to_string(),
span: Span::unknown(),
},
],
};
let expected_style = NuStyle {
bg: Some("red".to_string()),
fg: Some("blue".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
let cols = vec!["invalid".to_string()];
let vals = vec![Value::nothing(Span::unknown())];
assert_eq!(get_style_from_value(&cols, &vals), None);
let record = Record {
cols: vec!["invalid".to_string()],
vals: vec![Value::nothing(Span::unknown())],
};
assert_eq!(get_style_from_value(&record), None);
// Test case 3: some values are valid
let cols = vec!["bg".to_string(), "invalid".to_string()];
let vals = vec![
Value::String {
val: "green".to_string(),
span: Span::unknown(),
},
Value::nothing(Span::unknown()),
];
let record = Record {
cols: vec!["bg".to_string(), "invalid".to_string()],
vals: vec![
Value::String {
val: "green".to_string(),
span: Span::unknown(),
},
Value::nothing(Span::unknown()),
],
};
let expected_style = NuStyle {
bg: Some("green".to_string()),
fg: 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]

View File

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

View File

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

View File

@ -3,7 +3,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
struct Arguments {
@ -112,7 +112,7 @@ impl Command for BytesIndexOf {
description: "Returns index of pattern for specific column",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::test_int(0),
@ -122,8 +122,7 @@ impl Command for BytesIndexOf {
},
Value::test_int(-1),
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

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

View File

@ -3,8 +3,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape,
Type, Value,
};
struct Arguments {
@ -104,7 +104,7 @@ impl Command for BytesReplace {
description: "Find and replace all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC",
result: Some(Value::List {
vals: vec![Value::Record {
vals: vec![Value::test_record(Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
@ -120,8 +120,7 @@ impl Command for BytesReplace {
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
})],
span: Span::test_data(),
}),
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
Category, Example, PipelineData, Record, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
@ -94,18 +94,18 @@ impl Command for UniqBy {
example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit",
result: Some(Value::List {
vals: vec![
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("apple"), Value::test_int(9)],
),
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("pear"), Value::test_int(3)],
),
Value::test_record(
vec!["fruit", "count"],
vec![Value::test_string("orange"), Value::test_int(7)],
),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("apple"), Value::test_int(9)],
}),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("pear"), Value::test_int(3)],
}),
Value::test_record(Record {
cols: vec!["fruit".to_string(), "count".to_string()],
vals: vec![Value::test_string("orange"), Value::test_int(7)],
}),
],
span: Span::test_data(),
}),
@ -115,8 +115,7 @@ impl Command for UniqBy {
fn validate(vec: Vec<Value>, columns: &Vec<String>, span: Span) -> Result<(), ShellError> {
if let Some(Value::Record {
cols,
vals: _input_vals,
val: record,
span: val_span,
}) = vec.first()
{
@ -131,7 +130,7 @@ fn validate(vec: Vec<Value>, columns: &Vec<String>, span: Span) -> Result<(), Sh
));
}
if let Some(nonexistent) = nonexistent_column(columns.clone(), cols.to_vec()) {
if let Some(nonexistent) = nonexistent_column(columns.clone(), record.cols.clone()) {
return Err(ShellError::CantFindColumn {
col_name: nonexistent,
span,

View File

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

View File

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

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