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.
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the
standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
I will submit a PR for all relevant documentation changes as soon as
this PR is approved.
This commit is contained in:
David Scholberg 2023-06-23 14:08:02 -04:00 committed by GitHub
parent cbb9f8efe4
commit 81abb17b38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -15,6 +15,7 @@ struct Arguments {
cell_paths: Option<Vec<CellPath>>,
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(&regex_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());