mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-06 19:45:29 +00:00
Make ouch support paths with dot-dot (..) for input files/directories
This commit is contained in:
parent
b343e7872c
commit
965041310c
45
src/cli.rs
45
src/cli.rs
@ -1,7 +1,8 @@
|
|||||||
use std::{convert::TryFrom, path::PathBuf, vec::Vec};
|
use std::{convert::TryFrom, fs, path::{Path, PathBuf}, vec::Vec};
|
||||||
|
|
||||||
use clap::{Arg, Values};
|
use clap::{Arg, Values};
|
||||||
// use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use error::Error;
|
||||||
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::extension::Extension;
|
use crate::extension::Extension;
|
||||||
@ -10,11 +11,11 @@ use crate::file::File;
|
|||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum CommandKind {
|
pub enum CommandKind {
|
||||||
Compression(
|
Compression(
|
||||||
// Files to be compressed
|
/// Files to be compressed
|
||||||
Vec<PathBuf>,
|
Vec<PathBuf>,
|
||||||
),
|
),
|
||||||
Decompression(
|
Decompression(
|
||||||
// Files to be decompressed and their extensions
|
/// Files to be decompressed and their extensions
|
||||||
Vec<File>,
|
Vec<File>,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -66,23 +67,30 @@ pub fn get_matches() -> clap::ArgMatches<'static> {
|
|||||||
clap_app().get_matches()
|
clap_app().get_matches()
|
||||||
}
|
}
|
||||||
|
|
||||||
// holy spaghetti code
|
|
||||||
impl TryFrom<clap::ArgMatches<'static>> for Command {
|
impl TryFrom<clap::ArgMatches<'static>> for Command {
|
||||||
type Error = error::Error;
|
type Error = error::Error;
|
||||||
|
|
||||||
fn try_from(matches: clap::ArgMatches<'static>) -> error::OuchResult<Command> {
|
fn try_from(matches: clap::ArgMatches<'static>) -> error::OuchResult<Command> {
|
||||||
let process_decompressible_input = |input_files: Values| {
|
let process_decompressible_input = |input_files: Values| {
|
||||||
let input_files =
|
let input_files =
|
||||||
input_files.map(|filename| (filename, Extension::new(filename)));
|
input_files.map(|filename| (Path::new(filename), Extension::new(filename)));
|
||||||
|
|
||||||
for file in input_files.clone() {
|
for file in input_files.clone() {
|
||||||
if let (file, Err(_)) = file {
|
match file {
|
||||||
return Err(error::Error::InputsMustHaveBeenDecompressible(file.into()));
|
(filename, Ok(_)) => {
|
||||||
|
let path = Path::new(filename);
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(error::Error::FileNotFound(filename.into()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(filename, Err(_)) => {
|
||||||
|
return Err(error::Error::InputsMustHaveBeenDecompressible(filename.into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(input_files
|
Ok(input_files
|
||||||
.map(|(filename, extension)| (PathBuf::from(filename), extension.unwrap()))
|
.map(|(filename, extension)| (fs::canonicalize(filename).unwrap(), extension.unwrap()))
|
||||||
.map(File::from)
|
.map(File::from)
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
};
|
};
|
||||||
@ -104,13 +112,20 @@ impl TryFrom<clap::ArgMatches<'static>> for Command {
|
|||||||
if output_is_compressible {
|
if output_is_compressible {
|
||||||
// The supplied output is compressible, so we'll compress our inputs to it
|
// The supplied output is compressible, so we'll compress our inputs to it
|
||||||
|
|
||||||
// println!(
|
let canonical_paths = input_files.clone().map(Path::new).map(fs::canonicalize);
|
||||||
// "{}: trying to compress input files into '{}'",
|
for (filename, canonical_path) in input_files.zip(canonical_paths.clone()) {
|
||||||
// "info".yellow(),
|
if let Err(err) = canonical_path {
|
||||||
// output_file
|
let path = PathBuf::from(filename);
|
||||||
// );
|
if !path.exists() {
|
||||||
|
return Err(Error::FileNotFound(path))
|
||||||
|
}
|
||||||
|
|
||||||
let input_files = input_files.map(PathBuf::from).collect();
|
eprintln!("{} {}", "[ERROR]".red(), err);
|
||||||
|
return Err(Error::IOError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_files = canonical_paths.map(Result::unwrap).collect();
|
||||||
|
|
||||||
return Ok(Command {
|
return Ok(Command {
|
||||||
kind: CommandKind::Compression(input_files),
|
kind: CommandKind::Compression(input_files),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use tar::Builder;
|
use tar::Builder;
|
||||||
@ -20,12 +20,20 @@ impl TarCompressor {
|
|||||||
|
|
||||||
fn make_archive_from_files(input_filenames: Vec<PathBuf>) -> OuchResult<Vec<u8>> {
|
fn make_archive_from_files(input_filenames: Vec<PathBuf>) -> OuchResult<Vec<u8>> {
|
||||||
|
|
||||||
|
let change_dir_and_return_parent = |filename: &PathBuf| -> OuchResult<PathBuf> {
|
||||||
|
let previous_location = env::current_dir()?;
|
||||||
|
let parent = filename.parent().unwrap();
|
||||||
|
env::set_current_dir(parent)?;
|
||||||
|
Ok(previous_location)
|
||||||
|
};
|
||||||
|
|
||||||
let buf = Vec::new();
|
let buf = Vec::new();
|
||||||
let mut b = Builder::new(buf);
|
let mut b = Builder::new(buf);
|
||||||
|
|
||||||
for filename in input_filenames {
|
for filename in input_filenames {
|
||||||
// TODO: check if file exists
|
let previous_location = change_dir_and_return_parent(&filename)?;
|
||||||
|
// Safe unwrap since this filename came from `fs::canonicalize`.
|
||||||
|
let filename = filename.file_name().unwrap();
|
||||||
for entry in WalkDir::new(&filename) {
|
for entry in WalkDir::new(&filename) {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
@ -34,6 +42,7 @@ impl TarCompressor {
|
|||||||
}
|
}
|
||||||
b.append_file(path, &mut fs::File::open(path)?)?;
|
b.append_file(path, &mut fs::File::open(path)?)?;
|
||||||
}
|
}
|
||||||
|
env::set_current_dir(previous_location)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(b.into_inner()?)
|
Ok(b.into_inner()?)
|
||||||
|
37
src/error.rs
37
src/error.rs
@ -1,4 +1,4 @@
|
|||||||
use std::fmt;
|
use std::{fmt, path::PathBuf};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
||||||
@ -10,29 +10,29 @@ pub enum Error {
|
|||||||
InvalidUnicode,
|
InvalidUnicode,
|
||||||
InvalidInput,
|
InvalidInput,
|
||||||
IOError,
|
IOError,
|
||||||
FileNotFound,
|
FileNotFound(PathBuf),
|
||||||
AlreadyExists,
|
AlreadyExists,
|
||||||
InvalidZipArchive(&'static str),
|
InvalidZipArchive(&'static str),
|
||||||
PermissionDenied,
|
PermissionDenied,
|
||||||
UnsupportedZipArchive(&'static str),
|
UnsupportedZipArchive(&'static str),
|
||||||
InputsMustHaveBeenDecompressible(String),
|
InputsMustHaveBeenDecompressible(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type OuchResult<T> = Result<T, Error>;
|
pub type OuchResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} ", "[ERROR]".red())?;
|
||||||
match self {
|
match self {
|
||||||
Error::MissingExtensionError(filename) => {
|
Error::MissingExtensionError(filename) => {
|
||||||
write!(f, "cannot compress to \'{}\', likely because it has an unsupported (or missing) extension.", filename)
|
write!(f, "cannot compress to \'{}\', likely because it has an unsupported (or missing) extension.", filename)
|
||||||
},
|
},
|
||||||
Error::InputsMustHaveBeenDecompressible(file) => {
|
Error::InputsMustHaveBeenDecompressible(file) => {
|
||||||
write!(f, "file '{}' is not decompressible", file.red())
|
write!(f, "file '{:?}' is not decompressible", file)
|
||||||
},
|
},
|
||||||
// TODO: find out a way to attach the missing file in question here
|
Error::FileNotFound(file) => {
|
||||||
Error::FileNotFound => {
|
// TODO: check if file == ""
|
||||||
write!(f, "file not found!")
|
write!(f, "file {:?} not found!", file)
|
||||||
}
|
}
|
||||||
_err => {
|
_err => {
|
||||||
// TODO
|
// TODO
|
||||||
@ -45,11 +45,11 @@ impl fmt::Display for Error {
|
|||||||
impl From<std::io::Error> for Error {
|
impl From<std::io::Error> for Error {
|
||||||
fn from(err: std::io::Error) -> Self {
|
fn from(err: std::io::Error) -> Self {
|
||||||
match err.kind() {
|
match err.kind() {
|
||||||
std::io::ErrorKind::NotFound => Self::FileNotFound,
|
std::io::ErrorKind::NotFound => Self::FileNotFound("".into()),
|
||||||
std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
|
std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
|
||||||
std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
|
std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
|
||||||
_other => {
|
_other => {
|
||||||
println!("{}: {:#?}", "IO error".red(), err);
|
println!("{}: {}", "IO error".red(), err);
|
||||||
Self::IOError
|
Self::IOError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,27 +62,12 @@ impl From<zip::result::ZipError> for Error {
|
|||||||
match err {
|
match err {
|
||||||
Io(io_err) => Self::from(io_err),
|
Io(io_err) => Self::from(io_err),
|
||||||
InvalidArchive(filename) => Self::InvalidZipArchive(filename),
|
InvalidArchive(filename) => Self::InvalidZipArchive(filename),
|
||||||
FileNotFound => Self::FileNotFound,
|
FileNotFound => Self::FileNotFound("".into()),
|
||||||
UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename)
|
UnsupportedArchive(filename) => Self::UnsupportedZipArchive(filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl From<niffler::error::Error> for Error {
|
|
||||||
// fn from(err: niffler::error::Error) -> Self {
|
|
||||||
// use niffler::error::Error as NifErr;
|
|
||||||
// match err {
|
|
||||||
// NifErr::FeatureDisabled => {
|
|
||||||
// // Ouch is using Niffler with all its features so
|
|
||||||
// // this should be unreachable.
|
|
||||||
// unreachable!();
|
|
||||||
// },
|
|
||||||
// NifErr::FileTooShort => Self::FileTooShort,
|
|
||||||
// NifErr::IOError(io_err) => Self::from(io_err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl From<walkdir::Error> for Error {
|
impl From<walkdir::Error> for Error {
|
||||||
fn from(err: walkdir::Error) -> Self {
|
fn from(err: walkdir::Error) -> Self {
|
||||||
eprintln!("{}: {}", "error".red(), err);
|
eprintln!("{}: {}", "error".red(), err);
|
||||||
|
@ -15,7 +15,7 @@ mod decompressors;
|
|||||||
|
|
||||||
fn main() -> error::OuchResult<()>{
|
fn main() -> error::OuchResult<()>{
|
||||||
let print_error = |err| {
|
let print_error = |err| {
|
||||||
println!("{}: {}", "error".red(), err);
|
println!("{}", err);
|
||||||
};
|
};
|
||||||
let matches = cli::get_matches();
|
let matches = cli::get_matches();
|
||||||
match cli::Command::try_from(matches) {
|
match cli::Command::try_from(matches) {
|
||||||
|
@ -8,7 +8,8 @@ where
|
|||||||
P: AsRef<Path> + 'a {
|
P: AsRef<Path> + 'a {
|
||||||
let exists = path.as_ref().exists();
|
let exists = path.as_ref().exists();
|
||||||
if !exists {
|
if !exists {
|
||||||
eprintln!("{}: could not find file {:?}", "error".red(), path.as_ref());
|
eprintln!("{}: could not find file {:?}", "[ERROR]".red(), path.as_ref());
|
||||||
|
return Err(Error::FileNotFound(PathBuf::from(path.as_ref())));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -16,7 +17,7 @@ where
|
|||||||
pub (crate) fn check_for_multiple_files(files: &Vec<PathBuf>, format: &CompressionFormat) -> OuchResult<()> {
|
pub (crate) fn check_for_multiple_files(files: &Vec<PathBuf>, format: &CompressionFormat) -> OuchResult<()> {
|
||||||
|
|
||||||
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(Error::InvalidInput);
|
return Err(Error::InvalidInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,13 +28,13 @@ pub (crate) fn create_path_if_non_existent(path: &Path) -> OuchResult<()> {
|
|||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
println!(
|
println!(
|
||||||
"{}: attempting to create folder {:?}.",
|
"{}: attempting to create folder {:?}.",
|
||||||
"info".yellow(),
|
"[INFO]".yellow(),
|
||||||
&path
|
&path
|
||||||
);
|
);
|
||||||
std::fs::create_dir_all(path)?;
|
std::fs::create_dir_all(path)?;
|
||||||
println!(
|
println!(
|
||||||
"{}: directory {:#?} created.",
|
"{}: directory {:#?} created.",
|
||||||
"info".yellow(),
|
"[INFO]".yellow(),
|
||||||
fs::canonicalize(&path)?
|
fs::canonicalize(&path)?
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user