From 02657ee5bcfa70e1785bcf119202e2ca738582cf Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Fri, 22 Oct 2021 14:44:13 +0300 Subject: [PATCH 01/22] Remove the variants from `CompressionFormat` --- src/commands.rs | 40 ---------------------------------------- src/extension.rs | 18 +++++------------- 2 files changed, 5 insertions(+), 53 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 2a627ddad..ba4e0b86e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -222,26 +222,6 @@ fn compress_files(files: Vec, formats: Vec, output_f let mut writer = archive::tar::build_archive_from_paths(&files, writer)?; writer.flush()?; } - Tgz => { - let encoder = flate2::write::GzEncoder::new(writer, Default::default()); - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } - Tbz => { - let encoder = bzip2::write::BzEncoder::new(writer, Default::default()); - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } - Tlzma => { - let encoder = xz2::write::XzEncoder::new(writer, 6); - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } - Tzst => { - let encoder = zstd::stream::write::Encoder::new(writer, Default::default())?; - let writer = archive::tar::build_archive_from_paths(&files, encoder)?; - writer.finish()?.flush()?; - } Zip => { eprintln!("{yellow}Warning:{reset}", yellow = *colors::YELLOW, reset = *colors::RESET); eprintln!("\tCompressing .zip entirely in memory."); @@ -334,26 +314,6 @@ fn decompress_file( let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); } - Tgz => { - let reader = chain_reader_decoder(&Gzip, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } - Tbz => { - let reader = chain_reader_decoder(&Bzip, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } - Tlzma => { - let reader = chain_reader_decoder(&Lzma, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } - Tzst => { - let reader = chain_reader_decoder(&Zstd, reader)?; - let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?; - info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder)); - } Zip => { eprintln!("Compressing first into .zip."); eprintln!("Warning: .zip archives with extra extensions have a downside."); diff --git a/src/extension.rs b/src/extension.rs index 9ff200f70..1349a2811 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -11,17 +11,13 @@ pub enum CompressionFormat { Bzip, // .bz Lzma, // .lzma Tar, // .tar (technically not a compression extension, but will do for now) - Tgz, // .tgz - Tbz, // .tbz - Tlzma, // .tlzma - Tzst, // .tzst Zstd, // .zst Zip, // .zip } impl CompressionFormat { pub fn is_archive_format(&self) -> bool { - matches!(self, Tar | Tgz | Tbz | Tlzma | Tzst | Zip) + matches!(self, Tar | Zip) } } @@ -36,10 +32,6 @@ impl fmt::Display for CompressionFormat { Zstd => ".zst", Lzma => ".lz", Tar => ".tar", - Tgz => ".tgz", - Tbz => ".tbz", - Tlzma => ".tlz", - Tzst => ".tzst", Zip => ".zip", } ) @@ -61,10 +53,10 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Tar, - "tgz" => Tgz, - "tbz" | "tbz2" => Tbz, - "txz" | "tlz" | "tlzma" => Tlzma, - "tzst" => Tzst, + // "tgz" => Tgz, + // "tbz" | "tbz2" => Tbz, + // "txz" | "tlz" | "tlzma" => Tlzma, + // "tzst" => Tzst, "zip" => Zip, "bz" | "bz2" => Bzip, "gz" => Gzip, From caca7901c487f183526e9a1ec2c9e2432653cc6b Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Fri, 22 Oct 2021 14:47:44 +0300 Subject: [PATCH 02/22] Run `cargo fmt` --- src/extension.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/extension.rs b/src/extension.rs index 1349a2811..137b963ff 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -7,12 +7,12 @@ use self::CompressionFormat::*; #[derive(Clone, PartialEq, Eq, Debug)] /// Accepted extensions for input and output pub enum CompressionFormat { - Gzip, // .gz - Bzip, // .bz - Lzma, // .lzma - Tar, // .tar (technically not a compression extension, but will do for now) - Zstd, // .zst - Zip, // .zip + Gzip, // .gz + Bzip, // .bz + Lzma, // .lzma + Tar, // .tar (technically not a compression extension, but will do for now) + Zstd, // .zst + Zip, // .zip } impl CompressionFormat { From 6b6ade8c9ae7c442452e50a835874e29f2bbfa0d Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Fri, 22 Oct 2021 14:49:32 +0300 Subject: [PATCH 03/22] Break down `tgz`, `tbz`, etc to `tar + gz/bz` --- src/extension.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/extension.rs b/src/extension.rs index 137b963ff..445fde198 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -51,17 +51,17 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Tar, - // "tgz" => Tgz, - // "tbz" | "tbz2" => Tbz, - // "txz" | "tlz" | "tlzma" => Tlzma, - // "tzst" => Tzst, - "zip" => Zip, - "bz" | "bz2" => Bzip, - "gz" => Gzip, - "xz" | "lzma" | "lz" => Lzma, - "zst" => Zstd, + extensions.append(&mut match extension { + "tar" => vec![Tar], + "tgz" => vec![Gzip, Tar], + "tbz" | "tbz2" => vec![Bzip, Tar], + "txz" | "tlz" | "tlzma" => vec![Lzma, Tar], + "tzst" => vec![Zstd, Tar], + "zip" => vec![Zip], + "bz" | "bz2" => vec![Bzip], + "gz" => vec![Gzip], + "xz" | "lzma" | "lz" => vec![Lzma], + "zst" => vec![Zstd], _ => break, }); From 604616e04234d9df442aa4aa4433366ad8f881ed Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Sun, 31 Oct 2021 11:09:35 +0200 Subject: [PATCH 04/22] Use proper `match` with no wildcard when detecting if it's archive --- src/extension.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/extension.rs b/src/extension.rs index 445fde198..54ba75e6c 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -17,7 +17,14 @@ pub enum CompressionFormat { impl CompressionFormat { pub fn is_archive_format(&self) -> bool { - matches!(self, Tar | Zip) + // Keep this match like that without a wildcard `_` so we don't forget to update it + match self { + Tar | Zip => true, + Gzip => false, + Bzip => false, + Lzma => false, + Zstd => false, + } } } From 4921d3d3d22429ae7ebff7bd7913195c796dc98b Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Mon, 1 Nov 2021 01:09:37 +0200 Subject: [PATCH 05/22] Add `Extension` wrapper that lets us keep tgz instead of forcing tar.gz --- src/commands.rs | 54 ++++++++++++++++++++++++++---------------- src/extension.rs | 61 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 34 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index ba4e0b86e..28872cf37 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -17,6 +17,7 @@ use crate::{ extension::{ self, CompressionFormat::{self, *}, + Extension, }, info, utils::nice_directory_display, @@ -56,9 +57,7 @@ pub fn run(args: Opts, skip_questions_positively: Option) -> crate::Result return Err(Error::with_reason(reason)); } - if !formats.get(0).map(CompressionFormat::is_archive_format).unwrap_or(false) - && represents_several_files(&files) - { + if !formats.get(0).map(Extension::is_archive).unwrap_or(false) && represents_several_files(&files) { // This piece of code creates a suggestion for compressing multiple files // It says: // Change from file.bz.xz @@ -86,7 +85,7 @@ pub fn run(args: Opts, skip_questions_positively: Option) -> crate::Result return Err(Error::with_reason(reason)); } - if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive_format()) { + if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive()) { let reason = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) .detail(format!("Found the format '{}' in an incorrect position.", format)) .detail(format!("'{}' can only be used at the start of the file extension.", format)) @@ -108,12 +107,28 @@ pub fn run(args: Opts, skip_questions_positively: Option) -> crate::Result // `ouch compress file.tar.gz file.tar.gz.xz` should produce `file.tar.gz.xz` and not `file.tar.gz.tar.gz.xz` let input_extensions = extension::extensions_from_path(&files[0]); + // We calculate the formats that are left if we filter out a sublist at the start of what we have that's the same as the input formats + let mut new_formats = Vec::with_capacity(formats.len()); + for (inp_ext, out_ext) in input_extensions.iter().zip(&formats) { + if inp_ext.compression_formats == out_ext.compression_formats { + new_formats.push(out_ext.clone()); + } else if inp_ext + .compression_formats + .iter() + .zip(&out_ext.compression_formats) + .all(|(inp, out)| inp == out) + { + let new_ext = Extension::new( + &out_ext.compression_formats[..inp_ext.compression_formats.len()], + &out_ext.display_text, + ); + new_formats.push(new_ext); + break; + } + } // If the input is a sublist at the start of `formats` then remove the extensions - // Note: If input_extensions is empty this counts as true - if !input_extensions.is_empty() - && input_extensions.len() < formats.len() - && input_extensions.iter().zip(&formats).all(|(inp, out)| inp == out) - { + // Note: If input_extensions is empty then it will make `formats` empty too, which we don't want + if !input_extensions.is_empty() && new_formats != formats { // Safety: // We checked above that input_extensions isn't empty, so files[0] has a extension. // @@ -124,8 +139,7 @@ pub fn run(args: Opts, skip_questions_positively: Option) -> crate::Result to_utf(files[0].as_path().file_name().unwrap()), to_utf(&output_path) ); - let drain_iter = formats.drain(..input_extensions.len()); - drop(drain_iter); // Remove the extensions from `formats` + formats = new_formats; } } let compress_result = compress_files(files, formats, output_file); @@ -185,7 +199,7 @@ pub fn run(args: Opts, skip_questions_positively: Option) -> crate::Result Ok(()) } -fn compress_files(files: Vec, formats: Vec, output_file: fs::File) -> crate::Result<()> { +fn compress_files(files: Vec, formats: Vec, output_file: fs::File) -> crate::Result<()> { let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file); let mut writer: Box = Box::new(file_writer); @@ -208,13 +222,13 @@ fn compress_files(files: Vec, formats: Vec, output_f encoder }; - for format in formats.iter().skip(1).rev() { + for format in formats.iter().flat_map(Extension::iter).skip(1).collect::>().iter().rev() { writer = chain_writer_encoder(format, writer); } - match formats[0] { + match formats[0].compression_formats[0] { Gzip | Bzip | Lzma | Zstd => { - writer = chain_writer_encoder(&formats[0], writer); + writer = chain_writer_encoder(&formats[0].compression_formats[0], writer); let mut reader = fs::File::open(&files[0]).unwrap(); io::copy(&mut reader, &mut writer)?; } @@ -248,7 +262,7 @@ fn compress_files(files: Vec, formats: Vec, output_f // file_name is only used when extracting single file formats, no archive formats like .tar or .zip fn decompress_file( input_file_path: &Path, - formats: Vec, + formats: Vec, output_folder: Option<&Path>, file_name: &Path, skip_questions_positively: Option, @@ -270,7 +284,7 @@ fn decompress_file( // in-memory decompression/copying first. // // Any other Zip decompression done can take up the whole RAM and freeze ouch. - if let [Zip] = *formats.as_slice() { + if formats.len() == 1 && *formats[0].compression_formats.as_slice() == [Zip] { utils::create_dir_if_non_existent(output_folder)?; let zip_archive = zip::ZipArchive::new(reader)?; let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?; @@ -294,15 +308,15 @@ fn decompress_file( Ok(decoder) }; - for format in formats.iter().skip(1).rev() { + for format in formats.iter().flat_map(Extension::iter).skip(1).collect::>().iter().rev() { reader = chain_reader_decoder(format, reader)?; } utils::create_dir_if_non_existent(output_folder)?; - match formats[0] { + match formats[0].compression_formats[0] { Gzip | Bzip | Lzma | Zstd => { - reader = chain_reader_decoder(&formats[0], reader)?; + reader = chain_reader_decoder(&formats[0].compression_formats[0], reader)?; // TODO: improve error treatment let mut writer = fs::File::create(&output_path)?; diff --git a/src/extension.rs b/src/extension.rs index 54ba75e6c..0ac1ec1bf 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -4,7 +4,40 @@ use std::{ffi::OsStr, fmt, path::Path}; use self::CompressionFormat::*; -#[derive(Clone, PartialEq, Eq, Debug)] +/// A wrapper around `CompressionFormat` that allows combinations like `tgz` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Extension { + pub compression_formats: Vec, + pub display_text: String, +} + +impl Extension { + /// # Panics: + /// Will panic if `formats` is empty + pub fn new(formats: impl Into>, text: impl Into) -> Self { + let formats = formats.into(); + assert!(!formats.is_empty()); + Self { compression_formats: formats, display_text: text.into() } + } + + /// Checks if the first format in `compression_formats` is an archive + pub fn is_archive(&self) -> bool { + // Safety: we check that `compression_formats` is not empty in `Self::new` + self.compression_formats[0].is_archive_format() + } + + pub fn iter(&self) -> impl Iterator { + self.compression_formats.iter() + } +} + +impl fmt::Display for Extension { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.display_text) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] /// Accepted extensions for input and output pub enum CompressionFormat { Gzip, // .gz @@ -45,7 +78,7 @@ impl fmt::Display for CompressionFormat { } } -pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec) { +pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec) { // // 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 // @@ -58,17 +91,17 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec vec![Tar], - "tgz" => vec![Gzip, Tar], - "tbz" | "tbz2" => vec![Bzip, Tar], - "txz" | "tlz" | "tlzma" => vec![Lzma, Tar], - "tzst" => vec![Zstd, Tar], - "zip" => vec![Zip], - "bz" | "bz2" => vec![Bzip], - "gz" => vec![Gzip], - "xz" | "lzma" | "lz" => vec![Lzma], - "zst" => vec![Zstd], + extensions.push(match extension { + "tar" => Extension::new([Tar], extension), + "tgz" => Extension::new([Tar, Gzip], extension), + "tbz" | "tbz2" => Extension::new([Tar, Bzip], extension), + "txz" | "tlz" | "tlzma" => Extension::new([Tar, Lzma], extension), + "tzst" => Extension::new([Tar, Zstd], ".tzst"), + "zip" => Extension::new([Zip], extension), + "bz" | "bz2" => Extension::new([Bzip], extension), + "gz" => Extension::new([Gzip], extension), + "xz" | "lzma" | "lz" => Extension::new([Lzma], extension), + "zst" => Extension::new([Zstd], extension), _ => break, }); @@ -81,7 +114,7 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Vec { +pub fn extensions_from_path(path: &Path) -> Vec { let (_, extensions) = separate_known_extensions_from_name(path); extensions } From 4f9caeeec42599aa478d7cc89377becbb6e7d39e Mon Sep 17 00:00:00 2001 From: figsoda Date: Mon, 1 Nov 2021 09:39:21 -0400 Subject: [PATCH 06/22] rewrite ci --- .github/workflows/build-and-test.yml | 87 ++++++++ .github/workflows/build.yml | 317 --------------------------- 2 files changed, 87 insertions(+), 317 deletions(-) create mode 100644 .github/workflows/build-and-test.yml delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 000000000..47232f95d --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,87 @@ +name: build-and-test + +on: [push, pull_request] + +jobs: + build: + name: build + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - target: x86_64-apple-darwin + os: macos-latest + # - target: x86_64-pc-windows-gnu + # os: windows-latest + # ext: .exe + - target: x86_64-pc-windows-msvc + os: windows-latest + ext: .exe + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + - target: x86_64-unknown-linux-musl + os: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install dependencies (musl) + if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} + run: | + sudo apt-get update + sudo apt-get install help2man musl-tools + + - name: Build and test on stable + run: | + rustup toolchain install stable --profile minimal -t ${{ matrix.target }} + cargo +stable build --target ${{ matrix.target }} + cargo +stable test --target ${{ matrix.target }} + + - name: Release on nightly + run: | + rustup toolchain install nightly --profile minimal -t ${{ matrix.target }} + cargo +nightly build --release --target ${{ matrix.target }} + env: + GEN_COMPLETIONS: 1 + RUSTFLAGS: -Z strip=symbols + + - name: Upload bianry + uses: actions/upload-artifact@v2 + with: + name: ouch-${{ matrix.target }}${{ matrix.ext }} + path: target/${{ matrix.target }}/release/ouch${{ matrix.ext }} + + - name: Build man page and find completions (musl) + if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} + run: | + help2man target/${{ matrix.target }}/release/ouch > ouch.1 + cp -r target/${{ matrix.target }}/release/build/ouch-*/out/completions . + + - name: Upload completions (musl) + if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} + uses: actions/upload-artifact@v2 + with: + name: completions + path: completions + + - name: Upload man page (musl) + if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} + uses: actions/upload-artifact@v2 + with: + name: ouch.1 + path: ouch.1 + + clippy-rustfmt: + name: clippy-rustfmt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: "Cargo: clippy, fmt" + run: | + rustup toolchain install stable --profile minimal -c clippy + rustup toolchain install nightly --profile minimal -c rustfmt + cargo +stable clippy -- -D warnings + cargo +nightly fmt -- --check diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 2c5276607..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,317 +0,0 @@ -on: [push, pull_request] - -name: build-and-test - - -jobs: - # aarch64-glibc: - # name: Ubuntu 18.04 (for ARMv8 - glibc) - # runs-on: ubuntu-18.04 - # steps: - # - uses: actions/checkout@v2 - # - uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # target: aarch64-unknown-linux-gnu - # override: true - - # - name: Install binutils-arm-none-eabi - # run: | - # sudo apt-get update - # sudo apt-get install binutils-aarch64-linux-gnu - - # - uses: actions-rs/cargo@v1 - # with: - # use-cross: true - # command: build - # args: --target=aarch64-unknown-linux-gnu - - # - name: Run cargo test - # uses: actions-rs/cargo@v1 - # with: - # use-cross: true - # command: test - # args: --target=aarch64-unknown-linux-gnu - - # - name: Strip binary - # run: aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/ouch - - # - name: Upload binary - # uses: actions/upload-artifact@v2 - # with: - # name: 'ouch-aarch64-linux-gnu' - # path: target/aarch64-unknown-linux-gnu/release/ouch - - - # armv7-glibc: - # name: Ubuntu 18.04 (for ARMv7 - glibc) - # continue-on-error: true - # runs-on: ubuntu-18.04 - # steps: - # - uses: actions/checkout@v2 - # - uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # target: armv7-unknown-linux-gnueabihf - # override: true - - # - name: Install binutils-arm-none-eabi - # run: | - # sudo apt-get update - # sudo apt-get install binutils-arm-none-eabi - - # - uses: actions-rs/cargo@v1 - # with: - # use-cross: true - # command: build - # args: --target=armv7-unknown-linux-gnueabihf - - # - name: Run cargo test - # uses: actions-rs/cargo@v1 - # with: - # use-cross: true - # command: test - # args: --target=armv7-unknown-linux-gnueabihf - - # - name: Strip binary - # run: arm-none-eabi-strip target/armv7-unknown-linux-gnueabihf/release/ouch - - # - name: Upload binary - # uses: actions/upload-artifact@v2 - # with: - # name: 'ouch-armv7-linux-gnueabihf' - # path: target/armv7-unknown-linux-gnueabihf/release/ouch - - - x86_64_musl: - name: Ubuntu 20.04 (musl) - runs-on: ubuntu-20.04 - strategy: - matrix: - rust: - - stable - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - 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 help2man musl-tools - - - name: Run cargo build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --target x86_64-unknown-linux-musl - env: - GEN_COMPLETIONS: 1 - - - name: Run cargo test - uses: actions-rs/cargo@v1 - with: - command: test - args: --target x86_64-unknown-linux-musl - - - name: Build man page and find completions - run: | - help2man target/x86_64-unknown-linux-musl/release/ouch > ouch.1 - cp -r target/x86_64-unknown-linux-musl/release/build/ouch-*/out/completions . - - - name: Strip binary - run: strip target/x86_64-unknown-linux-musl/release/ouch - - - name: Upload binary - uses: actions/upload-artifact@v2 - with: - name: 'ouch-x86_64-linux-musl' - path: target/x86_64-unknown-linux-musl/release/ouch - - - name: Upload completions - uses: actions/upload-artifact@v2 - with: - name: completions - path: completions - - - name: Upload man page - uses: actions/upload-artifact@v2 - with: - name: ouch.1 - path: ouch.1 - - x86_64_glibc: - name: Ubuntu 20.04 (glibc) - runs-on: ubuntu-20.04 - strategy: - matrix: - rust: - - stable - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - - name: Run cargo build - uses: actions-rs/cargo@v1 - with: - command: build - - - name: Run cargo test - uses: actions-rs/cargo@v1 - with: - command: test - - # - name: Strip binary - # run: strip target/release/ouch - - # - name: Upload binary - # uses: actions/upload-artifact@v2 - # with: - # name: 'ouch-x86_64-linux-gnu' - # path: target/release/ouch - - - x86_64_macos: - name: macOS (x86_64) - runs-on: macos-latest - strategy: - matrix: - rust: - - stable - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-apple-darwin - override: true - - - name: Run cargo build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release - - - name: Run cargo test - uses: actions-rs/cargo@v1 - with: - command: test - - - name: Strip binary - run: strip target/release/ouch - - - name: Upload binary - uses: actions/upload-artifact@v2 - with: - name: 'ouch-x86_64-apple-darwin' - path: target/release/ouch - - - windows-msvc: - name: Windows Server (MSVC) - runs-on: windows-latest - strategy: - matrix: - rust: - - stable - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - override: true - - - name: Run cargo build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release - - - name: Run cargo test - uses: actions-rs/cargo@v1 - with: - command: test - - - name: Upload binary - uses: actions/upload-artifact@v2 - with: - name: 'ouch-x86_64-pc-windows-msvc' - path: target\release\ouch.exe - - - # windows-mingw: - # name: Windows Server (MinGW) - # runs-on: windows-2019 - # strategy: - # matrix: - # rust: - # - stable - # steps: - # - name: Checkout sources - # uses: actions/checkout@v2 - - # - name: Install toolchain - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # target: x86_64-pc-windows-gnu - # override: true - - # - name: Run cargo build - # uses: actions-rs/cargo@v1 - # with: - # command: build - # args: --target x86_64-pc-windows-gnu - - # - name: Run cargo test - # uses: actions-rs/cargo@v1 - # with: - # command: test - # args: --target x86_64-pc-windows-gnu - - # - name: Upload binary - # uses: actions/upload-artifact@v2 - # with: - # name: 'ouch-x86_64-pc-windows-gnu' - # path: target\x86_64-pc-windows-gnu\release\ouch.exe - - fmt: - name: Check sourcecode format - runs-on: ubuntu-latest - - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - target: x86_64-unknown-linux-musl - components: rustfmt - override: true - - - name: Check format with cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check From 15e922b7ba75134218515b9d8be5d915de46d758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 01:16:42 -0300 Subject: [PATCH 07/22] Fixing some Extension tests --- src/extension.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/extension.rs b/src/extension.rs index d0ae41bb3..07beb0375 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -85,14 +85,6 @@ impl fmt::Display for 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) { // // 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 @@ -130,15 +122,23 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec Vec { let (_, extensions) = separate_known_extensions_from_name(path); extensions } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extensions_from_path() { + use CompressionFormat::*; + let path = Path::new("bolovo.tar.gz"); + + let extensions: Vec = extensions_from_path(&path); + let formats: Vec<&CompressionFormat> = extensions.iter().flat_map(Extension::iter).collect::>(); + + assert_eq!(formats, vec![&Tar, &Gzip]); + } +} From d2d4a929e1d6848f3daaafc3956399ae9441d374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 04:57:26 -0300 Subject: [PATCH 08/22] Docs improvements + Removed some dead code --- src/archive/tar.rs | 1 - src/cli.rs | 11 +++++---- src/commands.rs | 2 +- src/dialogs.rs | 23 +++++++++--------- src/error.rs | 60 +++++++++++++++++++++++++++++++++------------- src/extension.rs | 22 +++++++++++------ src/lib.rs | 10 +++----- src/macros.rs | 4 ++-- src/opts.rs | 30 +++++++++++++++-------- src/utils.rs | 41 +++++++++++++++---------------- 10 files changed, 122 insertions(+), 82 deletions(-) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 5aadb462d..1130598f8 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -71,7 +71,6 @@ where FinalError::with_title("Could not create archive") .detail("Unexpected error while trying to read file") .detail(format!("Error: {}.", err)) - .into_owned() })?; } } diff --git a/src/cli.rs b/src/cli.rs index cd853def2..17c6c7140 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,4 @@ -//! CLI configuration step, uses definitions from `opts.rs`. -//! -//! Also used to treat some inputs. +//! CLI related functions, uses the clap argparsing definitions from `opts.rs`. use std::{ path::{Path, PathBuf}, @@ -13,8 +11,11 @@ use fs_err as fs; use crate::{Error, Opts, QuestionPolicy, Subcommand}; impl Opts { - /// A helper method that calls `clap::Parser::parse` and then translates relative paths to absolute. - /// Also determines if the user wants to skip questions or not + /// A helper method that calls `clap::Parser::parse`. + /// + /// And: + /// 1. Make paths absolute. + /// 2. Checks the QuestionPolicy. pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> { let mut opts: Self = Self::parse(); diff --git a/src/commands.rs b/src/commands.rs index 7467772d6..6cfd87718 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -37,7 +37,7 @@ fn represents_several_files(files: &[PathBuf]) -> bool { } /// Entrypoint of ouch, receives cli options and matches Subcommand -/// to decide current operation +/// to decide what to do pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { match args.cmd { Subcommand::Compress { files, output: output_path } => { diff --git a/src/dialogs.rs b/src/dialogs.rs index ce1810271..cbfe7546f 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -1,7 +1,7 @@ //! Pretty (and colored) dialog for asking [Y/n] for the end user. //! //! Example: -//! "Do you want to overwrite 'archive.targz'? [Y/n]" +//! "Do you want to overwrite 'archive.tar.gz'? [Y/n]" use std::{ borrow::Cow, @@ -10,19 +10,21 @@ use std::{ use crate::utils::colors; -/// Represents a confirmation dialog +/// Confirmation dialog for end user with [Y/n] question. +/// +/// If the placeholder is found in the prompt text, it will be replaced to form the final message. pub struct Confirmation<'a> { - /// Represents the message to the displayed + /// The message to be displayed with the placeholder text in it. /// e.g.: "Do you want to overwrite 'FILE'?" pub prompt: &'a str, - /// Represents a placeholder to be changed at runtime + /// The placeholder text that will be replaced in the `ask` function: /// e.g.: Some("FILE") pub placeholder: Option<&'a str>, } impl<'a> Confirmation<'a> { - /// New Confirmation + /// Creates a new Confirmation. pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self { Self { prompt, placeholder: pattern } } @@ -35,20 +37,17 @@ impl<'a> Confirmation<'a> { (Some(placeholder), Some(subs)) => Cow::Owned(self.prompt.replace(placeholder, subs)), }; + // Ask the same question to end while no valid answers are given loop { print!("{} [{}Y{}/{}n{}] ", message, *colors::GREEN, *colors::RESET, *colors::RED, *colors::RESET); io::stdout().flush()?; let mut answer = String::new(); io::stdin().read_line(&mut answer)?; - let trimmed_answer = answer.trim(); - if trimmed_answer.is_empty() { - return Ok(true); - } - - match trimmed_answer.to_ascii_lowercase().as_ref() { - "y" | "yes" => return Ok(true), + answer.make_ascii_lowercase(); + match answer.trim() { + "" | "y" | "yes" => return Ok(true), "n" | "no" => return Ok(false), _ => continue, // Try again } diff --git a/src/error.rs b/src/error.rs index f395e7ac4..77a675145 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,6 @@ -//! Error type definitions. +//! Error types definitions. //! -//! All the unexpected user-side errors should be treated in this file, that does not include -//! errors made by devs in our implementation. -//! -//! TODO: wrap `FinalError` in a variant to keep all `FinalError::display_and_crash()` function -//! calls inside of this module. - -#![allow(missing_docs)] +//! All usage errors will pass throught the Error enum, a lot of them in the Error::Custom. use std::{ fmt::{self, Display}, @@ -15,32 +9,64 @@ use std::{ use crate::utils::colors::*; -/// Custom Ouch Errors +/// All errors that can be generated by `ouch` #[derive(Debug, PartialEq)] pub enum Error { + /// Extension found is not supported and known to ouch UnknownExtensionError(String), + /// TO BE REMOVED MissingExtensionError(PathBuf), - IoError { reason: String }, + /// Not every IoError, some of them get filtered by `From` into other variants + IoError { + /// TODO + reason: String, + }, + /// Detected from io::Error if .kind() is io::ErrorKind::NotFound FileNotFound(PathBuf), + /// TO BE REMOVED AlreadyExists, + /// TO BE REMOVED InvalidZipArchive(&'static str), - PermissionDenied { error_title: String }, + /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied + PermissionDenied { + /// TODO + error_title: String, + }, + /// TO BE REMOVED UnsupportedZipArchive(&'static str), + /// TO BE REMOVED InternalError, + /// TO BE REMOVED CompressingRootFolder, + /// TO BE REMOVED MissingArgumentsForCompression, + /// TO BE REMOVED MissingArgumentsForDecompression, + /// TO BE REMOVED CompressionTypo, - WalkdirError { reason: String }, - Custom { reason: FinalError }, + /// Specialized walkdir's io::Error wrapper with additional information on the error + WalkdirError { + /// TODO + reason: String, + }, + /// Custom and unique errors are reported in this variant + Custom { + /// TODO + reason: FinalError, + }, } +/// Alias to std's Result with ouch's Error pub type Result = std::result::Result; +/// Pretty final error message for end users, crashing the program after display. #[derive(Clone, Debug, Default, PartialEq)] pub struct FinalError { + /// Should be made of just one line, appears after the "[ERROR]" part title: String, + /// Shown as a unnumbered list in yellow details: Vec, + /// Shown as green at the end to give hints on how to work around this error, if it's fixable hints: Vec, } @@ -68,23 +94,22 @@ impl Display for FinalError { } impl FinalError { + /// Only constructor pub fn with_title(title: impl ToString) -> Self { Self { title: title.to_string(), details: vec![], hints: vec![] } } + /// Add one detail line, can have multiple pub fn detail(mut self, detail: impl ToString) -> Self { self.details.push(detail.to_string()); self } + /// Add one hint line, can have multiple pub fn hint(mut self, hint: impl ToString) -> Self { self.hints.push(hint.to_string()); self } - - pub fn into_owned(&mut self) -> Self { - std::mem::take(self) - } } impl fmt::Display for Error { @@ -151,6 +176,7 @@ impl fmt::Display for Error { } impl Error { + /// TO BE REMOVED pub fn with_reason(reason: FinalError) -> Self { Self::Custom { reason } } diff --git a/src/extension.rs b/src/extension.rs index 07beb0375..c9dc05565 100644 --- a/src/extension.rs +++ b/src/extension.rs @@ -7,7 +7,9 @@ use self::CompressionFormat::*; /// A wrapper around `CompressionFormat` that allows combinations like `tgz` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Extension { + /// One extension like "tgz" can be made of multiple CompressionFormats ([Tar, Gz]) pub compression_formats: Vec, + /// The input text for this extension, like "tgz", "tar" or "xz" pub display_text: String, } @@ -26,6 +28,7 @@ impl Extension { self.compression_formats[0].is_archive_format() } + /// Iteration to inner compression formats, useful for flat_mapping pub fn iter(&self) -> impl Iterator { self.compression_formats.iter() } @@ -37,16 +40,21 @@ impl fmt::Display for Extension { } } -#[allow(missing_docs)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] /// Accepted extensions for input and output pub enum CompressionFormat { - Gzip, // .gz - Bzip, // .bz - Lzma, // .lzma - Tar, // .tar (technically not a compression extension, but will do for now) - Zstd, // .zst - Zip, // .zip + /// .gz + Gzip, + /// .bz .bz2 + Bzip, + /// .xz .lzma .lz + Lzma, + /// tar, tgz, tbz, tbz2, txz, tlz, tlzma, tzst + Tar, + /// .zst + Zstd, + /// .zip + Zip, } impl CompressionFormat { diff --git a/src/lib.rs b/src/lib.rs index 40384a62f..71c51fea9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,4 @@ -//! This library is meant to be published, just used internally by our binary crate at `main.rs`. -//! -//! A module shall be public only if: -//! 1. It's required by `main.rs`, or -//! 2. It's required by some integration tests at tests/ folder. +//! This library isn't meant to be published, but used internally by our binary crate `main.rs`. #![warn(missing_docs)] @@ -17,12 +13,12 @@ pub mod error; pub mod extension; pub mod utils; -/// CLI configuration step, uses definitions from `opts.rs`, also used to treat some inputs. +/// CLI argparsing definitions, using `clap`. pub mod opts; pub use error::{Error, Result}; pub use opts::{Opts, Subcommand}; pub use utils::QuestionPolicy; -/// The status code ouch has when an error is encountered +/// The status code returned from `ouch` on error pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE; diff --git a/src/macros.rs b/src/macros.rs index 5bf72055f..1057120ab 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,6 +1,6 @@ //! Macros used on ouch. -/// Macro that prints message in INFO mode +/// Macro that prints [INFO] messages, wraps [`println`]. #[macro_export] macro_rules! info { ($($arg:tt)*) => { @@ -9,7 +9,7 @@ macro_rules! info { }; } -/// Prints the `[Info]` tag +/// Helper to display "[INFO]", colored yellow pub fn _info_helper() { use crate::utils::colors::{RESET, YELLOW}; diff --git a/src/opts.rs b/src/opts.rs index 243a5a081..14d699767 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -6,41 +6,51 @@ use std::path::PathBuf; #[derive(Parser, Debug)] #[clap(version, about)] pub struct Opts { - /// Skip overwrite questions positively. + /// Skip [Y/n] questions positively. #[clap(short, long, conflicts_with = "no")] pub yes: bool, - /// Skip overwrite questions negatively. + /// Skip [Y/n] questions negatively. #[clap(short, long)] pub no: bool, - /// Action to take + /// Ouch and claps subcommands #[clap(subcommand)] pub cmd: Subcommand, } -/// Actions to take +// CAREFUL: this docs can accidentally become part of the --help message if they get too long +// this was tested in clap 3.0.0-beta5. +/// Repository: https://github.com/ouch-org/ouch +// +// Ouch commands: +// - `compress` +// - `decompress` +// - `list` +// +// Clap commands: +// - `help` #[derive(Parser, PartialEq, Eq, Debug)] pub enum Subcommand { - /// Compress files. Alias: c + /// Compress one or more files into one output file. #[clap(alias = "c")] Compress { - /// Files to be compressed + /// Files to be compressed. #[clap(required = true, min_values = 1)] files: Vec, - /// The resulting file. Its extensions specify how the files will be compressed and they need to be supported + /// The resulting file. It's extensions can be used to specify the compression formats. #[clap(required = true, value_hint = ValueHint::FilePath)] output: PathBuf, }, - /// Compress files. Alias: d + /// Decompresses one or more files, optionally into another folder. #[clap(alias = "d")] Decompress { - /// Files to be decompressed + /// Files to be decompressed. #[clap(required = true, min_values = 1)] files: Vec, - /// Decompress files in a directory other than the current + /// Choose to files in a directory other than the current #[clap(short, long = "dir", value_hint = ValueHint::DirPath)] output_dir: Option, }, diff --git a/src/utils.rs b/src/utils.rs index e8aaf524b..7d19964f7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -//! Utils used on ouch. +//! Random stuff used on ouch. use std::{ cmp, env, @@ -11,7 +11,7 @@ use fs_err as fs; use crate::{dialogs::Confirmation, info}; -/// Checks if the given path represents an empty directory. +/// Checks given path points to an empty directory. pub fn dir_is_empty(dir_path: &Path) -> bool { let is_empty = |mut rd: std::fs::ReadDir| rd.next().is_none(); @@ -37,21 +37,18 @@ pub fn strip_cur_dir(source_path: &Path) -> PathBuf { .unwrap_or_else(|_| source_path.to_path_buf()) } -/// Changes the process' current directory to the directory that contains the -/// file pointed to by `filename` and returns the directory that the process -/// was in before this function was called. +/// Returns current directory, but before change the process' directory to the +/// one that contains the file pointed to by `filename`. pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result { let previous_location = env::current_dir()?; let parent = filename.parent().ok_or(crate::Error::CompressingRootFolder)?; - env::set_current_dir(parent)?; Ok(previous_location) } -/// Centralizes the decision of overwriting a file or not, -/// whether the user has already passed a question_policy or not. +/// Check if QuestionPolicy flags were set, otherwise, ask user if they want to overwrite. pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result { match question_policy { QuestionPolicy::AlwaysYes => Ok(true), @@ -65,13 +62,17 @@ pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> } } -/// Converts an OsStr to utf8. +/// Converts an OsStr to utf8 with custom formatting. +/// +/// This is different from [`Path::display`]. +/// +/// See https://gist.github.com/marcospb19/ebce5572be26397cf08bbd0fd3b65ac1 for a comparison. pub fn to_utf(os_str: impl AsRef) -> String { let text = format!("{:?}", os_str.as_ref()); text.trim_matches('"').to_string() } -/// Treats weird paths for better user messages. +/// Display the directory name, but change to "current directory" when necessary. pub fn nice_directory_display(os_str: impl AsRef) -> String { let text = to_utf(os_str); if text == "." { @@ -81,11 +82,6 @@ pub fn nice_directory_display(os_str: impl AsRef) -> String { } } -/// Struct used to overload functionality onto Byte presentation. -pub struct Bytes { - bytes: f64, -} - /// Module with a list of bright colors. #[allow(dead_code)] pub mod colors { @@ -116,10 +112,15 @@ pub mod colors { color!(YELLOW = "\u{1b}[38;5;11m"); } +/// Struct useful to printing bytes as kB, MB, GB, etc. +pub struct Bytes { + bytes: f64, +} + impl Bytes { const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"]; - /// New Byte structure + /// Create a new Bytes. pub fn new(bytes: u64) -> Self { Self { bytes: bytes as f64 } } @@ -141,13 +142,13 @@ impl std::fmt::Display for Bytes { } #[derive(Debug, PartialEq, Clone, Copy)] -/// How overwrite questions should be handled +/// Determines if overwrite questions should be skipped or asked to the user pub enum QuestionPolicy { - /// Ask everytime + /// Ask the user every time Ask, - /// Skip overwrite questions positively + /// Set by `--yes`, will say 'Y' to all overwrite questions AlwaysYes, - /// Skip overwrite questions negatively + /// Set by `--no`, will say 'N' to all overwrite questions AlwaysNo, } From 6cebf78da635462e759345089dad01a79e398029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 05:18:34 -0300 Subject: [PATCH 09/22] Simplify Error::Custom code conversions --- src/commands.rs | 16 ++++++++-------- src/error.rs | 7 ------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 6cfd87718..c6a89ca6d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -20,7 +20,7 @@ use crate::{ }, info, utils::{self, dir_is_empty, nice_directory_display, to_utf}, - Error, Opts, QuestionPolicy, Subcommand, + Opts, QuestionPolicy, Subcommand, }; // Used in BufReader and BufWriter to perform less syscalls @@ -45,7 +45,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { let mut formats = extension::extensions_from_path(&output_path); if formats.is_empty() { - let reason = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) + let error = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) .detail("You shall supply the compression format via the extension.") .hint("Try adding something like .tar.gz or .zip to the output file.") .hint("") @@ -53,7 +53,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { .hint(format!(" ouch compress ... {}.tar.gz", to_utf(&output_path))) .hint(format!(" ouch compress ... {}.zip", to_utf(&output_path))); - return Err(Error::with_reason(reason)); + return Err(error.into()); } if !formats.get(0).map(Extension::is_archive).unwrap_or(false) && represents_several_files(&files) { @@ -73,7 +73,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { let mut suggested_output_path = output_path.clone(); suggested_output_path.replace_range(empty_range, ".tar"); - let reason = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) + let error = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) .detail("You are trying to compress multiple files.") .detail(format!("The compression format '{}' cannot receive multiple files.", &formats[0])) .detail("The only supported formats that archive files into an archive are .tar and .zip.") @@ -81,21 +81,21 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { .hint(format!("From: {}", output_path)) .hint(format!(" To : {}", suggested_output_path)); - return Err(Error::with_reason(reason)); + return Err(error.into()); } if let Some(format) = formats.iter().skip(1).find(|format| format.is_archive()) { - let reason = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) + let error = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) .detail(format!("Found the format '{}' in an incorrect position.", format)) .detail(format!("'{}' can only be used at the start of the file extension.", format)) .hint(format!("If you wish to compress multiple files, start the extension with '{}'.", format)) .hint(format!("Otherwise, remove the last '{}' from '{}'.", format, to_utf(&output_path))); - return Err(Error::with_reason(reason)); + return Err(error.into()); } if output_path.exists() && !utils::user_wants_to_overwrite(&output_path, question_policy)? { - // User does not want to overwrite this file + // User does not want to overwrite this file, skip and return without any errors return Ok(()); } diff --git a/src/error.rs b/src/error.rs index 77a675145..7b8102b29 100644 --- a/src/error.rs +++ b/src/error.rs @@ -175,13 +175,6 @@ impl fmt::Display for Error { } } -impl Error { - /// TO BE REMOVED - pub fn with_reason(reason: FinalError) -> Self { - Self::Custom { reason } - } -} - impl From for Error { fn from(err: std::io::Error) -> Self { match err.kind() { From 70c81ed8a480dec1a2868beb704608f74c387306 Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Tue, 2 Nov 2021 10:58:58 +0200 Subject: [PATCH 10/22] Fix non-archives overwriting files without asking and error-ing on dirs --- src/commands.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 7467772d6..25abe961b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -326,8 +326,22 @@ fn decompress_file( Gzip | Bzip | Lzma | Zstd => { reader = chain_reader_decoder(&formats[0].compression_formats[0], reader)?; - // TODO: improve error treatment - let mut writer = fs::File::create(&output_path)?; + let mut writer = match fs::OpenOptions::new().write(true).create_new(true).open(&output_path) { + Ok(w) => w, + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => { + if utils::user_wants_to_overwrite(&output_path, question_policy)? { + if output_path.is_dir() { + // We can't just use `fs::File::create(&output_path)` because it would return io::ErrorKind::IsADirectory + // ToDo: Maybe we should emphasise that `output_path` is a directory and everything inside it will be gone? + fs::remove_dir_all(&output_path)?; + } + fs::File::create(&output_path)? + } else { + return Ok(()); + } + } + Err(e) => return Err(Error::from(e)), + }; io::copy(&mut reader, &mut writer)?; files_unpacked = vec![output_path]; From fadc41289635369f7eaae4f1e312abc3bf83ddc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 06:13:24 -0300 Subject: [PATCH 11/22] Improve error message when no extensions given to decompress Fixes #137 --- src/commands.rs | 26 +++++++++++++++----------- src/utils.rs | 15 +++++++++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index c6a89ca6d..8883ceb2c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -19,7 +19,7 @@ use crate::{ Extension, }, info, - utils::{self, dir_is_empty, nice_directory_display, to_utf}, + utils::{self, concatenate_list_of_os_str, dir_is_empty, nice_directory_display, to_utf}, Opts, QuestionPolicy, Subcommand, }; @@ -36,8 +36,7 @@ fn represents_several_files(files: &[PathBuf]) -> bool { files.iter().any(is_non_empty_dir) || files.len() > 1 } -/// Entrypoint of ouch, receives cli options and matches Subcommand -/// to decide what to do +/// Entrypoint of ouch, receives cli options and matches Subcommand to decide what to do pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { match args.cmd { Subcommand::Compress { files, output: output_path } => { @@ -176,15 +175,20 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { .map(|(input_path, _)| PathBuf::from(input_path)) .collect(); - // Error if !files_missing_format.is_empty() { - eprintln!("Some file you asked ouch to decompress lacks a supported extension."); - eprintln!("Could not decompress {}.", to_utf(&files_missing_format[0])); - todo!( - "Dev note: add this error variant and pass the Vec to it, all the files \ - lacking extension shall be shown: {:#?}.", - files_missing_format - ); + let error = FinalError::with_title("Cannot decompress files without extensions") + .detail(format!( + "Files without supported extensions: {}", + concatenate_list_of_os_str(&files_missing_format) + )) + .detail("Decompression formats are detected automatically by the file extension") + .hint("Provide a file with a supported extension:") + .hint(" ouch decompress example.tar.gz") + .hint("") + .hint("Or overwrite this option with the '--format' flag:") + .hint(format!(" ouch decompress {} --format tar.gz", to_utf(&files_missing_format[0]))); + + return Err(error.into()); } // From Option to Option<&Path> diff --git a/src/utils.rs b/src/utils.rs index 7d19964f7..334186e01 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -72,6 +72,21 @@ pub fn to_utf(os_str: impl AsRef) -> String { text.trim_matches('"').to_string() } +/// Converts a slice of AsRef to comma separated String +/// +/// Panics if the slice is empty. +pub fn concatenate_list_of_os_str(os_strs: &[impl AsRef]) -> String { + let mut iter = os_strs.iter().map(AsRef::as_ref); + + let mut string = to_utf(iter.next().unwrap()); // May panic + + for os_str in iter { + string += ", "; + string += &to_utf(os_str); + } + string +} + /// Display the directory name, but change to "current directory" when necessary. pub fn nice_directory_display(os_str: impl AsRef) -> String { let text = to_utf(os_str); From 047102ec07f3a39ee099dfa9d3efbae750fb22f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 06:17:41 -0300 Subject: [PATCH 12/22] Remove InternalError, replaced by unreachable! macro --- src/dialogs.rs | 2 +- src/error.rs | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/dialogs.rs b/src/dialogs.rs index cbfe7546f..dbf6a73a1 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -33,7 +33,7 @@ impl<'a> Confirmation<'a> { pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result { let message = match (self.placeholder, substitute) { (None, _) => Cow::Borrowed(self.prompt), - (Some(_), None) => return Err(crate::Error::InternalError), + (Some(_), None) => unreachable!("dev error, should be reported, we checked this won't happen"), (Some(placeholder), Some(subs)) => Cow::Owned(self.prompt.replace(placeholder, subs)), }; diff --git a/src/error.rs b/src/error.rs index 7b8102b29..e51449d49 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,8 +35,6 @@ pub enum Error { /// TO BE REMOVED UnsupportedZipArchive(&'static str), /// TO BE REMOVED - InternalError, - /// TO BE REMOVED CompressingRootFolder, /// TO BE REMOVED MissingArgumentsForCompression, @@ -151,13 +149,6 @@ impl fmt::Display for Error { .hint("") .hint("Example: `ouch decompress imgs.tar.gz`") } - Error::InternalError => { - FinalError::with_title("InternalError :(") - .detail("This should not have happened") - .detail("It's probably our fault") - .detail("Please help us improve by reporting the issue at:") - .detail(format!(" {}https://github.com/ouch-org/ouch/issues ", *CYAN)) - } Error::IoError { reason } => FinalError::with_title(reason), Error::CompressionTypo => { FinalError::with_title("Possible typo detected") From 21134aaeff62ee13df9f2f64eb7bf63d92665ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 06:28:55 -0300 Subject: [PATCH 13/22] Improve message when no extensions given to compress file --- src/commands.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 8883ceb2c..be9cea12c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -45,12 +45,13 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { if formats.is_empty() { let error = FinalError::with_title(format!("Cannot compress to '{}'.", to_utf(&output_path))) - .detail("You shall supply the compression format via the extension.") - .hint("Try adding something like .tar.gz or .zip to the output file.") + .detail("You shall supply the compression format") + .hint("Try adding supported extensions (see --help):") + .hint(format!(" ouch compress ... {}.tar.gz", to_utf(&output_path))) + .hint(format!(" ouch compress ... {}.zip", to_utf(&output_path))) .hint("") - .hint("Examples:") - .hint(format!(" ouch compress ... {}.tar.gz", to_utf(&output_path))) - .hint(format!(" ouch compress ... {}.zip", to_utf(&output_path))); + .hint("Alternatively, you can overwrite this option by using the '--format' flag:") + .hint(format!(" ouch compress ... {} --format tar.gz", to_utf(&output_path))); return Err(error.into()); } From 5c0f24f5677958a224f4454e9ae86e3c01a1dd50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 06:30:59 -0300 Subject: [PATCH 14/22] Change alignment of error message replacement suggestion --- src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.rs b/src/commands.rs index be9cea12c..8159d174a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -79,7 +79,7 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> { .detail("The only supported formats that archive files into an archive are .tar and .zip.") .hint(format!("Try inserting '.tar' or '.zip' before '{}'.", &formats[0])) .hint(format!("From: {}", output_path)) - .hint(format!(" To : {}", suggested_output_path)); + .hint(format!("To: {}", suggested_output_path)); return Err(error.into()); } From df3bb3b72b009482d3ad5b330dd502be90f25a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20Bezerra?= Date: Tue, 2 Nov 2021 07:21:03 -0300 Subject: [PATCH 15/22] Simplify cli canonicalize implementation --- src/cli.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 17c6c7140..6d7b4a9e3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,7 @@ //! CLI related functions, uses the clap argparsing definitions from `opts.rs`. use std::{ + io, path::{Path, PathBuf}, vec::Vec, }; @@ -8,7 +9,7 @@ use std::{ use clap::Parser; use fs_err as fs; -use crate::{Error, Opts, QuestionPolicy, Subcommand}; +use crate::{Opts, QuestionPolicy, Subcommand}; impl Opts { /// A helper method that calls `clap::Parser::parse`. @@ -17,7 +18,7 @@ impl Opts { /// 1. Make paths absolute. /// 2. Checks the QuestionPolicy. pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> { - let mut opts: Self = Self::parse(); + let mut opts = Self::parse(); let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. }) = &mut opts.cmd; *files = canonicalize_files(files)?; @@ -34,19 +35,6 @@ impl Opts { } } -fn canonicalize(path: impl AsRef) -> crate::Result { - match fs::canonicalize(&path.as_ref()) { - Ok(abs_path) => Ok(abs_path), - Err(io_err) => { - if !path.as_ref().exists() { - Err(Error::FileNotFound(path.as_ref().into())) - } else { - Err(io_err.into()) - } - } - } -} - -fn canonicalize_files(files: &[impl AsRef]) -> crate::Result> { - files.iter().map(canonicalize).collect() +fn canonicalize_files(files: &[impl AsRef]) -> io::Result> { + files.iter().map(fs::canonicalize).collect() } From 2fcec33ec62d03be150b96e2ca48ac9b964c11c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 07:36:03 -0300 Subject: [PATCH 16/22] Removing obsolete error variants --- src/error.rs | 65 +++++++--------------------------------------------- 1 file changed, 8 insertions(+), 57 deletions(-) diff --git a/src/error.rs b/src/error.rs index e51449d49..81c5eef46 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,49 +9,28 @@ use std::{ use crate::utils::colors::*; +#[allow(missing_docs)] /// All errors that can be generated by `ouch` #[derive(Debug, PartialEq)] pub enum Error { - /// Extension found is not supported and known to ouch - UnknownExtensionError(String), - /// TO BE REMOVED - MissingExtensionError(PathBuf), /// Not every IoError, some of them get filtered by `From` into other variants - IoError { - /// TODO - reason: String, - }, + IoError { reason: String }, /// Detected from io::Error if .kind() is io::ErrorKind::NotFound FileNotFound(PathBuf), - /// TO BE REMOVED + /// NEEDS MORE CONTEXT AlreadyExists, - /// TO BE REMOVED + /// From zip::result::ZipError::InvalidArchive InvalidZipArchive(&'static str), /// Detected from io::Error if .kind() is io::ErrorKind::PermissionDenied - PermissionDenied { - /// TODO - error_title: String, - }, - /// TO BE REMOVED + PermissionDenied { error_title: String }, + /// From zip::result::ZipError::UnsupportedArchive UnsupportedZipArchive(&'static str), /// TO BE REMOVED CompressingRootFolder, - /// TO BE REMOVED - MissingArgumentsForCompression, - /// TO BE REMOVED - MissingArgumentsForDecompression, - /// TO BE REMOVED - CompressionTypo, /// Specialized walkdir's io::Error wrapper with additional information on the error - WalkdirError { - /// TODO - reason: String, - }, + WalkdirError { reason: String }, /// Custom and unique errors are reported in this variant - Custom { - /// TODO - reason: FinalError, - }, + Custom { reason: FinalError }, } /// Alias to std's Result with ouch's Error @@ -113,12 +92,6 @@ impl FinalError { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let err = match self { - Error::MissingExtensionError(filename) => { - FinalError::with_title(format!("Cannot compress to {:?}", filename)) - .detail("Ouch could not detect the compression format") - .hint("Use a supported format extension, like '.zip' or '.tar.gz'") - .hint("Check https://github.com/ouch-org/ouch for a full list of supported formats") - } Error::WalkdirError { reason } => FinalError::with_title(reason), Error::FileNotFound(file) => { if file == Path::new("") { @@ -132,29 +105,7 @@ impl fmt::Display for Error { .detail("This is unadvisable since ouch does compressions in-memory.") .hint("Use a more appropriate tool for this, such as rsync.") } - Error::MissingArgumentsForCompression => { - FinalError::with_title("Could not compress") - .detail("The compress command requires at least 2 arguments") - .hint("You must provide:") - .hint(" - At least one input argument.") - .hint(" - The output argument.") - .hint("") - .hint("Example: `ouch compress image.png img.zip`") - } - Error::MissingArgumentsForDecompression => { - FinalError::with_title("Could not decompress") - .detail("The compress command requires at least one argument") - .hint("You must provide:") - .hint(" - At least one input argument.") - .hint("") - .hint("Example: `ouch decompress imgs.tar.gz`") - } Error::IoError { reason } => FinalError::with_title(reason), - Error::CompressionTypo => { - FinalError::with_title("Possible typo detected") - .hint(format!("Did you mean '{}ouch compress{}'?", *MAGENTA, *RESET)) - } - Error::UnknownExtensionError(_) => todo!(), Error::AlreadyExists => todo!(), Error::InvalidZipArchive(_) => todo!(), Error::PermissionDenied { error_title } => FinalError::with_title(error_title).detail("Permission denied"), From 8ef1b25b12f021122cf83d94b650e1c722e943e7 Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Tue, 2 Nov 2021 12:38:10 +0200 Subject: [PATCH 17/22] Ask to overwrite dirs when decompressing archives --- src/archive/tar.rs | 7 +++++++ src/archive/zip.rs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index 5aadb462d..a778728e6 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -34,6 +34,13 @@ pub fn unpack_archive( continue; } + if file_path.is_dir() { + // We can't just use `fs::File::create(&file_path)` because it would return io::ErrorKind::IsADirectory + // ToDo: Maybe we should emphasise that `file_path` is a directory and everything inside it will be gone? + fs::remove_dir_all(&file_path)?; + fs::File::create(&file_path)?; + } + file.unpack_in(output_folder)?; info!("{:?} extracted. ({})", output_folder.join(file.path()?), Bytes::new(file.size())); diff --git a/src/archive/zip.rs b/src/archive/zip.rs index b62afd777..83de64739 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -41,6 +41,13 @@ where continue; } + if file_path.is_dir() { + // We can't just use `fs::File::create(&file_path)` because it would return io::ErrorKind::IsADirectory + // ToDo: Maybe we should emphasise that `file_path` is a directory and everything inside it will be gone? + fs::remove_dir_all(&file_path)?; + fs::File::create(&file_path)?; + } + check_for_comments(&file); match (&*file.name()).ends_with('/') { From 547b8c91e55971401e4408d47d3f10c45b09da54 Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Tue, 2 Nov 2021 13:34:04 +0200 Subject: [PATCH 18/22] Extract function --- src/commands.rs | 22 ++++++---------------- src/utils.rs | 24 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 25abe961b..c07015c03 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -326,22 +326,12 @@ fn decompress_file( Gzip | Bzip | Lzma | Zstd => { reader = chain_reader_decoder(&formats[0].compression_formats[0], reader)?; - let mut writer = match fs::OpenOptions::new().write(true).create_new(true).open(&output_path) { - Ok(w) => w, - Err(e) if e.kind() == io::ErrorKind::AlreadyExists => { - if utils::user_wants_to_overwrite(&output_path, question_policy)? { - if output_path.is_dir() { - // We can't just use `fs::File::create(&output_path)` because it would return io::ErrorKind::IsADirectory - // ToDo: Maybe we should emphasise that `output_path` is a directory and everything inside it will be gone? - fs::remove_dir_all(&output_path)?; - } - fs::File::create(&output_path)? - } else { - return Ok(()); - } - } - Err(e) => return Err(Error::from(e)), - }; + let writer = utils::create_or_ask_overwrite(&output_path, question_policy)?; + if writer.is_none() { + // Means that the user doesn't want to overwrite + return Ok(()); + } + let mut writer = writer.unwrap(); io::copy(&mut reader, &mut writer)?; files_unpacked = vec![output_path]; diff --git a/src/utils.rs b/src/utils.rs index e8aaf524b..e571bf8e4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,13 +3,35 @@ use std::{ cmp, env, ffi::OsStr, + io, path::Component, path::{Path, PathBuf}, }; use fs_err as fs; -use crate::{dialogs::Confirmation, info}; +use crate::{dialogs::Confirmation, info, Error}; + +/// Create the file if it doesn't exist and if it does then ask to overwrite it. +/// If the user doesn't want to overwrite then we return [`Ok(None)`] +pub fn create_or_ask_overwrite(path: &Path, question_policy: QuestionPolicy) -> Result, Error> { + match fs::OpenOptions::new().write(true).create_new(true).open(path) { + Ok(w) => Ok(Some(w)), + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => { + if user_wants_to_overwrite(path, question_policy)? { + if path.is_dir() { + // We can't just use `fs::File::create(&path)` because it would return io::ErrorKind::IsADirectory + // ToDo: Maybe we should emphasise that `path` is a directory and everything inside it will be gone? + fs::remove_dir_all(path)?; + } + Ok(Some(fs::File::create(path)?)) + } else { + Ok(None) + } + } + Err(e) => Err(Error::from(e)), + } +} /// Checks if the given path represents an empty directory. pub fn dir_is_empty(dir_path: &Path) -> bool { From 7f5ff0faf1c95705e451bfa7da522eb2f75bc2a0 Mon Sep 17 00:00:00 2001 From: Spyros Roum Date: Tue, 2 Nov 2021 13:34:38 +0200 Subject: [PATCH 19/22] Fix archives panicking when asked to overwrite file --- src/archive/tar.rs | 4 ++-- src/archive/zip.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/archive/tar.rs b/src/archive/tar.rs index a778728e6..c71cdbbfc 100644 --- a/src/archive/tar.rs +++ b/src/archive/tar.rs @@ -35,10 +35,10 @@ pub fn unpack_archive( } if file_path.is_dir() { - // We can't just use `fs::File::create(&file_path)` because it would return io::ErrorKind::IsADirectory // ToDo: Maybe we should emphasise that `file_path` is a directory and everything inside it will be gone? fs::remove_dir_all(&file_path)?; - fs::File::create(&file_path)?; + } else if file_path.is_file() { + fs::remove_file(&file_path)?; } file.unpack_in(output_folder)?; diff --git a/src/archive/zip.rs b/src/archive/zip.rs index 83de64739..908512938 100644 --- a/src/archive/zip.rs +++ b/src/archive/zip.rs @@ -42,10 +42,10 @@ where } if file_path.is_dir() { - // We can't just use `fs::File::create(&file_path)` because it would return io::ErrorKind::IsADirectory // ToDo: Maybe we should emphasise that `file_path` is a directory and everything inside it will be gone? fs::remove_dir_all(&file_path)?; - fs::File::create(&file_path)?; + } else if file_path.is_file() { + fs::remove_file(&file_path)?; } check_for_comments(&file); From 800cf8d39fb8b49b09612fd0b793a695f02a7b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 09:49:49 -0300 Subject: [PATCH 20/22] Bump version 0.3.0 --- Cargo.toml | 2 +- install.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5627f517..298b45435 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ouch" -version = "0.2.0" +version = "0.3.0" authors = ["Vinícius Rodrigues Miguel ", "João M. Bezerra "] edition = "2018" readme = "README.md" diff --git a/install.sh b/install.sh index a05924fdb..9a3c06da3 100644 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #! /usr/bin/sh # Needs to be updated each version bump -VERSION="0.2.0" +VERSION="0.3.0" DOWNLOAD_LOCATION="/tmp/ouch-binary" INSTALLATION_LOCATION="/usr/local/bin/ouch" From 000ce62eb3cde0b076c162d952089f48747458ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20M=2E=20Bezerra?= Date: Tue, 2 Nov 2021 10:53:06 -0300 Subject: [PATCH 21/22] Updating Cargo.lock --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75733f18e..ef911d1c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,9 +234,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.103" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" +checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" [[package]] name = "libz-sys" @@ -293,7 +293,7 @@ dependencies = [ [[package]] name = "ouch" -version = "0.2.0" +version = "0.3.0" dependencies = [ "atty", "bzip2", @@ -315,15 +315,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" [[package]] name = "ppv-lite86" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "proc-macro-error" @@ -351,9 +351,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid", ] @@ -442,9 +442,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" dependencies = [ "proc-macro2", "quote", From cd77b987ac48183fc254862bb0463092233eb8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Marcos=20Bezerra?= Date: Tue, 2 Nov 2021 11:26:16 -0300 Subject: [PATCH 22/22] readme: refer to list command for the next release --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1d706cc9e..4453fd577 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ 3. Same syntax, various formats. 4. Encoding and decoding streams, it's fast. 5. No runtime dependencies (for _Linux x86_64_). +6. Listing archive contents with tree formatting (in next release!). ## Usage