From 57ebec385fae82d00cf3ab26b2d7fdf093daef64 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 23 Feb 2021 14:16:13 -0600 Subject: [PATCH] add ansi strip subcommand (#3095) * add ansi subcommand * changed example test, added additional test --- crates/nu-command/src/commands.rs | 1 + .../src/commands/{ansi.rs => ansi/command.rs} | 8 +- crates/nu-command/src/commands/ansi/mod.rs | 5 + crates/nu-command/src/commands/ansi/strip.rs | 121 ++++++++++++++++++ .../src/commands/default_context.rs | 1 + 5 files changed, 132 insertions(+), 4 deletions(-) rename crates/nu-command/src/commands/{ansi.rs => ansi/command.rs} (99%) create mode 100644 crates/nu-command/src/commands/ansi/mod.rs create mode 100644 crates/nu-command/src/commands/ansi/strip.rs diff --git a/crates/nu-command/src/commands.rs b/crates/nu-command/src/commands.rs index 1785f074c1..e55e1be027 100644 --- a/crates/nu-command/src/commands.rs +++ b/crates/nu-command/src/commands.rs @@ -141,6 +141,7 @@ pub(crate) use autoview::Autoview; pub(crate) use cd::Cd; pub(crate) use ansi::Ansi; +pub(crate) use ansi::AnsiStrip; pub(crate) use append::Command as Append; pub(crate) use autoenv::Autoenv; pub(crate) use autoenv_trust::AutoenvTrust; diff --git a/crates/nu-command/src/commands/ansi.rs b/crates/nu-command/src/commands/ansi/command.rs similarity index 99% rename from crates/nu-command/src/commands/ansi.rs rename to crates/nu-command/src/commands/ansi/command.rs index 87428c9c5d..1b55d1a3d8 100644 --- a/crates/nu-command/src/commands/ansi.rs +++ b/crates/nu-command/src/commands/ansi/command.rs @@ -5,7 +5,7 @@ use nu_errors::ShellError; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_source::Tagged; -pub struct Ansi; +pub struct Command; #[derive(Deserialize)] struct AnsiArgs { @@ -15,7 +15,7 @@ struct AnsiArgs { } #[async_trait] -impl WholeStreamCommand for Ansi { +impl WholeStreamCommand for Command { fn name(&self) -> &str { "ansi" } @@ -279,13 +279,13 @@ pub fn str_to_ansi(s: String) -> Option { #[cfg(test)] mod tests { - use super::Ansi; + use super::Command; use super::ShellError; #[test] fn examples_work_as_expected() -> Result<(), ShellError> { use crate::examples::test as test_examples; - test_examples(Ansi {}) + test_examples(Command {}) } } diff --git a/crates/nu-command/src/commands/ansi/mod.rs b/crates/nu-command/src/commands/ansi/mod.rs new file mode 100644 index 0000000000..274777ad89 --- /dev/null +++ b/crates/nu-command/src/commands/ansi/mod.rs @@ -0,0 +1,5 @@ +mod command; +mod strip; + +pub use command::Command as Ansi; +pub use strip::SubCommand as AnsiStrip; diff --git a/crates/nu-command/src/commands/ansi/strip.rs b/crates/nu-command/src/commands/ansi/strip.rs new file mode 100644 index 0000000000..a4856a75af --- /dev/null +++ b/crates/nu-command/src/commands/ansi/strip.rs @@ -0,0 +1,121 @@ +use crate::prelude::*; +use nu_engine::WholeStreamCommand; +use nu_errors::ShellError; +use nu_protocol::ShellTypeName; +use nu_protocol::{ + ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, +}; +use nu_source::Tag; +use strip_ansi_escapes::strip; + +pub struct SubCommand; + +#[derive(Deserialize)] +struct Arguments { + rest: Vec, +} + +#[async_trait] +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "ansi strip" + } + + fn signature(&self) -> Signature { + Signature::build("ansi strip").rest( + SyntaxShape::ColumnPath, + "optionally, remove ansi sequences by column paths", + ) + } + + fn usage(&self) -> &str { + "strip ansi escape sequences from string" + } + + async fn run(&self, args: CommandArgs) -> Result { + operate(args).await + } + + fn examples(&self) -> Vec { + vec![Example { + description: "strip ansi escape sequences from string", + example: "echo [$(ansi gb) 'hello' $(ansi reset)] | str collect | ansi strip", + result: None, + }] + } +} + +async fn operate(args: CommandArgs) -> Result { + let (Arguments { rest }, input) = args.process().await?; + let column_paths: Vec<_> = rest; + + Ok(input + .map(move |v| { + if column_paths.is_empty() { + ReturnSuccess::value(action(&v, v.tag())?) + } else { + let mut ret = v; + + for path in &column_paths { + ret = ret.swap_data_by_column_path( + path, + Box::new(move |old| action(old, old.tag())), + )?; + } + + ReturnSuccess::value(ret) + } + }) + .to_output_stream()) +} + +fn action(input: &Value, tag: impl Into) -> Result { + match &input.value { + UntaggedValue::Primitive(Primitive::String(astring)) => { + let stripped_string = { + if let Ok(bytes) = strip(&astring) { + String::from_utf8_lossy(&bytes).to_string() + } else { + astring.to_string() + } + }; + + Ok(UntaggedValue::string(stripped_string).into_value(tag)) + } + 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::ShellError; + use super::{action, SubCommand}; + use nu_protocol::UntaggedValue; + use nu_source::Tag; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(SubCommand {}) + } + + #[test] + fn test_stripping() { + let input_string = + UntaggedValue::string("\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld") + .into_untagged_value(); + let expected = UntaggedValue::string("Hello Nu World").into_untagged_value(); + + let actual = action(&input_string, Tag::unknown()).unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/commands/default_context.rs b/crates/nu-command/src/commands/default_context.rs index 4f0d801d19..045defc02a 100644 --- a/crates/nu-command/src/commands/default_context.rs +++ b/crates/nu-command/src/commands/default_context.rs @@ -111,6 +111,7 @@ pub fn create_default_context(interactive: bool) -> Result