diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 6ca5ec621..48326d885 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -166,7 +166,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { StrContains, StrDowncase, StrEndswith, - StrFindReplace, + StrReplace, StrIndexOf, StrKebabCase, StrLength, @@ -370,6 +370,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { MatchDeprecated, NthDeprecated, UnaliasDeprecated, + StrFindReplaceDeprecated, }; #[cfg(feature = "dataframe")] diff --git a/crates/nu-command/src/deprecated/mod.rs b/crates/nu-command/src/deprecated/mod.rs index 79bfab90b..fbfd61c61 100644 --- a/crates/nu-command/src/deprecated/mod.rs +++ b/crates/nu-command/src/deprecated/mod.rs @@ -3,6 +3,7 @@ mod nth; mod pivot; mod str_datetime; mod str_decimal; +mod str_find_replace; mod str_int; mod unalias; @@ -11,6 +12,7 @@ pub use nth::NthDeprecated; pub use pivot::PivotDeprecated; pub use str_datetime::StrDatetimeDeprecated; pub use str_decimal::StrDecimalDeprecated; +pub use str_find_replace::StrFindReplaceDeprecated; pub use str_int::StrIntDeprecated; pub use unalias::UnaliasDeprecated; diff --git a/crates/nu-command/src/deprecated/str_find_replace.rs b/crates/nu-command/src/deprecated/str_find_replace.rs new file mode 100644 index 000000000..4bbeb98e6 --- /dev/null +++ b/crates/nu-command/src/deprecated/str_find_replace.rs @@ -0,0 +1,36 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, PipelineData, Signature, +}; + +#[derive(Clone)] +pub struct StrFindReplaceDeprecated; + +impl Command for StrFindReplaceDeprecated { + fn name(&self) -> &str { + "str find-replace" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Deprecated) + } + + fn usage(&self) -> &str { + "Deprecated command" + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Err(nu_protocol::ShellError::DeprecatedCommand( + self.name().to_string(), + "str replace".to_string(), + call.head, + )) + } +} diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index a3510bcd6..0d65910b1 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -14,7 +14,7 @@ use crate::To; #[cfg(test)] use super::{ Ansi, Date, From, If, Into, Math, Path, Random, Split, SplitColumn, SplitRow, Str, StrCollect, - StrFindReplace, StrLength, Url, Wrap, + StrLength, StrReplace, Url, Wrap, }; #[cfg(test)] @@ -31,7 +31,7 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.add_decl(Box::new(Str)); working_set.add_decl(Box::new(StrCollect)); working_set.add_decl(Box::new(StrLength)); - working_set.add_decl(Box::new(StrFindReplace)); + working_set.add_decl(Box::new(StrReplace)); working_set.add_decl(Box::new(BuildString)); working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(If)); diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs index f570479e6..5725faf5a 100644 --- a/crates/nu-command/src/filters/reduce.rs +++ b/crates/nu-command/src/filters/reduce.rs @@ -67,7 +67,7 @@ impl Command for Reduce { }), }, Example { - example: r#"[ i o t ] | reduce -f "Arthur, King of the Britons" {|it, acc| $acc | str find-replace -a $it "X" }"#, + example: r#"[ i o t ] | reduce -f "Arthur, King of the Britons" {|it, acc| $acc | str replace -a $it "X" }"#, description: "Replace selected characters in a string with 'X'", result: Some(Value::String { val: "ArXhur, KXng Xf Xhe BrXXXns".to_string(), diff --git a/crates/nu-command/src/strings/str_/mod.rs b/crates/nu-command/src/strings/str_/mod.rs index 1d1d39911..ba60aeeae 100644 --- a/crates/nu-command/src/strings/str_/mod.rs +++ b/crates/nu-command/src/strings/str_/mod.rs @@ -4,10 +4,10 @@ mod collect; mod contains; mod downcase; mod ends_with; -mod find_replace; mod index_of; mod length; mod lpad; +mod replace; mod reverse; mod rpad; mod starts_with; @@ -21,10 +21,10 @@ pub use collect::*; pub use contains::SubCommand as StrContains; pub use downcase::SubCommand as StrDowncase; pub use ends_with::SubCommand as StrEndswith; -pub use find_replace::SubCommand as StrFindReplace; pub use index_of::SubCommand as StrIndexOf; pub use length::SubCommand as StrLength; pub use lpad::SubCommand as StrLpad; +pub use replace::SubCommand as StrReplace; pub use reverse::SubCommand as StrReverse; pub use rpad::SubCommand as StrRpad; pub use starts_with::SubCommand as StrStartsWith; diff --git a/crates/nu-command/src/strings/str_/find_replace.rs b/crates/nu-command/src/strings/str_/replace.rs similarity index 74% rename from crates/nu-command/src/strings/str_/find_replace.rs rename to crates/nu-command/src/strings/str_/replace.rs index 2d0ac02c0..6c337a672 100644 --- a/crates/nu-command/src/strings/str_/find_replace.rs +++ b/crates/nu-command/src/strings/str_/replace.rs @@ -1,11 +1,10 @@ use nu_engine::CallExt; -use nu_protocol::ast::Call; -use nu_protocol::ast::CellPath; -use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::Category; -use nu_protocol::Spanned; -use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; -use regex::Regex; +use nu_protocol::{ + ast::{Call, CellPath}, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; +use regex::{NoExpand, Regex}; use std::sync::Arc; struct Arguments { @@ -13,6 +12,7 @@ struct Arguments { find: String, replace: String, column_paths: Vec, + literal_replace: bool, } #[derive(Clone)] @@ -20,11 +20,11 @@ pub struct SubCommand; impl Command for SubCommand { fn name(&self) -> &str { - "str find-replace" + "str replace" } fn signature(&self) -> Signature { - Signature::build("str find-replace") + Signature::build("str replace") .required("find", SyntaxShape::String, "the pattern to find") .required("replace", SyntaxShape::String, "the replacement pattern") .rest( @@ -33,6 +33,11 @@ impl Command for SubCommand { "optionally find and replace text by column paths", ) .switch("all", "replace all occurrences of find string", Some('a')) + .switch( + "no-expand", + "do not expand the replace parameter as a regular expression", + Some('n'), + ) .category(Category::Strings) } @@ -54,7 +59,7 @@ impl Command for SubCommand { vec![ Example { description: "Find and replace contents with capture group", - example: "'my_library.rb' | str find-replace '(.+).rb' '$1.nu'", + example: "'my_library.rb' | str replace '(.+).rb' '$1.nu'", result: Some(Value::String { val: "my_library.nu".to_string(), span: Span::test_data(), @@ -62,7 +67,7 @@ impl Command for SubCommand { }, Example { description: "Find and replace all occurrences of find string", - example: "'abc abc abc' | str find-replace -a 'b' 'z'", + example: "'abc abc abc' | str replace -a 'b' 'z'", result: Some(Value::String { val: "azc azc azc".to_string(), span: Span::test_data(), @@ -71,7 +76,7 @@ impl Command for SubCommand { Example { description: "Find and replace all occurrences of find string in table", example: - "[[ColA ColB ColC]; [abc abc ads]] | str find-replace -a 'b' 'z' ColA ColC", + "[[ColA ColB ColC]; [abc abc ads]] | str replace -a 'b' 'z' ColA ColC", result: Some(Value::List { vals: vec![Value::Record { cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()], @@ -94,6 +99,15 @@ impl Command for SubCommand { span: Span::test_data(), }), }, + Example { + description: "Find and replace contents without using the replace parameter as a regular expression", + example: r#"'dogs_$1_cats' | str replace '\$1' '$2' -n"#, + result: Some(Value::String { + val: "dogs_$2_cats".to_string(), + span: Span::test_data(), + }), + }, + ] } } @@ -106,12 +120,14 @@ fn operate( ) -> Result { let find: Spanned = call.req(engine_state, stack, 0)?; let replace: Spanned = call.req(engine_state, stack, 1)?; + let literal_replace = call.has_flag("no-expand"); let options = Arc::new(Arguments { all: call.has_flag("all"), find: find.item, replace: replace.item, column_paths: call.rest(engine_state, stack, 2)?, + literal_replace, }); let head = call.head; input.map( @@ -142,7 +158,11 @@ struct FindReplace<'a>(&'a str, &'a str); fn action( input: &Value, Arguments { - find, replace, all, .. + find, + replace, + all, + literal_replace, + .. }: &Arguments, head: Span, ) -> Value { @@ -155,12 +175,24 @@ fn action( Ok(re) => { if *all { Value::String { - val: re.replace_all(val, replacement).to_string(), + val: { + if *literal_replace { + re.replace_all(val, NoExpand(replacement)).to_string() + } else { + re.replace_all(val, replacement).to_string() + } + }, span: head, } } else { Value::String { - val: re.replace(val, replacement).to_string(), + val: { + if *literal_replace { + re.replace(val, NoExpand(replacement)).to_string() + } else { + re.replace(val, replacement).to_string() + } + }, span: head, } } @@ -206,6 +238,7 @@ mod tests { find: String::from("Cargo.(.+)"), replace: String::from("Carga.$1"), column_paths: vec![], + literal_replace: false, all: false, }; diff --git a/crates/nu-command/tests/commands/str_/mod.rs b/crates/nu-command/tests/commands/str_/mod.rs index 37698c16a..7e589fd88 100644 --- a/crates/nu-command/tests/commands/str_/mod.rs +++ b/crates/nu-command/tests/commands/str_/mod.rs @@ -165,7 +165,7 @@ fn find_and_replaces() { cwd: dirs.test(), pipeline( r#" open sample.toml - | str find-replace KATZ "5289" fortune.teller.phone + | str replace KATZ "5289" fortune.teller.phone | get fortune.teller.phone "# )); @@ -190,7 +190,7 @@ fn find_and_replaces_without_passing_field() { r#" open sample.toml | get fortune.teller.phone - | str find-replace KATZ "5289" + | str replace KATZ "5289" "# )); diff --git a/docs/sample_config/default_config.nu b/docs/sample_config/default_config.nu index dc0ce241f..b81c4987a 100644 --- a/docs/sample_config/default_config.nu +++ b/docs/sample_config/default_config.nu @@ -7,7 +7,7 @@ module completions { # # This is a simplified version of completions for git branches and git remotes def "nu-complete git branches" [] { - ^git branch | lines | each { |line| $line | str find-replace '[\*\+] ' '' | str trim } + ^git branch | lines | each { |line| $line | str replace '[\*\+] ' '' | str trim } } def "nu-complete git remotes" [] { diff --git a/tests/shell/mod.rs b/tests/shell/mod.rs index bdb5ef006..d4122fadf 100644 --- a/tests/shell/mod.rs +++ b/tests/shell/mod.rs @@ -14,7 +14,7 @@ fn plugins_are_declared_with_wix() { r#" open Cargo.toml | get bin.name - | str find-replace "nu_plugin_(extra|core)_(.*)" "nu_plugin_$2" + | str replace "nu_plugin_(extra|core)_(.*)" "nu_plugin_$2" | drop | sort-by | wrap cargo | merge {