diff --git a/crates/nu-command/src/bytes/add.rs b/crates/nu-command/src/bytes/add.rs new file mode 100644 index 0000000000..1a4ed3ac14 --- /dev/null +++ b/crates/nu-command/src/bytes/add.rs @@ -0,0 +1,163 @@ +use super::{operate, BytesArgument}; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::ast::CellPath; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::Category; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; + +struct Arguments { + added_data: Vec, + index: Option, + end: bool, + column_paths: Option>, +} + +impl BytesArgument for Arguments { + fn take_column_paths(&mut self) -> Option> { + self.column_paths.take() + } +} + +#[derive(Clone)] + +pub struct BytesAdd; + +impl Command for BytesAdd { + fn name(&self) -> &str { + "bytes add" + } + + fn signature(&self) -> Signature { + Signature::build("bytes add") + .required("data", SyntaxShape::Binary, "the binary to add") + .named( + "index", + SyntaxShape::Int, + "index to insert binary data", + Some('i'), + ) + .switch("end", "add to the end of binary", Some('e')) + .rest( + "rest", + SyntaxShape::CellPath, + "optionally matches prefix of text by column paths", + ) + .category(Category::Bytes) + } + + fn usage(&self) -> &str { + "add specified bytes to the input" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["append", "truncate", "padding"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let added_data: Vec = call.req(engine_state, stack, 0)?; + let column_paths: Vec = call.rest(engine_state, stack, 1)?; + let column_paths = (!column_paths.is_empty()).then_some(column_paths); + let index: Option = call.get_flag(engine_state, stack, "index")?; + let end = call.has_flag("end"); + + let arg = Arguments { + added_data, + index, + end, + column_paths, + }; + operate(add, arg, input, call.head, engine_state.ctrlc.clone()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Add bytes `0x[AA]` to `0x[1F FF AA AA]`", + example: "0x[1F FF AA AA] | bytes add 0x[AA]", + result: Some(Value::Binary { + val: vec![0xAA, 0x1F, 0xFF, 0xAA, 0xAA], + span: Span::test_data(), + }), + }, + Example { + description: "Add bytes `0x[AA BB]` to `0x[1F FF AA AA]` at index 1", + example: "0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1", + result: Some(Value::Binary { + val: vec![0x1F, 0xAA, 0xBB, 0xFF, 0xAA, 0xAA], + span: Span::test_data(), + }), + }, + Example { + description: "Add bytes `0x[11]` to `0x[FF AA AA]` at the end", + example: "0x[FF AA AA] | bytes add 0x[11] -e", + result: Some(Value::Binary { + val: vec![0xFF, 0xAA, 0xAA, 0x11], + span: Span::test_data(), + }), + }, + Example { + description: "Add bytes `0x[11 22 33]` to `0x[FF AA AA]` at the end, at index 1(the index is start from end)", + example: "0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1", + result: Some(Value::Binary { + val: vec![0xFF, 0xAA, 0x11, 0x22, 0x33, 0xBB], + span: Span::test_data(), + }), + }, + ] + } +} + +fn add(input: &[u8], args: &Arguments, span: Span) -> Value { + match args.index { + None => { + if args.end { + let mut added_data = args.added_data.clone(); + let mut result = input.to_vec(); + result.append(&mut added_data); + Value::Binary { val: result, span } + } else { + let mut result = args.added_data.clone(); + let mut input = input.to_vec(); + result.append(&mut input); + Value::Binary { val: result, span } + } + } + Some(mut indx) => { + let inserted_index = if args.end { + input.len().saturating_sub(indx) + } else { + if indx > input.len() { + indx = input.len() + } + indx + }; + let mut result = vec![]; + let mut prev_data = input[..inserted_index].to_vec(); + result.append(&mut prev_data); + let mut added_data = args.added_data.clone(); + result.append(&mut added_data); + let mut after_data = input[inserted_index..].to_vec(); + result.append(&mut after_data); + Value::Binary { val: result, span } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(BytesAdd {}) + } +} diff --git a/crates/nu-command/src/bytes/ends_with.rs b/crates/nu-command/src/bytes/ends_with.rs new file mode 100644 index 0000000000..3f96bbb2e5 --- /dev/null +++ b/crates/nu-command/src/bytes/ends_with.rs @@ -0,0 +1,112 @@ +use super::{operate, BytesArgument}; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::ast::CellPath; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::Category; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; + +struct Arguments { + pattern: Vec, + column_paths: Option>, +} + +impl BytesArgument for Arguments { + fn take_column_paths(&mut self) -> Option> { + self.column_paths.take() + } +} + +#[derive(Clone)] + +pub struct BytesEndsWith; + +impl Command for BytesEndsWith { + fn name(&self) -> &str { + "bytes ends-with" + } + + fn signature(&self) -> Signature { + Signature::build("bytes ends-with") + .required("pattern", SyntaxShape::Binary, "the pattern to match") + .rest( + "rest", + SyntaxShape::CellPath, + "optionally matches prefix of text by column paths", + ) + .category(Category::Bytes) + } + + fn usage(&self) -> &str { + "Check if bytes ends with a pattern" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["pattern", "match", "find", "search"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let pattern: Vec = call.req(engine_state, stack, 0)?; + let column_paths: Vec = call.rest(engine_state, stack, 1)?; + let column_paths = (!column_paths.is_empty()).then_some(column_paths); + let arg = Arguments { + pattern, + column_paths, + }; + operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Checks if binary ends with `0x[AA]`", + example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]", + result: Some(Value::Bool { + val: true, + span: Span::test_data(), + }), + }, + Example { + description: "Checks if binary ends with `0x[FF AA AA]`", + example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]", + result: Some(Value::Bool { + val: true, + span: Span::test_data(), + }), + }, + Example { + description: "Checks if binary ends with `0x[11]`", + example: "0x[1F FF AA AA] | bytes ends-with 0x[11]", + result: Some(Value::Bool { + val: false, + span: Span::test_data(), + }), + }, + ] + } +} + +fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value { + Value::Bool { + val: input.ends_with(pattern), + span, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(BytesEndsWith {}) + } +} diff --git a/crates/nu-command/src/bytes/length.rs b/crates/nu-command/src/bytes/length.rs index 9e8dbb162e..1283158c88 100644 --- a/crates/nu-command/src/bytes/length.rs +++ b/crates/nu-command/src/bytes/length.rs @@ -50,11 +50,7 @@ impl Command for BytesLen { input: PipelineData, ) -> Result { let column_paths: Vec = call.rest(engine_state, stack, 1)?; - let column_paths = if column_paths.is_empty() { - None - } else { - Some(column_paths) - }; + let column_paths = (!column_paths.is_empty()).then_some(column_paths); let arg = Arguments { column_paths }; operate(length, arg, input, call.head, engine_state.ctrlc.clone()) } diff --git a/crates/nu-command/src/bytes/mod.rs b/crates/nu-command/src/bytes/mod.rs index 8f961ea3c7..6f2674a0d9 100644 --- a/crates/nu-command/src/bytes/mod.rs +++ b/crates/nu-command/src/bytes/mod.rs @@ -1,5 +1,9 @@ +mod add; mod bytes_; +mod ends_with; mod length; +mod replace; +mod reverse; mod starts_with; use nu_protocol::ast::CellPath; @@ -7,8 +11,12 @@ use nu_protocol::{PipelineData, ShellError, Span, Value}; use std::sync::atomic::AtomicBool; use std::sync::Arc; +pub use add::BytesAdd; pub use bytes_::Bytes; +pub use ends_with::BytesEndsWith; pub use length::BytesLen; +pub use replace::BytesReplace; +pub use reverse::BytesReverse; pub use starts_with::BytesStartsWith; trait BytesArgument { diff --git a/crates/nu-command/src/bytes/replace.rs b/crates/nu-command/src/bytes/replace.rs new file mode 100644 index 0000000000..e719f834bc --- /dev/null +++ b/crates/nu-command/src/bytes/replace.rs @@ -0,0 +1,167 @@ +use super::{operate, BytesArgument}; +use nu_engine::CallExt; +use nu_protocol::{ + ast::{Call, CellPath}, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +struct Arguments { + find: Vec, + replace: Vec, + column_paths: Option>, + all: bool, +} + +impl BytesArgument for Arguments { + fn take_column_paths(&mut self) -> Option> { + self.column_paths.take() + } +} + +#[derive(Clone)] +pub struct BytesReplace; + +impl Command for BytesReplace { + fn name(&self) -> &str { + "bytes replace" + } + + fn signature(&self) -> Signature { + Signature::build("bytes replace") + .required("find", SyntaxShape::Binary, "the pattern to find") + .required("replace", SyntaxShape::Binary, "the replacement pattern") + .rest( + "rest", + SyntaxShape::CellPath, + "optionally find and replace text by column paths", + ) + .switch("all", "replace all occurrences of find binary", Some('a')) + .category(Category::Bytes) + } + + fn usage(&self) -> &str { + "Find and replace binary" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["search", "shift", "switch"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest(engine_state, stack, 2)?; + let column_paths = (!column_paths.is_empty()).then_some(column_paths); + let find = call.req::>(engine_state, stack, 0)?; + if find.is_empty() { + return Err(ShellError::UnsupportedInput( + "the pattern to find cannot be empty".to_string(), + call.head, + )); + } + + let arg = Arguments { + find, + replace: call.req::>(engine_state, stack, 1)?, + column_paths, + all: call.has_flag("all"), + }; + + operate(replace, arg, input, call.head, engine_state.ctrlc.clone()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Find and replace contents", + example: "0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF]", + result: Some(Value::Binary { + val: vec![0xFF, 0xFF, 0xAA, 0xFF], + span: Span::test_data(), + }), + }, + Example { + description: "Find and replace all occurrences of find binary", + example: "0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0]", + result: Some(Value::Binary { + val: vec![0xA0, 0xAA, 0xA0, 0xBB, 0xA0], + span: Span::test_data(), + }), + }, + Example { + description: "Find and replace all occurrences of find binary in table", + example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()], + vals: vec![ + Value::Binary { + val: vec![0x13, 0x12, 0x13], + span: Span::test_data(), + }, + Value::Binary { + val: vec![0x14, 0x15, 0x16], + span: Span::test_data(), + }, + Value::Binary { + val: vec![0x17, 0x18, 0x19], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + ] + } +} + +fn replace(input: &[u8], arg: &Arguments, span: Span) -> Value { + let mut replaced = vec![]; + let replace_all = arg.all; + + // doing find-and-replace stuff. + let (mut left, mut right) = (0, arg.find.len()); + let input_len = input.len(); + let pattern_len = arg.find.len(); + while right <= input_len { + if input[left..right] == arg.find { + let mut to_replace = arg.replace.clone(); + replaced.append(&mut to_replace); + left += pattern_len; + right += pattern_len; + if !replace_all { + break; + } + } else { + replaced.push(input[left]); + left += 1; + right += 1; + } + } + + let mut remain = input[left..].to_vec(); + replaced.append(&mut remain); + Value::Binary { + val: replaced, + span, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(BytesReplace {}) + } +} diff --git a/crates/nu-command/src/bytes/reverse.rs b/crates/nu-command/src/bytes/reverse.rs new file mode 100644 index 0000000000..6f554a7ec6 --- /dev/null +++ b/crates/nu-command/src/bytes/reverse.rs @@ -0,0 +1,100 @@ +use super::{operate, BytesArgument}; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::ast::CellPath; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::Category; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; + +struct Arguments { + column_paths: Option>, +} + +impl BytesArgument for Arguments { + fn take_column_paths(&mut self) -> Option> { + self.column_paths.take() + } +} + +#[derive(Clone)] + +pub struct BytesReverse; + +impl Command for BytesReverse { + fn name(&self) -> &str { + "bytes reverse" + } + + fn signature(&self) -> Signature { + Signature::build("bytes reverse") + .rest( + "rest", + SyntaxShape::CellPath, + "optionally matches prefix of text by column paths", + ) + .category(Category::Bytes) + } + + fn usage(&self) -> &str { + "Reverse every bytes in the pipeline" + } + + fn search_terms(&self) -> Vec<&str> { + vec!["convert", "inverse"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + let column_paths = (!column_paths.is_empty()).then_some(column_paths); + let arg = Arguments { column_paths }; + operate(reverse, arg, input, call.head, engine_state.ctrlc.clone()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Reverse bytes `0x[1F FF AA AA]`", + example: "0x[1F FF AA AA] | bytes reverse", + result: Some(Value::Binary { + val: vec![0xAA, 0xAA, 0xFF, 0x1F], + span: Span::test_data(), + }), + }, + Example { + description: "Reverse bytes `0x[FF AA AA]`", + example: "0x[FF AA AA] | bytes reverse", + result: Some(Value::Binary { + val: vec![0xAA, 0xAA, 0xFF], + span: Span::test_data(), + }), + }, + ] + } +} + +fn reverse(input: &[u8], _args: &Arguments, span: Span) -> Value { + let mut reversed_input = input.to_vec(); + reversed_input.reverse(); + Value::Binary { + val: reversed_input, + span, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(BytesReverse {}) + } +} diff --git a/crates/nu-command/src/bytes/starts_with.rs b/crates/nu-command/src/bytes/starts_with.rs index a022530a23..a7d6391eac 100644 --- a/crates/nu-command/src/bytes/starts_with.rs +++ b/crates/nu-command/src/bytes/starts_with.rs @@ -54,11 +54,7 @@ impl Command for BytesStartsWith { ) -> Result { let pattern: Vec = call.req(engine_state, stack, 0)?; let column_paths: Vec = call.rest(engine_state, stack, 1)?; - let column_paths = if column_paths.is_empty() { - None - } else { - Some(column_paths) - }; + let column_paths = (!column_paths.is_empty()).then_some(column_paths); let arg = Arguments { pattern, column_paths, diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 845b9827aa..f149669324 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -211,7 +211,11 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { bind_command! { Bytes, BytesLen, - BytesStartsWith + BytesStartsWith, + BytesEndsWith, + BytesReverse, + BytesReplace, + BytesAdd } // FileSystem