diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs new file mode 100644 index 000000000..c4161fa4e --- /dev/null +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -0,0 +1,171 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + Example, ShellError, Signature, Span, SyntaxShape, Value, +}; + +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "into binary" + } + + fn signature(&self) -> Signature { + Signature::build("into binary").rest( + "rest", + SyntaxShape::CellPath, + "column paths to convert to binary (for table input)", + ) + } + + fn usage(&self) -> &str { + "Convert value to a binary primitive" + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + into_binary(context, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "convert string to a nushell binary primitive", + example: "'This is a string that is exactly 52 characters long.' | into binary", + result: Some(Value::Binary { + val: "This is a string that is exactly 52 characters long." + .to_string() + .as_bytes() + .to_vec(), + span: Span::unknown(), + }), + }, + Example { + description: "convert a number to a nushell binary primitive", + example: "1 | into binary", + result: Some(Value::Binary { + val: i64::from(1).to_le_bytes().to_vec(), + span: Span::unknown(), + }), + }, + Example { + description: "convert a boolean to a nushell binary primitive", + example: "$true | into binary", + result: Some(Value::Binary { + val: i64::from(1).to_le_bytes().to_vec(), + span: Span::unknown(), + }), + }, + Example { + description: "convert a filesize to a nushell binary primitive", + example: "ls | where name == LICENSE | get size | into binary", + result: None, + }, + Example { + description: "convert a filepath to a nushell binary primitive", + example: "ls | where name == LICENSE | get name | path expand | into binary", + result: None, + }, + Example { + description: "convert a decimal to a nushell binary primitive", + example: "1.234 | into binary", + result: Some(Value::Binary { + val: 1.234f64.to_le_bytes().to_vec(), + span: Span::unknown(), + }), + }, + ] + } +} + +fn into_binary( + _context: &EvaluationContext, + call: &Call, + input: Value, +) -> Result { + let head = call.head; + // let column_paths: Vec = call.rest(context, 0)?; + + input.map(head, move |v| { + action(v, head) + // FIXME: Add back in column path support + // if column_paths.is_empty() { + // action(v, head) + // } else { + // let mut ret = v; + // for path in &column_paths { + // ret = + // ret.swap_data_by_cell_path(path, Box::new(move |old| action(old, old.tag())))?; + // } + + // Ok(ret) + // } + }) +} + +fn int_to_endian(n: i64) -> Vec { + if cfg!(target_endian = "little") { + n.to_le_bytes().to_vec() + } else { + n.to_be_bytes().to_vec() + } +} + +fn float_to_endian(n: f64) -> Vec { + if cfg!(target_endian = "little") { + n.to_le_bytes().to_vec() + } else { + n.to_be_bytes().to_vec() + } +} + +pub fn action(input: Value, span: Span) -> Value { + match input { + Value::Binary { .. } => input, + Value::Int { val, .. } => Value::Binary { + val: int_to_endian(val), + span, + }, + Value::Float { val, .. } => Value::Binary { + val: float_to_endian(val), + span, + }, + Value::Filesize { val, .. } => Value::Binary { + val: int_to_endian(val), + span, + }, + Value::String { val, .. } => Value::Binary { + val: val.as_bytes().to_vec(), + span, + }, + Value::Bool { val, .. } => Value::Binary { + val: int_to_endian(if val { 1i64 } else { 0 }), + span, + }, + Value::Date { val, .. } => Value::Binary { + val: val.format("%c").to_string().as_bytes().to_vec(), + span, + }, + + _ => Value::Error { + error: ShellError::UnsupportedInput("'into binary' for unsupported type".into(), span), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/conversions/into/command.rs b/crates/nu-command/src/conversions/into/command.rs new file mode 100644 index 000000000..f74c6436c --- /dev/null +++ b/crates/nu-command/src/conversions/into/command.rs @@ -0,0 +1,46 @@ +use nu_engine::get_full_help; +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + Signature, Value, +}; + +pub struct Into; + +impl Command for Into { + fn name(&self) -> &str { + "into" + } + + fn signature(&self) -> Signature { + Signature::build("into") + } + + fn usage(&self) -> &str { + "Apply into function." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::String { + val: get_full_help(&Into.signature(), &[], context), + span: call.head, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Into {}) + } +} diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs new file mode 100644 index 000000000..207a22481 --- /dev/null +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -0,0 +1,179 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + Example, ShellError, Signature, Span, SyntaxShape, Value, +}; + +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "into filesize" + } + + fn signature(&self) -> Signature { + Signature::build("into filesize").rest( + "rest", + SyntaxShape::CellPath, + "column paths to convert to filesize (for table input)", + ) + } + + fn usage(&self) -> &str { + "Convert value to filesize" + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + into_filesize(context, call, input) + } + + fn examples(&self) -> Vec { + vec![ + // Example { + // description: "Convert string to filesize in table", + // example: "[[bytes]; ['5'] [3.2] [4] [2kb]] | into filesize bytes", + // result: Some(Value::List { + // vals: vec![ + // Value::Record { + // cols: vec!["bytes".to_string()], + // vals: vec![Value::Filesize { + // val: 5, + // span: Span::unknown(), + // }], + // span: Span::unknown(), + // }, + // Value::Record { + // cols: vec!["bytes".to_string()], + // vals: vec![Value::Filesize { + // val: 3, + // span: Span::unknown(), + // }], + // span: Span::unknown(), + // }, + // Value::Record { + // cols: vec!["bytes".to_string()], + // vals: vec![Value::Filesize { + // val: 4, + // span: Span::unknown(), + // }], + // span: Span::unknown(), + // }, + // Value::Record { + // cols: vec!["bytes".to_string()], + // vals: vec![Value::Filesize { + // val: 2000, + // span: Span::unknown(), + // }], + // span: Span::unknown(), + // }, + // ], + // span: Span::unknown(), + // }), + // }, + Example { + description: "Convert string to filesize", + example: "'2' | into filesize", + result: Some(Value::Filesize { + val: 2, + span: Span::unknown(), + }), + }, + Example { + description: "Convert decimal to filesize", + example: "8.3 | into filesize", + result: Some(Value::Filesize { + val: 8, + span: Span::unknown(), + }), + }, + Example { + description: "Convert int to filesize", + example: "5 | into filesize", + result: Some(Value::Filesize { + val: 5, + span: Span::unknown(), + }), + }, + Example { + description: "Convert file size to filesize", + example: "4KB | into filesize", + result: Some(Value::Filesize { + val: 4000, + span: Span::unknown(), + }), + }, + ] + } +} + +fn into_filesize( + _context: &EvaluationContext, + call: &Call, + input: Value, +) -> Result { + let head = call.head; + // let call_paths: Vec = args.rest(0)?; + + input.map(head, move |v| { + action(v, head) + + // FIXME: Add back cell_path support + // if column_paths.is_empty() { + // action(&v, v.tag()) + // } else { + // let mut ret = v; + // for path in &column_paths { + // ret = ret.swap_data_by_column_path( + // path, + // Box::new(move |old| action(old, old.tag())), + // )?; + // } + + // Ok(ret) + // } + }) +} + +pub fn action(input: Value, span: Span) -> Value { + match input { + Value::Filesize { .. } => input, + Value::Int { val, .. } => Value::Filesize { val, span }, + Value::Float { val, .. } => Value::Filesize { + val: val as i64, + span, + }, + Value::String { val, .. } => match int_from_string(&val, span) { + Ok(val) => Value::Filesize { val, span }, + Err(error) => Value::Error { error }, + }, + _ => Value::Error { + error: ShellError::UnsupportedInput( + "'into filesize' for unsupported type".into(), + span, + ), + }, + } +} +fn int_from_string(a_string: &str, span: Span) -> Result { + match a_string.parse::() { + Ok(n) => Ok(n), + Err(_) => Err(ShellError::CantConvert("int".into(), span)), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs new file mode 100644 index 000000000..491379c70 --- /dev/null +++ b/crates/nu-command/src/conversions/into/int.rs @@ -0,0 +1,180 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EvaluationContext}, + Example, IntoValueStream, ShellError, Signature, Span, SyntaxShape, Value, +}; + +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "into int" + } + + fn signature(&self) -> Signature { + Signature::build("into int").rest( + "rest", + SyntaxShape::CellPath, + "column paths to convert to int (for table input)", + ) + } + + fn usage(&self) -> &str { + "Convert value to integer" + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + into_int(context, call, input) + } + + fn examples(&self) -> Vec { + vec![ + // Example { + // description: "Convert string to integer in table", + // example: "echo [[num]; ['-5'] [4] [1.5]] | into int num", + // result: Some(vec![ + // UntaggedValue::row(indexmap! { + // "num".to_string() => UntaggedValue::int(-5).into(), + // }) + // .into(), + // UntaggedValue::row(indexmap! { + // "num".to_string() => UntaggedValue::int(4).into(), + // }) + // .into(), + // UntaggedValue::row(indexmap! { + // "num".to_string() => UntaggedValue::int(1).into(), + // }) + // .into(), + // ]), + // }, + Example { + description: "Convert string to integer", + example: "'2' | into int", + result: Some(Value::Int { + val: 2, + span: Span::unknown(), + }), + }, + Example { + description: "Convert decimal to integer", + example: "5.9 | into int", + result: Some(Value::Int { + val: 5, + span: Span::unknown(), + }), + }, + Example { + description: "Convert decimal string to integer", + example: "'5.9' | into int", + result: Some(Value::Int { + val: 5, + span: Span::unknown(), + }), + }, + Example { + description: "Convert file size to integer", + example: "4KB | into int", + result: Some(Value::Int { + val: 4000, + span: Span::unknown(), + }), + }, + Example { + description: "Convert bool to integer", + example: "[$false, $true] | into int", + result: Some(Value::Stream { + stream: vec![ + Value::Int { + val: 0, + span: Span::unknown(), + }, + Value::Int { + val: 1, + span: Span::unknown(), + }, + ] + .into_iter() + .into_value_stream(), + span: Span::unknown(), + }), + }, + ] + } +} + +fn into_int( + _context: &EvaluationContext, + call: &Call, + input: Value, +) -> Result { + let head = call.head; + // let column_paths: Vec = call.rest(context, 0)?; + + input.map(head, move |v| { + action(v, head) + // FIXME: Add back cell_path support + // if column_paths.is_empty() { + // action(&v, v.tag()) + // } else { + // let mut ret = v; + // for path in &column_paths { + // ret = ret + // .swap_data_by_column_path(path, Box::new(move |old| action(old, old.tag())))?; + // } + + // Ok(ret) + // } + }) +} + +pub fn action(input: Value, span: Span) -> Value { + match input { + Value::Int { .. } => input, + Value::Filesize { val, .. } => Value::Int { val, span }, + Value::Float { val, .. } => Value::Int { + val: val as i64, + span, + }, + Value::String { val, .. } => match int_from_string(&val, span) { + Ok(val) => Value::Int { val, span }, + Err(error) => Value::Error { error }, + }, + Value::Bool { val, .. } => { + if val { + Value::Int { val: 1, span } + } else { + Value::Int { val: 0, span } + } + } + _ => Value::Error { + error: ShellError::UnsupportedInput("'into int' for unsupported type".into(), span), + }, + } +} + +fn int_from_string(a_string: &str, span: Span) -> Result { + match a_string.parse::() { + Ok(n) => Ok(n), + Err(_) => match a_string.parse::() { + Ok(f) => Ok(f as i64), + _ => Err(ShellError::CantConvert("into int".into(), span)), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/conversions/into/mod.rs b/crates/nu-command/src/conversions/into/mod.rs new file mode 100644 index 000000000..7563ca614 --- /dev/null +++ b/crates/nu-command/src/conversions/into/mod.rs @@ -0,0 +1,9 @@ +mod binary; +mod command; +mod filesize; +mod int; + +pub use self::filesize::SubCommand as IntoFilesize; +pub use binary::SubCommand as IntoBinary; +pub use command::Into; +pub use int::SubCommand as IntoInt; diff --git a/crates/nu-command/src/conversions/mod.rs b/crates/nu-command/src/conversions/mod.rs new file mode 100644 index 000000000..70608f782 --- /dev/null +++ b/crates/nu-command/src/conversions/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod into; + +pub use into::*; diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index 0ba33bfd0..bd995ce99 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -55,7 +55,7 @@ impl Command for For { let context = context.clone(); - Ok(values.map(call.head, move |x| { + values.map(call.head, move |x| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block); @@ -67,7 +67,7 @@ impl Command for For { Ok(value) => value, Err(error) => Value::Error { error }, } - })) + }) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 9e03619cd..f691ff59b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -42,6 +42,10 @@ pub fn create_default_context() -> Rc> { Help, Hide, If, + Into, + IntoBinary, + IntoFilesize, + IntoInt, Length, Let, LetEnv, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 366ab69cb..621d5540e 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -7,7 +7,7 @@ use nu_protocol::{ Value, }; -use super::{From, Split}; +use super::{From, Into, Split}; pub fn test_examples(cmd: impl Command + 'static) { let examples = cmd.examples(); @@ -19,6 +19,7 @@ pub fn test_examples(cmd: impl Command + 'static) { let engine_state = engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); working_set.add_decl(Box::new(From)); + working_set.add_decl(Box::new(Into)); working_set.add_decl(Box::new(Split)); // Adding the command that is being tested to the working set @@ -30,6 +31,10 @@ pub fn test_examples(cmd: impl Command + 'static) { EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); for example in examples { + // Skip tests that don't have results to compare to + if example.result.is_none() { + continue; + } let start = std::time::Instant::now(); let (block, delta) = { @@ -38,7 +43,7 @@ pub fn test_examples(cmd: impl Command + 'static) { let (output, err) = parse(&mut working_set, None, example.example.as_bytes(), false); if let Some(err) = err { - panic!("test parse error: {:?}", err) + panic!("test parse error in `{}`: {:?}", example.example, err) } (output, working_set.render()) @@ -52,7 +57,7 @@ pub fn test_examples(cmd: impl Command + 'static) { }; match eval_block(&state, &block, Value::nothing()) { - Err(err) => panic!("test eval error: {:?}", err), + Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err), Ok(result) => { println!("input: {}", example.example); println!("result: {:?}", result); diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 7818955d4..4e0f0a7f4 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,3 +1,4 @@ +mod conversions; mod core_commands; mod default_context; mod env; @@ -10,7 +11,8 @@ mod strings; mod system; mod viewers; -pub(crate) use core_commands::*; +pub use conversions::*; +pub use core_commands::*; pub use default_context::*; pub use env::*; pub use example_test::test_examples; diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs index b8efed377..c96b48a76 100644 --- a/crates/nu-command/src/strings/split/column.rs +++ b/crates/nu-command/src/strings/split/column.rs @@ -51,9 +51,9 @@ fn split_column( let rest: Vec> = call.rest(context, 1)?; let collapse_empty = call.has_flag("collapse-empty"); - Ok(input.map(name_span, move |x| { + input.map(name_span, move |x| { split_column_helper(&x, &separator, &rest, collapse_empty, name_span) - })) + }) } fn split_column_helper( @@ -100,9 +100,12 @@ fn split_column_helper( }) } } - Value::Record { - cols, - vals, + Value::List { + vals: vec![Value::Record { + cols, + vals, + span: head, + }], span: head, } } else { diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs index b356bbf6f..596a9fc73 100644 --- a/crates/nu-engine/src/from_value.rs +++ b/crates/nu-engine/src/from_value.rs @@ -124,6 +124,12 @@ impl FromValue for CellPath { span, }], }), + Value::Int { val, .. } => Ok(CellPath { + members: vec![PathMember::Int { + val: *val as usize, + span, + }], + }), v => Err(ShellError::CantConvert("cell path".into(), v.span())), } } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index f767f80e2..8ece8dc39 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -365,38 +365,29 @@ impl Value { } } - pub fn map(self, span: Span, mut f: F) -> Value + pub fn map(self, span: Span, mut f: F) -> Result where Self: Sized, F: FnMut(Self) -> Value + 'static, { match self { - Value::List { vals, .. } => Value::List { - vals: vals.into_iter().map(f).collect(), + Value::List { vals, .. } => Ok(Value::Stream { + stream: vals.into_iter().map(f).into_value_stream(), span, - }, - Value::Stream { stream, .. } => Value::Stream { + }), + Value::Stream { stream, .. } => Ok(Value::Stream { stream: stream.map(f).into_value_stream(), span, - }, - Value::Range { val, .. } => Value::Stream { + }), + Value::Range { val, .. } => Ok(Value::Stream { stream: val.into_iter().map(f).into_value_stream(), span, - }, + }), v => { - if v.as_string().is_ok() { - Value::List { - vals: vec![f(v)], - span, - } - } else { - Value::Error { - error: ShellError::PipelineMismatch { - expected: Type::String, - expected_span: span, - origin: v.span(), - }, - } + let output = f(v); + match output { + Value::Error { error } => Err(error), + v => Ok(v), } } } @@ -406,11 +397,12 @@ impl Value { where Self: Sized, U: IntoIterator, + ::IntoIter: 'static, F: FnMut(Self) -> U + 'static, { match self { - Value::List { vals, .. } => Value::List { - vals: vals.into_iter().map(f).flatten().collect(), + Value::List { vals, .. } => Value::Stream { + stream: vals.into_iter().map(f).flatten().into_value_stream(), span, }, Value::Stream { stream, .. } => Value::Stream { @@ -421,22 +413,10 @@ impl Value { stream: val.into_iter().map(f).flatten().into_value_stream(), span, }, - v => { - if v.as_string().is_ok() { - Value::List { - vals: f(v).into_iter().collect(), - span, - } - } else { - Value::Error { - error: ShellError::PipelineMismatch { - expected: Type::String, - expected_span: span, - origin: v.span(), - }, - } - } - } + v => Value::Stream { + stream: f(v).into_iter().into_value_stream(), + span, + }, } } } @@ -510,6 +490,9 @@ impl PartialOrd for Value { (Value::List { vals: lhs, .. }, Value::Stream { stream: rhs, .. }) => { lhs.partial_cmp(&rhs.clone().collect::>()) } + (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => { + lhs.partial_cmp(rhs) + } (_, _) => None, } }