mirror of
https://github.com/nushell/nushell
synced 2025-01-12 13:19:01 +00:00
Table operating commands. (#1686)
* Table operating commands. * Updated merge test for clarity. * More clarity. * Better like this..
This commit is contained in:
parent
d834708be8
commit
cf53264438
17 changed files with 854 additions and 30 deletions
|
@ -297,20 +297,25 @@ pub fn create_default_context(
|
|||
whole_stream_command(GroupBy),
|
||||
whole_stream_command(First),
|
||||
whole_stream_command(Last),
|
||||
whole_stream_command(Skip),
|
||||
whole_stream_command(Nth),
|
||||
whole_stream_command(Drop),
|
||||
whole_stream_command(Format),
|
||||
whole_stream_command(Where),
|
||||
whole_stream_command(Compact),
|
||||
whole_stream_command(Default),
|
||||
whole_stream_command(Skip),
|
||||
whole_stream_command(SkipUntil),
|
||||
whole_stream_command(SkipWhile),
|
||||
whole_stream_command(Keep),
|
||||
whole_stream_command(KeepUntil),
|
||||
whole_stream_command(KeepWhile),
|
||||
whole_stream_command(Range),
|
||||
whole_stream_command(Rename),
|
||||
whole_stream_command(Uniq),
|
||||
whole_stream_command(Each),
|
||||
whole_stream_command(IsEmpty),
|
||||
// Table manipulation
|
||||
whole_stream_command(Merge),
|
||||
whole_stream_command(Shuffle),
|
||||
whole_stream_command(Wrap),
|
||||
whole_stream_command(Pivot),
|
||||
|
|
|
@ -55,11 +55,15 @@ pub(crate) mod histogram;
|
|||
pub(crate) mod history;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod is_empty;
|
||||
pub(crate) mod keep;
|
||||
pub(crate) mod keep_until;
|
||||
pub(crate) mod keep_while;
|
||||
pub(crate) mod last;
|
||||
pub(crate) mod lines;
|
||||
pub(crate) mod ls;
|
||||
#[allow(unused)]
|
||||
pub(crate) mod map_max_by;
|
||||
pub(crate) mod merge;
|
||||
pub(crate) mod mkdir;
|
||||
pub(crate) mod mv;
|
||||
pub(crate) mod next;
|
||||
|
@ -86,6 +90,7 @@ pub(crate) mod shells;
|
|||
pub(crate) mod shuffle;
|
||||
pub(crate) mod size;
|
||||
pub(crate) mod skip;
|
||||
pub(crate) mod skip_until;
|
||||
pub(crate) mod skip_while;
|
||||
pub(crate) mod sort_by;
|
||||
pub(crate) mod split_by;
|
||||
|
@ -170,11 +175,15 @@ pub(crate) use help::Help;
|
|||
pub(crate) use histogram::Histogram;
|
||||
pub(crate) use history::History;
|
||||
pub(crate) use insert::Insert;
|
||||
pub(crate) use keep::Keep;
|
||||
pub(crate) use keep_until::KeepUntil;
|
||||
pub(crate) use keep_while::KeepWhile;
|
||||
pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use map_max_by::MapMaxBy;
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
pub(crate) use mv::Move;
|
||||
pub(crate) use next::Next;
|
||||
|
@ -199,6 +208,7 @@ pub(crate) use shells::Shells;
|
|||
pub(crate) use shuffle::Shuffle;
|
||||
pub(crate) use size::Size;
|
||||
pub(crate) use skip::Skip;
|
||||
pub(crate) use skip_until::SkipUntil;
|
||||
pub(crate) use skip_while::SkipWhile;
|
||||
pub(crate) use sort_by::SortBy;
|
||||
pub(crate) use split_by::SplitBy;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
|
@ -5,6 +6,7 @@ use nu_errors::ShellError;
|
|||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use futures::stream::once;
|
||||
pub struct Edit;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -41,41 +43,104 @@ impl WholeStreamCommand for Edit {
|
|||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, edit)?.run()
|
||||
Ok(args.process_raw(registry, edit)?.run())
|
||||
}
|
||||
}
|
||||
|
||||
fn edit(
|
||||
EditArgs { field, replacement }: EditArgs,
|
||||
RunnableContext { input, .. }: RunnableContext,
|
||||
context: RunnableContext,
|
||||
raw_args: RawCommandArgs,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let mut input = input;
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let registry = context.registry.clone();
|
||||
let mut input_stream = context.input;
|
||||
|
||||
let stream = async_stream! {
|
||||
match input.next().await {
|
||||
Some(obj @ Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
}) => match obj.replace_data_at_column_path(&field, replacement.clone()) {
|
||||
Some(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"edit could not find place to insert column",
|
||||
"column name",
|
||||
obj.tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
while let Some(input) = input_stream.next().await {
|
||||
let replacement = replacement.clone();
|
||||
match replacement {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let for_block = input.clone();
|
||||
let input_clone = input.clone();
|
||||
let input_stream = once(async { Ok(for_block) }).to_input_stream();
|
||||
|
||||
Some(Value { tag, ..}) => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input_stream,
|
||||
&scope.clone().set_it(input_clone),
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let errors = context.get_errors();
|
||||
if let Some(error) = errors.first() {
|
||||
yield Err(error.clone());
|
||||
}
|
||||
|
||||
match input {
|
||||
obj @ Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => {
|
||||
if let Some(result) = stream.next().await {
|
||||
match obj.replace_data_at_column_path(&field, result.clone()) {
|
||||
Some(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"edit could not find place to insert column",
|
||||
"column name",
|
||||
obj.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, ..} => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {
|
||||
match input {
|
||||
obj @ Value {
|
||||
value: UntaggedValue::Row(_),
|
||||
..
|
||||
} => match obj.replace_data_at_column_path(&field, replacement.clone()) {
|
||||
Some(v) => yield Ok(ReturnSuccess::Value(v)),
|
||||
None => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"edit could not find place to insert column",
|
||||
"column name",
|
||||
obj.tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
Value { tag, ..} => {
|
||||
yield Err(ShellError::labeled_error(
|
||||
"Unrecognized type in stream",
|
||||
"original value",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
|
|
49
crates/nu-cli/src/commands/keep.rs
Normal file
49
crates/nu-cli/src/commands/keep.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub struct Keep;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct KeepArgs {
|
||||
rows: Option<Tagged<usize>>,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Keep {
|
||||
fn name(&self) -> &str {
|
||||
"keep"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep").optional(
|
||||
"rows",
|
||||
SyntaxShape::Int,
|
||||
"starting from the front, the number of rows to keep",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keep the number of rows only"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, keep)?.run()
|
||||
}
|
||||
}
|
||||
|
||||
fn keep(KeepArgs { rows }: KeepArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
let rows_desired = if let Some(quantity) = rows {
|
||||
*quantity
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
Ok(OutputStream::from_input(context.input.take(rows_desired)))
|
||||
}
|
98
crates/nu-cli/src/commands/keep_until.rs
Normal file
98
crates/nu-cli/src/commands/keep_until.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct KeepUntil;
|
||||
|
||||
impl WholeStreamCommand for KeepUntil {
|
||||
fn name(&self) -> &str {
|
||||
"keep-until"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep-until")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to stop keeping rows",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keeps rows until the condition matches."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = args.call_info.scope.clone();
|
||||
let call_info = args.evaluate_once(®istry)?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let objects = call_info.input.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &scope.clone().set_it(item.clone()));
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
futures::future::ready(return_value)
|
||||
});
|
||||
|
||||
Ok(objects.from_input_stream())
|
||||
}
|
||||
}
|
98
crates/nu-cli/src/commands/keep_while.rs
Normal file
98
crates/nu-cli/src/commands/keep_while.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct KeepWhile;
|
||||
|
||||
impl WholeStreamCommand for KeepWhile {
|
||||
fn name(&self) -> &str {
|
||||
"keep-while"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("keep-while")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to keep rows",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Keeps rows while the condition matches."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = args.call_info.scope.clone();
|
||||
let call_info = args.evaluate_once(®istry)?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let objects = call_info.input.take_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &scope.clone().set_it(item.clone()));
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
futures::future::ready(return_value)
|
||||
});
|
||||
|
||||
Ok(objects.from_input_stream())
|
||||
}
|
||||
}
|
95
crates/nu-cli/src/commands/merge.rs
Normal file
95
crates/nu-cli/src/commands/merge.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::value::merge_values;
|
||||
use crate::prelude::*;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
pub struct Merge;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MergeArgs {
|
||||
block: Block,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for Merge {
|
||||
fn name(&self) -> &str {
|
||||
"merge"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("merge").required(
|
||||
"block",
|
||||
SyntaxShape::Block,
|
||||
"the block to run and merge into the table",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Merge a table."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok(args.process_raw(registry, merge)?.run())
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(
|
||||
merge_args: MergeArgs,
|
||||
context: RunnableContext,
|
||||
raw_args: RawCommandArgs,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let block = merge_args.block;
|
||||
let registry = context.registry.clone();
|
||||
let mut input = context.input;
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
|
||||
let stream = async_stream! {
|
||||
let table: Option<Vec<Value>> = match run_block(&block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&Scope::empty()).await {
|
||||
Ok(mut stream) => Some(stream.drain_vec().await),
|
||||
Err(err) => {
|
||||
yield Err(err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let table = table.unwrap_or_else(|| vec![Value {
|
||||
value: UntaggedValue::row(IndexMap::default()),
|
||||
tag: raw_args.call_info.name_tag,
|
||||
}]);
|
||||
|
||||
let mut idx = 0;
|
||||
|
||||
while let Some(value) = input.next().await {
|
||||
let other = table.get(idx);
|
||||
|
||||
match other {
|
||||
Some(replacement) => {
|
||||
match merge_values(&value.value, &replacement.value) {
|
||||
Ok(merged_value) => yield ReturnSuccess::value(merged_value.into_value(&value.tag)),
|
||||
Err(err) => {
|
||||
let message = format!("The row at {:?} types mismatch", idx);
|
||||
yield Err(ShellError::labeled_error("Could not merge", &message, &value.tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => yield ReturnSuccess::value(value),
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
98
crates/nu-cli/src/commands/skip_until.rs
Normal file
98
crates/nu-cli/src/commands/skip_until.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use crate::commands::WholeStreamCommand;
|
||||
use crate::evaluate::evaluate_baseline_expr;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct SkipUntil;
|
||||
|
||||
impl WholeStreamCommand for SkipUntil {
|
||||
fn name(&self) -> &str {
|
||||
"skip-until"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("skip-until")
|
||||
.required(
|
||||
"condition",
|
||||
SyntaxShape::Math,
|
||||
"the condition that must be met to stop skipping",
|
||||
)
|
||||
.filter()
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Skips rows until the condition matches."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let scope = args.call_info.scope.clone();
|
||||
let call_info = args.evaluate_once(®istry)?;
|
||||
|
||||
let block = call_info.args.expect_nth(0)?.clone();
|
||||
|
||||
let condition = match block {
|
||||
Value {
|
||||
value: UntaggedValue::Block(block),
|
||||
tag,
|
||||
} => {
|
||||
if block.block.len() != 1 {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
match block.block[0].list.get(0) {
|
||||
Some(item) => match item {
|
||||
ClassifiedCommand::Expr(expr) => expr.clone(),
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Expected a condition",
|
||||
"expected a condition",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let objects = call_info.input.skip_while(move |item| {
|
||||
let condition = condition.clone();
|
||||
trace!("ITEM = {:?}", item);
|
||||
let result =
|
||||
evaluate_baseline_expr(&*condition, ®istry, &scope.clone().set_it(item.clone()));
|
||||
trace!("RESULT = {:?}", result);
|
||||
|
||||
let return_value = match result {
|
||||
Ok(ref v) if v.is_true() => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
futures::future::ready(return_value)
|
||||
});
|
||||
|
||||
Ok(objects.from_input_stream())
|
||||
}
|
||||
}
|
|
@ -22,6 +22,18 @@ pub fn date_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
|
|||
Ok(UntaggedValue::Primitive(Primitive::Date(date)))
|
||||
}
|
||||
|
||||
pub fn merge_values(
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||
match (left, right) {
|
||||
(UntaggedValue::Row(columns), UntaggedValue::Row(columns_b)) => {
|
||||
Ok(UntaggedValue::Row(columns.merge_from(columns_b)))
|
||||
}
|
||||
(left, right) => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_values(
|
||||
operator: Operator,
|
||||
left: &UntaggedValue,
|
||||
|
@ -153,3 +165,31 @@ pub fn format_for_column<'a>(
|
|||
.format_for_column(column)
|
||||
.pretty()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::UntaggedValue as v;
|
||||
use indexmap::indexmap;
|
||||
|
||||
use super::merge_values;
|
||||
|
||||
#[test]
|
||||
fn merges_tables() {
|
||||
let table_author_row = v::row(indexmap! {
|
||||
"name".into() => v::string("Andrés").into_untagged_value(),
|
||||
"country".into() => v::string("EC").into_untagged_value(),
|
||||
"date".into() => v::string("April 29-2020").into_untagged_value()
|
||||
});
|
||||
|
||||
let other_table_author_row = v::row(indexmap! {
|
||||
"name".into() => v::string("YK").into_untagged_value(),
|
||||
"country".into() => v::string("US").into_untagged_value(),
|
||||
"date".into() => v::string("October 10-2019").into_untagged_value()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
other_table_author_row,
|
||||
merge_values(&table_author_row, &other_table_author_row).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,31 @@
|
|||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn creates_a_new_table_with_the_new_row_given() {
|
||||
fn sets_the_column() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| edit dev-dependencies.pretty_assertions "7"
|
||||
| edit dev-dependencies.pretty_assertions "0.7.0"
|
||||
| get dev-dependencies.pretty_assertions
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "7");
|
||||
assert_eq!(actual, "0.7.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets_the_column_from_a_block_run_output() {
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
open cargo_sample.toml
|
||||
| edit dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor }
|
||||
| get dev-dependencies.pretty_assertions
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "0.7.0");
|
||||
}
|
||||
|
|
32
crates/nu-cli/tests/commands/keep.rs
Normal file
32
crates/nu-cli/tests/commands/keep.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn rows() {
|
||||
Playground::setup("keep_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"caballeros.csv",
|
||||
r#"
|
||||
name,lucky_code
|
||||
Andrés,1
|
||||
Jonathan,1
|
||||
Jason,2
|
||||
Yehuda,1
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open caballeros.csv
|
||||
| keep 3
|
||||
| get lucky_code
|
||||
| sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "4");
|
||||
})
|
||||
}
|
51
crates/nu-cli/tests/commands/keep_until.rs
Normal file
51
crates/nu-cli/tests/commands/keep_until.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn condition_is_met() {
|
||||
Playground::setup("keep-until_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"caballeros.txt",
|
||||
r#"
|
||||
CHICKEN SUMMARY report date: April 29th, 2020
|
||||
--------------------------------------------------------------------
|
||||
Chicken Collection,29/04/2020,30/04/2020,31/04/2020,
|
||||
Yellow Chickens,,,
|
||||
Andrés,1,1,1
|
||||
Jonathan,1,1,1
|
||||
Jason,1,1,1
|
||||
Yehuda,1,1,1
|
||||
Blue Chickens,,,
|
||||
Andrés,1,1,2
|
||||
Jonathan,1,1,2
|
||||
Jason,1,1,2
|
||||
Yehuda,1,1,2
|
||||
Red Chickens,,,
|
||||
Andrés,1,1,3
|
||||
Jonathan,1,1,3
|
||||
Jason,1,1,3
|
||||
Yehuda,1,1,3
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open --raw caballeros.txt
|
||||
| lines
|
||||
| skip 2
|
||||
| split-column ','
|
||||
| headers
|
||||
| skip-while "Chickens Collction" != "Blue Chickens"
|
||||
| keep-until "Chicken Collection" == "Red Chickens"
|
||||
| str "31/04/2020" --to-int
|
||||
| get "31/04/2020"
|
||||
| sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "8");
|
||||
})
|
||||
}
|
51
crates/nu-cli/tests/commands/keep_while.rs
Normal file
51
crates/nu-cli/tests/commands/keep_while.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn condition_is_met() {
|
||||
Playground::setup("keep-while_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"caballeros.txt",
|
||||
r#"
|
||||
CHICKEN SUMMARY report date: April 29th, 2020
|
||||
--------------------------------------------------------------------
|
||||
Chicken Collection,29/04/2020,30/04/2020,31/04/2020,
|
||||
Yellow Chickens,,,
|
||||
Andrés,1,1,1
|
||||
Jonathan,1,1,1
|
||||
Jason,1,1,1
|
||||
Yehuda,1,1,1
|
||||
Blue Chickens,,,
|
||||
Andrés,1,1,2
|
||||
Jonathan,1,1,2
|
||||
Jason,1,1,2
|
||||
Yehuda,1,1,2
|
||||
Red Chickens,,,
|
||||
Andrés,1,1,3
|
||||
Jonathan,1,1,3
|
||||
Jason,1,1,3
|
||||
Yehuda,1,1,3
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open --raw caballeros.txt
|
||||
| lines
|
||||
| skip 2
|
||||
| split-column ','
|
||||
| headers
|
||||
| skip 1
|
||||
| keep-while "Chicken Collection" != "Blue Chickens"
|
||||
| str "31/04/2020" --to-int
|
||||
| get "31/04/2020"
|
||||
| sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "4");
|
||||
})
|
||||
}
|
43
crates/nu-cli/tests/commands/merge.rs
Normal file
43
crates/nu-cli/tests/commands/merge.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn row() {
|
||||
Playground::setup("merge_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
FileWithContentToBeTrimmed(
|
||||
"caballeros.csv",
|
||||
r#"
|
||||
name,country,luck
|
||||
Andrés,Ecuador,0
|
||||
Jonathan,USA,0
|
||||
Jason,Canada,0
|
||||
Yehuda,USA,0
|
||||
"#,
|
||||
),
|
||||
FileWithContentToBeTrimmed(
|
||||
"new_caballeros.csv",
|
||||
r#"
|
||||
name,country,luck
|
||||
Andrés Robalino,Guayaquil Ecuador,1
|
||||
Jonathan Turner,New Zealand,1
|
||||
"#,
|
||||
),
|
||||
]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open caballeros.csv
|
||||
| merge { open new_caballeros.csv }
|
||||
| where country in: ["Guayaquil Ecuador" "New Zealand"]
|
||||
| get luck
|
||||
| sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "2");
|
||||
})
|
||||
}
|
|
@ -17,10 +17,14 @@ mod headers;
|
|||
mod histogram;
|
||||
mod insert;
|
||||
mod is_empty;
|
||||
mod keep;
|
||||
mod keep_until;
|
||||
mod keep_while;
|
||||
mod last;
|
||||
mod lines;
|
||||
mod ls;
|
||||
mod math;
|
||||
mod merge;
|
||||
mod mkdir;
|
||||
mod mv;
|
||||
mod open;
|
||||
|
@ -33,6 +37,7 @@ mod reverse;
|
|||
mod rm;
|
||||
mod save;
|
||||
mod semicolon;
|
||||
mod skip_until;
|
||||
mod sort_by;
|
||||
mod split_by;
|
||||
mod split_column;
|
||||
|
|
50
crates/nu-cli/tests/commands/skip_until.rs
Normal file
50
crates/nu-cli/tests/commands/skip_until.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
#[test]
|
||||
fn condition_is_met() {
|
||||
Playground::setup("skip-until_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContentToBeTrimmed(
|
||||
"caballeros.txt",
|
||||
r#"
|
||||
CHICKEN SUMMARY report date: April 29th, 2020
|
||||
--------------------------------------------------------------------
|
||||
Chicken Collection,29/04/2020,30/04/2020,31/04/2020,
|
||||
Yellow Chickens,,,
|
||||
Andrés,1,1,1
|
||||
Jonathan,1,1,1
|
||||
Jason,1,1,1
|
||||
Yehuda,1,1,1
|
||||
Blue Chickens,,,
|
||||
Andrés,1,1,2
|
||||
Jonathan,1,1,2
|
||||
Jason,1,1,2
|
||||
Yehuda,1,1,2
|
||||
Red Chickens,,,
|
||||
Andrés,1,1,3
|
||||
Jonathan,1,1,3
|
||||
Jason,1,1,3
|
||||
Yehuda,1,1,3
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test(), pipeline(
|
||||
r#"
|
||||
open --raw caballeros.txt
|
||||
| lines
|
||||
| skip 2
|
||||
| split-column ','
|
||||
| headers
|
||||
| skip-until "Chicken Collection" != "Red Chickens"
|
||||
| str "31/04/2020" --to-int
|
||||
| get "31/04/2020"
|
||||
| sum
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
|
||||
assert_eq!(actual, "12");
|
||||
})
|
||||
}
|
|
@ -4,7 +4,7 @@ use crate::value::{UntaggedValue, Value};
|
|||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use indexmap::IndexMap;
|
||||
use nu_source::{b, DebugDocBuilder, PrettyDebug, Spanned, Tag};
|
||||
use nu_source::{b, DebugDocBuilder, PrettyDebug, Spanned, SpannedItem, Tag};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::{Ord, Ordering, PartialOrd};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -125,6 +125,25 @@ impl Dictionary {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn merge_from(&self, other: &Dictionary) -> Dictionary {
|
||||
let mut obj = self.clone();
|
||||
|
||||
for column in other.keys() {
|
||||
let key = column.clone();
|
||||
let value_key = key.as_str();
|
||||
let value_spanned_key = value_key.spanned_unknown();
|
||||
|
||||
let other_column = match other.get_data_by_key(value_spanned_key) {
|
||||
Some(value) => value,
|
||||
None => UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(),
|
||||
};
|
||||
|
||||
obj.entries.insert(key, other_column);
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
/// Iterate the keys in the Dictionary
|
||||
pub fn keys(&self) -> impl Iterator<Item = &String> {
|
||||
self.entries.keys()
|
||||
|
|
Loading…
Reference in a new issue