forked from extern/nushell
Add hash command with base64 subcommand (#2769)
* WIP try testing hash command Ensure test worked fmt WIP get it working for other types of base64 Use optional named arg WIP * rebased and refactored a little with encoding and decoding Fix some typos Add some more charactersets refactor several args into the encoding config struct and fix character_set arg. It needs to match the field Add main hash command so it can be found via help Added tests for running the whole pipeline * add test case to cover invalid character sets * clippy and fmt
This commit is contained in:
@ -120,6 +120,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
||||
whole_stream_command(Autoview),
|
||||
whole_stream_command(Table),
|
||||
// Text manipulation
|
||||
whole_stream_command(Hash),
|
||||
whole_stream_command(HashBase64),
|
||||
whole_stream_command(Split),
|
||||
whole_stream_command(SplitColumn),
|
||||
whole_stream_command(SplitRow),
|
||||
|
@ -62,6 +62,7 @@ pub(crate) mod from_yaml;
|
||||
pub(crate) mod get;
|
||||
pub(crate) mod group_by;
|
||||
pub(crate) mod group_by_date;
|
||||
pub(crate) mod hash_;
|
||||
pub(crate) mod headers;
|
||||
pub(crate) mod help;
|
||||
pub(crate) mod histogram;
|
||||
@ -199,6 +200,7 @@ pub(crate) use from_yaml::FromYML;
|
||||
pub(crate) use get::Get;
|
||||
pub(crate) use group_by::Command as GroupBy;
|
||||
pub(crate) use group_by_date::GroupByDate;
|
||||
pub(crate) use hash_::{Hash, HashBase64};
|
||||
pub(crate) use headers::Headers;
|
||||
pub(crate) use help::Help;
|
||||
pub(crate) use histogram::Histogram;
|
||||
|
314
crates/nu-cli/src/commands/hash_/base64_.rs
Normal file
314
crates/nu-cli/src/commands/hash_/base64_.rs
Normal file
@ -0,0 +1,314 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{
|
||||
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{Tag, Tagged};
|
||||
|
||||
use base64::{decode_config, encode_config};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Arguments {
|
||||
pub character_set: Option<Tagged<String>>,
|
||||
pub encode: Tagged<bool>,
|
||||
pub decode: Tagged<bool>,
|
||||
pub rest: Vec<ColumnPath>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Base64Config {
|
||||
pub character_set: String,
|
||||
pub action_type: ActionType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum ActionType {
|
||||
Encode,
|
||||
Decode,
|
||||
}
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"hash base64"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("hash base64")
|
||||
.named(
|
||||
"character_set",
|
||||
SyntaxShape::String,
|
||||
"specify the character rules for encoding the input.\n\
|
||||
\tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\
|
||||
'binhex', 'bcrypt', 'crypt'",
|
||||
Some('c'),
|
||||
)
|
||||
.switch(
|
||||
"encode",
|
||||
"encode the input as base64. This is the default behavior if not specified.",
|
||||
Some('e')
|
||||
)
|
||||
.switch(
|
||||
"decode",
|
||||
"decode the input from base64",
|
||||
Some('d'))
|
||||
.rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally base64 encode / decode data by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"base64 encode or decode a value"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
operate(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Base64 encode a string with default settings",
|
||||
example: "echo 'username:password' | hash base64",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value()
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Base64 encode a string with the binhex character set",
|
||||
example: "echo 'username:password' | hash base64 --character_set binhex --encode",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("F@0NEPjJD97kE'&bEhFZEP3").into_untagged_value()
|
||||
]),
|
||||
},
|
||||
Example {
|
||||
description: "Base64 decode a value",
|
||||
example: "echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' | hash base64 --decode",
|
||||
result: Some(vec![
|
||||
UntaggedValue::string("username:password").into_untagged_value()
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn operate(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let name_tag = &args.call_info.name_tag.clone();
|
||||
|
||||
let (
|
||||
Arguments {
|
||||
encode,
|
||||
decode,
|
||||
character_set,
|
||||
rest,
|
||||
},
|
||||
input,
|
||||
) = args.process(®istry).await?;
|
||||
|
||||
if encode.item && decode.item {
|
||||
return Ok(OutputStream::one(Err(ShellError::labeled_error(
|
||||
"only one of --decode and --encode flags can be used",
|
||||
"conflicting flags",
|
||||
name_tag,
|
||||
))));
|
||||
}
|
||||
|
||||
// Default the action to be encoding if no flags are specified.
|
||||
let action_type = if *decode.item() {
|
||||
ActionType::Decode
|
||||
} else {
|
||||
ActionType::Encode
|
||||
};
|
||||
|
||||
// Default the character set to standard if the argument is not specified.
|
||||
let character_set = match character_set {
|
||||
Some(inner_tag) => inner_tag.item().to_string(),
|
||||
None => "standard".to_string(),
|
||||
};
|
||||
|
||||
let encoding_config = Base64Config {
|
||||
character_set,
|
||||
action_type,
|
||||
};
|
||||
|
||||
let column_paths: Vec<_> = rest;
|
||||
|
||||
Ok(input
|
||||
.map(move |v| {
|
||||
if column_paths.is_empty() {
|
||||
ReturnSuccess::value(action(&v, &encoding_config, v.tag())?)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
|
||||
for path in &column_paths {
|
||||
let config = encoding_config.clone();
|
||||
ret = ret.swap_data_by_column_path(
|
||||
path,
|
||||
Box::new(move |old| action(old, &config, old.tag())),
|
||||
)?;
|
||||
}
|
||||
|
||||
ReturnSuccess::value(ret)
|
||||
}
|
||||
})
|
||||
.to_output_stream())
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
base64_config: &Base64Config,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
match &input.value {
|
||||
UntaggedValue::Primitive(Primitive::Line(s))
|
||||
| UntaggedValue::Primitive(Primitive::String(s)) => {
|
||||
let base64_config_enum: base64::Config = if &base64_config.character_set == "standard" {
|
||||
base64::STANDARD
|
||||
} else if &base64_config.character_set == "standard-no-padding" {
|
||||
base64::STANDARD_NO_PAD
|
||||
} else if &base64_config.character_set == "url-safe" {
|
||||
base64::URL_SAFE
|
||||
} else if &base64_config.character_set == "url-safe-no-padding" {
|
||||
base64::URL_SAFE_NO_PAD
|
||||
} else if &base64_config.character_set == "binhex" {
|
||||
base64::BINHEX
|
||||
} else if &base64_config.character_set == "bcrypt" {
|
||||
base64::BCRYPT
|
||||
} else if &base64_config.character_set == "crypt" {
|
||||
base64::CRYPT
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"value is not an accepted character set",
|
||||
format!(
|
||||
"{} is not a valid character-set.\nPlease use `help hash base64` to see a list of valid character sets.",
|
||||
&base64_config.character_set
|
||||
),
|
||||
tag.into().span,
|
||||
));
|
||||
};
|
||||
|
||||
match base64_config.action_type {
|
||||
ActionType::Encode => Ok(UntaggedValue::string(encode_config(
|
||||
&s,
|
||||
base64_config_enum,
|
||||
))
|
||||
.into_value(tag)),
|
||||
ActionType::Decode => {
|
||||
let decode_result = decode_config(&s, base64_config_enum);
|
||||
|
||||
match decode_result {
|
||||
Ok(decoded_value) => Ok(UntaggedValue::string(
|
||||
std::string::String::from_utf8_lossy(&decoded_value),
|
||||
)
|
||||
.into_value(tag)),
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"value could not be base64 decoded",
|
||||
format!(
|
||||
"invalid base64 input for character set {}",
|
||||
&base64_config.character_set
|
||||
),
|
||||
tag.into().span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
let got = format!("got {}", other.type_name());
|
||||
Err(ShellError::labeled_error(
|
||||
"value is not string",
|
||||
got,
|
||||
tag.into().span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{action, ActionType, Base64Config};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::Tag;
|
||||
use nu_test_support::value::string;
|
||||
|
||||
#[test]
|
||||
fn base64_encode_standard() {
|
||||
let word = string("username:password");
|
||||
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ=").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "standard".to_string(),
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_standard_no_padding() {
|
||||
let word = string("username:password");
|
||||
let expected = UntaggedValue::string("dXNlcm5hbWU6cGFzc3dvcmQ").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "standard-no-padding".to_string(),
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_encode_url_safe() {
|
||||
let word = string("this is for url");
|
||||
let expected = UntaggedValue::string("dGhpcyBpcyBmb3IgdXJs").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "url-safe".to_string(),
|
||||
action_type: ActionType::Encode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_decode_binhex() {
|
||||
let word = string("A5\"KC9jRB@IIF'8bF!");
|
||||
let expected = UntaggedValue::string("a binhex test").into_untagged_value();
|
||||
|
||||
let actual = action(
|
||||
&word,
|
||||
&Base64Config {
|
||||
character_set: "binhex".to_string(),
|
||||
action_type: ActionType::Decode,
|
||||
},
|
||||
Tag::unknown(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
50
crates/nu-cli/src/commands/hash_/command.rs
Normal file
50
crates/nu-cli/src/commands/hash_/command.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
|
||||
|
||||
pub struct Command;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Command {
|
||||
fn name(&self) -> &str {
|
||||
"hash"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("hash").rest(
|
||||
SyntaxShape::ColumnPath,
|
||||
"optionally convert by column paths",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Apply hash function."
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(crate::commands::help::get_help(&Command, ®istry))
|
||||
.into_value(Tag::unknown()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Command;
|
||||
use super::ShellError;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
Ok(test_examples(Command {})?)
|
||||
}
|
||||
}
|
5
crates/nu-cli/src/commands/hash_/mod.rs
Normal file
5
crates/nu-cli/src/commands/hash_/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod base64_;
|
||||
mod command;
|
||||
|
||||
pub use base64_::SubCommand as HashBase64;
|
||||
pub use command::Command as Hash;
|
@ -765,60 +765,59 @@ impl VarSyntaxShapeDeductor {
|
||||
)],
|
||||
)?;
|
||||
}
|
||||
Operator::In | Operator::NotIn => {
|
||||
match var_side {
|
||||
BinarySide::Left => match &expr.expr {
|
||||
Expression::List(list) => {
|
||||
if !list.is_empty() {
|
||||
let shapes_in_list = self
|
||||
.get_shapes_in_list_or_insert_dependency(
|
||||
Operator::In | Operator::NotIn => match var_side {
|
||||
BinarySide::Left => match &expr.expr {
|
||||
Expression::List(list) => {
|
||||
if !list.is_empty() {
|
||||
let shapes_in_list = self.get_shapes_in_list_or_insert_dependency(
|
||||
var,
|
||||
bin_spanned,
|
||||
&list,
|
||||
(pipeline_idx, pipeline),
|
||||
registry,
|
||||
);
|
||||
match shapes_in_list {
|
||||
None => {}
|
||||
Some(shapes_in_list) => {
|
||||
self.checked_insert(
|
||||
var,
|
||||
bin_spanned,
|
||||
&list,
|
||||
(pipeline_idx, pipeline),
|
||||
registry,
|
||||
);
|
||||
match shapes_in_list {
|
||||
None => {}
|
||||
Some(shapes_in_list) => {
|
||||
self.checked_insert(
|
||||
var,
|
||||
VarShapeDeduction::from_usage_with_alternatives(
|
||||
&var.span,
|
||||
&shapes_in_list,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
VarShapeDeduction::from_usage_with_alternatives(
|
||||
&var.span,
|
||||
&shapes_in_list,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Expression::Table(_, _)
|
||||
| Expression::Literal(_)
|
||||
| Expression::ExternalWord
|
||||
| Expression::Synthetic(_)
|
||||
| Expression::Variable(_, _)
|
||||
| Expression::Binary(_)
|
||||
| Expression::Range(_)
|
||||
| Expression::Block(_)
|
||||
| Expression::Path(_)
|
||||
| Expression::FilePath(_)
|
||||
| Expression::ExternalCommand(_)
|
||||
| Expression::Command
|
||||
| Expression::Invocation(_)
|
||||
| Expression::Boolean(_)
|
||||
| Expression::Garbage => {unreachable!("Parser should have rejected code. In only applicable with rhs of type List")}
|
||||
},
|
||||
BinarySide::Right => {
|
||||
self.checked_insert(
|
||||
var,
|
||||
VarShapeDeduction::from_usage_with_alternatives(
|
||||
&var.span,
|
||||
&[SyntaxShape::Table],
|
||||
),
|
||||
)?;
|
||||
}
|
||||
Expression::Table(_, _)
|
||||
| Expression::Literal(_)
|
||||
| Expression::ExternalWord
|
||||
| Expression::Synthetic(_)
|
||||
| Expression::Variable(_, _)
|
||||
| Expression::Binary(_)
|
||||
| Expression::Range(_)
|
||||
| Expression::Block(_)
|
||||
| Expression::Path(_)
|
||||
| Expression::FilePath(_)
|
||||
| Expression::ExternalCommand(_)
|
||||
| Expression::Command
|
||||
| Expression::Invocation(_)
|
||||
| Expression::Boolean(_)
|
||||
| Expression::Garbage => {
|
||||
unreachable!("Parser should have rejected code. In only applicable with rhs of type List")
|
||||
}
|
||||
},
|
||||
BinarySide::Right => {
|
||||
self.checked_insert(
|
||||
var,
|
||||
VarShapeDeduction::from_usage_with_alternatives(
|
||||
&var.span,
|
||||
&[SyntaxShape::Table],
|
||||
),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
},
|
||||
Operator::Modulo => {
|
||||
self.checked_insert(
|
||||
var,
|
||||
|
Reference in New Issue
Block a user