From 2e0fb7c1a606aa1917734d9231dbe7a9721db39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 18 Aug 2023 00:18:16 +0300 Subject: [PATCH] Change `str replace` to match substring by default (#10038) --- crates/nu-command/src/strings/str_/replace.rs | 81 +++++++++++-------- crates/nu-command/tests/commands/str_/mod.rs | 2 +- src/tests/test_help.rs | 2 +- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/crates/nu-command/src/strings/str_/replace.rs b/crates/nu-command/src/strings/str_/replace.rs index 80d4060eaa..4bd354dde2 100644 --- a/crates/nu-command/src/strings/str_/replace.rs +++ b/crates/nu-command/src/strings/str_/replace.rs @@ -4,8 +4,8 @@ use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, - Value, + report_error_new, Category, Example, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Type, Value, }; struct Arguments { @@ -59,12 +59,17 @@ impl Command for SubCommand { ) .switch( "string", - "match the pattern as a substring of the input, instead of a regular expression", + "DEPRECATED option, will be removed in 0.85. Substring matching is now the default.", Some('s'), ) + .switch( + "regex", + "match the pattern as a regular expression in the input, instead of a substring", + Some('r'), + ) .switch( "multiline", - "multi-line regex mode: ^ and $ match begin/end of line; equivalent to (?m)", + "multi-line regex mode (implies --regex): ^ and $ match begin/end of line; equivalent to (?m)", Some('m'), ) .allow_variants_without_examples(true) @@ -91,7 +96,19 @@ impl Command for SubCommand { let cell_paths: Vec = call.rest(engine_state, stack, 2)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let literal_replace = call.has_flag("no-expand"); - let no_regex = call.has_flag("string"); + if call.has_flag("string") { + report_error_new( + engine_state, + &ShellError::GenericError( + "Deprecated option".into(), + "`str replace --string` is deprecated and will be removed in 0.85.".into(), + Some(call.head), + Some("Substring matching is now the default. Use `--regex` or `--multiline` for matching regular expressions.".into()), + vec![], + ), + ); + } + let no_regex = !call.has_flag("regex") && !call.has_flag("multiline"); let multiline = call.has_flag("multiline"); let args = Arguments { @@ -109,19 +126,29 @@ impl Command for SubCommand { fn examples(&self) -> Vec { vec![ Example { - description: "Find and replace contents with capture group", - example: "'my_library.rb' | str replace '(.+).rb' '$1.nu'", - result: Some(Value::test_string("my_library.nu")), + description: "Find and replace the first occurrence of a substring", + example: r"'c:\some\cool\path' | str replace 'c:\some\cool' '~'", + result: Some(Value::test_string("~\\path")), }, Example { - description: "Find and replace all occurrences of find string", - example: "'abc abc abc' | str replace -a 'b' 'z'", + description: "Find and replace all occurrences of a substring", + example: r#"'abc abc abc' | str replace -a 'b' 'z'"#, result: Some(Value::test_string("azc azc azc")), }, Example { - description: "Find and replace all occurrences of find string in table", + description: "Find and replace contents with capture group using regular expression", + example: "'my_library.rb' | str replace -r '(.+).rb' '$1.nu'", + result: Some(Value::test_string("my_library.nu")), + }, + Example { + description: "Find and replace all occurrences of find string using regular expression", + example: "'abc abc abc' | str replace -ar 'b' 'z'", + result: Some(Value::test_string("azc azc azc")), + }, + Example { + description: "Find and replace all occurrences of find string in table using regular expression", example: - "[[ColA ColB ColC]; [abc abc ads]] | str replace -a 'b' 'z' ColA ColC", + "[[ColA ColB ColC]; [abc abc ads]] | str replace -ar 'b' 'z' ColA ColC", result: Some(Value::List { vals: vec![Value::Record { cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()], @@ -136,9 +163,9 @@ impl Command for SubCommand { }), }, Example { - description: "Find and replace all occurrences of find string in record", + description: "Find and replace all occurrences of find string in record using regular expression", example: - "{ KeyA: abc, KeyB: abc, KeyC: ads } | str replace -a 'b' 'z' KeyA KeyC", + "{ KeyA: abc, KeyB: abc, KeyC: ads } | str replace -ar 'b' 'z' KeyA KeyC", result: Some(Value::Record { cols: vec!["KeyA".to_string(), "KeyB".to_string(), "KeyC".to_string()], vals: vec![ @@ -151,36 +178,26 @@ impl Command for SubCommand { }, 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", + example: r"'dogs_$1_cats' | str replace -r '\$1' '$2' -n", result: Some(Value::test_string("dogs_$2_cats")), }, Example { - description: "Find and replace the first occurrence using string replacement *not* regular expressions", - example: r"'c:\some\cool\path' | str replace 'c:\some\cool' '~' -s", - result: Some(Value::test_string("~\\path")), - }, - Example { - description: "Find and replace all occurrences using string replacement *not* regular expressions", - example: r#"'abc abc abc' | str replace -a 'b' 'z' -s"#, - result: Some(Value::test_string("azc azc azc")), - }, - Example { - description: "Use captures to manipulate the input text", - example: r#""abc-def" | str replace "(.+)-(.+)" "${2}_${1}""#, + description: "Use captures to manipulate the input text using regular expression", + example: r#""abc-def" | str replace -r "(.+)-(.+)" "${2}_${1}""#, result: Some(Value::test_string("def_abc")), }, Example { - description: "Find and replace with fancy-regex", - example: r"'a successful b' | str replace '\b([sS])uc(?:cs|s?)e(ed(?:ed|ing|s?)|ss(?:es|ful(?:ly)?|i(?:ons?|ve(?:ly)?)|ors?)?)\b' '${1}ucce$2'", + description: "Find and replace with fancy-regex using regular expression", + example: r"'a successful b' | str replace -r '\b([sS])uc(?:cs|s?)e(ed(?:ed|ing|s?)|ss(?:es|ful(?:ly)?|i(?:ons?|ve(?:ly)?)|ors?)?)\b' '${1}ucce$2'", result: Some(Value::test_string("a successful b")), }, Example { - description: "Find and replace with fancy-regex", - example: r#"'GHIKK-9+*' | str replace '[*[:xdigit:]+]' 'z'"#, + description: "Find and replace with fancy-regex using regular expression", + example: r#"'GHIKK-9+*' | str replace -r '[*[:xdigit:]+]' 'z'"#, result: Some(Value::test_string("GHIKK-z+*")), }, Example { - description: "Find and replace on individual lines (multiline)", + description: "Find and replace on individual lines using multiline regular expression", example: r#""non-matching line\n123. one line\n124. another line\n" | str replace -am '^[0-9]+\. ' ''"#, result: Some(Value::test_string("non-matching line\none line\nanother line\n")), }, diff --git a/crates/nu-command/tests/commands/str_/mod.rs b/crates/nu-command/tests/commands/str_/mod.rs index fe3b0aa015..eb54883b83 100644 --- a/crates/nu-command/tests/commands/str_/mod.rs +++ b/crates/nu-command/tests/commands/str_/mod.rs @@ -203,7 +203,7 @@ fn regex_error_in_pattern() { cwd: dirs.test(), pipeline( r#" 'source string' - | str replace 'source \Ufoo' "destination" + | str replace -r 'source \Ufoo' "destination" "# )); diff --git a/src/tests/test_help.rs b/src/tests/test_help.rs index d52a518efc..62180a31c3 100644 --- a/src/tests/test_help.rs +++ b/src/tests/test_help.rs @@ -20,7 +20,7 @@ fn can_get_help(#[case] exp_result: &str) -> TestResult { --f2:string, # f2 named no default --f3:int=33 # f3 named default 3 ] {{ true }}; - help t | ansi strip | find `{exp_result}` | get 0 | str replace -a '^(.*({exp_result}).*)$' '$2'"#, + help t | ansi strip | find `{exp_result}` | get 0 | str replace -ar '^(.*({exp_result}).*)$' '$2'"#, ), exp_result, )