mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-07 12:05:46 +00:00
commit
272b3069ea
@ -4,7 +4,7 @@ version = "0.2.0"
|
|||||||
authors = ["Vinícius Rodrigues Miguel <vrmiguel99@gmail.com>", "João M. Bezerra <marcospb19@hotmail.com>"]
|
authors = ["Vinícius Rodrigues Miguel <vrmiguel99@gmail.com>", "João M. Bezerra <marcospb19@hotmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/vrmiguel/ouch"
|
repository = "https://github.com/ouch-org/ouch"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
keywords = ["decompression", "compression", "zip", "tar", "gzip"]
|
keywords = ["decompression", "compression", "zip", "tar", "gzip"]
|
||||||
categories = ["command-line-utilities", "compression", "encoding"]
|
categories = ["command-line-utilities", "compression", "encoding"]
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
|
//! Archive compression algorithms
|
||||||
|
|
||||||
pub mod tar;
|
pub mod tar;
|
||||||
pub mod zip;
|
pub mod zip;
|
||||||
|
@ -17,6 +17,7 @@ use crate::{
|
|||||||
QuestionPolicy,
|
QuestionPolicy,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Unpacks the archive given by `archive` into the folder given by `into`.
|
||||||
pub fn unpack_archive(
|
pub fn unpack_archive(
|
||||||
reader: Box<dyn Read>,
|
reader: Box<dyn Read>,
|
||||||
output_folder: &Path,
|
output_folder: &Path,
|
||||||
@ -43,6 +44,7 @@ pub fn unpack_archive(
|
|||||||
Ok(files_unpacked)
|
Ok(files_unpacked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
|
||||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
||||||
where
|
where
|
||||||
W: Write,
|
W: Write,
|
||||||
|
@ -73,6 +73,7 @@ where
|
|||||||
Ok(unpacked_files)
|
Ok(unpacked_files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
|
||||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
||||||
where
|
where
|
||||||
W: Write + Seek,
|
W: Write + Seek,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
//! CLI arg parser configuration, command detection and input treatment.
|
//! CLI configuration step, uses definitions from `opts.rs`.
|
||||||
|
//!
|
||||||
|
//! Also used to treat some inputs.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -35,6 +35,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Entrypoint of ouch, receives cli options and matches Subcommand
|
||||||
|
/// to decide current operation
|
||||||
pub fn run(args: Opts, question_policy: QuestionPolicy) -> 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 } => {
|
||||||
@ -182,6 +184,11 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compress files into an `output_file`
|
||||||
|
//
|
||||||
|
// files are the list of paths to be compressed: ["dir/file1.txt", "dir/file2.txt"]
|
||||||
|
// formats contains each format necessary for compression, example: [Tar, Gz] (in compression order)
|
||||||
|
// output_file is the resulting compressed file name, example: "compressed.tar.gz"
|
||||||
fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_file: fs::File) -> crate::Result<()> {
|
fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_file: fs::File) -> crate::Result<()> {
|
||||||
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
||||||
|
|
||||||
@ -259,6 +266,8 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<CompressionFormat>, output_f
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decompress a file
|
||||||
|
//
|
||||||
// File at input_file_path is opened for reading, example: "archive.tar.gz"
|
// File at input_file_path is opened for reading, example: "archive.tar.gz"
|
||||||
// formats contains each format necessary for decompression, example: [Gz, Tar] (in decompression order)
|
// formats contains each format necessary for decompression, example: [Gz, Tar] (in decompression order)
|
||||||
// output_dir it's where the file will be decompressed to
|
// output_dir it's where the file will be decompressed to
|
||||||
|
@ -12,15 +12,22 @@ use crate::utils::colors;
|
|||||||
|
|
||||||
/// Represents a confirmation dialog
|
/// Represents a confirmation dialog
|
||||||
pub struct Confirmation<'a> {
|
pub struct Confirmation<'a> {
|
||||||
|
/// Represents the message to the displayed
|
||||||
|
/// e.g.: "Do you want to overwrite 'FILE'?"
|
||||||
pub prompt: &'a str,
|
pub prompt: &'a str,
|
||||||
|
|
||||||
|
/// Represents a placeholder to be changed at runtime
|
||||||
|
/// e.g.: Some("FILE")
|
||||||
pub placeholder: Option<&'a str>,
|
pub placeholder: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Confirmation<'a> {
|
impl<'a> Confirmation<'a> {
|
||||||
|
/// New Confirmation
|
||||||
pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
|
pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
|
||||||
Self { prompt, placeholder: pattern }
|
Self { prompt, placeholder: pattern }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates user message and receives a boolean input to be used on the program
|
||||||
pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
|
pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
|
||||||
let message = match (self.placeholder, substitute) {
|
let message = match (self.placeholder, substitute) {
|
||||||
(None, _) => Cow::Borrowed(self.prompt),
|
(None, _) => Cow::Borrowed(self.prompt),
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
//! TODO: wrap `FinalError` in a variant to keep all `FinalError::display_and_crash()` function
|
//! TODO: wrap `FinalError` in a variant to keep all `FinalError::display_and_crash()` function
|
||||||
//! calls inside of this module.
|
//! calls inside of this module.
|
||||||
|
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -13,6 +15,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::utils::colors::*;
|
use crate::utils::colors::*;
|
||||||
|
|
||||||
|
/// Custom Ouch Errors
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
UnknownExtensionError(String),
|
UnknownExtensionError(String),
|
||||||
@ -91,7 +94,7 @@ impl fmt::Display for Error {
|
|||||||
FinalError::with_title(format!("Cannot compress to {:?}", filename))
|
FinalError::with_title(format!("Cannot compress to {:?}", filename))
|
||||||
.detail("Ouch could not detect the compression format")
|
.detail("Ouch could not detect the compression format")
|
||||||
.hint("Use a supported format extension, like '.zip' or '.tar.gz'")
|
.hint("Use a supported format extension, like '.zip' or '.tar.gz'")
|
||||||
.hint("Check https://github.com/vrmiguel/ouch for a full list of supported formats")
|
.hint("Check https://github.com/ouch-org/ouch for a full list of supported formats")
|
||||||
}
|
}
|
||||||
Error::WalkdirError { reason } => FinalError::with_title(reason),
|
Error::WalkdirError { reason } => FinalError::with_title(reason),
|
||||||
Error::FileNotFound(file) => {
|
Error::FileNotFound(file) => {
|
||||||
@ -128,7 +131,7 @@ impl fmt::Display for Error {
|
|||||||
.detail("This should not have happened")
|
.detail("This should not have happened")
|
||||||
.detail("It's probably our fault")
|
.detail("It's probably our fault")
|
||||||
.detail("Please help us improve by reporting the issue at:")
|
.detail("Please help us improve by reporting the issue at:")
|
||||||
.detail(format!(" {}https://github.com/vrmiguel/ouch/issues ", *CYAN))
|
.detail(format!(" {}https://github.com/ouch-org/ouch/issues ", *CYAN))
|
||||||
}
|
}
|
||||||
Error::IoError { reason } => FinalError::with_title(reason),
|
Error::IoError { reason } => FinalError::with_title(reason),
|
||||||
Error::CompressionTypo => {
|
Error::CompressionTypo => {
|
||||||
|
@ -4,6 +4,7 @@ use std::{ffi::OsStr, fmt, path::Path};
|
|||||||
|
|
||||||
use self::CompressionFormat::*;
|
use self::CompressionFormat::*;
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[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 {
|
||||||
@ -20,6 +21,7 @@ pub enum CompressionFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CompressionFormat {
|
impl CompressionFormat {
|
||||||
|
/// Currently supported archive formats are .tar (and aliases to it) and .zip
|
||||||
pub fn is_archive_format(&self) -> bool {
|
pub fn is_archive_format(&self) -> bool {
|
||||||
matches!(self, Tar | Tgz | Tbz | Tlzma | Tzst | Zip)
|
matches!(self, Tar | Tgz | Tbz | Tlzma | Tzst | Zip)
|
||||||
}
|
}
|
||||||
@ -46,6 +48,19 @@ impl fmt::Display for CompressionFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use crate::extension::CompressionFormat::*;
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Extracts extensions from a path,
|
||||||
|
/// return both the remaining path and the list of extension objects
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use ouch::extension::{separate_known_extensions_from_name, CompressionFormat};
|
||||||
|
/// use std::path::Path;
|
||||||
|
///
|
||||||
|
/// let mut path = Path::new("bolovo.tar.gz");
|
||||||
|
/// assert_eq!(separate_known_extensions_from_name(&path), (Path::new("bolovo"), vec![CompressionFormat::Tar, CompressionFormat::Gzip]));
|
||||||
|
/// ```
|
||||||
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<CompressionFormat>) {
|
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<CompressionFormat>) {
|
||||||
// // TODO: check for file names with the name of an extension
|
// // TODO: check for file names with the name of an extension
|
||||||
// // TODO2: warn the user that currently .tar.gz is a .gz file named .tar
|
// // TODO2: warn the user that currently .tar.gz is a .gz file named .tar
|
||||||
@ -82,6 +97,15 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Compr
|
|||||||
(path, extensions)
|
(path, extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts extensions from a path, return only the list of extension objects
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use ouch::extension::{extensions_from_path, CompressionFormat};
|
||||||
|
/// use std::path::Path;
|
||||||
|
///
|
||||||
|
/// let mut path = Path::new("bolovo.tar.gz");
|
||||||
|
/// assert_eq!(extensions_from_path(&path), vec![CompressionFormat::Tar, CompressionFormat::Gzip]);
|
||||||
|
/// ```
|
||||||
pub fn extensions_from_path(path: &Path) -> Vec<CompressionFormat> {
|
pub fn extensions_from_path(path: &Path) -> Vec<CompressionFormat> {
|
||||||
let (_, extensions) = separate_known_extensions_from_name(path);
|
let (_, extensions) = separate_known_extensions_from_name(path);
|
||||||
extensions
|
extensions
|
||||||
|
25
src/lib.rs
25
src/lib.rs
@ -4,18 +4,21 @@
|
|||||||
//! 1. It's required by `main.rs`, or
|
//! 1. It's required by `main.rs`, or
|
||||||
//! 2. It's required by some integration tests at tests/ folder.
|
//! 2. It's required by some integration tests at tests/ folder.
|
||||||
|
|
||||||
// Public modules
|
#![warn(missing_docs)]
|
||||||
pub mod archive;
|
|
||||||
pub mod commands;
|
|
||||||
|
|
||||||
// Private modules
|
// Macros should be declared before
|
||||||
mod cli;
|
pub mod macros;
|
||||||
mod dialogs;
|
|
||||||
mod error;
|
pub mod archive;
|
||||||
mod extension;
|
pub mod cli;
|
||||||
mod macros;
|
pub mod commands;
|
||||||
mod opts;
|
pub mod dialogs;
|
||||||
mod utils;
|
pub mod error;
|
||||||
|
pub mod extension;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
/// CLI configuration step, uses definitions from `opts.rs`, also used to treat some inputs.
|
||||||
|
pub mod opts;
|
||||||
|
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use opts::{Opts, Subcommand};
|
pub use opts::{Opts, Subcommand};
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
//! Macros used on ouch.
|
||||||
|
|
||||||
|
/// Macro that prints message in INFO mode
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! info {
|
macro_rules! info {
|
||||||
($($arg:tt)*) => {
|
($($arg:tt)*) => {
|
||||||
@ -6,6 +9,7 @@ macro_rules! info {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prints the `[Info]` tag
|
||||||
pub fn _info_helper() {
|
pub fn _info_helper() {
|
||||||
use crate::utils::colors::{RESET, YELLOW};
|
use crate::utils::colors::{RESET, YELLOW};
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ use clap::{Parser, ValueHint};
|
|||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Command line options
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(version, about)]
|
#[clap(version, about)]
|
||||||
pub struct Opts {
|
pub struct Opts {
|
||||||
@ -13,10 +14,12 @@ pub struct Opts {
|
|||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub no: bool,
|
pub no: bool,
|
||||||
|
|
||||||
|
/// Action to take
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub cmd: Subcommand,
|
pub cmd: Subcommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Actions to take
|
||||||
#[derive(Parser, PartialEq, Eq, Debug)]
|
#[derive(Parser, PartialEq, Eq, Debug)]
|
||||||
pub enum Subcommand {
|
pub enum Subcommand {
|
||||||
/// Compress files. Alias: c
|
/// Compress files. Alias: c
|
||||||
|
15
src/utils.rs
15
src/utils.rs
@ -1,3 +1,5 @@
|
|||||||
|
//! Utils used on ouch.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp, env,
|
cmp, env,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
@ -16,6 +18,7 @@ pub fn dir_is_empty(dir_path: &Path) -> bool {
|
|||||||
dir_path.read_dir().map(is_empty).unwrap_or_default()
|
dir_path.read_dir().map(is_empty).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates the dir if non existent.
|
||||||
pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
fs::create_dir_all(path)?;
|
fs::create_dir_all(path)?;
|
||||||
@ -24,6 +27,9 @@ pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the current dir from the beginning of a path
|
||||||
|
/// normally used for presentation sake.
|
||||||
|
/// If this function fails, it will return source path as a PathBuf.
|
||||||
pub fn strip_cur_dir(source_path: &Path) -> PathBuf {
|
pub fn strip_cur_dir(source_path: &Path) -> PathBuf {
|
||||||
source_path
|
source_path
|
||||||
.strip_prefix(Component::CurDir)
|
.strip_prefix(Component::CurDir)
|
||||||
@ -44,6 +50,8 @@ pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
|
|||||||
Ok(previous_location)
|
Ok(previous_location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Centralizes the decision of overwriting a file or not,
|
||||||
|
/// whether the user has already passed a question_policy or not.
|
||||||
pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result<bool> {
|
pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result<bool> {
|
||||||
match question_policy {
|
match question_policy {
|
||||||
QuestionPolicy::AlwaysYes => Ok(true),
|
QuestionPolicy::AlwaysYes => Ok(true),
|
||||||
@ -57,11 +65,13 @@ pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts an OsStr to utf8.
|
||||||
pub fn to_utf(os_str: impl AsRef<OsStr>) -> String {
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Treats weird paths for better user messages.
|
||||||
pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> String {
|
pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> String {
|
||||||
let text = to_utf(os_str);
|
let text = to_utf(os_str);
|
||||||
if text == "." {
|
if text == "." {
|
||||||
@ -71,6 +81,7 @@ pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Struct used to overload functionality onto Byte presentation.
|
||||||
pub struct Bytes {
|
pub struct Bytes {
|
||||||
bytes: f64,
|
bytes: f64,
|
||||||
}
|
}
|
||||||
@ -87,6 +98,7 @@ pub mod colors {
|
|||||||
macro_rules! color {
|
macro_rules! color {
|
||||||
($name:ident = $value:literal) => {
|
($name:ident = $value:literal) => {
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
|
/// Inserts color onto text based on configuration
|
||||||
pub static $name: Lazy<&str> = Lazy::new(|| if *DISABLE_COLORED_TEXT { "" } else { $value });
|
pub static $name: Lazy<&str> = Lazy::new(|| if *DISABLE_COLORED_TEXT { "" } else { $value });
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
pub static $name: &&str = &"";
|
pub static $name: &&str = &"";
|
||||||
@ -107,6 +119,7 @@ pub mod colors {
|
|||||||
impl Bytes {
|
impl Bytes {
|
||||||
const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"];
|
const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"];
|
||||||
|
|
||||||
|
/// New Byte structure
|
||||||
pub fn new(bytes: u64) -> Self {
|
pub fn new(bytes: u64) -> Self {
|
||||||
Self { bytes: bytes as f64 }
|
Self { bytes: bytes as f64 }
|
||||||
}
|
}
|
||||||
@ -130,7 +143,7 @@ impl std::fmt::Display for Bytes {
|
|||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
/// How overwrite questions should be handled
|
/// How overwrite questions should be handled
|
||||||
pub enum QuestionPolicy {
|
pub enum QuestionPolicy {
|
||||||
/// Ask ever time
|
/// Ask everytime
|
||||||
Ask,
|
Ask,
|
||||||
/// Skip overwrite questions positively
|
/// Skip overwrite questions positively
|
||||||
AlwaysYes,
|
AlwaysYes,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user