Merge pull request #16 from vrmiguel/nightly

Update master from nightly
This commit is contained in:
Vinícius Miguel 2021-04-06 00:39:23 -03:00 committed by GitHub
commit d3de94dfca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 186 additions and 277 deletions

View File

@ -17,37 +17,33 @@ jobs:
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
toolchain: stable
target: x86_64-unknown-linux-musl
override: true
- name: Install dependencies for musl libc
run: |
sudo apt-get update
sudo apt-get install musl-tools
- name: Run cargo build
uses: actions-rs/cargo@v1
with:
command: build
args: --release
args: --release --target x86_64-unknown-linux-musl
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
- name: Install dependencies for Python test script
run: |
sudo apt-get update
sudo apt-get install libmagic1
python3 -m pip install python-magic
- name: Run test script
run: python3 makeshift_testing.py
- name: Strip binary
run: strip target/release/ouch
run: strip target/x86_64-unknown-linux-musl/release/ouch
- name: Upload binary
uses: actions/upload-artifact@v2
with:
name: 'ouch-ubuntu-18.04-glibc'
path: target/release/ouch
name: 'ouch-linux-musl'
path: target/x86_64-unknown-linux-musl/release/ouch
macos:
name: macOS
@ -77,11 +73,6 @@ jobs:
with:
command: test
- name: Install dependencies for Python test script
run: |
brew install libmagic
python3 -m pip install python-magic
- name: Strip binary
run: strip target/release/ouch
@ -91,9 +82,6 @@ jobs:
name: 'ouch-macOS'
path: target/release/ouch
- name: Run test script
run: python3 makeshift_testing.py
windows:
name: Windows Server
runs-on: windows-2019

View File

