diff --git a/src/check.rs b/src/check.rs index a4355e742..364fab92b 100644 --- a/src/check.rs +++ b/src/check.rs @@ -43,6 +43,7 @@ pub fn check_file_signature( path: &Path, extensions: &[Extension], question_policy: QuestionPolicy, + question_action: QuestionAction, ) -> Result { debug_assert!(path.file_name().is_some()); @@ -71,7 +72,7 @@ pub fn check_file_signature( ); // TODO: change question to: "do you want to proceed regardless of that"? - if !user_wants_to_continue(path, question_policy, QuestionAction::Decompression)? { + if !user_wants_to_continue(path, question_policy, question_action)? { return Ok(CheckFileSignatureControlFlow::HaltProgram); } diff --git a/src/commands/list.rs b/src/commands/list.rs index 9de520400..31bf8d06e 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -98,7 +98,7 @@ pub fn list_archive_contents( let _locks = lock_and_flush_output_stdio(); warn_user_about_loading_zip_in_memory(); - if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? { + if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Listing)? { return Ok(()); } } @@ -138,7 +138,7 @@ pub fn list_archive_contents( // Make thread own locks to keep output messages adjacent let locks = lock_and_flush_output_stdio(); warn_user_about_loading_zip_in_memory(); - if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? { + if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Listing)? { return Ok(()); } drop(locks); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index cd3e6bf9a..bb767571c 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -196,7 +196,12 @@ pub fn run(args: CliArgs, question_policy: QuestionPolicy, file_visibility_polic let (output_path, mut extensions) = extension::separate_known_extensions_from_name(path)?; let mut output_path = output_path.to_owned(); - match check::check_file_signature(path, &extensions, question_policy)? { + match check::check_file_signature( + path, + &extensions, + question_policy, + QuestionAction::Decompression, + )? { CheckFileSignatureControlFlow::HaltProgram => return Ok(()), CheckFileSignatureControlFlow::Continue => {} CheckFileSignatureControlFlow::ChangeToDetectedExtension { @@ -375,7 +380,7 @@ pub fn run(args: CliArgs, question_policy: QuestionPolicy, file_visibility_polic for path in files.iter() { let mut extensions = extension::extensions_from_path(path)?; - match check::check_file_signature(path, &extensions, question_policy)? { + match check::check_file_signature(path, &extensions, question_policy, QuestionAction::Listing)? { CheckFileSignatureControlFlow::HaltProgram => return Ok(()), CheckFileSignatureControlFlow::Continue => {} CheckFileSignatureControlFlow::ChangeToDetectedExtension { new_extension, .. } => { diff --git a/src/utils/question.rs b/src/utils/question.rs index 5105cf5b5..20d9e1d26 100644 --- a/src/utils/question.rs +++ b/src/utils/question.rs @@ -39,6 +39,8 @@ pub enum QuestionAction { Compression, /// question called from a decompression function Decompression, + /// question called from a listing function + Listing, } #[derive(Default)] @@ -68,6 +70,7 @@ pub fn user_wants_to_overwrite( QuestionPolicy::AlwaysYes => match question_action { QuestionAction::Decompression => Ok(Op::Merge), QuestionAction::Compression => Ok(Op::Overwrite), + QuestionAction::Listing => unreachable!("Listing cannot create file conflicts"), }, QuestionPolicy::AlwaysNo => Ok(Op::Cancel), QuestionPolicy::Ask => prompt_user_for_file_conflict_resolution(path, question_action), @@ -110,6 +113,7 @@ pub fn prompt_user_for_file_conflict_resolution( ], ) .ask(), + QuestionAction::Listing => unreachable!("Listing cannot create file conflicts"), } } @@ -176,6 +180,7 @@ pub fn user_wants_to_continue( let action = match question_action { QuestionAction::Compression => "compress", QuestionAction::Decompression => "decompress", + QuestionAction::Listing => "list", }; let path = format!("{}", PathFmt(path)); let path = Some(&*path); diff --git a/tests/list_prompt.rs b/tests/list_prompt.rs new file mode 100644 index 000000000..9821f5632 --- /dev/null +++ b/tests/list_prompt.rs @@ -0,0 +1,140 @@ +mod utils; + +use std::io::Write; + +use fs_err as fs; +use zip::{CompressionMethod, ZipWriter, write::SimpleFileOptions}; + +fn custom_extension_zip(dir: &std::path::Path) -> Vec { + let file = fs::File::create(dir.join("package.sublime-package")).unwrap(); + let mut writer = ZipWriter::new(file); + writer + .start_file( + "note.txt", + SimpleFileOptions::default().compression_method(CompressionMethod::Stored), + ) + .unwrap(); + writer.write_all(b"synthetic archive contents").unwrap(); + writer.finish().unwrap(); + fs::read(dir.join("package.sublime-package")).unwrap() +} + +#[test] +fn list_custom_extension_asks_to_list_and_does_not_extract() { + let (_tempdir, dir) = utils::testdir().unwrap(); + let archive = custom_extension_zip(dir); + let output = utils::cargo_bin() + .current_dir(dir) + .args(["list", "package.sublime-package", "--tree"]) + .write_stdin("y\n") + .assert() + .success() + .get_output() + .clone(); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains("Do you want to list '"), "{stderr}"); + assert!(!stderr.contains("Do you want to decompress '"), "{stderr}"); + assert!(String::from_utf8(output.stdout).unwrap().contains("note.txt")); + assert_eq!(fs::read(dir.join("package.sublime-package")).unwrap(), archive); + assert_eq!(fs::read_dir(dir).unwrap().count(), 1); +} + +#[test] +fn custom_extension_confirmation_policies_preserve_listing_and_extraction() { + for operation in ["list", "decompress"] { + for (flags, answer, proceed, prompt, format_warning) in [ + (vec![], "y\n", true, true, true), + (vec![], "n\n", false, true, true), + (vec!["--yes"], "", true, false, true), + (vec!["--no"], "", false, false, true), + (vec!["--format", "zip"], "", true, false, false), + (vec!["--quiet"], "y\n", true, true, false), + ] { + let (_tempdir, dir) = utils::testdir().unwrap(); + let archive = custom_extension_zip(dir); + let mut command = utils::cargo_bin(); + command + .current_dir(dir) + .args([operation, "package.sublime-package"]) + .args(&flags); + if operation == "list" { + command.arg("--tree"); + } else { + command.args(["--dir", "output"]); + } + let output = command.write_stdin(answer).assert().success().get_output().clone(); + let stderr = String::from_utf8(output.stderr).unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + let expected = format!("Do you want to {operation} '"); + assert_eq!(stderr.contains(&expected), prompt, "{operation} {flags:?}: {stderr}"); + assert_eq!( + stderr.contains("[WARNING] No recognized extensions in "), + format_warning, + "{operation} {flags:?}: {stderr}" + ); + if operation == "list" { + assert!(!stderr.contains("Do you want to decompress '"), "{stderr}"); + assert_eq!(stdout.contains("note.txt"), proceed, "{stdout}"); + assert_eq!(fs::read_dir(dir).unwrap().count(), 1); + } else if proceed { + assert_eq!( + fs::read(dir.join("output/note.txt")).unwrap(), + b"synthetic archive contents" + ); + assert_eq!(fs::read_dir(dir).unwrap().count(), 2); + } else { + assert_eq!(fs::read_dir(dir).unwrap().count(), 1); + } + assert_eq!(fs::read(dir.join("package.sublime-package")).unwrap(), archive); + } + } +} + +#[test] +fn layered_archive_listing_confirmation_uses_list() { + for format in ["zip", "7z"] { + for answer in ["y\n", "n\n"] { + let (_tempdir, dir) = utils::testdir().unwrap(); + let zip = custom_extension_zip(dir); + let archive = if format == "zip" { + zip + } else { + fs::write(dir.join("note.txt"), b"synthetic archive contents").unwrap(); + utils::cargo_bin() + .current_dir(dir) + .args(["compress", "note.txt", "archive.7z", "--yes"]) + .assert() + .success(); + let bytes = fs::read(dir.join("archive.7z")).unwrap(); + fs::remove_file(dir.join("archive.7z")).unwrap(); + fs::remove_file(dir.join("note.txt")).unwrap(); + bytes + }; + fs::remove_file(dir.join("package.sublime-package")).unwrap(); + let name = format!("archive.{format}.gz"); + let file = fs::File::create(dir.join(&name)).unwrap(); + let mut encoder = flate2::write::GzEncoder::new(file, flate2::Compression::default()); + encoder.write_all(&archive).unwrap(); + encoder.finish().unwrap(); + let original = fs::read(dir.join(&name)).unwrap(); + let output = utils::cargo_bin() + .current_dir(dir) + .args(["list", &name, "--tree"]) + .write_stdin(answer) + .assert() + .success() + .get_output() + .clone(); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains("[WARNING]"), "{stderr}"); + assert!(stderr.contains("Do you want to list '"), "{stderr}"); + assert!(!stderr.contains("Do you want to decompress '"), "{stderr}"); + assert_eq!( + String::from_utf8(output.stdout).unwrap().contains("note.txt"), + answer == "y\n" + ); + assert_eq!(fs::read(dir.join(&name)).unwrap(), original); + assert_eq!(fs::read_dir(dir).unwrap().count(), 1); + } + } +}