mirror of
https://github.com/nushell/nushell.git
synced 2024-11-23 00:43:33 +01:00
Add insert and update back (#4864)
This commit is contained in:
parent
6e69d40bb9
commit
0986eefb64
@ -76,6 +76,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||||||
Group,
|
Group,
|
||||||
GroupBy,
|
GroupBy,
|
||||||
Headers,
|
Headers,
|
||||||
|
Insert,
|
||||||
SplitBy,
|
SplitBy,
|
||||||
Keep,
|
Keep,
|
||||||
Merge,
|
Merge,
|
||||||
@ -107,6 +108,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||||||
Transpose,
|
Transpose,
|
||||||
Uniq,
|
Uniq,
|
||||||
Upsert,
|
Upsert,
|
||||||
|
Update,
|
||||||
UpdateCells,
|
UpdateCells,
|
||||||
Where,
|
Where,
|
||||||
Window,
|
Window,
|
||||||
@ -356,7 +358,6 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
bind_command! {
|
bind_command! {
|
||||||
InsertDeprecated,
|
|
||||||
PivotDeprecated,
|
PivotDeprecated,
|
||||||
StrDatetimeDeprecated,
|
StrDatetimeDeprecated,
|
||||||
StrDecimalDeprecated,
|
StrDecimalDeprecated,
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
use nu_protocol::{
|
|
||||||
ast::Call,
|
|
||||||
engine::{Command, EngineState, Stack},
|
|
||||||
Category, PipelineData, Signature,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InsertDeprecated;
|
|
||||||
|
|
||||||
impl Command for InsertDeprecated {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"insert"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build(self.name()).category(Category::Deprecated)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"Deprecated command"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
&self,
|
|
||||||
_engine_state: &EngineState,
|
|
||||||
_stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
_input: PipelineData,
|
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
|
||||||
Err(nu_protocol::ShellError::DeprecatedCommand(
|
|
||||||
self.name().to_string(),
|
|
||||||
"update".to_string(),
|
|
||||||
call.head,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
mod insert;
|
|
||||||
mod match_;
|
mod match_;
|
||||||
mod nth;
|
mod nth;
|
||||||
mod pivot;
|
mod pivot;
|
||||||
@ -7,7 +6,6 @@ mod str_decimal;
|
|||||||
mod str_int;
|
mod str_int;
|
||||||
mod unalias;
|
mod unalias;
|
||||||
|
|
||||||
pub use insert::InsertDeprecated;
|
|
||||||
pub use match_::MatchDeprecated;
|
pub use match_::MatchDeprecated;
|
||||||
pub use nth::NthDeprecated;
|
pub use nth::NthDeprecated;
|
||||||
pub use pivot::PivotDeprecated;
|
pub use pivot::PivotDeprecated;
|
||||||
|
149
crates/nu-command/src/filters/insert.rs
Normal file
149
crates/nu-command/src/filters/insert.rs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
use nu_engine::{eval_block, CallExt};
|
||||||
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
|
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
|
SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Insert;
|
||||||
|
|
||||||
|
impl Command for Insert {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"insert"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("insert")
|
||||||
|
.required(
|
||||||
|
"field",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"the name of the column to insert",
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"new value",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the new value to give the cell(s)",
|
||||||
|
)
|
||||||
|
.category(Category::Filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Insert a new column."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
insert(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Insert a new value",
|
||||||
|
example: "echo {'name': 'nu', 'stars': 5} | insert alias 'Nushell'",
|
||||||
|
result: Some(Value::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(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let span = call.head;
|
||||||
|
|
||||||
|
let cell_path: CellPath = call.req(engine_state, stack, 0)?;
|
||||||
|
let replacement: Value = call.req(engine_state, stack, 1)?;
|
||||||
|
|
||||||
|
let redirect_stdout = call.redirect_stdout;
|
||||||
|
let redirect_stderr = call.redirect_stderr;
|
||||||
|
|
||||||
|
let engine_state = engine_state.clone();
|
||||||
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
|
// Replace is a block, so set it up and run it instead of using it as the replacement
|
||||||
|
if replacement.as_block().is_ok() {
|
||||||
|
let capture_block: CaptureBlock = FromValue::from_value(&replacement)?;
|
||||||
|
let block = engine_state.get_block(capture_block.block_id).clone();
|
||||||
|
|
||||||
|
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||||
|
let orig_env_vars = stack.env_vars.clone();
|
||||||
|
let orig_env_hidden = stack.env_hidden.clone();
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |mut input| {
|
||||||
|
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||||
|
|
||||||
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
|
if let Some(var_id) = &var.var_id {
|
||||||
|
stack.add_var(*var_id, input.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = eval_block(
|
||||||
|
&engine_state,
|
||||||
|
&mut stack,
|
||||||
|
&block,
|
||||||
|
input.clone().into_pipeline_data(),
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
);
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(pd) => {
|
||||||
|
if let Err(e) =
|
||||||
|
input.insert_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
||||||
|
{
|
||||||
|
return Value::Error { error: e };
|
||||||
|
}
|
||||||
|
|
||||||
|
input
|
||||||
|
}
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctrlc,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
input.map(
|
||||||
|
move |mut input| {
|
||||||
|
let replacement = replacement.clone();
|
||||||
|
|
||||||
|
if let Err(e) = input.insert_data_at_cell_path(&cell_path.members, replacement) {
|
||||||
|
return Value::Error { error: e };
|
||||||
|
}
|
||||||
|
|
||||||
|
input
|
||||||
|
},
|
||||||
|
ctrlc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Insert {})
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ mod get;
|
|||||||
mod group;
|
mod group;
|
||||||
mod group_by;
|
mod group_by;
|
||||||
mod headers;
|
mod headers;
|
||||||
|
mod insert;
|
||||||
mod keep;
|
mod keep;
|
||||||
mod last;
|
mod last;
|
||||||
mod length;
|
mod length;
|
||||||
@ -38,6 +39,7 @@ mod sort_by;
|
|||||||
mod split_by;
|
mod split_by;
|
||||||
mod transpose;
|
mod transpose;
|
||||||
mod uniq;
|
mod uniq;
|
||||||
|
mod update;
|
||||||
mod update_cells;
|
mod update_cells;
|
||||||
mod upsert;
|
mod upsert;
|
||||||
mod where_;
|
mod where_;
|
||||||
@ -63,6 +65,7 @@ pub use get::Get;
|
|||||||
pub use group::Group;
|
pub use group::Group;
|
||||||
pub use group_by::GroupBy;
|
pub use group_by::GroupBy;
|
||||||
pub use headers::Headers;
|
pub use headers::Headers;
|
||||||
|
pub use insert::Insert;
|
||||||
pub use keep::*;
|
pub use keep::*;
|
||||||
pub use last::Last;
|
pub use last::Last;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
@ -85,6 +88,7 @@ pub use sort_by::SortBy;
|
|||||||
pub use split_by::SplitBy;
|
pub use split_by::SplitBy;
|
||||||
pub use transpose::Transpose;
|
pub use transpose::Transpose;
|
||||||
pub use uniq::*;
|
pub use uniq::*;
|
||||||
|
pub use update::Update;
|
||||||
pub use update_cells::UpdateCells;
|
pub use update_cells::UpdateCells;
|
||||||
pub use upsert::Upsert;
|
pub use upsert::Upsert;
|
||||||
pub use where_::Where;
|
pub use where_::Where;
|
||||||
|
159
crates/nu-command/src/filters/update.rs
Normal file
159
crates/nu-command/src/filters/update.rs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
use nu_engine::{eval_block, CallExt};
|
||||||
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
|
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
|
SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Update;
|
||||||
|
|
||||||
|
impl Command for Update {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"update"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("update")
|
||||||
|
.required(
|
||||||
|
"field",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"the name of the column to update",
|
||||||
|
)
|
||||||
|
.required(
|
||||||
|
"replacement value",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the new value to give the cell(s)",
|
||||||
|
)
|
||||||
|
.category(Category::Filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Update an existing column to have a new value."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
upsert(engine_state, stack, call, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Update a column value",
|
||||||
|
example: "echo {'name': 'nu', 'stars': 5} | update 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(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Use in block form for more involved updating logic",
|
||||||
|
example: "echo [[count fruit]; [1 'apple']] | update count {|f| $f.count + 1}",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["count".into(), "fruit".into()],
|
||||||
|
vals: vec![Value::test_int(2), Value::test_string("apple")],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}],
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upsert(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let span = call.head;
|
||||||
|
|
||||||
|
let cell_path: CellPath = call.req(engine_state, stack, 0)?;
|
||||||
|
let replacement: Value = call.req(engine_state, stack, 1)?;
|
||||||
|
|
||||||
|
let redirect_stdout = call.redirect_stdout;
|
||||||
|
let redirect_stderr = call.redirect_stderr;
|
||||||
|
|
||||||
|
let engine_state = engine_state.clone();
|
||||||
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
|
// Replace is a block, so set it up and run it instead of using it as the replacement
|
||||||
|
if replacement.as_block().is_ok() {
|
||||||
|
let capture_block: CaptureBlock = FromValue::from_value(&replacement)?;
|
||||||
|
let block = engine_state.get_block(capture_block.block_id).clone();
|
||||||
|
|
||||||
|
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||||
|
let orig_env_vars = stack.env_vars.clone();
|
||||||
|
let orig_env_hidden = stack.env_hidden.clone();
|
||||||
|
|
||||||
|
input.map(
|
||||||
|
move |mut input| {
|
||||||
|
stack.with_env(&orig_env_vars, &orig_env_hidden);
|
||||||
|
|
||||||
|
if let Some(var) = block.signature.get_positional(0) {
|
||||||
|
if let Some(var_id) = &var.var_id {
|
||||||
|
stack.add_var(*var_id, input.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = eval_block(
|
||||||
|
&engine_state,
|
||||||
|
&mut stack,
|
||||||
|
&block,
|
||||||
|
input.clone().into_pipeline_data(),
|
||||||
|
redirect_stdout,
|
||||||
|
redirect_stderr,
|
||||||
|
);
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Ok(pd) => {
|
||||||
|
if let Err(e) =
|
||||||
|
input.update_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
||||||
|
{
|
||||||
|
return Value::Error { error: e };
|
||||||
|
}
|
||||||
|
|
||||||
|
input
|
||||||
|
}
|
||||||
|
Err(e) => Value::Error { error: e },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctrlc,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
input.map(
|
||||||
|
move |mut input| {
|
||||||
|
let replacement = replacement.clone();
|
||||||
|
|
||||||
|
if let Err(e) = input.update_data_at_cell_path(&cell_path.members, replacement) {
|
||||||
|
return Value::Error { error: e };
|
||||||
|
}
|
||||||
|
|
||||||
|
input
|
||||||
|
},
|
||||||
|
ctrlc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Update {})
|
||||||
|
}
|
||||||
|
}
|
@ -112,7 +112,7 @@ fn upsert(
|
|||||||
match output {
|
match output {
|
||||||
Ok(pd) => {
|
Ok(pd) => {
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
input.replace_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
input.upsert_data_at_cell_path(&cell_path.members, pd.into_value(span))
|
||||||
{
|
{
|
||||||
return Value::Error { error: e };
|
return Value::Error { error: e };
|
||||||
}
|
}
|
||||||
@ -129,7 +129,7 @@ fn upsert(
|
|||||||
move |mut input| {
|
move |mut input| {
|
||||||
let replacement = replacement.clone();
|
let replacement = replacement.clone();
|
||||||
|
|
||||||
if let Err(e) = input.replace_data_at_cell_path(&cell_path.members, replacement) {
|
if let Err(e) = input.upsert_data_at_cell_path(&cell_path.members, replacement) {
|
||||||
return Value::Error { error: e };
|
return Value::Error { error: e };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
crates/nu-command/tests/commands/insert.rs
Normal file
28
crates/nu-command/tests/commands/insert.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_the_column() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
open cargo_sample.toml
|
||||||
|
| insert dev-dependencies.new_assertions "0.7.0"
|
||||||
|
| get dev-dependencies.new_assertions
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "0.7.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_the_column_conflict() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
open cargo_sample.toml
|
||||||
|
| insert dev-dependencies.pretty_assertions "0.7.0"
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(actual.err.contains("column already exists"));
|
||||||
|
}
|
@ -23,6 +23,7 @@ mod hash_;
|
|||||||
mod headers;
|
mod headers;
|
||||||
mod help;
|
mod help;
|
||||||
mod histogram;
|
mod histogram;
|
||||||
|
mod insert;
|
||||||
mod into_filesize;
|
mod into_filesize;
|
||||||
mod into_int;
|
mod into_int;
|
||||||
mod keep;
|
mod keep;
|
||||||
@ -61,6 +62,7 @@ mod str_;
|
|||||||
mod touch;
|
mod touch;
|
||||||
mod uniq;
|
mod uniq;
|
||||||
mod update;
|
mod update;
|
||||||
|
mod upsert;
|
||||||
mod use_;
|
mod use_;
|
||||||
mod where_;
|
mod where_;
|
||||||
#[cfg(feature = "which")]
|
#[cfg(feature = "which")]
|
||||||
|
@ -6,7 +6,7 @@ fn sets_the_column() {
|
|||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
open cargo_sample.toml
|
open cargo_sample.toml
|
||||||
| upsert dev-dependencies.pretty_assertions "0.7.0"
|
| update dev-dependencies.pretty_assertions "0.7.0"
|
||||||
| get dev-dependencies.pretty_assertions
|
| get dev-dependencies.pretty_assertions
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
@ -21,7 +21,7 @@ fn sets_the_column_from_a_block_run_output() {
|
|||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
open cargo_sample.toml
|
open cargo_sample.toml
|
||||||
| upsert dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor }
|
| update dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor }
|
||||||
| get dev-dependencies.pretty_assertions
|
| get dev-dependencies.pretty_assertions
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
@ -35,7 +35,7 @@ fn sets_the_column_from_a_block_full_stream_output() {
|
|||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
wrap content
|
wrap content
|
||||||
| upsert content { open --raw cargo_sample.toml | lines | first 5 }
|
| update content { open --raw cargo_sample.toml | lines | first 5 }
|
||||||
| get content.1
|
| get content.1
|
||||||
| str contains "nu"
|
| str contains "nu"
|
||||||
"#
|
"#
|
||||||
@ -50,7 +50,7 @@ fn sets_the_column_from_a_subexpression() {
|
|||||||
cwd: "tests/fixtures/formats", pipeline(
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
r#"
|
r#"
|
||||||
wrap content
|
wrap content
|
||||||
| upsert content (open --raw cargo_sample.toml | lines | first 5)
|
| update content (open --raw cargo_sample.toml | lines | first 5)
|
||||||
| get content.1
|
| get content.1
|
||||||
| str contains "nu"
|
| str contains "nu"
|
||||||
"#
|
"#
|
||||||
@ -58,3 +58,16 @@ fn sets_the_column_from_a_subexpression() {
|
|||||||
|
|
||||||
assert_eq!(actual.out, "true");
|
assert_eq!(actual.out, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn upsert_column_missing() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
open cargo_sample.toml
|
||||||
|
| update dev-dependencies.new_assertions "0.7.0"
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(actual.err.contains("cannot find column"));
|
||||||
|
}
|
||||||
|
60
crates/nu-command/tests/commands/upsert.rs
Normal file
60
crates/nu-command/tests/commands/upsert.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_the_column() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
open cargo_sample.toml
|
||||||
|
| upsert dev-dependencies.pretty_assertions "0.7.0"
|
||||||
|
| get dev-dependencies.pretty_assertions
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "0.7.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(features = "inc")]
|
||||||
|
#[test]
|
||||||
|
fn sets_the_column_from_a_block_run_output() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
open cargo_sample.toml
|
||||||
|
| upsert dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor }
|
||||||
|
| get dev-dependencies.pretty_assertions
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "0.7.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_the_column_from_a_block_full_stream_output() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
wrap content
|
||||||
|
| upsert content { open --raw cargo_sample.toml | lines | first 5 }
|
||||||
|
| get content.1
|
||||||
|
| str contains "nu"
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_the_column_from_a_subexpression() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
wrap content
|
||||||
|
| upsert content (open --raw cargo_sample.toml | lines | first 5)
|
||||||
|
| get content.1
|
||||||
|
| str contains "nu"
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
}
|
@ -226,7 +226,7 @@ impl PipelineData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_cell_path(
|
pub fn upsert_cell_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||||
@ -238,8 +238,8 @@ impl PipelineData {
|
|||||||
vals: stream.collect(),
|
vals: stream.collect(),
|
||||||
span: head,
|
span: head,
|
||||||
}
|
}
|
||||||
.update_cell_path(cell_path, callback),
|
.upsert_cell_path(cell_path, callback),
|
||||||
PipelineData::Value(v, ..) => v.update_cell_path(cell_path, callback),
|
PipelineData::Value(v, ..) => v.upsert_cell_path(cell_path, callback),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,13 @@ Either make sure {0} is a string, or add a 'to_string' entry for it in ENV_CONVE
|
|||||||
#[label = "value originates here"] Span,
|
#[label = "value originates here"] Span,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
#[error("Column already exists")]
|
||||||
|
#[diagnostic(code(nu::shell::column_already_exists), url(docsrs))]
|
||||||
|
ColumnAlreadyExists(
|
||||||
|
#[label = "column already exists"] Span,
|
||||||
|
#[label = "value originates here"] Span,
|
||||||
|
),
|
||||||
|
|
||||||
#[error("Not a list value")]
|
#[error("Not a list value")]
|
||||||
#[diagnostic(code(nu::shell::not_a_list), url(docsrs))]
|
#[diagnostic(code(nu::shell::not_a_list), url(docsrs))]
|
||||||
NotAList(
|
NotAList(
|
||||||
|
@ -682,7 +682,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Follow a given column path into the value: for example accessing select elements in a stream or list
|
/// Follow a given column path into the value: for example accessing select elements in a stream or list
|
||||||
pub fn update_cell_path(
|
pub fn upsert_cell_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
callback: Box<dyn FnOnce(&Value) -> Value>,
|
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||||
@ -693,11 +693,11 @@ impl Value {
|
|||||||
|
|
||||||
match new_val {
|
match new_val {
|
||||||
Value::Error { error } => Err(error),
|
Value::Error { error } => Err(error),
|
||||||
new_val => self.replace_data_at_cell_path(cell_path, new_val),
|
new_val => self.upsert_data_at_cell_path(cell_path, new_val),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace_data_at_cell_path(
|
pub fn upsert_data_at_cell_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
cell_path: &[PathMember],
|
cell_path: &[PathMember],
|
||||||
new_val: Value,
|
new_val: Value,
|
||||||
@ -716,7 +716,7 @@ impl Value {
|
|||||||
for col in cols.iter().zip(vals.iter_mut()) {
|
for col in cols.iter().zip(vals.iter_mut()) {
|
||||||
if col.0 == col_name {
|
if col.0 == col_name {
|
||||||
found = true;
|
found = true;
|
||||||
col.1.replace_data_at_cell_path(
|
col.1.upsert_data_at_cell_path(
|
||||||
&cell_path[1..],
|
&cell_path[1..],
|
||||||
new_val.clone(),
|
new_val.clone(),
|
||||||
)?
|
)?
|
||||||
@ -733,7 +733,7 @@ impl Value {
|
|||||||
vals: vec![],
|
vals: vec![],
|
||||||
span: new_val.span()?,
|
span: new_val.span()?,
|
||||||
};
|
};
|
||||||
new_col.replace_data_at_cell_path(
|
new_col.upsert_data_at_cell_path(
|
||||||
&cell_path[1..],
|
&cell_path[1..],
|
||||||
new_val,
|
new_val,
|
||||||
)?;
|
)?;
|
||||||
@ -754,7 +754,7 @@ impl Value {
|
|||||||
found = true;
|
found = true;
|
||||||
|
|
||||||
col.1
|
col.1
|
||||||
.replace_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
.upsert_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
@ -767,7 +767,7 @@ impl Value {
|
|||||||
vals: vec![],
|
vals: vec![],
|
||||||
span: new_val.span()?,
|
span: new_val.span()?,
|
||||||
};
|
};
|
||||||
new_col.replace_data_at_cell_path(&cell_path[1..], new_val)?;
|
new_col.upsert_data_at_cell_path(&cell_path[1..], new_val)?;
|
||||||
vals.push(new_col);
|
vals.push(new_col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -777,7 +777,190 @@ impl Value {
|
|||||||
PathMember::Int { val: row_num, span } => match self {
|
PathMember::Int { val: row_num, span } => match self {
|
||||||
Value::List { vals, .. } => {
|
Value::List { vals, .. } => {
|
||||||
if let Some(v) = vals.get_mut(*row_num) {
|
if let Some(v) = vals.get_mut(*row_num) {
|
||||||
v.replace_data_at_cell_path(&cell_path[1..], new_val)?
|
v.upsert_data_at_cell_path(&cell_path[1..], new_val)?
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::AccessBeyondEnd(vals.len(), *span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v => return Err(ShellError::NotAList(*span, v.span()?)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
*self = new_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Follow a given column path into the value: for example accessing select elements in a stream or list
|
||||||
|
pub fn update_cell_path(
|
||||||
|
&mut self,
|
||||||
|
cell_path: &[PathMember],
|
||||||
|
callback: Box<dyn FnOnce(&Value) -> Value>,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
let orig = self.clone();
|
||||||
|
|
||||||
|
let new_val = callback(&orig.follow_cell_path(cell_path)?);
|
||||||
|
|
||||||
|
match new_val {
|
||||||
|
Value::Error { error } => Err(error),
|
||||||
|
new_val => self.update_data_at_cell_path(cell_path, new_val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_data_at_cell_path(
|
||||||
|
&mut self,
|
||||||
|
cell_path: &[PathMember],
|
||||||
|
new_val: Value,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
match cell_path.first() {
|
||||||
|
Some(path_member) => match path_member {
|
||||||
|
PathMember::String {
|
||||||
|
val: col_name,
|
||||||
|
span,
|
||||||
|
} => match self {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
for val in vals.iter_mut() {
|
||||||
|
match val {
|
||||||
|
Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: v_span,
|
||||||
|
} => {
|
||||||
|
let mut found = false;
|
||||||
|
for col in cols.iter().zip(vals.iter_mut()) {
|
||||||
|
if col.0 == col_name {
|
||||||
|
found = true;
|
||||||
|
col.1.update_data_at_cell_path(
|
||||||
|
&cell_path[1..],
|
||||||
|
new_val.clone(),
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return Err(ShellError::CantFindColumn(*span, *v_span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v => return Err(ShellError::CantFindColumn(*span, v.span()?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: v_span,
|
||||||
|
} => {
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
for col in cols.iter().zip(vals.iter_mut()) {
|
||||||
|
if col.0 == col_name {
|
||||||
|
found = true;
|
||||||
|
|
||||||
|
col.1
|
||||||
|
.update_data_at_cell_path(&cell_path[1..], new_val.clone())?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return Err(ShellError::CantFindColumn(*span, *v_span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v => return Err(ShellError::CantFindColumn(*span, v.span()?)),
|
||||||
|
},
|
||||||
|
PathMember::Int { val: row_num, span } => match self {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
if let Some(v) = vals.get_mut(*row_num) {
|
||||||
|
v.update_data_at_cell_path(&cell_path[1..], new_val)?
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::AccessBeyondEnd(vals.len(), *span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v => return Err(ShellError::NotAList(*span, v.span()?)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
*self = new_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_data_at_cell_path(
|
||||||
|
&mut self,
|
||||||
|
cell_path: &[PathMember],
|
||||||
|
new_val: Value,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
match cell_path.first() {
|
||||||
|
Some(path_member) => match path_member {
|
||||||
|
PathMember::String {
|
||||||
|
val: col_name,
|
||||||
|
span,
|
||||||
|
} => match self {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
for val in vals.iter_mut() {
|
||||||
|
match val {
|
||||||
|
Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: v_span,
|
||||||
|
} => {
|
||||||
|
for col in cols.iter().zip(vals.iter_mut()) {
|
||||||
|
if col.0 == col_name {
|
||||||
|
if cell_path.len() == 1 {
|
||||||
|
return Err(ShellError::ColumnAlreadyExists(
|
||||||
|
*span, *v_span,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return col.1.insert_data_at_cell_path(
|
||||||
|
&cell_path[1..],
|
||||||
|
new_val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cols.push(col_name.clone());
|
||||||
|
vals.push(new_val.clone());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"table or record".into(),
|
||||||
|
*span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Record {
|
||||||
|
cols,
|
||||||
|
vals,
|
||||||
|
span: v_span,
|
||||||
|
} => {
|
||||||
|
for col in cols.iter().zip(vals.iter_mut()) {
|
||||||
|
if col.0 == col_name {
|
||||||
|
if cell_path.len() == 1 {
|
||||||
|
return Err(ShellError::ColumnAlreadyExists(*span, *v_span));
|
||||||
|
} else {
|
||||||
|
return col
|
||||||
|
.1
|
||||||
|
.insert_data_at_cell_path(&cell_path[1..], new_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cols.push(col_name.clone());
|
||||||
|
vals.push(new_val);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::UnsupportedInput(
|
||||||
|
"table or record".into(),
|
||||||
|
*span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PathMember::Int { val: row_num, span } => match self {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
if let Some(v) = vals.get_mut(*row_num) {
|
||||||
|
v.insert_data_at_cell_path(&cell_path[1..], new_val)?
|
||||||
} else {
|
} else {
|
||||||
return Err(ShellError::AccessBeyondEnd(vals.len(), *span));
|
return Err(ShellError::AccessBeyondEnd(vals.len(), *span));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user