From ec9089ebc55e961ffe4a058043b103159267d2b8 Mon Sep 17 00:00:00 2001 From: Andrej Kolchin Date: Tue, 23 Jul 2024 16:37:54 +0300 Subject: [PATCH] Initial commit --- Cargo.lock | 7 ++ crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/strings/base/decode.rs | 76 +++++++++++++++++ crates/nu-command/src/strings/base/encode.rs | 79 ++++++++++++++++++ crates/nu-command/src/strings/base/mod.rs | 88 ++++++++++++++++++++ crates/nu-command/src/strings/mod.rs | 2 + 7 files changed, 255 insertions(+) create mode 100644 crates/nu-command/src/strings/base/decode.rs create mode 100644 crates/nu-command/src/strings/base/encode.rs create mode 100644 crates/nu-command/src/strings/base/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 97f81d5185..94ec5cfee7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1147,6 +1147,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "deranged" version = "0.3.11" @@ -3044,6 +3050,7 @@ dependencies = [ "chrono-tz 0.8.6", "crossterm", "csv", + "data-encoding", "deunicode", "dialoguer", "digest", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index ea501578b6..2af5192056 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -102,6 +102,7 @@ v_htmlescape = { workspace = true } wax = { workspace = true } which = { workspace = true } unicode-width = { workspace = true } +data-encoding = { version = "2.6.0", features = ["alloc"] } [target.'cfg(windows)'.dependencies] winreg = { workspace = true } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4adcfe9214..4cbce1003c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -179,6 +179,8 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Char, Decode, Encode, + EncodeBase, + DecodeBase, DecodeBase64, EncodeBase64, DetectColumns, diff --git a/crates/nu-command/src/strings/base/decode.rs b/crates/nu-command/src/strings/base/decode.rs new file mode 100644 index 0000000000..af494750a6 --- /dev/null +++ b/crates/nu-command/src/strings/base/decode.rs @@ -0,0 +1,76 @@ +use nu_engine::command_prelude::*; + +#[derive(Clone)] +pub struct DecodeBase; + +impl Command for DecodeBase { + fn name(&self) -> &str { + "decode base" + } + + fn signature(&self) -> Signature { + Signature::build("decode base") + .input_output_types(vec![(Type::String, Type::Binary)]) + .allow_variants_without_examples(true) + .required("encoding", SyntaxShape::String, "encoding to use") + .category(Category::Formats) + } + + fn usage(&self) -> &str { + "Decode a value." + } + + fn extra_usage(&self) -> &str { + "TODO" + } + + fn examples(&self) -> Vec { + vec![] + } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let name: String = call.req(engine_state, stack, 0)?; + + decode(&name, call.span(), input) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let name: String = call.req_const(working_set, 0)?; + + decode(&name, call.span(), input) + } +} + +fn decode(name: &str, call_span: Span, input: PipelineData) -> Result { + let encoding = super::encoding(&name, call_span, call_span)?; + let metadata = input.metadata(); + let (input_str, input_span) = super::get_string(input, call_span)?; + let output = encoding.decode(input_str.as_bytes()).unwrap(); + + Ok(Value::binary(output, call_span).into_pipeline_data_with_metadata(metadata)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(DecodeBase) + } +} diff --git a/crates/nu-command/src/strings/base/encode.rs b/crates/nu-command/src/strings/base/encode.rs new file mode 100644 index 0000000000..970ce3c429 --- /dev/null +++ b/crates/nu-command/src/strings/base/encode.rs @@ -0,0 +1,79 @@ +use nu_engine::command_prelude::*; + +#[derive(Clone)] +pub struct EncodeBase; + +impl Command for EncodeBase { + fn name(&self) -> &str { + "encode base" + } + + fn signature(&self) -> Signature { + Signature::build("encode base") + .input_output_types(vec![ + (Type::String, Type::String), + (Type::Binary, Type::String), + ]) + .allow_variants_without_examples(true) + .required("encoding", SyntaxShape::String, "encoding to use") + .category(Category::Formats) + } + + fn usage(&self) -> &str { + "Encode a value." + } + + fn extra_usage(&self) -> &str { + "TODO" + } + + fn examples(&self) -> Vec { + vec![] + } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let name: String = call.req(engine_state, stack, 0)?; + + encode(&name, call.span(), input) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let name: String = call.req_const(working_set, 0)?; + + encode(&name, call.span(), input) + } +} + +fn encode(name: &str, call_span: Span, input: PipelineData) -> Result { + let encoding = super::encoding(name, call_span, call_span)?; + let metadata = input.metadata(); + let (input_bytes, input_span) = super::get_binary(input, call_span)?; + let output = encoding.encode(&input_bytes); + + Ok(Value::string(output, call_span).into_pipeline_data_with_metadata(metadata)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(EncodeBase) + } +} diff --git a/crates/nu-command/src/strings/base/mod.rs b/crates/nu-command/src/strings/base/mod.rs new file mode 100644 index 0000000000..e14516eab9 --- /dev/null +++ b/crates/nu-command/src/strings/base/mod.rs @@ -0,0 +1,88 @@ +use nu_protocol::{PipelineData, ShellError, Span, Value}; + +mod decode; +mod encode; + +pub use decode::DecodeBase; +pub use encode::EncodeBase; + +pub fn encoding( + name: &str, + val_span: Span, + call_span: Span, +) -> Result { + match name { + "base32" => Ok(data_encoding::BASE32), + "base32hex" => Ok(data_encoding::BASE32HEX), + "base32hex_nopad" => Ok(data_encoding::BASE32HEX_NOPAD), + "base32_dnscurve" => Ok(data_encoding::BASE32_DNSCURVE), + "base32_dnssec" => Ok(data_encoding::BASE32_DNSSEC), + "base32_nopad" => Ok(data_encoding::BASE32_NOPAD), + "base64" => Ok(data_encoding::BASE64), + "base64url" => Ok(data_encoding::BASE64URL), + "base64url_nopad" => Ok(data_encoding::BASE64URL_NOPAD), + "base64_mime" => Ok(data_encoding::BASE64_MIME), + "base64_mime_permissive" => Ok(data_encoding::BASE64_MIME_PERMISSIVE), + "base64_nopad" => Ok(data_encoding::BASE64_NOPAD), + "hexlower" => Ok(data_encoding::HEXLOWER), + "hexlower_permissive" => Ok(data_encoding::HEXLOWER_PERMISSIVE), + "hexupper" => Ok(data_encoding::HEXUPPER), + "hexupper_permissive" => Ok(data_encoding::HEXUPPER_PERMISSIVE), + _ => Err(ShellError::IncorrectValue { + msg: format!("Encoding '{name}' not found"), + val_span, + call_span, + }), + } +} + +pub fn get_string(input: PipelineData, call_span: Span) -> Result<(String, Span), ShellError> { + match input { + PipelineData::Value(val, ..) => { + let span = val.span(); + match val { + Value::String { val, .. } => Ok((val, span)), + + _ => { + todo!("Invalid type") + } + } + } + PipelineData::ListStream(..) => { + todo!() + } + PipelineData::ByteStream(stream, ..) => { + let span = stream.span(); + Ok((stream.into_string()?, span)) + } + PipelineData::Empty => { + todo!("Can't have empty data"); + } + } +} + +pub fn get_binary(input: PipelineData, call_span: Span) -> Result<(Vec, Span), ShellError> { + match input { + PipelineData::Value(val, ..) => { + let span = val.span(); + match val { + Value::Binary { val, .. } => Ok((val, span)), + Value::String { val, .. } => Ok((val.into_bytes(), span)), + + _ => { + todo!("Invalid type") + } + } + } + PipelineData::ListStream(..) => { + todo!() + } + PipelineData::ByteStream(stream, ..) => { + let span = stream.span(); + Ok((stream.into_bytes()?, span)) + } + PipelineData::Empty => { + todo!("Can't have empty data"); + } + } +} diff --git a/crates/nu-command/src/strings/mod.rs b/crates/nu-command/src/strings/mod.rs index 8b5af2dec4..d92d37329d 100644 --- a/crates/nu-command/src/strings/mod.rs +++ b/crates/nu-command/src/strings/mod.rs @@ -1,3 +1,4 @@ +mod base; mod char_; mod detect_columns; mod encode_decode; @@ -7,6 +8,7 @@ mod parse; mod split; mod str_; +pub use base::{DecodeBase, EncodeBase}; pub use char_::Char; pub use detect_columns::*; pub use encode_decode::*;