mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-22 17:40:17 +00:00
commit
95ab1e763b
@ -24,6 +24,8 @@ zip = "0.5.11"
|
|||||||
# Dependency from workspace
|
# Dependency from workspace
|
||||||
oof = { path = "./oof" }
|
oof = { path = "./oof" }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
termion = "1.5.6"
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
30
src/bytes.rs
30
src/bytes.rs
@ -1,30 +0,0 @@
|
|||||||
use std::cmp;
|
|
||||||
|
|
||||||
const UNITS: [&str; 4] = ["B", "kB", "MB", "GB"];
|
|
||||||
|
|
||||||
pub struct Bytes {
|
|
||||||
bytes: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bytes {
|
|
||||||
pub fn new(bytes: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
bytes: bytes as f64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Bytes {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let num = self.bytes;
|
|
||||||
debug_assert!(num >= 0.0);
|
|
||||||
if num < 1_f64 {
|
|
||||||
return write!(f, "{} B", num);
|
|
||||||
}
|
|
||||||
let delimiter = 1000_f64;
|
|
||||||
let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4);
|
|
||||||
|
|
||||||
write!(f, "{:.2} ", num / delimiter.powi(exponent))?;
|
|
||||||
write!(f, "{}", UNITS[exponent as usize])
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,8 +7,6 @@ use std::{
|
|||||||
|
|
||||||
use oof::{arg_flag, flag};
|
use oof::{arg_flag, flag};
|
||||||
|
|
||||||
pub const VERSION: &str = "0.1.5";
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Files to be compressed
|
/// Files to be compressed
|
||||||
@ -29,7 +27,6 @@ pub enum Command {
|
|||||||
pub struct CommandInfo {
|
pub struct CommandInfo {
|
||||||
pub command: Command,
|
pub command: Command,
|
||||||
pub flags: oof::Flags,
|
pub flags: oof::Flags,
|
||||||
// pub config: Config, // From .TOML, maybe, in the future
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls parse_args_and_flags_from using std::env::args_os ( argv )
|
/// Calls parse_args_and_flags_from using std::env::args_os ( argv )
|
||||||
@ -41,7 +38,6 @@ pub fn parse_args() -> crate::Result<ParsedArgs> {
|
|||||||
pub struct ParsedArgs {
|
pub struct ParsedArgs {
|
||||||
pub command: Command,
|
pub command: Command,
|
||||||
pub flags: oof::Flags,
|
pub flags: oof::Flags,
|
||||||
// pub program_called: OsString, // Useful?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize<'a, P>(path: P) -> crate::Result<PathBuf>
|
fn canonicalize<'a, P>(path: P) -> crate::Result<PathBuf>
|
||||||
@ -54,7 +50,7 @@ where
|
|||||||
if !path.as_ref().exists() {
|
if !path.as_ref().exists() {
|
||||||
Err(crate::Error::FileNotFound(PathBuf::from(path.as_ref())))
|
Err(crate::Error::FileNotFound(PathBuf::from(path.as_ref())))
|
||||||
} else {
|
} else {
|
||||||
eprintln!("{} {}", "[ERROR]", io_err);
|
eprintln!("[ERROR] {}", io_err);
|
||||||
Err(crate::Error::IoError)
|
Err(crate::Error::IoError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
265
src/commands.rs
Normal file
265
src/commands.rs
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::Write,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cli::Command,
|
||||||
|
compressors::{
|
||||||
|
BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor,
|
||||||
|
ZipCompressor,
|
||||||
|
},
|
||||||
|
decompressors::{
|
||||||
|
BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor,
|
||||||
|
TarDecompressor, ZipDecompressor,
|
||||||
|
},
|
||||||
|
dialogs::Confirmation,
|
||||||
|
extension::{CompressionFormat, Extension},
|
||||||
|
file::File,
|
||||||
|
utils,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn run(command: Command, flags: &oof::Flags) -> crate::Result<()> {
|
||||||
|
match command {
|
||||||
|
Command::Compress {
|
||||||
|
files,
|
||||||
|
compressed_output_path,
|
||||||
|
} => compress_files(files, &compressed_output_path, flags)?,
|
||||||
|
Command::Decompress {
|
||||||
|
files,
|
||||||
|
output_folder,
|
||||||
|
} => {
|
||||||
|
// From Option<PathBuf> to Option<&Path>
|
||||||
|
let output_folder = output_folder.as_ref().map(|path| Path::new(path));
|
||||||
|
for file in files.iter() {
|
||||||
|
decompress_file(file, output_folder, flags)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::ShowHelp => crate::help_command(),
|
||||||
|
Command::ShowVersion => crate::version_command(),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoxedCompressor = Box<dyn Compressor>;
|
||||||
|
type BoxedDecompressor = Box<dyn Decompressor>;
|
||||||
|
|
||||||
|
fn get_compressor(file: &File) -> crate::Result<(Option<BoxedCompressor>, BoxedCompressor)> {
|
||||||
|
let extension = match &file.extension {
|
||||||
|
Some(extension) => extension.clone(),
|
||||||
|
None => {
|
||||||
|
// This is reached when the output file given does not have an extension or has an unsupported one
|
||||||
|
return Err(crate::Error::MissingExtensionError(file.path.to_path_buf()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supported first compressors:
|
||||||
|
// .tar and .zip
|
||||||
|
let first_compressor: Option<Box<dyn Compressor>> = match extension.first_ext {
|
||||||
|
Some(ext) => match ext {
|
||||||
|
CompressionFormat::Tar => Some(Box::new(TarCompressor)),
|
||||||
|
CompressionFormat::Zip => Some(Box::new(ZipCompressor)),
|
||||||
|
CompressionFormat::Bzip => Some(Box::new(BzipCompressor)),
|
||||||
|
CompressionFormat::Gzip => Some(Box::new(GzipCompressor)),
|
||||||
|
CompressionFormat::Lzma => Some(Box::new(LzmaCompressor)),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supported second compressors:
|
||||||
|
// any
|
||||||
|
let second_compressor: Box<dyn Compressor> = match extension.second_ext {
|
||||||
|
CompressionFormat::Tar => Box::new(TarCompressor),
|
||||||
|
CompressionFormat::Zip => Box::new(ZipCompressor),
|
||||||
|
CompressionFormat::Bzip => Box::new(BzipCompressor),
|
||||||
|
CompressionFormat::Gzip => Box::new(GzipCompressor),
|
||||||
|
CompressionFormat::Lzma => Box::new(LzmaCompressor),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((first_compressor, second_compressor))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_decompressor(file: &File) -> crate::Result<(Option<BoxedDecompressor>, BoxedDecompressor)> {
|
||||||
|
let extension = match &file.extension {
|
||||||
|
Some(extension) => extension.clone(),
|
||||||
|
None => {
|
||||||
|
// This block *should* be unreachable
|
||||||
|
eprintln!(
|
||||||
|
"{} reached Evaluator::get_decompressor without known extension.",
|
||||||
|
"[internal error]".red()
|
||||||
|
);
|
||||||
|
return Err(crate::Error::InvalidInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let second_decompressor: Box<dyn Decompressor> = match extension.second_ext {
|
||||||
|
CompressionFormat::Tar => Box::new(TarDecompressor),
|
||||||
|
CompressionFormat::Zip => Box::new(ZipDecompressor),
|
||||||
|
CompressionFormat::Gzip => Box::new(GzipDecompressor),
|
||||||
|
CompressionFormat::Lzma => Box::new(LzmaDecompressor),
|
||||||
|
CompressionFormat::Bzip => Box::new(BzipDecompressor),
|
||||||
|
};
|
||||||
|
|
||||||
|
let first_decompressor: Option<Box<dyn Decompressor>> = match extension.first_ext {
|
||||||
|
Some(ext) => match ext {
|
||||||
|
CompressionFormat::Tar => Some(Box::new(TarDecompressor)),
|
||||||
|
CompressionFormat::Zip => Some(Box::new(ZipDecompressor)),
|
||||||
|
_other => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((first_decompressor, second_decompressor))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress_file_in_memory(
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
file_path: &Path,
|
||||||
|
decompressor: Option<Box<dyn Decompressor>>,
|
||||||
|
output_file: Option<File>,
|
||||||
|
extension: Option<Extension>,
|
||||||
|
flags: &oof::Flags,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let output_file_path = utils::get_destination_path(&output_file);
|
||||||
|
|
||||||
|
let file_name = file_path
|
||||||
|
.file_stem()
|
||||||
|
.map(Path::new)
|
||||||
|
.unwrap_or(output_file_path);
|
||||||
|
|
||||||
|
if "." == file_name.as_os_str() {
|
||||||
|
// I believe this is only possible when the supplied input has a name
|
||||||
|
// of the sort `.tar` or `.zip' and no output has been supplied.
|
||||||
|
// file_name = OsStr::new("ouch-output");
|
||||||
|
todo!("Pending review, what is this supposed to do??");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a decompressor to use, we'll create a file in-memory and decompress it
|
||||||
|
let decompressor = match decompressor {
|
||||||
|
Some(decompressor) => decompressor,
|
||||||
|
None => {
|
||||||
|
// There is no more processing to be done on the input file (or there is but currently unsupported)
|
||||||
|
// Therefore, we'll save what we have in memory into a file.
|
||||||
|
println!("{}: saving to {:?}.", "info".yellow(), file_name);
|
||||||
|
|
||||||
|
if file_name.exists() {
|
||||||
|
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
||||||
|
if !utils::permission_for_overwriting(&file_name, flags, &confirm)? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut f = fs::File::create(output_file_path.join(file_name))?;
|
||||||
|
f.write_all(&bytes)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = File {
|
||||||
|
path: file_name,
|
||||||
|
contents_in_memory: Some(bytes),
|
||||||
|
extension,
|
||||||
|
};
|
||||||
|
|
||||||
|
let decompression_result = decompressor.decompress(file, &output_file, flags)?;
|
||||||
|
if let DecompressionResult::FileInMemory(_) = decompression_result {
|
||||||
|
unreachable!("Shouldn't");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress_files(
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
output_path: &Path,
|
||||||
|
flags: &oof::Flags,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let mut output = File::from(output_path)?;
|
||||||
|
|
||||||
|
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
||||||
|
let (first_compressor, second_compressor) = get_compressor(&output)?;
|
||||||
|
|
||||||
|
if output_path.exists() && !utils::permission_for_overwriting(&output_path, flags, &confirm)? {
|
||||||
|
// The user does not want to overwrite the file
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = match first_compressor {
|
||||||
|
Some(first_compressor) => {
|
||||||
|
let mut entry = Entry::Files(files);
|
||||||
|
let bytes = first_compressor.compress(entry)?;
|
||||||
|
|
||||||
|
output.contents_in_memory = Some(bytes);
|
||||||
|
entry = Entry::InMemory(output);
|
||||||
|
second_compressor.compress(entry)?
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let entry = Entry::Files(files);
|
||||||
|
second_compressor.compress(entry)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}: writing to {:?}. ({})",
|
||||||
|
"info".yellow(),
|
||||||
|
output_path,
|
||||||
|
utils::Bytes::new(bytes.len() as u64)
|
||||||
|
);
|
||||||
|
fs::write(output_path, bytes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress_file(
|
||||||
|
file_path: &Path,
|
||||||
|
output: Option<&Path>,
|
||||||
|
flags: &oof::Flags,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
// The file to be decompressed
|
||||||
|
let file = File::from(file_path)?;
|
||||||
|
// The file must have a supported decompressible format
|
||||||
|
if file.extension == None {
|
||||||
|
return Err(crate::Error::MissingExtensionError(PathBuf::from(
|
||||||
|
file_path,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = match output {
|
||||||
|
Some(inner) => Some(File::from(inner)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let (first_decompressor, second_decompressor) = get_decompressor(&file)?;
|
||||||
|
|
||||||
|
let extension = file.extension.clone();
|
||||||
|
|
||||||
|
let decompression_result = second_decompressor.decompress(file, &output, &flags)?;
|
||||||
|
|
||||||
|
match decompression_result {
|
||||||
|
DecompressionResult::FileInMemory(bytes) => {
|
||||||
|
// We'll now decompress a file currently in memory.
|
||||||
|
// This will currently happen in the case of .bz, .xz and .lzma
|
||||||
|
decompress_file_in_memory(
|
||||||
|
bytes,
|
||||||
|
file_path,
|
||||||
|
first_decompressor,
|
||||||
|
output,
|
||||||
|
extension,
|
||||||
|
flags,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
DecompressionResult::FilesUnpacked(_files) => {
|
||||||
|
// If the file's last extension was an archival method,
|
||||||
|
// such as .tar, .zip or (to-do) .rar, then we won't look for
|
||||||
|
// further processing.
|
||||||
|
// The reason for this is that cases such as "file.xz.tar" are too rare
|
||||||
|
// to worry about, at least at the moment.
|
||||||
|
|
||||||
|
// TODO: use the `files` variable for something
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -3,20 +3,15 @@ use std::{fs, io::Write, path::PathBuf};
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use super::{Compressor, Entry};
|
use super::{Compressor, Entry};
|
||||||
use crate::{
|
use crate::{extension::CompressionFormat, file::File, utils};
|
||||||
bytes::Bytes,
|
|
||||||
extension::CompressionFormat,
|
|
||||||
file::File,
|
|
||||||
utils::{check_for_multiple_files, ensure_exists},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BzipCompressor {}
|
pub struct BzipCompressor;
|
||||||
|
|
||||||
impl BzipCompressor {
|
impl BzipCompressor {
|
||||||
fn compress_files(files: Vec<PathBuf>, format: CompressionFormat) -> crate::Result<Vec<u8>> {
|
fn compress_files(files: Vec<PathBuf>, format: CompressionFormat) -> crate::Result<Vec<u8>> {
|
||||||
check_for_multiple_files(&files, &format)?;
|
utils::check_for_multiple_files(&files, &format)?;
|
||||||
let path = &files[0];
|
let path = &files[0];
|
||||||
ensure_exists(path)?;
|
utils::ensure_exists(path)?;
|
||||||
let contents = {
|
let contents = {
|
||||||
let bytes = fs::read(path)?;
|
let bytes = fs::read(path)?;
|
||||||
Self::compress_bytes(&*bytes)?
|
Self::compress_bytes(&*bytes)?
|
||||||
@ -26,7 +21,7 @@ impl BzipCompressor {
|
|||||||
"{}: compressed {:?} into memory ({})",
|
"{}: compressed {:?} into memory ({})",
|
||||||
"info".yellow(),
|
"info".yellow(),
|
||||||
&path,
|
&path,
|
||||||
Bytes::new(contents.len() as u64)
|
utils::Bytes::new(contents.len() as u64)
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
|
@ -2,12 +2,6 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use crate::file::File;
|
use crate::file::File;
|
||||||
|
|
||||||
// pub enum CompressionResult {
|
|
||||||
// ZipArchive(Vec<u8>),
|
|
||||||
// TarArchive(Vec<u8>),
|
|
||||||
// FileInMemory(Vec<u8>)
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub enum Entry<'a> {
|
pub enum Entry<'a> {
|
||||||
Files(Vec<PathBuf>),
|
Files(Vec<PathBuf>),
|
||||||
InMemory(File<'a>),
|
InMemory(File<'a>),
|
||||||
|
@ -3,24 +3,19 @@ use std::{fs, io::Write, path::PathBuf};
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use super::{Compressor, Entry};
|
use super::{Compressor, Entry};
|
||||||
use crate::{
|
use crate::{extension::CompressionFormat, file::File, utils};
|
||||||
bytes::Bytes,
|
|
||||||
extension::CompressionFormat,
|
|
||||||
file::File,
|
|
||||||
utils::{check_for_multiple_files, ensure_exists},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct GzipCompressor {}
|
pub struct GzipCompressor;
|
||||||
|
|
||||||
impl GzipCompressor {
|
impl GzipCompressor {
|
||||||
pub fn compress_files(
|
pub fn compress_files(
|
||||||
files: Vec<PathBuf>,
|
files: Vec<PathBuf>,
|
||||||
format: CompressionFormat,
|
format: CompressionFormat,
|
||||||
) -> crate::Result<Vec<u8>> {
|
) -> crate::Result<Vec<u8>> {
|
||||||
check_for_multiple_files(&files, &format)?;
|
utils::check_for_multiple_files(&files, &format)?;
|
||||||
|
|
||||||
let path = &files[0];
|
let path = &files[0];
|
||||||
ensure_exists(path)?;
|
utils::ensure_exists(path)?;
|
||||||
|
|
||||||
let bytes = {
|
let bytes = {
|
||||||
let bytes = fs::read(path)?;
|
let bytes = fs::read(path)?;
|
||||||
@ -31,7 +26,7 @@ impl GzipCompressor {
|
|||||||
"{}: compressed {:?} into memory ({})",
|
"{}: compressed {:?} into memory ({})",
|
||||||
"info".yellow(),
|
"info".yellow(),
|
||||||
&path,
|
&path,
|
||||||
Bytes::new(bytes.len() as u64)
|
utils::Bytes::new(bytes.len() as u64)
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
|
@ -3,24 +3,19 @@ use std::{fs, io::Write, path::PathBuf};
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use super::{Compressor, Entry};
|
use super::{Compressor, Entry};
|
||||||
use crate::{
|
use crate::{extension::CompressionFormat, file::File, utils};
|
||||||
bytes::Bytes,
|
|
||||||
extension::CompressionFormat,
|
|
||||||
file::File,
|
|
||||||
utils::{check_for_multiple_files, ensure_exists},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct LzmaCompressor {}
|
pub struct LzmaCompressor;
|
||||||
|
|
||||||
impl LzmaCompressor {
|
impl LzmaCompressor {
|
||||||
pub fn compress_files(
|
pub fn compress_files(
|
||||||
files: Vec<PathBuf>,
|
files: Vec<PathBuf>,
|
||||||
format: CompressionFormat,
|
format: CompressionFormat,
|
||||||
) -> crate::Result<Vec<u8>> {
|
) -> crate::Result<Vec<u8>> {
|
||||||
check_for_multiple_files(&files, &format)?;
|
utils::check_for_multiple_files(&files, &format)?;
|
||||||
|
|
||||||
let path = &files[0];
|
let path = &files[0];
|
||||||
ensure_exists(path)?;
|
utils::ensure_exists(path)?;
|
||||||
|
|
||||||
let bytes = {
|
let bytes = {
|
||||||
let bytes = fs::read(path)?;
|
let bytes = fs::read(path)?;
|
||||||
@ -31,7 +26,7 @@ impl LzmaCompressor {
|
|||||||
"{}: compressed {:?} into memory ({})",
|
"{}: compressed {:?} into memory ({})",
|
||||||
"info".yellow(),
|
"info".yellow(),
|
||||||
&path,
|
&path,
|
||||||
Bytes::new(bytes.len() as u64)
|
utils::Bytes::new(bytes.len() as u64)
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//! This module contains the Compressor trait and an implementor for each format.
|
||||||
mod bzip;
|
mod bzip;
|
||||||
mod compressor;
|
mod compressor;
|
||||||
mod gzip;
|
mod gzip;
|
||||||
|
@ -7,7 +7,7 @@ use walkdir::WalkDir;
|
|||||||
use super::compressor::Entry;
|
use super::compressor::Entry;
|
||||||
use crate::{compressors::Compressor, file::File, utils};
|
use crate::{compressors::Compressor, file::File, utils};
|
||||||
|
|
||||||
pub struct TarCompressor {}
|
pub struct TarCompressor;
|
||||||
|
|
||||||
impl TarCompressor {
|
impl TarCompressor {
|
||||||
// TODO: implement this
|
// TODO: implement this
|
||||||
|
@ -8,7 +8,7 @@ use walkdir::WalkDir;
|
|||||||
use super::compressor::Entry;
|
use super::compressor::Entry;
|
||||||
use crate::{compressors::Compressor, file::File, utils};
|
use crate::{compressors::Compressor, file::File, utils};
|
||||||
|
|
||||||
pub struct ZipCompressor {}
|
pub struct ZipCompressor;
|
||||||
|
|
||||||
impl ZipCompressor {
|
impl ZipCompressor {
|
||||||
// TODO: this function does not seem to be working correctly ;/
|
// TODO: this function does not seem to be working correctly ;/
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! This module contains the Decompressor trait and an implementor for each format.
|
||||||
|
|
||||||
mod decompressor;
|
mod decompressor;
|
||||||
mod tar;
|
mod tar;
|
||||||
mod to_memory;
|
mod to_memory;
|
||||||
@ -5,11 +7,6 @@ mod zip;
|
|||||||
|
|
||||||
pub use decompressor::{DecompressionResult, Decompressor};
|
pub use decompressor::{DecompressionResult, Decompressor};
|
||||||
|
|
||||||
// These decompressors only decompress to memory,
|
pub use self::to_memory::{BzipDecompressor, GzipDecompressor, LzmaDecompressor};
|
||||||
// unlike {Tar, Zip}Decompressor which are capable of
|
// The .tar and .zip decompressors are capable of decompressing directly to storage
|
||||||
// decompressing directly to storage
|
pub use self::{tar::TarDecompressor, zip::ZipDecompressor};
|
||||||
pub use self::{
|
|
||||||
tar::TarDecompressor,
|
|
||||||
to_memory::{BzipDecompressor, GzipDecompressor, LzmaDecompressor},
|
|
||||||
zip::ZipDecompressor,
|
|
||||||
};
|
|
||||||
|
@ -8,10 +8,10 @@ use colored::Colorize;
|
|||||||
use tar::{self, Archive};
|
use tar::{self, Archive};
|
||||||
|
|
||||||
use super::decompressor::{DecompressionResult, Decompressor};
|
use super::decompressor::{DecompressionResult, Decompressor};
|
||||||
use crate::{bytes::Bytes, dialogs::Confirmation, file::File, utils};
|
use crate::{dialogs::Confirmation, file::File, utils};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TarDecompressor {}
|
pub struct TarDecompressor;
|
||||||
|
|
||||||
impl TarDecompressor {
|
impl TarDecompressor {
|
||||||
fn unpack_files(from: File, into: &Path, flags: &oof::Flags) -> crate::Result<Vec<PathBuf>> {
|
fn unpack_files(from: File, into: &Path, flags: &oof::Flags) -> crate::Result<Vec<PathBuf>> {
|
||||||
@ -48,7 +48,7 @@ impl TarDecompressor {
|
|||||||
"{}: {:?} extracted. ({})",
|
"{}: {:?} extracted. ({})",
|
||||||
"info".yellow(),
|
"info".yellow(),
|
||||||
into.join(file.path()?),
|
into.join(file.path()?),
|
||||||
Bytes::new(file.size())
|
utils::Bytes::new(file.size())
|
||||||
);
|
);
|
||||||
|
|
||||||
let file_path = fs::canonicalize(file_path)?;
|
let file_path = fs::canonicalize(file_path)?;
|
||||||
|
@ -6,14 +6,12 @@ use std::{
|
|||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
use super::decompressor::{DecompressionResult, Decompressor};
|
use super::decompressor::{DecompressionResult, Decompressor};
|
||||||
use crate::bytes::Bytes;
|
use crate::{extension::CompressionFormat, file::File, utils};
|
||||||
use crate::utils;
|
|
||||||
use crate::{extension::CompressionFormat, file::File};
|
|
||||||
|
|
||||||
struct DecompressorToMemory {}
|
struct DecompressorToMemory;
|
||||||
pub struct GzipDecompressor {}
|
pub struct GzipDecompressor;
|
||||||
pub struct LzmaDecompressor {}
|
pub struct LzmaDecompressor;
|
||||||
pub struct BzipDecompressor {}
|
pub struct BzipDecompressor;
|
||||||
|
|
||||||
fn get_decoder<'a>(
|
fn get_decoder<'a>(
|
||||||
format: CompressionFormat,
|
format: CompressionFormat,
|
||||||
@ -40,7 +38,7 @@ impl DecompressorToMemory {
|
|||||||
"{}: {:?} extracted into memory ({}).",
|
"{}: {:?} extracted into memory ({}).",
|
||||||
"info".yellow(),
|
"info".yellow(),
|
||||||
path,
|
path,
|
||||||
Bytes::new(bytes_read as u64)
|
utils::Bytes::new(bytes_read as u64)
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
|
@ -8,7 +8,7 @@ use colored::Colorize;
|
|||||||
use zip::{self, read::ZipFile, ZipArchive};
|
use zip::{self, read::ZipFile, ZipArchive};
|
||||||
|
|
||||||
use super::decompressor::{DecompressionResult, Decompressor};
|
use super::decompressor::{DecompressionResult, Decompressor};
|
||||||
use crate::{bytes::Bytes, dialogs::Confirmation, file::File, utils};
|
use crate::{dialogs::Confirmation, file::File, utils};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn __unix_set_permissions(file_path: &Path, file: &ZipFile) {
|
fn __unix_set_permissions(file_path: &Path, file: &ZipFile) {
|
||||||
@ -19,7 +19,7 @@ fn __unix_set_permissions(file_path: &Path, file: &ZipFile) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ZipDecompressor {}
|
pub struct ZipDecompressor;
|
||||||
|
|
||||||
impl ZipDecompressor {
|
impl ZipDecompressor {
|
||||||
fn check_for_comments(file: &ZipFile) {
|
fn check_for_comments(file: &ZipFile) {
|
||||||
@ -76,7 +76,7 @@ impl ZipDecompressor {
|
|||||||
"{}: \"{}\" extracted. ({})",
|
"{}: \"{}\" extracted. ({})",
|
||||||
"info".yellow(),
|
"info".yellow(),
|
||||||
file_path.display(),
|
file_path.display(),
|
||||||
Bytes::new(file.size())
|
utils::Bytes::new(file.size())
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut output_file = fs::File::create(&file_path)?;
|
let mut output_file = fs::File::create(&file_path)?;
|
||||||
|
@ -66,7 +66,10 @@ impl fmt::Display for Error {
|
|||||||
}
|
}
|
||||||
Error::MissingArgumentsForCompression => {
|
Error::MissingArgumentsForCompression => {
|
||||||
write!(f, "{} ", "[ERROR]".red())?;
|
write!(f, "{} ", "[ERROR]".red())?;
|
||||||
write!(f,"The compress subcommands demands at least 2 arguments, see usage: <TODO-USAGE>")
|
let spacing = " ";
|
||||||
|
writeln!(f,"The compress subcommands demands at least 2 arguments, an input file and an output file.")?;
|
||||||
|
writeln!(f,"{}Example: `ouch compress img.jpeg img.zip", spacing)?;
|
||||||
|
write!(f,"{}For more information, run `ouch --help`", spacing)
|
||||||
}
|
}
|
||||||
Error::InternalError => {
|
Error::InternalError => {
|
||||||
write!(f, "{} ", "[ERROR]".red())?;
|
write!(f, "{} ", "[ERROR]".red())?;
|
||||||
|
298
src/evaluator.rs
298
src/evaluator.rs
@ -1,298 +0,0 @@
|
|||||||
use std::{
|
|
||||||
fs,
|
|
||||||
io::Write,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use colored::Colorize;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
bytes::Bytes,
|
|
||||||
cli::{Command, VERSION},
|
|
||||||
compressors::{
|
|
||||||
BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor,
|
|
||||||
ZipCompressor,
|
|
||||||
},
|
|
||||||
decompressors::{
|
|
||||||
BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor,
|
|
||||||
TarDecompressor, ZipDecompressor,
|
|
||||||
},
|
|
||||||
dialogs::Confirmation,
|
|
||||||
extension::{CompressionFormat, Extension},
|
|
||||||
file::File,
|
|
||||||
utils,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Evaluator {}
|
|
||||||
|
|
||||||
type BoxedCompressor = Box<dyn Compressor>;
|
|
||||||
type BoxedDecompressor = Box<dyn Decompressor>;
|
|
||||||
|
|
||||||
impl Evaluator {
|
|
||||||
pub fn get_compressor(
|
|
||||||
file: &File,
|
|
||||||
) -> crate::Result<(Option<BoxedCompressor>, BoxedCompressor)> {
|
|
||||||
let extension = match &file.extension {
|
|
||||||
Some(extension) => extension.clone(),
|
|
||||||
None => {
|
|
||||||
// This block *should* be unreachable
|
|
||||||
eprintln!(
|
|
||||||
"{} reached Evaluator::get_decompressor without known extension.",
|
|
||||||
"[internal error]".red()
|
|
||||||
);
|
|
||||||
return Err(crate::Error::InternalError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Supported first compressors:
|
|
||||||
// .tar and .zip
|
|
||||||
let first_compressor: Option<Box<dyn Compressor>> = match extension.first_ext {
|
|
||||||
Some(ext) => match ext {
|
|
||||||
CompressionFormat::Tar => Some(Box::new(TarCompressor {})),
|
|
||||||
CompressionFormat::Zip => Some(Box::new(ZipCompressor {})),
|
|
||||||
// _other => Some(Box::new(NifflerCompressor {})),
|
|
||||||
_other => {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Supported second compressors:
|
|
||||||
// any
|
|
||||||
let second_compressor: Box<dyn Compressor> = match extension.second_ext {
|
|
||||||
CompressionFormat::Tar => Box::new(TarCompressor {}),
|
|
||||||
CompressionFormat::Zip => Box::new(ZipCompressor {}),
|
|
||||||
CompressionFormat::Bzip => Box::new(BzipCompressor {}),
|
|
||||||
CompressionFormat::Gzip => Box::new(GzipCompressor {}),
|
|
||||||
CompressionFormat::Lzma => Box::new(LzmaCompressor {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((first_compressor, second_compressor))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_decompressor(
|
|
||||||
file: &File,
|
|
||||||
) -> crate::Result<(Option<BoxedDecompressor>, BoxedDecompressor)> {
|
|
||||||
let extension = match &file.extension {
|
|
||||||
Some(extension) => extension.clone(),
|
|
||||||
None => {
|
|
||||||
// This block *should* be unreachable
|
|
||||||
eprintln!(
|
|
||||||
"{} reached Evaluator::get_decompressor without known extension.",
|
|
||||||
"[internal error]".red()
|
|
||||||
);
|
|
||||||
return Err(crate::Error::InvalidInput);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let second_decompressor: Box<dyn Decompressor> = match extension.second_ext {
|
|
||||||
CompressionFormat::Tar => Box::new(TarDecompressor {}),
|
|
||||||
CompressionFormat::Zip => Box::new(ZipDecompressor {}),
|
|
||||||
CompressionFormat::Gzip => Box::new(GzipDecompressor {}),
|
|
||||||
CompressionFormat::Lzma => Box::new(LzmaDecompressor {}),
|
|
||||||
CompressionFormat::Bzip => Box::new(BzipDecompressor {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_decompressor: Option<Box<dyn Decompressor>> = match extension.first_ext {
|
|
||||||
Some(ext) => match ext {
|
|
||||||
CompressionFormat::Tar => Some(Box::new(TarDecompressor {})),
|
|
||||||
CompressionFormat::Zip => Some(Box::new(ZipDecompressor {})),
|
|
||||||
_other => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((first_decompressor, second_decompressor))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decompress_file_in_memory(
|
|
||||||
bytes: Vec<u8>,
|
|
||||||
file_path: &Path,
|
|
||||||
decompressor: Option<Box<dyn Decompressor>>,
|
|
||||||
output_file: Option<File>,
|
|
||||||
extension: Option<Extension>,
|
|
||||||
flags: &oof::Flags,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let output_file_path = utils::get_destination_path(&output_file);
|
|
||||||
|
|
||||||
let file_name = file_path
|
|
||||||
.file_stem()
|
|
||||||
.map(Path::new)
|
|
||||||
.unwrap_or(output_file_path);
|
|
||||||
|
|
||||||
if "." == file_name.as_os_str() {
|
|
||||||
// I believe this is only possible when the supplied input has a name
|
|
||||||
// of the sort `.tar` or `.zip' and no output has been supplied.
|
|
||||||
// file_name = OsStr::new("ouch-output");
|
|
||||||
todo!("Pending review, what is this supposed to do??");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a decompressor to use, we'll create a file in-memory and decompress it
|
|
||||||
let decompressor = match decompressor {
|
|
||||||
Some(decompressor) => decompressor,
|
|
||||||
None => {
|
|
||||||
// There is no more processing to be done on the input file (or there is but currently unsupported)
|
|
||||||
// Therefore, we'll save what we have in memory into a file.
|
|
||||||
println!("{}: saving to {:?}.", "info".yellow(), file_name);
|
|
||||||
|
|
||||||
if file_name.exists() {
|
|
||||||
let confirm =
|
|
||||||
Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
|
||||||
if !utils::permission_for_overwriting(&file_name, flags, &confirm)? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut f = fs::File::create(output_file_path.join(file_name))?;
|
|
||||||
f.write_all(&bytes)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = File {
|
|
||||||
path: file_name,
|
|
||||||
contents_in_memory: Some(bytes),
|
|
||||||
extension,
|
|
||||||
};
|
|
||||||
|
|
||||||
let decompression_result = decompressor.decompress(file, &output_file, flags)?;
|
|
||||||
if let DecompressionResult::FileInMemory(_) = decompression_result {
|
|
||||||
unreachable!("Shouldn't");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compress_files(
|
|
||||||
files: Vec<PathBuf>,
|
|
||||||
output_path: &Path,
|
|
||||||
flags: &oof::Flags,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let mut output = File::from(output_path)?;
|
|
||||||
|
|
||||||
let confirm = Confirmation::new("Do you want to overwrite 'FILE'?", Some("FILE"));
|
|
||||||
let (first_compressor, second_compressor) = Self::get_compressor(&output)?;
|
|
||||||
|
|
||||||
if output_path.exists()
|
|
||||||
&& !utils::permission_for_overwriting(&output_path, flags, &confirm)?
|
|
||||||
{
|
|
||||||
// The user does not want to overwrite the file
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = match first_compressor {
|
|
||||||
Some(first_compressor) => {
|
|
||||||
let mut entry = Entry::Files(files);
|
|
||||||
let bytes = first_compressor.compress(entry)?;
|
|
||||||
|
|
||||||
output.contents_in_memory = Some(bytes);
|
|
||||||
entry = Entry::InMemory(output);
|
|
||||||
second_compressor.compress(entry)?
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let entry = Entry::Files(files);
|
|
||||||
second_compressor.compress(entry)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"{}: writing to {:?}. ({})",
|
|
||||||
"info".yellow(),
|
|
||||||
output_path,
|
|
||||||
Bytes::new(bytes.len() as u64)
|
|
||||||
);
|
|
||||||
fs::write(output_path, bytes)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decompress_file(
|
|
||||||
file_path: &Path,
|
|
||||||
output: Option<&Path>,
|
|
||||||
flags: &oof::Flags,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
// The file to be decompressed
|
|
||||||
let file = File::from(file_path)?;
|
|
||||||
// The file must have a supported decompressible format
|
|
||||||
if file.extension == None {
|
|
||||||
return Err(crate::Error::MissingExtensionError(PathBuf::from(
|
|
||||||
file_path,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = match output {
|
|
||||||
Some(inner) => Some(File::from(inner)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
let (first_decompressor, second_decompressor) = Self::get_decompressor(&file)?;
|
|
||||||
|
|
||||||
let extension = file.extension.clone();
|
|
||||||
|
|
||||||
let decompression_result = second_decompressor.decompress(file, &output, &flags)?;
|
|
||||||
|
|
||||||
match decompression_result {
|
|
||||||
DecompressionResult::FileInMemory(bytes) => {
|
|
||||||
// We'll now decompress a file currently in memory.
|
|
||||||
// This will currently happen in the case of .bz, .xz and .lzma
|
|
||||||
Self::decompress_file_in_memory(
|
|
||||||
bytes,
|
|
||||||
file_path,
|
|
||||||
first_decompressor,
|
|
||||||
output,
|
|
||||||
extension,
|
|
||||||
flags,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
DecompressionResult::FilesUnpacked(_files) => {
|
|
||||||
// If the file's last extension was an archival method,
|
|
||||||
// such as .tar, .zip or (to-do) .rar, then we won't look for
|
|
||||||
// further processing.
|
|
||||||
// The reason for this is that cases such as "file.xz.tar" are too rare
|
|
||||||
// to worry about, at least at the moment.
|
|
||||||
|
|
||||||
// TODO: use the `files` variable for something
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evaluate(command: Command, flags: &oof::Flags) -> crate::Result<()> {
|
|
||||||
match command {
|
|
||||||
Command::Compress {
|
|
||||||
files,
|
|
||||||
compressed_output_path,
|
|
||||||
} => Self::compress_files(files, &compressed_output_path, flags)?,
|
|
||||||
Command::Decompress {
|
|
||||||
files,
|
|
||||||
output_folder,
|
|
||||||
} => {
|
|
||||||
// From Option<PathBuf> to Option<&Path>
|
|
||||||
let output_folder = output_folder.as_ref().map(|path| Path::new(path));
|
|
||||||
for file in files.iter() {
|
|
||||||
Self::decompress_file(file, output_folder, flags)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Command::ShowHelp => help_message(),
|
|
||||||
Command::ShowVersion => version_message(),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn version_message() {
|
|
||||||
println!("ouch {}", VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn help_message() {
|
|
||||||
version_message();
|
|
||||||
println!("Vinícius R. M. & João M. Bezerra");
|
|
||||||
println!("ouch is a unified compression & decompression utility");
|
|
||||||
println!();
|
|
||||||
println!(" COMPRESSION USAGE:");
|
|
||||||
println!(" ouch compress <input...> output-file");
|
|
||||||
println!("DECOMPRESSION USAGE:");
|
|
||||||
println!(" ouch <input> [-o/--output output-folder]");
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use CompressionFormat::*;
|
use CompressionFormat::*;
|
||||||
|
|
||||||
use crate::utils::to_utf;
|
use crate::utils;
|
||||||
|
|
||||||
/// Represents the extension of a file, but only really caring about
|
/// Represents the extension of a file, but only really caring about
|
||||||
/// compression formats (and .tar).
|
/// compression formats (and .tar).
|
||||||
@ -49,7 +49,7 @@ impl Extension {
|
|||||||
_ if ext == "gz" => Ok(Gzip),
|
_ if ext == "gz" => Ok(Gzip),
|
||||||
_ if ext == "bz" || ext == "bz2" => Ok(Bzip),
|
_ if ext == "bz" || ext == "bz2" => Ok(Bzip),
|
||||||
_ if ext == "xz" || ext == "lz" || ext == "lzma" => Ok(Lzma),
|
_ if ext == "xz" || ext == "lz" || ext == "lzma" => Ok(Lzma),
|
||||||
other => Err(crate::Error::UnknownExtensionError(to_utf(other))),
|
other => Err(crate::Error::UnknownExtensionError(utils::to_utf(other))),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (first_ext, second_ext) = match get_extension_from_filename(&file_name) {
|
let (first_ext, second_ext) = match get_extension_from_filename(&file_name) {
|
||||||
@ -86,16 +86,11 @@ impl Extension {
|
|||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
/// Accepted extensions for input and output
|
/// Accepted extensions for input and output
|
||||||
pub enum CompressionFormat {
|
pub enum CompressionFormat {
|
||||||
// .gz
|
Gzip, // .gz
|
||||||
Gzip,
|
Bzip, // .bz
|
||||||
// .bz
|
Lzma, // .lzma
|
||||||
Bzip,
|
Tar, // .tar (technically not a compression extension, but will do for now)
|
||||||
// .lzma
|
Zip, // .zip
|
||||||
Lzma,
|
|
||||||
// .tar (technically not a compression extension, but will do for now)
|
|
||||||
Tar,
|
|
||||||
// .zip
|
|
||||||
Zip,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extension_from_os_str(ext: &OsStr) -> Result<CompressionFormat, crate::Error> {
|
fn extension_from_os_str(ext: &OsStr) -> Result<CompressionFormat, crate::Error> {
|
||||||
|
81
src/lib.rs
Normal file
81
src/lib.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Public modules
|
||||||
|
pub mod cli;
|
||||||
|
pub mod commands;
|
||||||
|
|
||||||
|
// Private modules
|
||||||
|
mod compressors;
|
||||||
|
mod decompressors;
|
||||||
|
mod dialogs;
|
||||||
|
mod error;
|
||||||
|
mod extension;
|
||||||
|
mod file;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
pub use error::{Error, Result};
|
||||||
|
|
||||||
|
const VERSION: &str = "0.1.5";
|
||||||
|
|
||||||
|
fn help_command() {
|
||||||
|
use utils::colors::*;
|
||||||
|
/*
|
||||||
|
ouch - Obvious Unified Compressed files Helper
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
ouch <files...> Decompresses files.
|
||||||
|
|
||||||
|
ouch compress <files...> OUTPUT.EXT Compresses files into OUTPUT.EXT,
|
||||||
|
where EXT must be a supported format.
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Display this help information.
|
||||||
|
-y, --yes Skip overwrite questions.
|
||||||
|
-n, --no Skip overwrite questions.
|
||||||
|
--version Display version information.
|
||||||
|
|
||||||
|
SPECIFIC FLAGS:
|
||||||
|
-o, --output FOLDER_PATH When decompressing, to decompress files to
|
||||||
|
another folder.
|
||||||
|
|
||||||
|
Visit https://github.com/vrmiguel/ouch for more usage examples.
|
||||||
|
*/
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"\
|
||||||
|
{cyan}ouch{reset} - Obvious Unified Compression files Helper
|
||||||
|
|
||||||
|
{cyan}USAGE:{reset}
|
||||||
|
{green}ouch {magenta}<files...>{reset} Decompresses files.
|
||||||
|
|
||||||
|
{green}ouch compress {magenta}<files...> OUTPUT.EXT{reset} Compresses files into {magenta}OUTPUT.EXT{reset},
|
||||||
|
where {magenta}EXT{reset} must be a supported format.
|
||||||
|
|
||||||
|
{cyan}FLAGS:{reset}
|
||||||
|
{yellow}-h{white}, {yellow}--help{reset} Display this help information.
|
||||||
|
{yellow}-y{white}, {yellow}--yes{reset} Skip overwrite questions.
|
||||||
|
{yellow}-n{white}, {yellow}--no{reset} Skip overwrite questions.
|
||||||
|
{yellow}--version{reset} Display version information.
|
||||||
|
|
||||||
|
{cyan}SPECIFIC FLAGS:{reset}
|
||||||
|
{yellow}-o{reset}, {yellow}--output{reset} FOLDER_PATH When decompressing, to decompress files to
|
||||||
|
another folder.
|
||||||
|
|
||||||
|
Visit https://github.com/vrmiguel/ouch for more usage examples.",
|
||||||
|
magenta = magenta(),
|
||||||
|
white = white(),
|
||||||
|
green = green(),
|
||||||
|
yellow = yellow(),
|
||||||
|
reset = reset(),
|
||||||
|
cyan = cyan()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn version_command() {
|
||||||
|
use utils::colors::*;
|
||||||
|
println!(
|
||||||
|
"{green}ouch{reset} {}",
|
||||||
|
crate::VERSION,
|
||||||
|
green = green(),
|
||||||
|
reset = reset(),
|
||||||
|
);
|
||||||
|
}
|
24
src/main.rs
24
src/main.rs
@ -1,19 +1,7 @@
|
|||||||
mod bytes;
|
use ouch::{
|
||||||
mod cli;
|
cli::{parse_args, ParsedArgs},
|
||||||
mod compressors;
|
commands, Result,
|
||||||
mod decompressors;
|
};
|
||||||
mod dialogs;
|
|
||||||
mod error;
|
|
||||||
mod evaluator;
|
|
||||||
mod extension;
|
|
||||||
mod file;
|
|
||||||
mod test;
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use error::{Error, Result};
|
|
||||||
use evaluator::Evaluator;
|
|
||||||
|
|
||||||
use crate::cli::ParsedArgs;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(err) = run() {
|
if let Err(err) = run() {
|
||||||
@ -23,6 +11,6 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> crate::Result<()> {
|
fn run() -> crate::Result<()> {
|
||||||
let ParsedArgs { command, flags } = cli::parse_args()?;
|
let ParsedArgs { command, flags } = parse_args()?;
|
||||||
Evaluator::evaluate(command, &flags)
|
commands::run(command, &flags)
|
||||||
}
|
}
|
||||||
|
143
src/utils.rs
143
src/utils.rs
@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
env,
|
cmp, env,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -25,7 +25,7 @@ macro_rules! debug {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ensure_exists<'a, P>(path: P) -> crate::Result<()>
|
pub fn ensure_exists<'a, P>(path: P) -> crate::Result<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path> + 'a,
|
P: AsRef<Path> + 'a,
|
||||||
{
|
{
|
||||||
@ -36,19 +36,26 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_for_multiple_files(
|
pub fn check_for_multiple_files(
|
||||||
files: &[PathBuf],
|
files: &[PathBuf],
|
||||||
format: &CompressionFormat,
|
format: &CompressionFormat,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
if files.len() != 1 {
|
if files.len() != 1 {
|
||||||
eprintln!("{}: cannot compress multiple files directly to {:#?}.\n Try using an intermediate archival method such as Tar.\n Example: filename.tar{}", "[ERROR]".red(), format, format);
|
eprintln!(
|
||||||
|
"{}: cannot compress multiple files directly to {:#?}.\n\
|
||||||
|
Try using an intermediate archival method such as Tar.\n\
|
||||||
|
Example: filename.tar{}",
|
||||||
|
"[ERROR]".red(),
|
||||||
|
format,
|
||||||
|
format
|
||||||
|
);
|
||||||
return Err(crate::Error::InvalidInput);
|
return Err(crate::Error::InvalidInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create_path_if_non_existent(path: &Path) -> crate::Result<()> {
|
pub fn create_path_if_non_existent(path: &Path) -> crate::Result<()> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
println!(
|
println!(
|
||||||
"{}: attempting to create folder {:?}.",
|
"{}: attempting to create folder {:?}.",
|
||||||
@ -65,7 +72,7 @@ pub(crate) fn create_path_if_non_existent(path: &Path) -> crate::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_destination_path<'a>(dest: &'a Option<File>) -> &'a Path {
|
pub fn get_destination_path<'a>(dest: &'a Option<File>) -> &'a Path {
|
||||||
match dest {
|
match dest {
|
||||||
Some(output_file) => {
|
Some(output_file) => {
|
||||||
// Must be None according to the way command-line arg. parsing in Ouch works
|
// Must be None according to the way command-line arg. parsing in Ouch works
|
||||||
@ -76,7 +83,7 @@ pub(crate) fn get_destination_path<'a>(dest: &'a Option<File>) -> &'a Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn change_dir_and_return_parent(filename: &Path) -> crate::Result<PathBuf> {
|
pub fn change_dir_and_return_parent(filename: &Path) -> crate::Result<PathBuf> {
|
||||||
let previous_location = env::current_dir()?;
|
let previous_location = env::current_dir()?;
|
||||||
|
|
||||||
let parent = if let Some(parent) = filename.parent() {
|
let parent = if let Some(parent) = filename.parent() {
|
||||||
@ -114,3 +121,125 @@ pub fn to_utf(os_str: impl AsRef<OsStr>) -> String {
|
|||||||
let text = format!("{:?}", os_str.as_ref());
|
let text = format!("{:?}", os_str.as_ref());
|
||||||
text.trim_matches('"').to_string()
|
text.trim_matches('"').to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Bytes {
|
||||||
|
bytes: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Module with a list of bright colors.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
pub mod colors {
|
||||||
|
use termion::color::*;
|
||||||
|
|
||||||
|
pub fn reset() -> &'static str {
|
||||||
|
Reset.fg_str()
|
||||||
|
}
|
||||||
|
pub fn black() -> &'static str {
|
||||||
|
LightBlack.fg_str()
|
||||||
|
}
|
||||||
|
pub fn blue() -> &'static str {
|
||||||
|
LightBlue.fg_str()
|
||||||
|
}
|
||||||
|
pub fn cyan() -> &'static str {
|
||||||
|
LightCyan.fg_str()
|
||||||
|
}
|
||||||
|
pub fn green() -> &'static str {
|
||||||
|
LightGreen.fg_str()
|
||||||
|
}
|
||||||
|
pub fn magenta() -> &'static str {
|
||||||
|
LightMagenta.fg_str()
|
||||||
|
}
|
||||||
|
pub fn red() -> &'static str {
|
||||||
|
LightRed.fg_str()
|
||||||
|
}
|
||||||
|
pub fn white() -> &'static str {
|
||||||
|
LightWhite.fg_str()
|
||||||
|
}
|
||||||
|
pub fn yellow() -> &'static str {
|
||||||
|
LightYellow.fg_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Termion does not support Windows
|
||||||
|
#[allow(dead_code, non_upper_case_globals)]
|
||||||
|
#[cfg(not(target_family = "unix"))]
|
||||||
|
pub mod colors {
|
||||||
|
pub fn empty() -> &'static str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
pub const reset: fn() -> &'static str = empty;
|
||||||
|
pub const black: fn() -> &'static str = empty;
|
||||||
|
pub const blue: fn() -> &'static str = empty;
|
||||||
|
pub const cyan: fn() -> &'static str = empty;
|
||||||
|
pub const green: fn() -> &'static str = empty;
|
||||||
|
pub const magenta: fn() -> &'static str = empty;
|
||||||
|
pub const red: fn() -> &'static str = empty;
|
||||||
|
pub const white: fn() -> &'static str = empty;
|
||||||
|
pub const yellow: fn() -> &'static str = empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bytes {
|
||||||
|
const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"];
|
||||||
|
|
||||||
|
pub fn new(bytes: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
bytes: bytes as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Bytes {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let num = self.bytes;
|
||||||
|
debug_assert!(num >= 0.0);
|
||||||
|
if num < 1_f64 {
|
||||||
|
return write!(f, "{} B", num);
|
||||||
|
}
|
||||||
|
let delimiter = 1000_f64;
|
||||||
|
let exponent = cmp::min((num.ln() / 6.90775).floor() as i32, 4);
|
||||||
|
|
||||||
|
write!(f, "{:.2} ", num / delimiter.powi(exponent))?;
|
||||||
|
write!(f, "{}B", Bytes::UNIT_PREFIXES[exponent as usize])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pretty_bytes_formatting() {
|
||||||
|
fn format_bytes(bytes: u64) -> String {
|
||||||
|
format!("{}", Bytes::new(bytes))
|
||||||
|
}
|
||||||
|
let b = 1;
|
||||||
|
let kb = b * 1000;
|
||||||
|
let mb = kb * 1000;
|
||||||
|
let gb = mb * 1000;
|
||||||
|
|
||||||
|
assert_eq!("0 B", format_bytes(0)); // This is weird
|
||||||
|
assert_eq!("1.00 B", format_bytes(b));
|
||||||
|
assert_eq!("999.00 B", format_bytes(b * 999));
|
||||||
|
assert_eq!("12.00 MB", format_bytes(mb * 12));
|
||||||
|
assert_eq!("123.00 MB", format_bytes(mb * 123));
|
||||||
|
assert_eq!("5.50 MB", format_bytes(mb * 5 + kb * 500));
|
||||||
|
assert_eq!("7.54 GB", format_bytes(gb * 7 + 540 * mb));
|
||||||
|
assert_eq!("1.20 TB", format_bytes(gb * 1200));
|
||||||
|
|
||||||
|
// bytes
|
||||||
|
assert_eq!("234.00 B", format_bytes(234));
|
||||||
|
assert_eq!("999.00 B", format_bytes(999));
|
||||||
|
// kilobytes
|
||||||
|
assert_eq!("2.23 kB", format_bytes(2234));
|
||||||
|
assert_eq!("62.50 kB", format_bytes(62500));
|
||||||
|
assert_eq!("329.99 kB", format_bytes(329990));
|
||||||
|
// megabytes
|
||||||
|
assert_eq!("2.75 MB", format_bytes(2750000));
|
||||||
|
assert_eq!("55.00 MB", format_bytes(55000000));
|
||||||
|
assert_eq!("987.65 MB", format_bytes(987654321));
|
||||||
|
// gigabytes
|
||||||
|
assert_eq!("5.28 GB", format_bytes(5280000000));
|
||||||
|
assert_eq!("95.20 GB", format_bytes(95200000000));
|
||||||
|
assert_eq!("302.00 GB", format_bytes(302000000000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user