diff --git a/crates/nu-command/src/filters/default.rs b/crates/nu-command/src/filters/default.rs index cc6345883e..8f78323c0c 100644 --- a/crates/nu-command/src/filters/default.rs +++ b/crates/nu-command/src/filters/default.rs @@ -1,7 +1,7 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, Example, PipelineData, Signature, Spanned, SyntaxShape, Value}; +use nu_protocol::{Category, Example, PipelineData, Signature, Span, Spanned, SyntaxShape, Value}; #[derive(Clone)] pub struct Default; @@ -13,12 +13,12 @@ impl Command for Default { fn signature(&self) -> Signature { Signature::build("default") - .required("column name", SyntaxShape::String, "the name of the column") .required( - "column value", + "default value", SyntaxShape::Any, - "the value of the column to default", + "the value to use as a default", ) + .optional("column name", SyntaxShape::String, "the name of the column") .category(Category::Filters) } @@ -37,11 +37,26 @@ impl Command for Default { } fn examples(&self) -> Vec { - vec![Example { - description: "Give a default 'target' to all file entries", - example: "ls -la | default target 'nothing'", - result: None, - }] + vec![ + Example { + description: "Give a default 'target' column to all file entries", + example: "ls -la | default 'nothing' target ", + result: None, + }, + Example { + description: "Default the `$nothing` value in a list", + example: "[1, 2, $nothing, 4] | default 3", + result: Some(Value::List { + vals: vec![ + Value::test_int(1), + Value::test_int(2), + Value::test_int(3), + Value::test_int(4), + ], + span: Span::test_data(), + }), + }, + ] } } @@ -51,42 +66,52 @@ fn default( call: &Call, input: PipelineData, ) -> Result { - let column: Spanned = call.req(engine_state, stack, 0)?; - let value: Value = call.req(engine_state, stack, 1)?; + let value: Value = call.req(engine_state, stack, 0)?; + let column: Option> = call.opt(engine_state, stack, 1)?; let ctrlc = engine_state.ctrlc.clone(); - input.map( - move |item| match item { - Value::Record { - mut cols, - mut vals, - span, - } => { - let mut idx = 0; - let mut found = false; + if let Some(column) = column { + input.map( + move |item| match item { + Value::Record { + mut cols, + mut vals, + span, + } => { + let mut idx = 0; + let mut found = false; - while idx < cols.len() { - if cols[idx] == column.item { - found = true; - if matches!(vals[idx], Value::Nothing { .. }) { - vals[idx] = value.clone(); + while idx < cols.len() { + if cols[idx] == column.item { + found = true; + if matches!(vals[idx], Value::Nothing { .. }) { + vals[idx] = value.clone(); + } } + idx += 1; } - idx += 1; - } - if !found { - cols.push(column.item.clone()); - vals.push(value.clone()); - } + if !found { + cols.push(column.item.clone()); + vals.push(value.clone()); + } - Value::Record { cols, vals, span } - } - _ => item, - }, - ctrlc, - ) + Value::Record { cols, vals, span } + } + _ => item, + }, + ctrlc, + ) + } else { + input.map( + move |item| match item { + Value::Nothing { .. } => value.clone(), + x => x, + }, + ctrlc, + ) + } } #[cfg(test)] diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs index 27bbbf90f9..e7a3e38e05 100644 --- a/crates/nu-command/src/filters/empty.rs +++ b/crates/nu-command/src/filters/empty.rs @@ -1,8 +1,8 @@ -use nu_engine::{eval_block, CallExt}; +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, Value, + Category, Example, IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -20,12 +20,6 @@ impl Command for Empty { SyntaxShape::CellPath, "the names of the columns to check emptiness", ) - .named( - "block", - SyntaxShape::Block(Some(vec![SyntaxShape::Any])), - "an optional block to replace if empty", - Some('b'), - ) .category(Category::Filters) } @@ -46,7 +40,7 @@ impl Command for Empty { fn examples(&self) -> Vec { vec![ Example { - description: "Check if a value is empty", + description: "Check if a string is empty", example: "'' | empty?", result: Some(Value::Bool { val: true, @@ -54,46 +48,21 @@ impl Command for Empty { }), }, Example { - description: "more than one column", - example: "[[meal size]; [arepa small] [taco '']] | empty? meal size", - result: Some( - Value::List { - vals: vec![ - Value::Record{cols: vec!["meal".to_string(), "size".to_string()], vals: vec![ - Value::Bool{val: false, span: Span::test_data()}, - Value::Bool{val: false, span: Span::test_data()} - ], span: Span::test_data()}, - Value::Record{cols: vec!["meal".to_string(), "size".to_string()], vals: vec![ - Value::Bool{val: false, span: Span::test_data()}, - Value::Bool{val: true, span: Span::test_data()} - ], span: Span::test_data()} - ], span: Span::test_data() - }) + description: "Check if a list is empty", + example: "[] | empty?", + result: Some(Value::Bool { + val: true, + span: Span::test_data(), + }), }, Example { - description: "use a block if setting the empty cell contents is wanted", - example: "[[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 -b { |_| [33 37] }", - result: Some( - Value::List { - vals: vec![ - Value::Record{ - cols: vec!["2020/04/16".to_string(), "2020/07/10".to_string(), "2020/11/16".to_string()], - vals: vec![ - Value::List{vals: vec![ - Value::Int{val: 33, span: Span::test_data()}, - Value::Int{val: 37, span: Span::test_data()} - ], span: Span::test_data()}, - Value::List{vals: vec![ - Value::Int{val: 27, span: Span::test_data()}, - ], span: Span::test_data()}, - Value::List{vals: vec![ - Value::Int{val: 37, span: Span::test_data()}, - ], span: Span::test_data()}, - ], span: Span::test_data()} - ], span: Span::test_data() - } - ) - } + description: "Check if more than one column are empty", + example: "[[meal size]; [arepa small] [taco '']] | empty? meal size", + result: Some(Value::Bool { + val: false, + span: Span::test_data(), + }), + }, ] } } @@ -106,126 +75,77 @@ fn empty( ) -> Result { let head = call.head; let columns: Vec = call.rest(engine_state, stack, 0)?; - let has_block = call.has_flag("block"); - let block = if has_block { - let block_expr = call - .get_flag_expr("block") - .expect("internal error: expected block"); + if !columns.is_empty() { + for val in input { + for column in &columns { + let val = val.clone(); + match val.follow_cell_path(&column.members) { + Ok(Value::Nothing { .. }) => {} + Ok(_) => { + return Ok(Value::Bool { + val: false, + span: head, + } + .into_pipeline_data()) + } + Err(err) => return Err(err), + } + } + } - let block_id = block_expr - .as_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), head))?; - - let b = engine_state.get_block(block_id); - let evaluated_block = eval_block( - engine_state, - stack, - b, - PipelineData::new(head), - call.redirect_stdout, - call.redirect_stderr, - )?; - Some(evaluated_block.into_value(head)) + Ok(Value::Bool { + val: true, + span: head, + } + .into_pipeline_data()) } else { - None - }; + match input { + PipelineData::ExternalStream { stdout, .. } => match stdout { + Some(s) => { + let bytes = s.into_bytes(); - input.map( - move |value| { - let columns = columns.clone(); - - process_row(value, block.as_ref(), columns, head) - }, - engine_state.ctrlc.clone(), - ) -} - -fn process_row( - input: Value, - default_block: Option<&Value>, - column_paths: Vec, - head: Span, -) -> Value { - match input { - Value::Record { - cols: _, - ref vals, - span, - } => { - if column_paths.is_empty() { - let is_empty = vals.iter().all(|v| v.clone().is_empty()); - if default_block.is_some() { - if is_empty { - Value::Bool { val: true, span } - } else { - input.clone() - } - } else { - Value::Bool { - val: is_empty, - span, - } - } - } else { - let mut obj = input.clone(); - for column in column_paths { - let path = column.into_string(); - let data = input.get_data_by_key(&path); - let is_empty = match data { - Some(x) => x.is_empty(), - None => true, - }; - - let default = if let Some(x) = default_block { - if is_empty { - x.clone() - } else { - Value::Bool { val: true, span } + match bytes { + Ok(s) => Ok(Value::Bool { + val: s.item.is_empty(), + span: head, } - } else { - Value::Bool { - val: is_empty, - span, - } - }; - let r = obj.update_cell_path(&column.members, Box::new(move |_| default)); - if let Err(error) = r { - return Value::Error { error }; + .into_pipeline_data()), + Err(err) => Err(err), } } - obj - } - } - Value::List { vals, .. } if vals.iter().all(|v| v.as_record().is_ok()) => { - { - // we have records - if column_paths.is_empty() { - let is_empty = vals.is_empty() && vals.iter().all(|v| v.clone().is_empty()); - - Value::Bool { - val: is_empty, - span: head, - } - } else { - Value::Bool { - val: true, - span: head, - } + None => Ok(Value::Bool { + val: true, + span: head, } - } - } - Value::List { vals, .. } => { - let empty = vals.iter().all(|v| v.clone().is_empty()); - Value::Bool { - val: empty, + .into_pipeline_data()), + }, + PipelineData::ListStream(s, ..) => Ok(Value::Bool { + val: s.count() == 0, span: head, } + .into_pipeline_data()), + PipelineData::Value(value, ..) => { + let answer = is_empty(value); + + Ok(Value::Bool { + val: answer, + span: head, + } + .into_pipeline_data()) + } } - other => Value::Bool { - val: other.is_empty(), - span: head, - }, + } +} + +pub fn is_empty(value: Value) -> bool { + match value { + Value::List { vals, .. } => vals.is_empty(), + Value::String { val, .. } => val.is_empty(), + Value::Binary { val, .. } => val.is_empty(), + Value::Nothing { .. } => true, + Value::Record { cols, .. } => cols.is_empty(), + _ => false, } } diff --git a/crates/nu-command/tests/commands/rename.rs b/crates/nu-command/tests/commands/rename.rs index 1d5b35d479..08d0163ce9 100644 --- a/crates/nu-command/tests/commands/rename.rs +++ b/crates/nu-command/tests/commands/rename.rs @@ -50,7 +50,7 @@ fn keeps_remaining_original_names_given_less_new_names_than_total_original_names open los_cuatro_mosqueteros.txt | lines | wrap name - | default hit "arepa!" + | default "arepa!" hit | rename mosqueteros | get hit | length diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs index 4a3448602b..a06352ceec 100644 --- a/src/tests/test_table_operations.rs +++ b/src/tests/test_table_operations.rs @@ -235,7 +235,7 @@ fn length_for_rows() -> TestResult { #[test] fn length_defaulted_columns() -> TestResult { run_test( - r#"echo [[name, age]; [test, 10]] | default age 11 | get 0 | columns | length"#, + r#"echo [[name, age]; [test, 10]] | default 11 age | get 0 | columns | length"#, "2", ) }