From 81abb17b38977247459be8f721da9a7296aa2702 Mon Sep 17 00:00:00 2001 From: David Scholberg Date: Fri, 23 Jun 2023 14:08:02 -0400 Subject: [PATCH] Add multiline regex flag to str replace (#9508) # Description This change adds a new flag to the `str replace` command: `--multiline`, `-m`. This flag will automatically add the multiline regex flag `(?m)` to the beginning of the supplied regex, allowing for the `^` and `$` regex characters to match the beginnings and ends of lines, respectively. The main advantage of this addition is to make `str replace` more closely match sed's default behavior of performing matches per-line with a simple cli flag as opposed to forcing the user to add the {somewhat clunky) regex flag themselves. This could be an especially valuable addition since [`str replace` is listed as an alternative to sed in the official documentation](https://www.nushell.sh/book/coming_from_bash.html). With this change, the following two commands would be functionally equivalent: ```bash # bash printf "non-matching line\n123. one line\n124. another line\n" | sed -r 's/^[0-9]+\. //' ``` ```bash # nu "non-matching line\n123. one line\n124. another line\n" | str replace -am '^[0-9]+\. ' '' ``` both producing the following output: ``` non-matching line one line another line ``` # User-Facing Changes 1. Adds a new flag to the `str replace` command: `--multiline`, `-m`. # Tests + Formatting I have update the unit tests to test this flag. # After Submitting I will submit a PR for all relevant documentation changes as soon as this PR is approved. --- crates/nu-command/src/strings/str_/replace.rs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/strings/str_/replace.rs b/crates/nu-command/src/strings/str_/replace.rs index f7d842fed..6f02ee0ef 100644 --- a/crates/nu-command/src/strings/str_/replace.rs +++ b/crates/nu-command/src/strings/str_/replace.rs @@ -15,6 +15,7 @@ struct Arguments { cell_paths: Option>, literal_replace: bool, no_regex: bool, + multiline: bool, } impl CmdArgument for Arguments { @@ -53,6 +54,11 @@ impl Command for SubCommand { "match the pattern as a substring of the input, instead of a regular expression", Some('s'), ) + .switch( + "multiline", + "multi-line regex mode: ^ and $ match begin/end of line; equivalent to (?m)", + Some('m'), + ) .category(Category::Strings) } @@ -77,6 +83,7 @@ impl Command for SubCommand { 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"); + let multiline = call.has_flag("multiline"); let args = Arguments { all: call.has_flag("all"), @@ -85,6 +92,7 @@ impl Command for SubCommand { cell_paths, literal_replace, no_regex, + multiline, }; operate(action, args, input, call.head, engine_state.ctrlc.clone()) } @@ -148,6 +156,11 @@ impl Command for SubCommand { example: r#"'GHIKK-9+*' | str replace '[*[:xdigit:]+]' 'z'"#, result: Some(Value::test_string("GHIKK-z+*")), }, + Example { + description: "Find and replace on individual lines (multiline)", + 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")), + }, ] } @@ -163,6 +176,7 @@ fn action( all, literal_replace, no_regex, + multiline, .. }: &Arguments, head: Span, @@ -185,7 +199,12 @@ fn action( } } else { // use regular expressions to replace strings - let regex = Regex::new(find_str); + let flags = match multiline { + true => "(?m)", + false => "", + }; + let regex_string = flags.to_string() + find_str; + let regex = Regex::new(®ex_string); match regex { Ok(re) => { @@ -264,6 +283,7 @@ mod tests { literal_replace: false, all: false, no_regex: false, + multiline: false, }; let actual = action(&word, &options, Span::test_data());