diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4cbce1003c..4adcfe9214 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -179,8 +179,6 @@ 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/base32.rs b/crates/nu-command/src/strings/base/base32.rs new file mode 100644 index 0000000000..6053fd9e8c --- /dev/null +++ b/crates/nu-command/src/strings/base/base32.rs @@ -0,0 +1,62 @@ +use data_encoding::Encoding; + +use nu_engine::command_prelude::*; + +pub struct Base32Config { + lower: bool, + lower_span: Option, + nopad: bool, + nopad_span: Option, + dnscurve: bool, + dnscurve_span: Option, + dnssec: bool, + dnssec_span: Option, +} + +impl Base32Config { + pub fn new(engine_state: &EngineState, stack: &mut Stack, call: &Call) -> Self { + Base32Config { + lower: call.get_flag(engine_state, stack, "lower"), + lower_span: call.get_flag_span(stack, "lower"), + nopad: call.get_flag(engine_state, stack, "nopad"), + nopad_span: call.get_flag_span(stack, "nopad"), + dnscurve: call.get_flag(engine_state, stack, "dnscurve"), + dnscurve_span: call.get_flag_span(stack, "dnscurve"), + dnssec: call.get_flag(engine_state, stack, "dnssec"), + dnssec_span: call.get_flag_span(stack, "dnssec"), + } + } +} + +fn base32_encoding(config: Base32Config) -> Result { + if let Some(dnscurve_span) = config.dnscurve_span { + if let Some(lower_span) = config.lower_span { + return Err(ShellError::IncompatibleParameters { + left_message: "Inapplicable to DNSCURVE".to_string(), + left_span: lower_span, + right_message: "DNSCURVE must be used standalone".to_string(), + right_span: dnscurve_span, + }); + } + if let Some(nopad_span) = config.nopad_span { + return Err(ShellError::IncompatibleParameters { + left_message: "Inapplicable to DNSCURVE".to_string(), + left_span: nopad_span, + right_message: "DNSCURVE must be used standalone".to_string(), + right_span: dnscurve_span, + }); + } + if let Some(dnssec) = config.dnssec_span { + return Err(ShellError::IncompatibleParameters { + left_message: "Inapplicable to DNSCURVE".to_string(), + left_span: nopad_span, + right_message: "DNSCURVE must be used standalone".to_string(), + right_span: dnscurve_span, + }); + } + + return Ok(data_encoding::BASE32_DNSCURVE); + } + + todo!() +} diff --git a/crates/nu-command/src/strings/base/decode_base32.rs b/crates/nu-command/src/strings/base/decode_base32.rs new file mode 100644 index 0000000000..a9a9493946 --- /dev/null +++ b/crates/nu-command/src/strings/base/decode_base32.rs @@ -0,0 +1,66 @@ +use nu_engine::command_prelude::*; + +#[derive(Clone)] +pub struct DecodeBase32; + +impl Command for DecodeBase32 { + fn name(&self) -> &str { + "decode base32" + } + + fn signature(&self) -> Signature { + Signature::build("decode base32") + .input_output_types(vec![(Type::String, Type::Binary)]) + .allow_variants_without_examples(true) + .switch("lower", "Use a lowercase version of Base32.", None) + .switch("nopad", "Do not pad the output.", None) + .switch("dnscurve", "Use DNSCURVE Base32 variant.", None) + .switch("dnssec", "Use DNSSEC Base32 variant.", None) + .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 { + todo!() + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + todo!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(DecodeBase32) + } +} diff --git a/crates/nu-command/src/strings/base/decode_base32hex.rs b/crates/nu-command/src/strings/base/decode_base32hex.rs new file mode 100644 index 0000000000..f0fec6d6be --- /dev/null +++ b/crates/nu-command/src/strings/base/decode_base32hex.rs @@ -0,0 +1,67 @@ +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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(DecodeBase) + } +} diff --git a/crates/nu-command/src/strings/base/decode.rs b/crates/nu-command/src/strings/base/decode_base64.rs similarity index 100% rename from crates/nu-command/src/strings/base/decode.rs rename to crates/nu-command/src/strings/base/decode_base64.rs diff --git a/crates/nu-command/src/strings/base/decode_hex.rs b/crates/nu-command/src/strings/base/decode_hex.rs new file mode 100644 index 0000000000..af494750a6 --- /dev/null +++ b/crates/nu-command/src/strings/base/decode_hex.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_base32.rs b/crates/nu-command/src/strings/base/encode_base32.rs new file mode 100644 index 0000000000..d2411bd8ab --- /dev/null +++ b/crates/nu-command/src/strings/base/encode_base32.rs @@ -0,0 +1,70 @@ +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) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(EncodeBase) + } +} diff --git a/crates/nu-command/src/strings/base/encode.rs b/crates/nu-command/src/strings/base/encode_base32hex.rs similarity index 100% rename from crates/nu-command/src/strings/base/encode.rs rename to crates/nu-command/src/strings/base/encode_base32hex.rs diff --git a/crates/nu-command/src/strings/base/encode_base64.rs b/crates/nu-command/src/strings/base/encode_base64.rs new file mode 100644 index 0000000000..970ce3c429 --- /dev/null +++ b/crates/nu-command/src/strings/base/encode_base64.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/encode_hex.rs b/crates/nu-command/src/strings/base/encode_hex.rs new file mode 100644 index 0000000000..970ce3c429 --- /dev/null +++ b/crates/nu-command/src/strings/base/encode_hex.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 index e14516eab9..44f5460de8 100644 --- a/crates/nu-command/src/strings/base/mod.rs +++ b/crates/nu-command/src/strings/base/mod.rs @@ -1,42 +1,52 @@ -use nu_protocol::{PipelineData, ShellError, Span, Value}; +use data_encoding::Encoding; -mod decode; -mod encode; +use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Value}; -pub use decode::DecodeBase; -pub use encode::EncodeBase; +mod decode_base32; +mod decode_base32hex; +mod decode_base64; +mod decode_hex; +mod encode_base32; +mod encode_base32hex; +mod encode_base64; +mod encode_hex; -pub fn encoding( - name: &str, - val_span: Span, +mod base32; + +fn decode( + encoding: Encoding, 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, - }), - } + input: PipelineData, +) -> Result { + let metadata = input.metadata(); + let (input_str, input_span) = get_string(input, call_span)?; + let output = match encoding.decode(input_str.as_bytes()) { + Ok(output) => output, + Err(err) => { + return Err(ShellError::IncorrectValue { + msg: err.to_string(), + val_span: input_span, + call_span, + }); + } + }; + + Ok(Value::binary(output, call_span).into_pipeline_data_with_metadata(metadata)) } -pub fn get_string(input: PipelineData, call_span: Span) -> Result<(String, Span), ShellError> { +fn encode( + encoding: Encoding, + call_span: Span, + input: PipelineData, +) -> Result { + let metadata = input.metadata(); + let (input_bytes, _) = get_binary(input, call_span)?; + let output = encoding.encode(&input_bytes); + + Ok(Value::string(output, call_span).into_pipeline_data_with_metadata(metadata)) +} + +fn get_string(input: PipelineData, call_span: Span) -> Result<(String, Span), ShellError> { match input { PipelineData::Value(val, ..) => { let span = val.span(); @@ -55,19 +65,19 @@ pub fn get_string(input: PipelineData, call_span: Span) -> Result<(String, Span) let span = stream.span(); Ok((stream.into_string()?, span)) } - PipelineData::Empty => { - todo!("Can't have empty data"); - } + PipelineData::Empty => Err(ShellError::PipelineEmpty { + dst_span: call_span, + }), } } -pub fn get_binary(input: PipelineData, call_span: Span) -> Result<(Vec, Span), ShellError> { +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)), + Value::String { val, .. } => Ok((val.into_bytes(), span)), _ => { todo!("Invalid type")