add -f, --force for save command (#7262)

# Description

Closes: #6920 

# User-Facing Changes

```
❯ "asdf" | save dump.rdb
Error:
  × Destination file already exists
   ╭─[entry #21:1:1]
 1 │ "asdf" | save dump.rdb
   ·               ────┬───
   ·                   ╰── Destination file '/tmp/dump.rdb' already exists
   ╰────
  help: you can use -f, --force to force overwriting the destination
```
# Tests + Formatting

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` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# 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.
This commit is contained in:
WindSoilder 2022-12-01 20:26:17 +08:00 committed by GitHub
parent 6fadc72553
commit bc3dc98b34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 3 deletions

View File

@ -45,6 +45,7 @@ impl Command for Save {
) )
.switch("raw", "save file as raw binary", Some('r')) .switch("raw", "save file as raw binary", Some('r'))
.switch("append", "append input to the end of the file", Some('a')) .switch("append", "append input to the end of the file", Some('a'))
.switch("force", "overwrite the destination", Some('f'))
.category(Category::FileSystem) .category(Category::FileSystem)
} }
@ -57,6 +58,7 @@ impl Command for Save {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let raw = call.has_flag("raw"); let raw = call.has_flag("raw");
let append = call.has_flag("append"); let append = call.has_flag("append");
let force = call.has_flag("force");
let span = call.head; let span = call.head;
@ -64,7 +66,21 @@ impl Command for Save {
let arg_span = path.span; let arg_span = path.span;
let path = Path::new(&path.item); let path = Path::new(&path.item);
let file = match (append, path.exists()) { let path_exists = path.exists();
if path_exists && !force && !append {
return Err(ShellError::GenericError(
"Destination file already exists".into(),
format!(
"Destination file '{}' already exists",
path.to_string_lossy()
),
Some(arg_span),
Some("you can use -f, --force to force overwriting the destination".into()),
Vec::new(),
));
}
let file = match (append, path_exists) {
(true, true) => std::fs::OpenOptions::new() (true, true) => std::fs::OpenOptions::new()
.write(true) .write(true)
.append(true) .append(true)

View File

@ -1,4 +1,4 @@
use nu_test_support::fs::file_contents; use nu_test_support::fs::{file_contents, Stub};
use nu_test_support::nu; use nu_test_support::nu;
use nu_test_support::playground::Playground; use nu_test_support::playground::Playground;
use std::io::Write; use std::io::Write;
@ -149,3 +149,31 @@ fn save_string_and_stream_as_raw() {
) )
}) })
} }
#[test]
fn save_not_override_file_by_default() {
Playground::setup("save_test_8", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("log.txt")]);
let actual = nu!(
cwd: dirs.root(),
r#""abcd" | save save_test_8/log.txt"#
);
assert!(actual.err.contains("Destination file already exists"));
})
}
#[test]
fn save_override_works() {
Playground::setup("save_test_9", |dirs, sandbox| {
sandbox.with_files(vec![Stub::EmptyFile("log.txt")]);
let expected_file = dirs.test().join("log.txt");
nu!(
cwd: dirs.root(),
r#""abcd" | save save_test_9/log.txt -f"#
);
let actual = file_contents(expected_file);
assert_eq!(actual, "abcd");
})
}

View File

@ -910,7 +910,15 @@ pub fn eval_element_with_input(
Argument::Positional(expr.clone()), Argument::Positional(expr.clone()),
Argument::Named(( Argument::Named((
Spanned { Spanned {
item: "--raw".into(), item: "raw".into(),
span: *span,
},
None,
None,
)),
Argument::Named((
Spanned {
item: "force".into(),
span: *span, span: *span,
}, },
None, None,