@ -12,10 +12,6 @@
- [Installation](#Installation)
- [Supported operating systems](#Supported-operating-systems)
**Note**
* This README represents the new, but not yet implemented, interface that `ouch` will use.
* For current usage instructions, check [the old README](https://github.com/vrmiguel/ouch/blob/0f453e9dfc70066056b9cc40e8032dcc6ee703bc/README.md).
## Usage
### Decompressing files

View File

@ -1,89 +0,0 @@
#!/usr/bin/python3
"""
Little integration testing script while proper integration tests in Rust aren't implemented.
"""
import magic, os, hashlib
def make_random_file():
with open('test-file', 'wb') as fout:
fout.write(os.urandom(2048))
def sanity_check_format(format: str):
make_random_file()
md5sum = hashlib.md5(open('test-file', 'rb').read()).hexdigest()
os.system(f"cargo run -- -i test-file -o test-file.{format}")
os.remove('test-file')
os.system(f"cargo run -- -i test-file.{format}")
if md5sum != hashlib.md5(open('test-file', 'rb').read()).hexdigest():
print("Something went wrong with tar (de)compression.")
os._exit(2)
os.remove('test-file')
os.remove(f'test-file.{format}')
if __name__ == "__main__":
# We'll use MIME sniffing through magic numbers to
# verify if ouch is actually outputting the file formats
# that it should
m = magic.open(magic.MAGIC_MIME)
try:
os.mkdir("testbuilds")
except OSError:
print ("Could not make testbuilds folder. Exiting.")
os._exit(2)
os.chdir("testbuilds")
m.load()
files = [
"src.tar",
"src.zip",
"src.tar.gz",
"src.tar.bz",
"src.tar.bz2",
"src.tar.lz",
"src.tar.lzma",
]
expected_mime_types = [
"application/x-tar",
"application/zip",
"application/gzip",
"application/x-bzip2",
"application/x-bzip2",
"application/x-xz",
"application/x-xz"
]
for file in files:
rv = os.system(f"cargo run -- -i ../src/ -o {file}")
if rv != 0:
print(f"Failed while compressing {file}")
for (file, expected_mime) in zip(files, expected_mime_types):
if m.file(file) != expected_mime:
print(f"Test failed at file {file}")
os._exit(2)
for (idx, file) in enumerate(files):
rv = os.system(f"cargo run -- -i {file} -o out{idx}/")
if rv != 0:
print(f"Failed while decompressing {file}")
os._exit(2)
os.chdir("..")
os.system("rm -rf testbuilds")
# We'll now verify if ouch is not altering the data it is compressing
# and decompressing
sanity_check_format("tar")
sanity_check_format("tar.gz")
sanity_check_format("tar.bz")
sanity_check_format("tar.bz2")
sanity_check_format("tar.lz")
sanity_check_format("tar.lzma")

View File

@ -91,7 +91,7 @@ impl FlagType {
#[cfg(target_family = "windows")]
{
use std::os::windows::ffi::OsStrExt;
iter = text.encode_wide
iter = text.encode_wide();
}
// 45 is the code for a hyphen

View File

@ -126,7 +126,7 @@ pub fn filter_flags(
// For each letter in the short arg, except the last one
for (i, letter) in letters.iter().copied().enumerate() {
// Safety: this loop only runs when len >= 1
// Safety: this loop only runs when len >= 1, so this subtraction is safe
let is_last_letter = i == letters.len() - 1;
let flag_info = short_flags_info.get(&letter).unwrap_or_else(|| {
@ -149,16 +149,15 @@ pub fn filter_flags(
}
// pop the next one
let flag_argument = iter.next();
flag_argument.unwrap_or_else(|| {
let flag_argument = iter.next().unwrap_or_else(|| {
panic!(
"USer errror: argument flag `argument_flag` came at last, but it \
requires an argument"
)
});
// Otherwise, insert it (TODO: grab next one and add it)
// result_flags.argument_flags.insert(flag_info.long);
// Otherwise, insert it.
result_flags.argument_flags.insert(flag_name, flag_argument);
} else {
// If it was already inserted
if result_flags.boolean_flags.contains(flag_name) {

View File

@ -5,6 +5,7 @@ pub fn trim_double_hyphen(flag_text: &str) -> &str {
chars.as_str()
}
// Currently unused
/// Util function to skip the single leading short flag hyphen.
pub fn trim_single_hyphen(flag_text: &str) -> &str {
let mut chars = flag_text.chars();

View File

@ -2,6 +2,10 @@ use std::{env, ffi::OsString, io, path::PathBuf, vec::Vec};
use oof::{arg_flag, flag};
use crate::debug;
pub const VERSION: &str = "0.1.5";
#[derive(PartialEq, Eq, Debug)]
pub enum Command {
/// Files to be compressed
@ -66,8 +70,9 @@ pub fn parse_args_from(mut args: Vec<OsString>) -> crate::Result<ParsedArgs> {
let mut files: Vec<PathBuf> = args.into_iter().map(PathBuf::from).collect();
if files.len() < 2 {
panic!("The compress subcommands demands at least 2 arguments, see usage:.......");
return Err(crate::Error::MissingArgumentsForCompression);
}
// Safety: we checked that args.len() >= 2
let compressed_output_path = files.pop().unwrap();
@ -82,16 +87,18 @@ pub fn parse_args_from(mut args: Vec<OsString>) -> crate::Result<ParsedArgs> {
// Defaults to decompression when there is no subcommand
None => {
flags_info.push(arg_flag!('o', "output"));
debug!(&flags_info);
// Parse flags
let (args, mut flags) = oof::filter_flags(args, &flags_info)?;
debug!((&args, &flags));
let files: Vec<_> = args.into_iter().map(PathBuf::from).collect();
// TODO: This line doesn't seem to be working correctly
let output_folder = flags.take_arg("output").map(PathBuf::from);
// Is the output here fully correct?
// With the paths not canonicalized?
let command = Command::Decompress {
files,
output_folder,
@ -102,98 +109,4 @@ pub fn parse_args_from(mut args: Vec<OsString>) -> crate::Result<ParsedArgs> {
};
Ok(parsed_args)
}
#[cfg(test)]
mod tests {
use super::*;
fn gen_args(text: &str) -> Vec<OsString> {
let args = text.split_whitespace();
args.map(OsString::from).collect()
}
// // util for test the argument parsing
// macro_rules! test {
// ($expected_command:expr, $input_text:expr) => {{
// assert_eq!(
// $expected_command,
// oof::try_arg_parsing($input_text.split_whitespace())
// )
// }};
// }
macro_rules! parse {
($input_text:expr) => {{
let args = gen_args($input_text);
parse_args_from(args).unwrap()
}};
}
#[test]
// The absolute flags that ignore all the other argparsing rules are --help and --version
fn test_absolute_flags() {
let expected = Command::ShowHelp;
assert_eq!(expected, parse!("").command);
assert_eq!(expected, parse!("-h").command);
assert_eq!(expected, parse!("--help").command);
assert_eq!(expected, parse!("aaaaaaaa --help -o -e aaa").command);
assert_eq!(expected, parse!("aaaaaaaa -h").command);
assert_eq!(expected, parse!("--help compress aaaaaaaa").command);
assert_eq!(expected, parse!("compress --help").command);
assert_eq!(expected, parse!("--version --help").command);
assert_eq!(expected, parse!("aaaaaaaa -v aaaa -h").command);
let expected = Command::ShowVersion;
assert_eq!(expected, parse!("ouch --version").command);
assert_eq!(expected, parse!("ouch a --version b").command);
}
#[test]
fn test_arg_parsing_compress_subcommand() {
let files = ["a", "b", "c"].iter().map(PathBuf::from).collect();
let expected = Command::Compress {
files,
compressed_output_path: "d".into(),
};
assert_eq!(expected, parse!("compress a b c d").command);
}
#[test]
fn test_arg_parsing_decompress_subcommand() {
let files: Vec<_> = ["a", "b", "c"].iter().map(PathBuf::from).collect();
let expected = Command::Decompress {
files: files.clone(),
output_folder: None,
};
assert_eq!(expected, parse!("a b c").command);
let expected = Command::Decompress {
files,
output_folder: Some("folder".into()),
};
assert_eq!(expected, parse!("a b c --output folder").command);
assert_eq!(expected, parse!("a b --output folder c").command);
assert_eq!(expected, parse!("a --output folder b c").command);
assert_eq!(expected, parse!("--output folder a b c").command);
}
// #[test]
// fn test_arg_parsing_decompress_subcommand() {
// let files: Vec<PathBuf> = ["a", "b", "c"].iter().map(PathBuf::from).collect();
// let expected = Ok(Command::Decompress {
// files: files.clone(),
// });
// test!(expected, "ouch a b c");
// let files: Vec<PathBuf> = ["a", "b", "c", "d"].iter().map(PathBuf::from).collect();
// let expected = Ok(Command::Decompress {
// files: files.clone(),
// });
// test!(expected, "ouch a b c d");
// }
}
}

View File

@ -29,6 +29,7 @@ impl TarCompressor {
for entry in WalkDir::new(&filename) {
let entry = entry?;
let path = entry.path();
println!("Compressing {:?}", path);
if path.is_dir() {
continue;
}

View File

@ -68,7 +68,9 @@ impl ZipCompressor {
if entry_path.is_dir() {
continue;
}
writer.start_file(entry_path.to_string_lossy(), options)?;
println!("Compressing {:?}", entry_path);
let file_bytes = std::fs::read(entry.path())?;
writer.write_all(&*file_bytes)?;
}

View File

@ -14,7 +14,7 @@ use crate::{dialogs::Confirmation, file::File, utils};
pub struct 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>> {
println!(
"{}: attempting to decompress {:?}",
"ouch".bright_blue(),

View File

@ -15,22 +15,14 @@ pub enum Error {
InvalidZipArchive(&'static str),
PermissionDenied,
UnsupportedZipArchive(&'static str),
// InputsMustBeDecompressible(PathBuf),
InternalError,
CompressingRootFolder,
MissingArgumentsForCompression,
WalkdirError,
}
pub type Result<T> = std::result::Result<T, Error>;
// impl std::error::Error for Error {
// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
// // TODO: get rid of PartialEq and Eq in self::Error in order to
// // correctly use `source`.
// None
// }
// }
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
@ -42,19 +34,18 @@ impl fmt::Display for Error {
match self {
Error::MissingExtensionError(filename) => {
write!(f, "{} ", "[ERROR]".red())?;
// TODO: show MIME type of the unsupported file
write!(f, "cannot compress to \'{}\', likely because it has an unsupported (or missing) extension.", filename)
}
// Error::InputsMustBeDecompressible(file) => {
// write!(f, "{} ", "[ERROR]".red())?;
// write!(f, "file '{:?}' is not decompressible", file)
// }
Error::WalkdirError => {
// Already printed in the From block
write!(f, "")
}
Error::FileNotFound(file) => {
write!(f, "{} ", "[ERROR]".red())?;
// TODO: check if file == ""
if file == &PathBuf::from("") {
return write!(f, "file not found!");
}
write!(f, "file {:?} not found!", file)
}
Error::CompressingRootFolder => {
@ -73,6 +64,10 @@ impl fmt::Display for Error {
"rsync".green()
)
}
Error::MissingArgumentsForCompression => {
write!(f, "{} ", "[ERROR]".red())?;
write!(f,"The compress subcommands demands at least 2 arguments, see usage: <TODO-USAGE>")
}
Error::InternalError => {
write!(f, "{} ", "[ERROR]".red())?;
write!(f, "You've reached an internal error! This really should not have happened.\nPlease file an issue at {}", "https://github.com/vrmiguel/ouch".green())

View File

@ -7,19 +7,20 @@ use std::{
use colored::Colorize;
use crate::{
cli::Command,
cli::{VERSION, Command},
compressors::{
BzipCompressor, Compressor, Entry, GzipCompressor, LzmaCompressor, TarCompressor,
Entry, Compressor, BzipCompressor, GzipCompressor, LzmaCompressor, TarCompressor,
ZipCompressor,
},
decompressors::{
BzipDecompressor, DecompressionResult, Decompressor, GzipDecompressor, LzmaDecompressor,
TarDecompressor, ZipDecompressor,
},
dialogs::Confirmation,
extension::{CompressionFormat, Extension},
file::File,
},
dialogs::Confirmation,
extension::{CompressionFormat, Extension},
file::File,
utils,
debug
};
pub struct Evaluator {}
@ -31,6 +32,7 @@ impl Evaluator {
pub fn get_compressor(
file: &File,
) -> crate::Result<(Option<BoxedCompressor>, BoxedCompressor)> {
let extension = match &file.extension {
Some(extension) => extension.clone(),
None => {
@ -211,8 +213,8 @@ impl Evaluator {
file_path: &Path,
output: Option<&Path>,
flags: &oof::Flags,
) -> crate::Result<()> {
let file = File::from(file_path)?;
) -> crate::Result<()> {
let file = debug!(File::from(file_path)?);
let output = match output {
Some(inner) => Some(File::from(inner)?),
None => None,
@ -266,9 +268,25 @@ impl Evaluator {
Self::decompress_file(file, output_folder, flags)?;
}
}
Command::ShowHelp => todo!("call help function"),
Command::ShowVersion => todo!("call version function"),
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]");
}

View File

@ -7,7 +7,7 @@ use std::{
use CompressionFormat::*;
use crate::utils::to_utf;
use crate::{debug, utils::to_utf};
/// Represents the extension of a file, but only really caring about
/// compression formats (and .tar).

View File

@ -19,13 +19,12 @@ pub struct File<'a> {
impl<'a> File<'a> {
pub fn from(path: &'a Path) -> crate::Result<Self> {
let extension = Extension::from(path.as_ref())?;
eprintln!("dev warning: Should we really ignore the errors from the convertion above?");
let extension = Extension::from(path.as_ref()).ok();
Ok(File {
path,
contents_in_memory: None,
extension: Some(extension),
extension
})
}
}

View File

@ -23,6 +23,6 @@ fn main() {
fn run() -> crate::Result<()> {
let ParsedArgs { command, flags } = cli::parse_args()?;
dbg!(&command);
debug!(&command);
Evaluator::evaluate(command, &flags)
}

View File

@ -1,39 +1,112 @@
// TODO: remove tests of CompressionFormat::try_from since that's no longer used anywhere
use std::{fs, path::Path};
// use std::{
// convert::TryFrom,
// ffi::{OsStr, OsString},
// fs,
// path::Path,
// };
#[allow(dead_code)]
// ouch's command-line logic uses fs::canonicalize on its inputs so we cannot
// use made-up files for testing.
// make_dummy_file therefores creates a small temporary file to bypass fs::canonicalize errors
fn make_dummy_file<'a, P>(path: P) -> crate::Result<()>
where
P: AsRef<Path> + 'a,
{
fs::write(path.as_ref(), &[2, 3, 4, 5, 6, 7, 8, 9, 10])?;
Ok(())
}
// use crate::{
// cli::Command,
// extension::{CompressionFormat, CompressionFormat::*, Extension},
// file::File,
// };
#[allow(dead_code)]
fn make_dummy_files<'a, P>(paths: &[P]) -> crate::Result<()>
where
P: AsRef<Path> + 'a,
{
let _ = paths
.iter()
.map(make_dummy_file)
.map(Result::unwrap)
.collect::<Vec<_>>();
Ok(())
}
// // Helper
// fn gen_args(text: &str) -> Vec<OsString> {
// let args = text.split_whitespace();
// args.map(OsString::from).collect()
// }
#[cfg(test)]
mod tests {
use super::{make_dummy_files};
use crate::cli;
use crate::cli::Command;
use std::{ffi::OsString, fs, path::PathBuf};
fn gen_args(text: &str) -> Vec<OsString> {
let args = text.split_whitespace();
args.map(OsString::from).collect()
}
macro_rules! parse {
($input_text:expr) => {{
let args = gen_args($input_text);
cli::parse_args_from(args).unwrap()
}};
}
#[test]
// The absolute flags that ignore all the other argparsing rules are --help and --version
fn test_absolute_flags() {
let expected = Command::ShowHelp;
assert_eq!(expected, parse!("").command);
assert_eq!(expected, parse!("-h").command);
assert_eq!(expected, parse!("--help").command);
assert_eq!(expected, parse!("aaaaaaaa --help -o -e aaa").command);
assert_eq!(expected, parse!("aaaaaaaa -h").command);
assert_eq!(expected, parse!("--help compress aaaaaaaa").command);
assert_eq!(expected, parse!("compress --help").command);
assert_eq!(expected, parse!("--version --help").command);
assert_eq!(expected, parse!("aaaaaaaa -v aaaa -h").command);
let expected = Command::ShowVersion;
assert_eq!(expected, parse!("ouch --version").command);
assert_eq!(expected, parse!("ouch a --version b").command);
}
#[test]
fn test_arg_parsing_compress_subcommand() -> crate::Result<()> {
let files = vec!["a", "b", "c"];
make_dummy_files(&*files)?;
let files= files.iter().map(fs::canonicalize).map(Result::unwrap).collect();
let expected = Command::Compress {
files,
compressed_output_path: "d".into(),
};
assert_eq!(expected, parse!("compress a b c d").command);
fs::remove_file("a")?;
fs::remove_file("b")?;
fs::remove_file("c")?;
Ok(())
}
#[test]
fn test_arg_parsing_decompress_subcommand() {
let files: Vec<_> = ["a", "b", "c"].iter().map(PathBuf::from).collect();
let expected = Command::Decompress {
files: files.clone(),
output_folder: None,
};
assert_eq!(expected, parse!("a b c").command);
let expected = Command::Decompress {
files,
output_folder: Some("folder".into()),
};
assert_eq!(expected, parse!("a b c --output folder").command);
assert_eq!(expected, parse!("a b --output folder c").command);
assert_eq!(expected, parse!("a --output folder b c").command);
assert_eq!(expected, parse!("--output folder a b c").command);
}
}
// #[cfg(test)]
// mod cli {
// use super::*;
// // ouch's command-line logic uses fs::canonicalize on its inputs so we cannot
// // use made-up files for testing.
// // make_dummy_file therefores creates a small temporary file to bypass fs::canonicalize errors
// fn make_dummy_file<'a, P>(path: P) -> crate::Result<()>
// where
// P: AsRef<Path> + 'a,
// {
// fs::write(path.as_ref(), &[2, 3, 4, 5, 6, 7, 8, 9, 10])?;
// Ok(())
// }
// #[test]
// fn decompress_files_into_folder() -> crate::Result<()> {
// make_dummy_file("file.zip")?;

View File

@ -9,6 +9,19 @@ use colored::Colorize;
use crate::{dialogs::Confirmation, extension::CompressionFormat, file::File};
#[macro_export]
#[cfg(debug_assertions)]
macro_rules! debug {
($x:expr) => { dbg!($x) }
}
#[macro_export]
#[cfg(not(debug_assertions))]
macro_rules! debug {
($x:expr) => { std::convert::identity($x) }
}
pub(crate) fn ensure_exists<'a, P>(path: P) -> crate::Result<()>
where
P: AsRef<Path> + 'a,
@ -69,7 +82,7 @@ pub(crate) fn change_dir_and_return_parent(filename: &Path) -> crate::Result<Pat
return Err(crate::Error::CompressingRootFolder);
};
env::set_current_dir(parent).unwrap();
env::set_current_dir(parent).ok().ok_or(crate::Error::CompressingRootFolder)?;
Ok(previous_location)
}