mirror of
https://github.com/nushell/nushell.git
synced 2025-06-03 08:36:32 +02:00
related to - https://github.com/nushell/nushell/pull/9404 # Description to support our cratification effort and moving non-1.0 commands outside of the main focus, this PR - creates a new `nu-cmd-base` crate to hold the common structs, traits and functions used by all command-related crates - to start the transition, moves the `input_handler` module from `nu-command` to `nu-cmd-base` # User-Facing Changes ``` $nothing ``` # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - ⚫ `toolkit test` - ⚫ `toolkit test stdlib` # After Submitting ``` $nothing ```
205 lines
6.3 KiB
Rust
205 lines
6.3 KiB
Rust
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
|
|
use nu_engine::CallExt;
|
|
use nu_protocol::ast::{Call, CellPath};
|
|
use nu_protocol::engine::{EngineState, Stack};
|
|
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
|
|
|
enum HexDecodingError {
|
|
InvalidLength(usize),
|
|
InvalidDigit(usize, char),
|
|
}
|
|
|
|
fn hex_decode(value: &str) -> Result<Vec<u8>, HexDecodingError> {
|
|
let mut digits = value
|
|
.chars()
|
|
.enumerate()
|
|
.filter(|(_, c)| !c.is_whitespace());
|
|
|
|
let mut res = Vec::with_capacity(value.len() / 2);
|
|
loop {
|
|
let c1 = match digits.next() {
|
|
Some((ind, c)) => match c.to_digit(16) {
|
|
Some(d) => d,
|
|
None => return Err(HexDecodingError::InvalidDigit(ind, c)),
|
|
},
|
|
None => return Ok(res),
|
|
};
|
|
let c2 = match digits.next() {
|
|
Some((ind, c)) => match c.to_digit(16) {
|
|
Some(d) => d,
|
|
None => return Err(HexDecodingError::InvalidDigit(ind, c)),
|
|
},
|
|
None => {
|
|
return Err(HexDecodingError::InvalidLength(value.len()));
|
|
}
|
|
};
|
|
res.push((c1 << 4 | c2) as u8);
|
|
}
|
|
}
|
|
|
|
fn hex_digit(num: u8) -> char {
|
|
match num {
|
|
0..=9 => (num + b'0') as char,
|
|
10..=15 => (num - 10 + b'A') as char,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn hex_encode(bytes: &[u8]) -> String {
|
|
let mut res = String::with_capacity(bytes.len() * 2);
|
|
for byte in bytes {
|
|
res.push(hex_digit(byte >> 4));
|
|
res.push(hex_digit(byte & 0b1111));
|
|
}
|
|
res
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct HexConfig {
|
|
pub action_type: ActionType,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
pub enum ActionType {
|
|
Encode,
|
|
Decode,
|
|
}
|
|
|
|
struct Arguments {
|
|
cell_paths: Option<Vec<CellPath>>,
|
|
encoding_config: HexConfig,
|
|
}
|
|
|
|
impl CmdArgument for Arguments {
|
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
|
self.cell_paths.take()
|
|
}
|
|
}
|
|
|
|
pub fn operate(
|
|
action_type: ActionType,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
|
|
|
let args = Arguments {
|
|
encoding_config: HexConfig { action_type },
|
|
cell_paths,
|
|
};
|
|
|
|
general_operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
|
}
|
|
|
|
fn action(
|
|
input: &Value,
|
|
// only used for `decode` action
|
|
args: &Arguments,
|
|
command_span: Span,
|
|
) -> Value {
|
|
let hex_config = &args.encoding_config;
|
|
|
|
match input {
|
|
// Propagate existing errors.
|
|
Value::Error { .. } => input.clone(),
|
|
Value::Binary { val, .. } => match hex_config.action_type {
|
|
ActionType::Encode => Value::string(hex_encode(val.as_ref()), command_span),
|
|
ActionType::Decode => Value::Error {
|
|
error: Box::new(ShellError::UnsupportedInput(
|
|
"Binary data can only be encoded".to_string(),
|
|
"value originates from here".into(),
|
|
command_span,
|
|
// This line requires the Value::Error {} match above.
|
|
input.expect_span(),
|
|
)),
|
|
},
|
|
},
|
|
Value::String { val, .. } => {
|
|
match hex_config.action_type {
|
|
ActionType::Encode => Value::Error {
|
|
error: Box::new(ShellError::UnsupportedInput(
|
|
"String value can only be decoded".to_string(),
|
|
"value originates from here".into(),
|
|
command_span,
|
|
// This line requires the Value::Error {} match above.
|
|
input.expect_span(),
|
|
)),
|
|
},
|
|
|
|
ActionType::Decode => match hex_decode(val.as_ref()) {
|
|
Ok(decoded_value) => Value::binary(decoded_value, command_span),
|
|
Err(HexDecodingError::InvalidLength(len)) => Value::Error {
|
|
error: Box::new(ShellError::GenericError(
|
|
"value could not be hex decoded".to_string(),
|
|
format!("invalid hex input length: {len}. The length should be even"),
|
|
Some(command_span),
|
|
None,
|
|
Vec::new(),
|
|
)),
|
|
},
|
|
Err(HexDecodingError::InvalidDigit(index, digit)) => Value::Error {
|
|
error: Box::new(ShellError::GenericError(
|
|
"value could not be hex decoded".to_string(),
|
|
format!("invalid hex digit: '{digit}' at index {index}. Only 0-9, A-F, a-f are allowed in hex encoding"),
|
|
Some(command_span),
|
|
None,
|
|
Vec::new(),
|
|
)),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
other => Value::Error {
|
|
error: Box::new(ShellError::TypeMismatch {
|
|
err_message: format!("string or binary, not {}", other.get_type()),
|
|
span: other.span().unwrap_or(command_span),
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{action, ActionType, Arguments, HexConfig};
|
|
use nu_protocol::{Span, Value};
|
|
|
|
#[test]
|
|
fn hex_encode() {
|
|
let word = Value::binary([77, 97, 110], Span::test_data());
|
|
let expected = Value::test_string("4D616E");
|
|
|
|
let actual = action(
|
|
&word,
|
|
&Arguments {
|
|
encoding_config: HexConfig {
|
|
action_type: ActionType::Encode,
|
|
},
|
|
cell_paths: None,
|
|
},
|
|
Span::test_data(),
|
|
);
|
|
assert_eq!(actual, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn hex_decode() {
|
|
let word = Value::test_string("4D 61\r\n\n6E");
|
|
let expected = Value::binary([77, 97, 110], Span::test_data());
|
|
|
|
let actual = action(
|
|
&word,
|
|
&Arguments {
|
|
encoding_config: HexConfig {
|
|
action_type: ActionType::Decode,
|
|
},
|
|
cell_paths: None,
|
|
},
|
|
Span::test_data(),
|
|
);
|
|
assert_eq!(actual, expected);
|
|
}
|
|
}
|