mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-23 18:10:22 +00:00
Merge branch 'master' into completions-manpage
This commit is contained in:
commit
2816b837e2
@ -11,13 +11,13 @@ use walkdir::WalkDir;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
info,
|
info,
|
||||||
utils::{self, Bytes},
|
utils::{self, Bytes, QuestionPolicy},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn unpack_archive(
|
pub fn unpack_archive(
|
||||||
reader: Box<dyn Read>,
|
reader: Box<dyn Read>,
|
||||||
output_folder: &Path,
|
output_folder: &Path,
|
||||||
skip_questions_positively: Option<bool>,
|
question_policy: QuestionPolicy,
|
||||||
) -> crate::Result<Vec<PathBuf>> {
|
) -> crate::Result<Vec<PathBuf>> {
|
||||||
let mut archive = tar::Archive::new(reader);
|
let mut archive = tar::Archive::new(reader);
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ pub fn unpack_archive(
|
|||||||
let mut file = file?;
|
let mut file = file?;
|
||||||
|
|
||||||
let file_path = output_folder.join(file.path()?);
|
let file_path = output_folder.join(file.path()?);
|
||||||
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, skip_questions_positively)? {
|
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, question_policy)? {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use zip::{self, read::ZipFile, ZipArchive};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
info,
|
info,
|
||||||
utils::{self, dir_is_empty, strip_cur_dir, Bytes},
|
utils::{self, dir_is_empty, strip_cur_dir, Bytes, QuestionPolicy},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::utf8::get_invalid_utf8_paths;
|
use self::utf8::get_invalid_utf8_paths;
|
||||||
@ -20,7 +20,7 @@ use self::utf8::get_invalid_utf8_paths;
|
|||||||
pub fn unpack_archive<R>(
|
pub fn unpack_archive<R>(
|
||||||
mut archive: ZipArchive<R>,
|
mut archive: ZipArchive<R>,
|
||||||
into: &Path,
|
into: &Path,
|
||||||
skip_questions_positively: Option<bool>,
|
question_policy: QuestionPolicy,
|
||||||
) -> crate::Result<Vec<PathBuf>>
|
) -> crate::Result<Vec<PathBuf>>
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read + Seek,
|
||||||
@ -34,7 +34,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let file_path = into.join(file_path);
|
let file_path = into.join(file_path);
|
||||||
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, skip_questions_positively)? {
|
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, question_policy)? {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,23 +7,24 @@ use std::{
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
|
pub use crate::utils::QuestionPolicy;
|
||||||
use crate::{Error, Opts, Subcommand};
|
use crate::{Error, Opts, Subcommand};
|
||||||
|
|
||||||
impl Opts {
|
impl Opts {
|
||||||
/// A helper method that calls `clap::Parser::parse` and then translates relative paths to absolute.
|
/// A helper method that calls `clap::Parser::parse` and then translates relative paths to absolute.
|
||||||
/// Also determines if the user wants to skip questions or not
|
/// Also determines if the user wants to skip questions or not
|
||||||
pub fn parse_args() -> crate::Result<(Self, Option<bool>)> {
|
pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> {
|
||||||
let mut opts: Self = Self::parse();
|
let mut opts: Self = Self::parse();
|
||||||
|
|
||||||
let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. }) = &mut opts.cmd;
|
let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. }) = &mut opts.cmd;
|
||||||
*files = canonicalize_files(files)?;
|
*files = canonicalize_files(files)?;
|
||||||
|
|
||||||
let skip_questions_positively = if opts.yes {
|
let skip_questions_positively = if opts.yes {
|
||||||
Some(true)
|
QuestionPolicy::AlwaysYes
|
||||||
} else if opts.no {
|
} else if opts.no {
|
||||||
Some(false)
|
QuestionPolicy::AlwaysNo
|
||||||
} else {
|
} else {
|
||||||
None
|
QuestionPolicy::Ask
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((opts, skip_questions_positively))
|
Ok((opts, skip_questions_positively))
|
||||||
|
@ -20,7 +20,7 @@ use crate::{
|
|||||||
info,
|
info,
|
||||||
utils::nice_directory_display,
|
utils::nice_directory_display,
|
||||||
utils::to_utf,
|
utils::to_utf,
|
||||||
utils::{self, dir_is_empty},
|
utils::{self, dir_is_empty, QuestionPolicy},
|
||||||
Error, Opts, Subcommand,
|
Error, Opts, Subcommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ fn represents_several_files(files: &[PathBuf]) -> bool {
|
|||||||
files.iter().any(is_non_empty_dir) || files.len() > 1
|
files.iter().any(is_non_empty_dir) || files.len() > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result<()> {
|
pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
||||||
match args.cmd {
|
match args.cmd {
|
||||||
Subcommand::Compress { files, output: output_path } => {
|
Subcommand::Compress { files, output: output_path } => {
|
||||||
// Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma]
|
// Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma]
|
||||||
@ -93,7 +93,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
|||||||
return Err(Error::with_reason(reason));
|
return Err(Error::with_reason(reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
if output_path.exists() && !utils::user_wants_to_overwrite(&output_path, skip_questions_positively)? {
|
if output_path.exists() && !utils::user_wants_to_overwrite(&output_path, question_policy)? {
|
||||||
// User does not want to overwrite this file
|
// User does not want to overwrite this file
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
|||||||
let output_folder = output_folder.as_ref().map(|path| path.as_ref());
|
let output_folder = output_folder.as_ref().map(|path| path.as_ref());
|
||||||
|
|
||||||
for ((input_path, formats), file_name) in files.iter().zip(formats).zip(output_paths) {
|
for ((input_path, formats), file_name) in files.iter().zip(formats).zip(output_paths) {
|
||||||
decompress_file(input_path, formats, output_folder, file_name, skip_questions_positively)?;
|
decompress_file(input_path, formats, output_folder, file_name, question_policy)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,7 +288,7 @@ fn decompress_file(
|
|||||||
formats: Vec<extension::CompressionFormat>,
|
formats: Vec<extension::CompressionFormat>,
|
||||||
output_folder: Option<&Path>,
|
output_folder: Option<&Path>,
|
||||||
file_name: &Path,
|
file_name: &Path,
|
||||||
skip_questions_positively: Option<bool>,
|
question_policy: QuestionPolicy,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
// TODO: improve error message
|
// TODO: improve error message
|
||||||
let reader = fs::File::open(&input_file_path)?;
|
let reader = fs::File::open(&input_file_path)?;
|
||||||
@ -310,7 +310,7 @@ fn decompress_file(
|
|||||||
if let [Zip] = *formats.as_slice() {
|
if let [Zip] = *formats.as_slice() {
|
||||||
utils::create_dir_if_non_existent(output_folder)?;
|
utils::create_dir_if_non_existent(output_folder)?;
|
||||||
let zip_archive = zip::ZipArchive::new(reader)?;
|
let zip_archive = zip::ZipArchive::new(reader)?;
|
||||||
let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
|
let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -348,27 +348,27 @@ fn decompress_file(
|
|||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_path));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_path));
|
||||||
}
|
}
|
||||||
Tar => {
|
Tar => {
|
||||||
let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?;
|
let _ = crate::archive::tar::unpack_archive(reader, output_folder, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
||||||
}
|
}
|
||||||
Tgz => {
|
Tgz => {
|
||||||
let reader = chain_reader_decoder(&Gzip, reader)?;
|
let reader = chain_reader_decoder(&Gzip, reader)?;
|
||||||
let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?;
|
let _ = crate::archive::tar::unpack_archive(reader, output_folder, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
||||||
}
|
}
|
||||||
Tbz => {
|
Tbz => {
|
||||||
let reader = chain_reader_decoder(&Bzip, reader)?;
|
let reader = chain_reader_decoder(&Bzip, reader)?;
|
||||||
let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?;
|
let _ = crate::archive::tar::unpack_archive(reader, output_folder, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
||||||
}
|
}
|
||||||
Tlzma => {
|
Tlzma => {
|
||||||
let reader = chain_reader_decoder(&Lzma, reader)?;
|
let reader = chain_reader_decoder(&Lzma, reader)?;
|
||||||
let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?;
|
let _ = crate::archive::tar::unpack_archive(reader, output_folder, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
||||||
}
|
}
|
||||||
Tzst => {
|
Tzst => {
|
||||||
let reader = chain_reader_decoder(&Zstd, reader)?;
|
let reader = chain_reader_decoder(&Zstd, reader)?;
|
||||||
let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?;
|
let _ = crate::archive::tar::unpack_archive(reader, output_folder, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
||||||
}
|
}
|
||||||
Zip => {
|
Zip => {
|
||||||
@ -383,7 +383,7 @@ fn decompress_file(
|
|||||||
io::copy(&mut reader, &mut vec)?;
|
io::copy(&mut reader, &mut vec)?;
|
||||||
let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
|
let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
|
||||||
|
|
||||||
let _ = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
|
let _ = crate::archive::zip::unpack_archive(zip_archive, output_folder, question_policy)?;
|
||||||
|
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
||||||
}
|
}
|
||||||
|
21
src/utils.rs
21
src/utils.rs
@ -43,11 +43,11 @@ pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
|
|||||||
Ok(previous_location)
|
Ok(previous_location)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_wants_to_overwrite(path: &Path, skip_questions_positively: Option<bool>) -> crate::Result<bool> {
|
pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result<bool> {
|
||||||
match skip_questions_positively {
|
match question_policy {
|
||||||
Some(true) => Ok(true),
|
QuestionPolicy::AlwaysYes => Ok(true),
|
||||||
Some(false) => Ok(false),
|
QuestionPolicy::AlwaysNo => Ok(false),
|
||||||
None => {
|
QuestionPolicy::Ask => {
|
||||||
let path = to_utf(strip_cur_dir(path));
|
let path = to_utf(strip_cur_dir(path));
|
||||||
let path = Some(path.as_str());
|
let path = Some(path.as_str());
|
||||||
let placeholder = Some("FILE");
|
let placeholder = Some("FILE");
|
||||||
@ -126,6 +126,17 @@ impl std::fmt::Display for Bytes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
/// How overwrite questions should be handled
|
||||||
|
pub enum QuestionPolicy {
|
||||||
|
/// Ask ever time
|
||||||
|
Ask,
|
||||||
|
/// Skip overwrite questions positively
|
||||||
|
AlwaysYes,
|
||||||
|
/// Skip overwrite questions negatively
|
||||||
|
AlwaysNo,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -7,7 +7,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ouch::{commands::run, Opts, Subcommand};
|
use ouch::{cli::QuestionPolicy, commands::run, Opts, Subcommand};
|
||||||
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use utils::*;
|
use utils::*;
|
||||||
@ -180,7 +180,7 @@ fn extract_files(archive_path: &Path) -> Vec<PathBuf> {
|
|||||||
output: Some(extraction_output_folder.clone()),
|
output: Some(extraction_output_folder.clone()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
run(command, None).expect("Failed to extract");
|
run(command, QuestionPolicy::Ask).expect("Failed to extract");
|
||||||
|
|
||||||
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ouch::{commands::run, Opts, Subcommand};
|
use ouch::{cli::QuestionPolicy, commands::run, Opts, Subcommand};
|
||||||
|
|
||||||
pub fn create_empty_dir(at: &Path, filename: &str) -> PathBuf {
|
pub fn create_empty_dir(at: &Path, filename: &str) -> PathBuf {
|
||||||
let dirname = Path::new(filename);
|
let dirname = Path::new(filename);
|
||||||
@ -27,7 +27,7 @@ pub fn compress_files(at: &Path, paths_to_compress: &[PathBuf], format: &str) ->
|
|||||||
no: false,
|
no: false,
|
||||||
cmd: Subcommand::Compress { files: paths_to_compress.to_vec(), output: archive_path.clone() },
|
cmd: Subcommand::Compress { files: paths_to_compress.to_vec(), output: archive_path.clone() },
|
||||||
};
|
};
|
||||||
run(command, None).expect("Failed to compress test dummy files");
|
run(command, QuestionPolicy::Ask).expect("Failed to compress test dummy files");
|
||||||
|
|
||||||
archive_path
|
archive_path
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ pub fn extract_files(archive_path: &Path) -> Vec<PathBuf> {
|
|||||||
output: Some(extraction_output_folder.clone()),
|
output: Some(extraction_output_folder.clone()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
run(command, None).expect("Failed to extract");
|
run(command, QuestionPolicy::Ask).expect("Failed to extract");
|
||||||
|
|
||||||
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user