mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-06 19:45:29 +00:00
Merge branch 'master' into Remove-tar-combinations-from-CompressionFormat
This commit is contained in:
commit
75cad36c5c
87
.github/workflows/build-and-test.yml
vendored
Normal file
87
.github/workflows/build-and-test.yml
vendored
Normal file
@ -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
|
276
.github/workflows/build.yml
vendored
276
.github/workflows/build.yml
vendored
@ -1,276 +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 musl-tools
|
|
||||||
|
|
||||||
- name: Run cargo build
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
args: --release --target x86_64-unknown-linux-musl
|
|
||||||
|
|
||||||
- name: Run cargo test
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --target x86_64-unknown-linux-musl
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
@ -4,12 +4,12 @@ Feel free to open an issue anytime you wish to ask a question, suggest a feature
|
|||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
1. Be kind, considerate and respectfull.
|
1. Be nice to other people.
|
||||||
2. If editing .rs files, run `rustfmt` on them before commiting.
|
2. If editing the Rust source code, remember to run `rustfmt` (otherwise, CI will warn you the code was not properly formatted).
|
||||||
|
|
||||||
Note that we are using `unstable` features of `rustfmt`, so you will need to change your toolchain to nightly.
|
Note: we are using `unstable` features of `rustfmt`! Nightly toolchain is required (will likely be installed automatically, cause the toolchain was specified in the project root).
|
||||||
|
|
||||||
# Suggestions
|
# Suggestions
|
||||||
|
|
||||||
1. Ask for some guidance before solving an error if you feel like it.
|
1. If you wish to, you can ask for some guidance before solving an issue.
|
||||||
2. If editing Rust code, run `clippy` before commiting.
|
2. Run `cargo clippy` too.
|
||||||
|
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -114,6 +114,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_generate"
|
||||||
|
version = "3.0.0-beta.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "097ab5db1c3417442270cd57c8dd39f6c3114d3ce09d595f9efddbb1fcfaa799"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@ -148,6 +157,12 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs-err"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ebd3504ad6116843b8375ad70df74e7bfe83cac77a1f3fe73200c844d43bfe0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@ -283,7 +298,9 @@ dependencies = [
|
|||||||
"atty",
|
"atty",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_generate",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"fs-err",
|
||||||
"infer",
|
"infer",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -4,7 +4,7 @@ version = "0.2.0"
|
|||||||
authors = ["Vinícius Rodrigues Miguel <vrmiguel99@gmail.com>", "João M. Bezerra <marcospb19@hotmail.com>"]
|
authors = ["Vinícius Rodrigues Miguel <vrmiguel99@gmail.com>", "João M. Bezerra <marcospb19@hotmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/vrmiguel/ouch"
|
repository = "https://github.com/ouch-org/ouch"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
keywords = ["decompression", "compression", "zip", "tar", "gzip"]
|
keywords = ["decompression", "compression", "zip", "tar", "gzip"]
|
||||||
categories = ["command-line-utilities", "compression", "encoding"]
|
categories = ["command-line-utilities", "compression", "encoding"]
|
||||||
@ -15,6 +15,7 @@ description = "A command-line utility for easily compressing and decompressing f
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "=3.0.0-beta.5" # Keep it pinned while in beta!
|
clap = "=3.0.0-beta.5" # Keep it pinned while in beta!
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
|
fs-err = "2.6.0"
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
bzip2 = "0.4.3"
|
bzip2 = "0.4.3"
|
||||||
@ -25,6 +26,10 @@ zip = { version = "0.5.13", default-features = false, features = ["defl
|
|||||||
flate2 = { version = "1.0.22", default-features = false, features = ["zlib"] }
|
flate2 = { version = "1.0.22", default-features = false, features = ["zlib"] }
|
||||||
zstd = { version = "0.9.0", default-features = false, features = ["thin"] }
|
zstd = { version = "0.9.0", default-features = false, features = ["thin"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
clap = "=3.0.0-beta.5"
|
||||||
|
clap_generate = "=3.0.0-beta.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
infer = "0.5.0"
|
infer = "0.5.0"
|
||||||
|
@ -35,11 +35,11 @@ ouch decompress a.zip b.tar.gz c.tar
|
|||||||
ouch d a.zip
|
ouch d a.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
You can redirect the decompression results to another folder with the `-o/--output` flag.
|
You can redirect the decompression results to another folder with the `-d/--dir` flag.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Decompress 'summer_vacation.zip' inside of new folder 'pictures'
|
# Decompress 'summer_vacation.zip' inside of new folder 'pictures'
|
||||||
ouch decompress summer_vacation.zip -o pictures
|
ouch decompress summer_vacation.zip -d pictures
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compressing
|
### Compressing
|
||||||
|
22
build.rs
Normal file
22
build.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use clap::{ArgEnum, IntoApp};
|
||||||
|
use clap_generate::{generate_to, Shell};
|
||||||
|
|
||||||
|
use std::{env, fs::create_dir_all, path::Path};
|
||||||
|
|
||||||
|
include!("src/opts.rs");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-env-changed=GEN_COMPLETIONS");
|
||||||
|
|
||||||
|
if env::var_os("GEN_COMPLETIONS") != Some("1".into()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let out = &Path::new(&env::var_os("OUT_DIR").unwrap()).join("completions");
|
||||||
|
create_dir_all(out).unwrap();
|
||||||
|
let app = &mut Opts::into_app();
|
||||||
|
|
||||||
|
for shell in Shell::value_variants() {
|
||||||
|
generate_to(*shell, app, "ouch", out).unwrap();
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,4 @@
|
|||||||
|
//! Archive compression algorithms
|
||||||
|
|
||||||
pub mod tar;
|
pub mod tar;
|
||||||
pub mod zip;
|
pub mod zip;
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
//! Contains Tar-specific building and unpacking functions
|
//! Contains Tar-specific building and unpacking functions
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env,
|
||||||
io::prelude::*,
|
io::prelude::*,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use fs_err as fs;
|
||||||
use tar;
|
use tar;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::FinalError,
|
||||||
info,
|
info,
|
||||||
utils::{self, Bytes},
|
utils::{self, Bytes},
|
||||||
|
QuestionPolicy,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Unpacks the archive given by `archive` into the folder given by `into`.
|
||||||
pub fn unpack_archive(
|
pub fn unpack_archive(
|
||||||
reader: Box<dyn Read>,
|
reader: Box<dyn Read>,
|
||||||
output_folder: &Path,
|
output_folder: &Path,
|
||||||
skip_questions_positively: Option<bool>,
|
question_policy: QuestionPolicy,
|
||||||
) -> crate::Result<Vec<PathBuf>> {
|
) -> crate::Result<Vec<PathBuf>> {
|
||||||
let mut archive = tar::Archive::new(reader);
|
let mut archive = tar::Archive::new(reader);
|
||||||
|
|
||||||
@ -26,7 +30,7 @@ pub fn unpack_archive(
|
|||||||
let mut file = file?;
|
let mut file = file?;
|
||||||
|
|
||||||
let file_path = output_folder.join(file.path()?);
|
let file_path = output_folder.join(file.path()?);
|
||||||
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, skip_questions_positively)? {
|
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, question_policy)? {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +44,7 @@ pub fn unpack_archive(
|
|||||||
Ok(files_unpacked)
|
Ok(files_unpacked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
|
||||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
||||||
where
|
where
|
||||||
W: Write,
|
W: Write,
|
||||||
@ -62,7 +67,12 @@ where
|
|||||||
builder.append_dir(path, path)?;
|
builder.append_dir(path, path)?;
|
||||||
} else {
|
} else {
|
||||||
let mut file = fs::File::open(path)?;
|
let mut file = fs::File::open(path)?;
|
||||||
builder.append_file(path, &mut file)?;
|
builder.append_file(path, file.file_mut()).map_err(|err| {
|
||||||
|
FinalError::with_title("Could not create archive")
|
||||||
|
.detail("Unexpected error while trying to read file")
|
||||||
|
.detail(format!("Error: {}.", err))
|
||||||
|
.into_owned()
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
env::set_current_dir(previous_location)?;
|
env::set_current_dir(previous_location)?;
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
//! Contains Zip-specific building and unpacking functions
|
//! Contains Zip-specific building and unpacking functions
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env,
|
||||||
io::{self, prelude::*},
|
io::{self, prelude::*},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use fs_err as fs;
|
||||||
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use zip::{self, read::ZipFile, ZipArchive};
|
use zip::{self, read::ZipFile, ZipArchive};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
info,
|
info,
|
||||||
utils::{self, dir_is_empty, strip_cur_dir, Bytes},
|
utils::{self, dir_is_empty, strip_cur_dir, Bytes},
|
||||||
|
QuestionPolicy,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::utf8::get_invalid_utf8_paths;
|
use self::utf8::get_invalid_utf8_paths;
|
||||||
@ -20,7 +23,7 @@ use self::utf8::get_invalid_utf8_paths;
|
|||||||
pub fn unpack_archive<R>(
|
pub fn unpack_archive<R>(
|
||||||
mut archive: ZipArchive<R>,
|
mut archive: ZipArchive<R>,
|
||||||
into: &Path,
|
into: &Path,
|
||||||
skip_questions_positively: Option<bool>,
|
question_policy: QuestionPolicy,
|
||||||
) -> crate::Result<Vec<PathBuf>>
|
) -> crate::Result<Vec<PathBuf>>
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read + Seek,
|
||||||
@ -34,7 +37,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let file_path = into.join(file_path);
|
let file_path = into.join(file_path);
|
||||||
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, skip_questions_positively)? {
|
if file_path.exists() && !utils::user_wants_to_overwrite(&file_path, question_policy)? {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +73,7 @@ where
|
|||||||
Ok(unpacked_files)
|
Ok(unpacked_files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compresses the archives given by `input_filenames` into the file given previously to `writer`.
|
||||||
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
pub fn build_archive_from_paths<W>(input_filenames: &[PathBuf], writer: W) -> crate::Result<W>
|
||||||
where
|
where
|
||||||
W: Write + Seek,
|
W: Write + Seek,
|
||||||
@ -126,10 +130,11 @@ fn check_for_comments(file: &ZipFile) {
|
|||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn __unix_set_permissions(file_path: &Path, file: &ZipFile) -> crate::Result<()> {
|
fn __unix_set_permissions(file_path: &Path, file: &ZipFile) -> crate::Result<()> {
|
||||||
|
use std::fs::Permissions;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
if let Some(mode) = file.unix_mode() {
|
if let Some(mode) = file.unix_mode() {
|
||||||
fs::set_permissions(file_path, fs::Permissions::from_mode(mode))?;
|
fs::set_permissions(file_path, Permissions::from_mode(mode))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
60
src/cli.rs
60
src/cli.rs
@ -1,70 +1,32 @@
|
|||||||
//! CLI arg parser configuration, command detection and input treatment.
|
//! CLI configuration step, uses definitions from `opts.rs`.
|
||||||
|
//!
|
||||||
|
//! Also used to treat some inputs.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::{Parser, ValueHint};
|
use clap::Parser;
|
||||||
|
use fs_err as fs;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::{Error, Opts, QuestionPolicy, Subcommand};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[clap(version, about)]
|
|
||||||
pub struct Opts {
|
|
||||||
/// Skip overwrite questions positively.
|
|
||||||
#[clap(short, long, conflicts_with = "no")]
|
|
||||||
pub yes: bool,
|
|
||||||
|
|
||||||
/// Skip overwrite questions negatively.
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub no: bool,
|
|
||||||
|
|
||||||
#[clap(subcommand)]
|
|
||||||
pub cmd: Subcommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, PartialEq, Eq, Debug)]
|
|
||||||
pub enum Subcommand {
|
|
||||||
/// Compress files. Alias: c
|
|
||||||
#[clap(alias = "c")]
|
|
||||||
Compress {
|
|
||||||
/// Files to be compressed
|
|
||||||
#[clap(required = true, min_values = 1)]
|
|
||||||
files: Vec<PathBuf>,
|
|
||||||
|
|
||||||
/// The resulting file. Its extensions specify how the files will be compressed and they need to be supported
|
|
||||||
#[clap(required = true, value_hint = ValueHint::FilePath)]
|
|
||||||
output: PathBuf,
|
|
||||||
},
|
|
||||||
/// Compress files. Alias: d
|
|
||||||
#[clap(alias = "d")]
|
|
||||||
Decompress {
|
|
||||||
/// Files to be decompressed
|
|
||||||
#[clap(required = true, min_values = 1)]
|
|
||||||
files: Vec<PathBuf>,
|
|
||||||
|
|
||||||
/// Decompress files in a directory other than the current
|
|
||||||
#[clap(short, long, value_hint = ValueHint::DirPath)]
|
|
||||||
output: Option<PathBuf>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opts {
|
impl Opts {
|
||||||
/// A helper method that calls `clap::Parser::parse` and then translates relative paths to absolute.
|
/// 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
|
/// Also determines if the user wants to skip questions or not
|
||||||
pub fn parse_args() -> crate::Result<(Self, Option<bool>)> {
|
pub fn parse_args() -> crate::Result<(Self, QuestionPolicy)> {
|
||||||
let mut opts: Self = Self::parse();
|
let mut opts: Self = Self::parse();
|
||||||
|
|
||||||
let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. }) = &mut opts.cmd;
|
let (Subcommand::Compress { files, .. } | Subcommand::Decompress { files, .. }) = &mut opts.cmd;
|
||||||
*files = canonicalize_files(files)?;
|
*files = canonicalize_files(files)?;
|
||||||
|
|
||||||
let skip_questions_positively = if opts.yes {
|
let skip_questions_positively = if opts.yes {
|
||||||
Some(true)
|
QuestionPolicy::AlwaysYes
|
||||||
} else if opts.no {
|
} else if opts.no {
|
||||||
Some(false)
|
QuestionPolicy::AlwaysNo
|
||||||
} else {
|
} else {
|
||||||
None
|
QuestionPolicy::Ask
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((opts, skip_questions_positively))
|
Ok((opts, skip_questions_positively))
|
||||||
@ -72,7 +34,7 @@ impl Opts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn canonicalize(path: impl AsRef<Path>) -> crate::Result<PathBuf> {
|
fn canonicalize(path: impl AsRef<Path>) -> crate::Result<PathBuf> {
|
||||||
match std::fs::canonicalize(&path.as_ref()) {
|
match fs::canonicalize(&path.as_ref()) {
|
||||||
Ok(abs_path) => Ok(abs_path),
|
Ok(abs_path) => Ok(abs_path),
|
||||||
Err(io_err) => {
|
Err(io_err) => {
|
||||||
if !path.as_ref().exists() {
|
if !path.as_ref().exists() {
|
||||||
|
@ -3,16 +3,15 @@
|
|||||||
//! Also, where correctly call functions based on the detected `Command`.
|
//! Also, where correctly call functions based on the detected `Command`.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
|
||||||
io::{self, BufReader, BufWriter, Read, Write},
|
io::{self, BufReader, BufWriter, Read, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use fs_err as fs;
|
||||||
use utils::colors;
|
use utils::colors;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archive,
|
archive,
|
||||||
cli::{Opts, Subcommand},
|
|
||||||
error::FinalError,
|
error::FinalError,
|
||||||
extension::{
|
extension::{
|
||||||
self,
|
self,
|
||||||
@ -20,10 +19,8 @@ use crate::{
|
|||||||
Extension,
|
Extension,
|
||||||
},
|
},
|
||||||
info,
|
info,
|
||||||
utils::nice_directory_display,
|
utils::{self, dir_is_empty, nice_directory_display, to_utf},
|
||||||
utils::to_utf,
|
Error, Opts, QuestionPolicy, Subcommand,
|
||||||
utils::{self, dir_is_empty},
|
|
||||||
Error,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used in BufReader and BufWriter to perform less syscalls
|
// Used in BufReader and BufWriter to perform less syscalls
|
||||||
@ -39,7 +36,9 @@ fn represents_several_files(files: &[PathBuf]) -> bool {
|
|||||||
files.iter().any(is_non_empty_dir) || files.len() > 1
|
files.iter().any(is_non_empty_dir) || files.len() > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result<()> {
|
/// Entrypoint of ouch, receives cli options and matches Subcommand
|
||||||
|
/// to decide current operation
|
||||||
|
pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
||||||
match args.cmd {
|
match args.cmd {
|
||||||
Subcommand::Compress { files, output: output_path } => {
|
Subcommand::Compress { files, output: output_path } => {
|
||||||
// Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma]
|
// Formats from path extension, like "file.tar.gz.xz" -> vec![Tar, Gzip, Lzma]
|
||||||
@ -95,7 +94,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
|||||||
return Err(Error::with_reason(reason));
|
return Err(Error::with_reason(reason));
|
||||||
}
|
}
|
||||||
|
|
||||||
if output_path.exists() && !utils::user_wants_to_overwrite(&output_path, skip_questions_positively)? {
|
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
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -160,7 +159,7 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
|||||||
|
|
||||||
compress_result?;
|
compress_result?;
|
||||||
}
|
}
|
||||||
Subcommand::Decompress { files, output: output_folder } => {
|
Subcommand::Decompress { files, output_dir } => {
|
||||||
let mut output_paths = vec![];
|
let mut output_paths = vec![];
|
||||||
let mut formats = vec![];
|
let mut formats = vec![];
|
||||||
|
|
||||||
@ -189,16 +188,21 @@ pub fn run(args: Opts, skip_questions_positively: Option<bool>) -> crate::Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
// From Option<PathBuf> to Option<&Path>
|
// From Option<PathBuf> to Option<&Path>
|
||||||
let output_folder = output_folder.as_ref().map(|path| path.as_ref());
|
let output_dir = output_dir.as_ref().map(|path| path.as_ref());
|
||||||
|
|
||||||
for ((input_path, formats), file_name) in files.iter().zip(formats).zip(output_paths) {
|
for ((input_path, formats), file_name) in files.iter().zip(formats).zip(output_paths) {
|
||||||
decompress_file(input_path, formats, output_folder, file_name, skip_questions_positively)?;
|
decompress_file(input_path, formats, output_dir, file_name, question_policy)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compress files into an `output_file`
|
||||||
|
//
|
||||||
|
// files are the list of paths to be compressed: ["dir/file1.txt", "dir/file2.txt"]
|
||||||
|
// formats contains each format necessary for compression, example: [Tar, Gz] (in compression order)
|
||||||
|
// output_file is the resulting compressed file name, example: "compressed.tar.gz"
|
||||||
fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs::File) -> crate::Result<()> {
|
fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs::File) -> crate::Result<()> {
|
||||||
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
||||||
|
|
||||||
@ -256,26 +260,28 @@ fn compress_files(files: Vec<PathBuf>, formats: Vec<Extension>, output_file: fs:
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decompress a file
|
||||||
|
//
|
||||||
// File at input_file_path is opened for reading, example: "archive.tar.gz"
|
// File at input_file_path is opened for reading, example: "archive.tar.gz"
|
||||||
// formats contains each format necessary for decompression, example: [Gz, Tar] (in decompression order)
|
// formats contains each format necessary for decompression, example: [Gz, Tar] (in decompression order)
|
||||||
// output_folder it's where the file will be decompressed to
|
// output_dir it's where the file will be decompressed to
|
||||||
// file_name is only used when extracting single file formats, no archive formats like .tar or .zip
|
// file_name is only used when extracting single file formats, no archive formats like .tar or .zip
|
||||||
fn decompress_file(
|
fn decompress_file(
|
||||||
input_file_path: &Path,
|
input_file_path: &Path,
|
||||||
formats: Vec<Extension>,
|
formats: Vec<Extension>,
|
||||||
output_folder: Option<&Path>,
|
output_dir: Option<&Path>,
|
||||||
file_name: &Path,
|
file_name: &Path,
|
||||||
skip_questions_positively: Option<bool>,
|
question_policy: QuestionPolicy,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
// TODO: improve error message
|
// TODO: improve error message
|
||||||
let reader = fs::File::open(&input_file_path)?;
|
let reader = fs::File::open(&input_file_path)?;
|
||||||
|
|
||||||
// Output path is used by single file formats
|
// Output path is used by single file formats
|
||||||
let output_path =
|
let output_path =
|
||||||
if let Some(output_folder) = output_folder { output_folder.join(file_name) } else { file_name.to_path_buf() };
|
if let Some(output_dir) = output_dir { output_dir.join(file_name) } else { file_name.to_path_buf() };
|
||||||
|
|
||||||
// Output folder is used by archive file formats (zip and tar)
|
// Output folder is used by archive file formats (zip and tar)
|
||||||
let output_folder = output_folder.unwrap_or_else(|| Path::new("."));
|
let output_dir = output_dir.unwrap_or_else(|| Path::new("."));
|
||||||
|
|
||||||
// Zip archives are special, because they require io::Seek, so it requires it's logic separated
|
// Zip archives are special, because they require io::Seek, so it requires it's logic separated
|
||||||
// from decoder chaining.
|
// from decoder chaining.
|
||||||
@ -285,10 +291,10 @@ fn decompress_file(
|
|||||||
//
|
//
|
||||||
// Any other Zip decompression done can take up the whole RAM and freeze ouch.
|
// Any other Zip decompression done can take up the whole RAM and freeze ouch.
|
||||||
if formats.len() == 1 && *formats[0].compression_formats.as_slice() == [Zip] {
|
if formats.len() == 1 && *formats[0].compression_formats.as_slice() == [Zip] {
|
||||||
utils::create_dir_if_non_existent(output_folder)?;
|
utils::create_dir_if_non_existent(output_dir)?;
|
||||||
let zip_archive = zip::ZipArchive::new(reader)?;
|
let zip_archive = zip::ZipArchive::new(reader)?;
|
||||||
let _files = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
|
let _files = crate::archive::zip::unpack_archive(zip_archive, output_dir, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_dir));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +318,9 @@ fn decompress_file(
|
|||||||
reader = chain_reader_decoder(format, reader)?;
|
reader = chain_reader_decoder(format, reader)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::create_dir_if_non_existent(output_folder)?;
|
utils::create_dir_if_non_existent(output_dir)?;
|
||||||
|
|
||||||
|
let files_unpacked;
|
||||||
|
|
||||||
match formats[0].compression_formats[0] {
|
match formats[0].compression_formats[0] {
|
||||||
Gzip | Bzip | Lzma | Zstd => {
|
Gzip | Bzip | Lzma | Zstd => {
|
||||||
@ -322,11 +330,10 @@ fn decompress_file(
|
|||||||
let mut writer = fs::File::create(&output_path)?;
|
let mut writer = fs::File::create(&output_path)?;
|
||||||
|
|
||||||
io::copy(&mut reader, &mut writer)?;
|
io::copy(&mut reader, &mut writer)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_path));
|
files_unpacked = vec![output_path];
|
||||||
}
|
}
|
||||||
Tar => {
|
Tar => {
|
||||||
let _ = crate::archive::tar::unpack_archive(reader, output_folder, skip_questions_positively)?;
|
files_unpacked = crate::archive::tar::unpack_archive(reader, output_dir, question_policy)?;
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
|
||||||
}
|
}
|
||||||
Zip => {
|
Zip => {
|
||||||
eprintln!("Compressing first into .zip.");
|
eprintln!("Compressing first into .zip.");
|
||||||
@ -340,11 +347,12 @@ fn decompress_file(
|
|||||||
io::copy(&mut reader, &mut vec)?;
|
io::copy(&mut reader, &mut vec)?;
|
||||||
let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
|
let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
|
||||||
|
|
||||||
let _ = crate::archive::zip::unpack_archive(zip_archive, output_folder, skip_questions_positively)?;
|
files_unpacked = crate::archive::zip::unpack_archive(zip_archive, output_dir, question_policy)?;
|
||||||
|
|
||||||
info!("Successfully decompressed archive in {}.", nice_directory_display(output_folder));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Successfully decompressed archive in {}.", nice_directory_display(output_dir));
|
||||||
|
info!("Files unpacked: {}", files_unpacked.len());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,22 @@ use crate::utils::colors;
|
|||||||
|
|
||||||
/// Represents a confirmation dialog
|
/// Represents a confirmation dialog
|
||||||
pub struct Confirmation<'a> {
|
pub struct Confirmation<'a> {
|
||||||
|
/// Represents the message to the displayed
|
||||||
|
/// e.g.: "Do you want to overwrite 'FILE'?"
|
||||||
pub prompt: &'a str,
|
pub prompt: &'a str,
|
||||||
|
|
||||||
|
/// Represents a placeholder to be changed at runtime
|
||||||
|
/// e.g.: Some("FILE")
|
||||||
pub placeholder: Option<&'a str>,
|
pub placeholder: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Confirmation<'a> {
|
impl<'a> Confirmation<'a> {
|
||||||
|
/// New Confirmation
|
||||||
pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
|
pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
|
||||||
Self { prompt, placeholder: pattern }
|
Self { prompt, placeholder: pattern }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates user message and receives a boolean input to be used on the program
|
||||||
pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
|
pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
|
||||||
let message = match (self.placeholder, substitute) {
|
let message = match (self.placeholder, substitute) {
|
||||||
(None, _) => Cow::Borrowed(self.prompt),
|
(None, _) => Cow::Borrowed(self.prompt),
|
||||||
|
25
src/error.rs
25
src/error.rs
@ -6,6 +6,8 @@
|
|||||||
//! TODO: wrap `FinalError` in a variant to keep all `FinalError::display_and_crash()` function
|
//! TODO: wrap `FinalError` in a variant to keep all `FinalError::display_and_crash()` function
|
||||||
//! calls inside of this module.
|
//! calls inside of this module.
|
||||||
|
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -13,6 +15,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::utils::colors::*;
|
use crate::utils::colors::*;
|
||||||
|
|
||||||
|
/// Custom Ouch Errors
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
UnknownExtensionError(String),
|
UnknownExtensionError(String),
|
||||||
@ -21,7 +24,7 @@ pub enum Error {
|
|||||||
FileNotFound(PathBuf),
|
FileNotFound(PathBuf),
|
||||||
AlreadyExists,
|
AlreadyExists,
|
||||||
InvalidZipArchive(&'static str),
|
InvalidZipArchive(&'static str),
|
||||||
PermissionDenied,
|
PermissionDenied { error_title: String },
|
||||||
UnsupportedZipArchive(&'static str),
|
UnsupportedZipArchive(&'static str),
|
||||||
InternalError,
|
InternalError,
|
||||||
CompressingRootFolder,
|
CompressingRootFolder,
|
||||||
@ -78,6 +81,10 @@ impl FinalError {
|
|||||||
self.hints.push(hint.to_string());
|
self.hints.push(hint.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_owned(&mut self) -> Self {
|
||||||
|
std::mem::take(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@ -87,7 +94,7 @@ impl fmt::Display for Error {
|
|||||||
FinalError::with_title(format!("Cannot compress to {:?}", filename))
|
FinalError::with_title(format!("Cannot compress to {:?}", filename))
|
||||||
.detail("Ouch could not detect the compression format")
|
.detail("Ouch could not detect the compression format")
|
||||||
.hint("Use a supported format extension, like '.zip' or '.tar.gz'")
|
.hint("Use a supported format extension, like '.zip' or '.tar.gz'")
|
||||||
.hint("Check https://github.com/vrmiguel/ouch for a full list of supported formats")
|
.hint("Check https://github.com/ouch-org/ouch for a full list of supported formats")
|
||||||
}
|
}
|
||||||
Error::WalkdirError { reason } => FinalError::with_title(reason),
|
Error::WalkdirError { reason } => FinalError::with_title(reason),
|
||||||
Error::FileNotFound(file) => {
|
Error::FileNotFound(file) => {
|
||||||
@ -124,7 +131,7 @@ impl fmt::Display for Error {
|
|||||||
.detail("This should not have happened")
|
.detail("This should not have happened")
|
||||||
.detail("It's probably our fault")
|
.detail("It's probably our fault")
|
||||||
.detail("Please help us improve by reporting the issue at:")
|
.detail("Please help us improve by reporting the issue at:")
|
||||||
.detail(format!(" {}https://github.com/vrmiguel/ouch/issues ", *CYAN))
|
.detail(format!(" {}https://github.com/ouch-org/ouch/issues ", *CYAN))
|
||||||
}
|
}
|
||||||
Error::IoError { reason } => FinalError::with_title(reason),
|
Error::IoError { reason } => FinalError::with_title(reason),
|
||||||
Error::CompressionTypo => {
|
Error::CompressionTypo => {
|
||||||
@ -134,7 +141,7 @@ impl fmt::Display for Error {
|
|||||||
Error::UnknownExtensionError(_) => todo!(),
|
Error::UnknownExtensionError(_) => todo!(),
|
||||||
Error::AlreadyExists => todo!(),
|
Error::AlreadyExists => todo!(),
|
||||||
Error::InvalidZipArchive(_) => todo!(),
|
Error::InvalidZipArchive(_) => todo!(),
|
||||||
Error::PermissionDenied => todo!(),
|
Error::PermissionDenied { error_title } => FinalError::with_title(error_title).detail("Permission denied"),
|
||||||
Error::UnsupportedZipArchive(_) => todo!(),
|
Error::UnsupportedZipArchive(_) => todo!(),
|
||||||
Error::Custom { reason } => reason.clone(),
|
Error::Custom { reason } => reason.clone(),
|
||||||
};
|
};
|
||||||
@ -152,8 +159,8 @@ impl 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 => panic!("{}", err),
|
std::io::ErrorKind::NotFound => todo!(),
|
||||||
std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
|
std::io::ErrorKind::PermissionDenied => Self::PermissionDenied { error_title: err.to_string() },
|
||||||
std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
|
std::io::ErrorKind::AlreadyExists => Self::AlreadyExists,
|
||||||
_other => Self::IoError { reason: err.to_string() },
|
_other => Self::IoError { reason: err.to_string() },
|
||||||
}
|
}
|
||||||
@ -177,3 +184,9 @@ impl From<walkdir::Error> for Error {
|
|||||||
Self::WalkdirError { reason: err.to_string() }
|
Self::WalkdirError { reason: err.to_string() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FinalError> for Error {
|
||||||
|
fn from(err: FinalError) -> Self {
|
||||||
|
Self::Custom { reason: err }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -37,6 +37,7 @@ impl fmt::Display for Extension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
/// Accepted extensions for input and output
|
/// Accepted extensions for input and output
|
||||||
pub enum CompressionFormat {
|
pub enum CompressionFormat {
|
||||||
@ -49,6 +50,7 @@ pub enum CompressionFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CompressionFormat {
|
impl CompressionFormat {
|
||||||
|
/// Currently supported archive formats are .tar (and aliases to it) and .zip
|
||||||
pub fn is_archive_format(&self) -> bool {
|
pub fn is_archive_format(&self) -> bool {
|
||||||
// Keep this match like that without a wildcard `_` so we don't forget to update it
|
// Keep this match like that without a wildcard `_` so we don't forget to update it
|
||||||
match self {
|
match self {
|
||||||
@ -78,6 +80,19 @@ impl fmt::Display for CompressionFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use crate::extension::CompressionFormat::*;
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Extracts extensions from a path,
|
||||||
|
/// return both the remaining path and the list of extension objects
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use ouch::extension::{separate_known_extensions_from_name, CompressionFormat};
|
||||||
|
/// use std::path::Path;
|
||||||
|
///
|
||||||
|
/// let mut path = Path::new("bolovo.tar.gz");
|
||||||
|
/// assert_eq!(separate_known_extensions_from_name(&path), (Path::new("bolovo"), vec![CompressionFormat::Tar, CompressionFormat::Gzip]));
|
||||||
|
/// ```
|
||||||
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Extension>) {
|
pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Extension>) {
|
||||||
// // TODO: check for file names with the name of an extension
|
// // TODO: check for file names with the name of an extension
|
||||||
// // TODO2: warn the user that currently .tar.gz is a .gz file named .tar
|
// // TODO2: warn the user that currently .tar.gz is a .gz file named .tar
|
||||||
@ -114,6 +129,15 @@ pub fn separate_known_extensions_from_name(mut path: &Path) -> (&Path, Vec<Exten
|
|||||||
(path, extensions)
|
(path, extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extracts extensions from a path, return only the list of extension objects
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use ouch::extension::{extensions_from_path, CompressionFormat};
|
||||||
|
/// use std::path::Path;
|
||||||
|
///
|
||||||
|
/// let mut path = Path::new("bolovo.tar.gz");
|
||||||
|
/// assert_eq!(extensions_from_path(&path), vec![CompressionFormat::Tar, CompressionFormat::Gzip]);
|
||||||
|
/// ```
|
||||||
pub fn extensions_from_path(path: &Path) -> Vec<Extension> {
|
pub fn extensions_from_path(path: &Path) -> Vec<Extension> {
|
||||||
let (_, extensions) = separate_known_extensions_from_name(path);
|
let (_, extensions) = separate_known_extensions_from_name(path);
|
||||||
extensions
|
extensions
|
||||||
|
22
src/lib.rs
22
src/lib.rs
@ -4,19 +4,25 @@
|
|||||||
//! 1. It's required by `main.rs`, or
|
//! 1. It's required by `main.rs`, or
|
||||||
//! 2. It's required by some integration tests at tests/ folder.
|
//! 2. It's required by some integration tests at tests/ folder.
|
||||||
|
|
||||||
// Public modules
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
// Macros should be declared before
|
||||||
|
pub mod macros;
|
||||||
|
|
||||||
|
pub mod archive;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
pub mod dialogs;
|
||||||
|
pub mod error;
|
||||||
|
pub mod extension;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
// Private modules
|
/// CLI configuration step, uses definitions from `opts.rs`, also used to treat some inputs.
|
||||||
pub mod archive;
|
pub mod opts;
|
||||||
mod dialogs;
|
|
||||||
mod error;
|
|
||||||
mod extension;
|
|
||||||
mod macros;
|
|
||||||
mod utils;
|
|
||||||
|
|
||||||
pub use error::{Error, Result};
|
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 ouch has when an error is encountered
|
||||||
pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE;
|
pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE;
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
//! Macros used on ouch.
|
||||||
|
|
||||||
|
/// Macro that prints message in INFO mode
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! info {
|
macro_rules! info {
|
||||||
($($arg:tt)*) => {
|
($($arg:tt)*) => {
|
||||||
@ -6,6 +9,7 @@ macro_rules! info {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prints the `[Info]` tag
|
||||||
pub fn _info_helper() {
|
pub fn _info_helper() {
|
||||||
use crate::utils::colors::{RESET, YELLOW};
|
use crate::utils::colors::{RESET, YELLOW};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use ouch::{cli::Opts, commands, Result};
|
use ouch::{commands, Opts, Result};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(err) = run() {
|
if let Err(err) = run() {
|
||||||
|
47
src/opts.rs
Normal file
47
src/opts.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use clap::{Parser, ValueHint};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Command line options
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(version, about)]
|
||||||
|
pub struct Opts {
|
||||||
|
/// Skip overwrite questions positively.
|
||||||
|
#[clap(short, long, conflicts_with = "no")]
|
||||||
|
pub yes: bool,
|
||||||
|
|
||||||
|
/// Skip overwrite questions negatively.
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub no: bool,
|
||||||
|
|
||||||
|
/// Action to take
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub cmd: Subcommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actions to take
|
||||||
|
#[derive(Parser, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Subcommand {
|
||||||
|
/// Compress files. Alias: c
|
||||||
|
#[clap(alias = "c")]
|
||||||
|
Compress {
|
||||||
|
/// Files to be compressed
|
||||||
|
#[clap(required = true, min_values = 1)]
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// The resulting file. Its extensions specify how the files will be compressed and they need to be supported
|
||||||
|
#[clap(required = true, value_hint = ValueHint::FilePath)]
|
||||||
|
output: PathBuf,
|
||||||
|
},
|
||||||
|
/// Compress files. Alias: d
|
||||||
|
#[clap(alias = "d")]
|
||||||
|
Decompress {
|
||||||
|
/// Files to be decompressed
|
||||||
|
#[clap(required = true, min_values = 1)]
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// Decompress files in a directory other than the current
|
||||||
|
#[clap(short, long = "dir", value_hint = ValueHint::DirPath)]
|
||||||
|
output_dir: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
}
|
39
src/utils.rs
39
src/utils.rs
@ -1,20 +1,24 @@
|
|||||||
|
//! Utils used on ouch.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp, env,
|
cmp, env,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
fs::{self, ReadDir},
|
|
||||||
path::Component,
|
path::Component,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use fs_err as fs;
|
||||||
|
|
||||||
use crate::{dialogs::Confirmation, info};
|
use crate::{dialogs::Confirmation, info};
|
||||||
|
|
||||||
/// Checks if the given path represents an empty directory.
|
/// Checks if the given path represents an empty directory.
|
||||||
pub fn dir_is_empty(dir_path: &Path) -> bool {
|
pub fn dir_is_empty(dir_path: &Path) -> bool {
|
||||||
let is_empty = |mut rd: ReadDir| rd.next().is_none();
|
let is_empty = |mut rd: std::fs::ReadDir| rd.next().is_none();
|
||||||
|
|
||||||
dir_path.read_dir().map(is_empty).unwrap_or_default()
|
dir_path.read_dir().map(is_empty).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates the dir if non existent.
|
||||||
pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
fs::create_dir_all(path)?;
|
fs::create_dir_all(path)?;
|
||||||
@ -23,6 +27,9 @@ pub fn create_dir_if_non_existent(path: &Path) -> crate::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the current dir from the beginning of a path
|
||||||
|
/// normally used for presentation sake.
|
||||||
|
/// If this function fails, it will return source path as a PathBuf.
|
||||||
pub fn strip_cur_dir(source_path: &Path) -> PathBuf {
|
pub fn strip_cur_dir(source_path: &Path) -> PathBuf {
|
||||||
source_path
|
source_path
|
||||||
.strip_prefix(Component::CurDir)
|
.strip_prefix(Component::CurDir)
|
||||||
@ -43,11 +50,13 @@ pub fn cd_into_same_dir_as(filename: &Path) -> crate::Result<PathBuf> {
|
|||||||
Ok(previous_location)
|
Ok(previous_location)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_wants_to_overwrite(path: &Path, skip_questions_positively: Option<bool>) -> crate::Result<bool> {
|
/// Centralizes the decision of overwriting a file or not,
|
||||||
match skip_questions_positively {
|
/// whether the user has already passed a question_policy or not.
|
||||||
Some(true) => Ok(true),
|
pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result<bool> {
|
||||||
Some(false) => Ok(false),
|
match question_policy {
|
||||||
None => {
|
QuestionPolicy::AlwaysYes => Ok(true),
|
||||||
|
QuestionPolicy::AlwaysNo => Ok(false),
|
||||||
|
QuestionPolicy::Ask => {
|
||||||
let path = to_utf(strip_cur_dir(path));
|
let path = to_utf(strip_cur_dir(path));
|
||||||
let path = Some(path.as_str());
|
let path = Some(path.as_str());
|
||||||
let placeholder = Some("FILE");
|
let placeholder = Some("FILE");
|
||||||
@ -56,11 +65,13 @@ pub fn user_wants_to_overwrite(path: &Path, skip_questions_positively: Option<bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts an OsStr to utf8.
|
||||||
pub fn to_utf(os_str: impl AsRef<OsStr>) -> String {
|
pub fn to_utf(os_str: impl AsRef<OsStr>) -> String {
|
||||||
let text = format!("{:?}", os_str.as_ref());
|
let text = format!("{:?}", os_str.as_ref());
|
||||||
text.trim_matches('"').to_string()
|
text.trim_matches('"').to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Treats weird paths for better user messages.
|
||||||
pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> String {
|
pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> String {
|
||||||
let text = to_utf(os_str);
|
let text = to_utf(os_str);
|
||||||
if text == "." {
|
if text == "." {
|
||||||
@ -70,6 +81,7 @@ pub fn nice_directory_display(os_str: impl AsRef<OsStr>) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Struct used to overload functionality onto Byte presentation.
|
||||||
pub struct Bytes {
|
pub struct Bytes {
|
||||||
bytes: f64,
|
bytes: f64,
|
||||||
}
|
}
|
||||||
@ -86,6 +98,7 @@ pub mod colors {
|
|||||||
macro_rules! color {
|
macro_rules! color {
|
||||||
($name:ident = $value:literal) => {
|
($name:ident = $value:literal) => {
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
|
/// Inserts color onto text based on configuration
|
||||||
pub static $name: Lazy<&str> = Lazy::new(|| if *DISABLE_COLORED_TEXT { "" } else { $value });
|
pub static $name: Lazy<&str> = Lazy::new(|| if *DISABLE_COLORED_TEXT { "" } else { $value });
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
pub static $name: &&str = &"";
|
pub static $name: &&str = &"";
|
||||||
@ -106,6 +119,7 @@ pub mod colors {
|
|||||||
impl Bytes {
|
impl Bytes {
|
||||||
const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"];
|
const UNIT_PREFIXES: [&'static str; 6] = ["", "k", "M", "G", "T", "P"];
|
||||||
|
|
||||||
|
/// New Byte structure
|
||||||
pub fn new(bytes: u64) -> Self {
|
pub fn new(bytes: u64) -> Self {
|
||||||
Self { bytes: bytes as f64 }
|
Self { bytes: bytes as f64 }
|
||||||
}
|
}
|
||||||
@ -126,6 +140,17 @@ impl std::fmt::Display for Bytes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
/// How overwrite questions should be handled
|
||||||
|
pub enum QuestionPolicy {
|
||||||
|
/// Ask everytime
|
||||||
|
Ask,
|
||||||
|
/// Skip overwrite questions positively
|
||||||
|
AlwaysYes,
|
||||||
|
/// Skip overwrite questions negatively
|
||||||
|
AlwaysNo,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env,
|
||||||
io::prelude::*,
|
io::prelude::*,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ouch::{
|
use ouch::{commands::run, Opts, QuestionPolicy, Subcommand};
|
||||||
cli::{Opts, Subcommand},
|
|
||||||
commands::run,
|
use fs_err as fs;
|
||||||
};
|
|
||||||
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use utils::*;
|
use utils::*;
|
||||||
@ -180,10 +179,10 @@ fn extract_files(archive_path: &Path) -> Vec<PathBuf> {
|
|||||||
no: false,
|
no: false,
|
||||||
cmd: Subcommand::Decompress {
|
cmd: Subcommand::Decompress {
|
||||||
files: vec![archive_path.to_owned()],
|
files: vec![archive_path.to_owned()],
|
||||||
output: Some(extraction_output_folder.clone()),
|
output_dir: Some(extraction_output_folder.clone()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
run(command, None).expect("Failed to extract");
|
run(command, QuestionPolicy::Ask).expect("Failed to extract");
|
||||||
|
|
||||||
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,11 @@
|
|||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use ouch::{
|
use fs_err as fs;
|
||||||
cli::{Opts, Subcommand},
|
|
||||||
commands::run,
|
use ouch::{commands::run, Opts, QuestionPolicy, Subcommand};
|
||||||
};
|
|
||||||
|
|
||||||
pub fn create_empty_dir(at: &Path, filename: &str) -> PathBuf {
|
pub fn create_empty_dir(at: &Path, filename: &str) -> PathBuf {
|
||||||
let dirname = Path::new(filename);
|
let dirname = Path::new(filename);
|
||||||
@ -30,7 +26,7 @@ pub fn compress_files(at: &Path, paths_to_compress: &[PathBuf], format: &str) ->
|
|||||||
no: false,
|
no: false,
|
||||||
cmd: Subcommand::Compress { files: paths_to_compress.to_vec(), output: archive_path.clone() },
|
cmd: Subcommand::Compress { files: paths_to_compress.to_vec(), output: archive_path.clone() },
|
||||||
};
|
};
|
||||||
run(command, None).expect("Failed to compress test dummy files");
|
run(command, QuestionPolicy::Ask).expect("Failed to compress test dummy files");
|
||||||
|
|
||||||
archive_path
|
archive_path
|
||||||
}
|
}
|
||||||
@ -52,10 +48,10 @@ pub fn extract_files(archive_path: &Path) -> Vec<PathBuf> {
|
|||||||
no: false,
|
no: false,
|
||||||
cmd: Subcommand::Decompress {
|
cmd: Subcommand::Decompress {
|
||||||
files: vec![archive_path.to_owned()],
|
files: vec![archive_path.to_owned()],
|
||||||
output: Some(extraction_output_folder.clone()),
|
output_dir: Some(extraction_output_folder.clone()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
run(command, None).expect("Failed to extract");
|
run(command, QuestionPolicy::Ask).expect("Failed to extract");
|
||||||
|
|
||||||
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
fs::read_dir(extraction_output_folder).unwrap().map(Result::unwrap).map(|entry| entry.path()).collect()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user