Skip to content

Commit

Permalink
add features to extract/archive tar+zstd
Browse files Browse the repository at this point in the history
  • Loading branch information
tamada committed May 26, 2024
1 parent 288a601 commit b510c80
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 38 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "totebag"
version = "0.4.6"
description = "A tool for archiving files and directories and extracting several archive formats."
description = "A tool for extracting/archiving files and directories in multiple formats."
repository = "https://github.com/tamada/totebag"
readme = "README.md"
authors = [
Expand All @@ -23,7 +23,8 @@ tar = "0.4.40"
time = "0.3.36"
unrar = "0.5.3"
xz2 = "0.1.7"
zip = "1.1.1"
zip = "2.1.0"
zstd = "0.13.1"

[build-dependencies]
clap = { version = "4.5.4", features = ["derive"] }
Expand Down
17 changes: 13 additions & 4 deletions src/archiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::PathBuf;
use crate::archiver::lha::LhaArchiver;
use crate::archiver::rar::RarArchiver;
use crate::archiver::sevenz::SevenZArchiver;
use crate::archiver::tar::{TarArchiver, TarBz2Archiver, TarGzArchiver, TarXzArchiver};
use crate::archiver::tar::{TarArchiver, TarBz2Archiver, TarGzArchiver, TarXzArchiver, TarZstdArchiver};
use crate::archiver::zip::ZipArchiver;
use crate::cli::{Result, ToteError};
use crate::format::{find_format, Format};
Expand Down Expand Up @@ -33,6 +33,7 @@ pub fn create_archiver(dest: &PathBuf) -> Result<Box<dyn Archiver>> {
Format::TarGz => Ok(Box::new(TarGzArchiver {})),
Format::TarBz2 => Ok(Box::new(TarBz2Archiver {})),
Format::TarXz => Ok(Box::new(TarXzArchiver {})),
Format::TarZstd => Ok(Box::new(TarZstdArchiver {})),
Format::LHA => Ok(Box::new(LhaArchiver {})),
Format::Rar => Ok(Box::new(RarArchiver {})),
Format::SevenZ => Ok(Box::new(SevenZArchiver {})),
Expand Down Expand Up @@ -163,9 +164,17 @@ mod tests {
assert!(a7.is_ok());
assert_eq!(a7.unwrap().format(), Format::SevenZ);

let a8 = create_archiver(&PathBuf::from("results/test.unknown"));
assert!(a8.is_err());
if let Err(e) = a8 {
let a8 = create_archiver(&PathBuf::from("results/test.tar.zst"));
assert!(a8.is_ok());
assert_eq!(a8.unwrap().format(), Format::TarZstd);

let a9 = create_archiver(&PathBuf::from("results/test.lha"));
assert!(a9.is_ok());
assert_eq!(a9.unwrap().format(), Format::LHA);

let a10 = create_archiver(&PathBuf::from("results/test.unknown"));
assert!(a10.is_err());
if let Err(e) = a10 {
if let ToteError::UnknownFormat(msg) = e {
assert_eq!(msg, "test.unknown: unknown format".to_string());
} else {
Expand Down
48 changes: 45 additions & 3 deletions src/archiver/tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ pub(super) struct TarGzArchiver {
}
pub(super) struct TarBz2Archiver {
}

pub(super) struct TarXzArchiver {
}
pub(super) struct TarZstdArchiver {
}

impl Archiver for TarArchiver {
fn perform(&self, inout: &ArchiverOpts) -> Result<()> {
Expand All @@ -36,6 +37,7 @@ impl Archiver for TarGzArchiver{
Format::TarGz
}
}

impl Archiver for TarBz2Archiver {
fn perform(&self, opts: &ArchiverOpts) -> Result<()> {
write_tar(opts, |file| BzEncoder::new(file, bzip2::Compression::best()))
Expand All @@ -49,11 +51,23 @@ impl Archiver for TarXzArchiver {
fn perform(&self, inout: &ArchiverOpts) -> Result<()> {
write_tar(inout, |file| XzEncoder::new(file, 9))
}

fn format(&self) -> Format {
Format::TarXz
}
}

impl Archiver for TarZstdArchiver {
fn perform(&self, inout: &ArchiverOpts) -> Result<()> {
write_tar(inout, |file|
zstd::Encoder::new(file, 9).unwrap())
}

fn format(&self) -> Format {
Format::TarZstd
}
}

fn write_tar<F, W: Write>(opts: &ArchiverOpts, f: F) -> Result<()>
where F: FnOnce(File) -> W {
match opts.destination() {
Expand Down Expand Up @@ -110,16 +124,16 @@ fn process_file<W: Write>(builder: &mut Builder<W>, target: PathBuf) -> Result<(
mod tests {
use std::path::PathBuf;

use super::*;
use crate::archiver::Archiver;
use crate::archiver::tar::{TarArchiver, TarGzArchiver, TarBz2Archiver};
use crate::archiver::ArchiverOpts;
use crate::format::Format;

fn run_test<F>(f: F)
where
F: FnOnce() -> PathBuf,
{
// setup(); // 予めやりたい処理
// setup(); // preprocessing process
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
match result {
Ok(path) => teardown(path),
Expand Down Expand Up @@ -170,6 +184,34 @@ mod tests {
});
}

#[test]
fn test_tarxz() {
run_test(|| {
let archiver = TarXzArchiver{};
let inout = ArchiverOpts::create(PathBuf::from("results/test.tar.xz"), vec![PathBuf::from("src"), PathBuf::from("Cargo.toml")], true, true, false);
let result = archiver.perform(&inout);
let path = PathBuf::from("results/test.tar.xz");
assert!(result.is_ok());
assert!(path.exists());
assert_eq!(archiver.format(), Format::TarXz);
path
});
}

#[test]
fn test_tarzstd() {
run_test(|| {
let archiver = TarZstdArchiver{};
let inout = ArchiverOpts::create(PathBuf::from("results/test.tar.zst"), vec![PathBuf::from("src"), PathBuf::from("Cargo.toml")], true, true, false);
let result = archiver.perform(&inout);
let path = PathBuf::from("results/test.tar.zst");
assert!(result.is_ok());
assert!(path.exists());
assert_eq!(archiver.format(), Format::TarZstd);
path
});
}

fn teardown(path: PathBuf) {
let _ = std::fs::remove_file(path);
}
Expand Down
1 change: 1 addition & 0 deletions src/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub fn create_extractor(file: &PathBuf) -> Result<Box<dyn Extractor>> {
Format::TarGz => Ok(Box::new(tar::TarGzExtractor {})),
Format::TarBz2 => Ok(Box::new(tar::TarBz2Extractor {})),
Format::TarXz => Ok(Box::new(tar::TarXzExtractor {})),
Format::TarZstd => Ok(Box::new(tar::TarZstdExtractor {})),
Format::LHA => Ok(Box::new(lha::LhaExtractor {})),
Format::SevenZ => Ok(Box::new(sevenz::SevenZExtractor {})),
Format::Unknown(s) => Err(ToteError::UnknownFormat(format!(
Expand Down
93 changes: 66 additions & 27 deletions src/extractor/tar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@ use std::{fs::File, path::PathBuf};
use tar::Archive;
use xz2::read::XzDecoder;

use crate::cli::{ToteError, Result};
use crate::cli::{Result, ToteError};
use crate::extractor::{Extractor, ExtractorOpts};
use crate::format::Format;

pub(super) struct TarExtractor {
}
pub(super) struct TarGzExtractor {
}
pub(super) struct TarBz2Extractor {
}
pub(super) struct TarXzExtractor {
}
pub(super) struct TarExtractor {}
pub(super) struct TarGzExtractor {}
pub(super) struct TarBz2Extractor {}
pub(super) struct TarXzExtractor {}
pub(super) struct TarZstdExtractor {}

impl Extractor for TarExtractor {
fn list_archives(&self, archive_file: PathBuf) -> Result<Vec<String>> {
Expand Down Expand Up @@ -90,8 +87,29 @@ impl Extractor for TarXzExtractor {
}
}

fn open_tar_file<F, R: Read>(file: &PathBuf, opener: F) -> Result<Archive<R>>
where F: FnOnce(File) -> R {
impl Extractor for TarZstdExtractor {
fn list_archives(&self, archive_file: PathBuf) -> Result<Vec<String>> {
match open_tar_file(&archive_file, |f| zstd::Decoder::new(f).unwrap()) {
Err(e) => Err(e),
Ok(archive) => list_tar(archive),
}
}

fn perform(&self, archive_file: PathBuf, opts: &ExtractorOpts) -> Result<()> {
match open_tar_file(&archive_file, |f| zstd::Decoder::new(f).unwrap()) {
Err(e) => Err(e),
Ok(archive) => extract_tar(archive, archive_file, opts),
}
}
fn format(&self) -> Format {
Format::TarZstd
}
}

fn open_tar_file<F, R: Read>(file: &PathBuf, opener: F) -> Result<Archive<R>>
where
F: FnOnce(File) -> R,
{
let file = match File::open(file) {
Ok(f) => f,
Err(e) => return Err(ToteError::IOError(e)),
Expand All @@ -100,7 +118,11 @@ fn open_tar_file<F, R: Read>(file: &PathBuf, opener: F) -> Result<Archive<R>>
Ok(Archive::new(writer))
}

fn extract_tar<R: Read>(mut archive: tar::Archive<R>, original: PathBuf, opts: &ExtractorOpts) -> Result<()> {
fn extract_tar<R: Read>(
mut archive: tar::Archive<R>,
original: PathBuf,
opts: &ExtractorOpts,
) -> Result<()> {
for entry in archive.entries().unwrap() {
let mut entry = entry.unwrap();
let path = entry.header().path().unwrap();
Expand All @@ -109,7 +131,8 @@ fn extract_tar<R: Read>(mut archive: tar::Archive<R>, original: PathBuf, opts: &
continue;
}
let size = entry.header().size().unwrap();
opts.v.verbose(format!("extracting {:?} ({} bytes)", path, size));
opts.v
.verbose(format!("extracting {:?} ({} bytes)", path, size));

let dest = opts.destination(&original).join(path);
if entry.header().entry_type().is_file() {
Expand Down Expand Up @@ -142,7 +165,7 @@ mod tests {

#[test]
fn test_list_tar_file() {
let extractor = TarExtractor{};
let extractor = TarExtractor {};
let file = PathBuf::from("testdata/test.tar");
match extractor.list_archives(file) {
Ok(r) => {
Expand All @@ -151,14 +174,14 @@ mod tests {
assert_eq!(r.get(1), Some("build.rs".to_string()).as_ref());
assert_eq!(r.get(2), Some("LICENSE".to_string()).as_ref());
assert_eq!(r.get(3), Some("README.md".to_string()).as_ref());
},
}
Err(_) => assert!(false),
}
}

#[test]
fn test_extract_archive() {
let e = TarExtractor{};
let e = TarExtractor {};
let file = PathBuf::from("testdata/test.tar");
let opts = ExtractorOpts {
dest: PathBuf::from("results/tar"),
Expand All @@ -167,19 +190,18 @@ mod tests {
v: create_verboser(false),
};
match e.perform(file, &opts) {
Ok(_) => {
Ok(_) => {
assert!(true);
assert!(PathBuf::from("results/tar/Cargo.toml").exists());
std::fs::remove_dir_all(PathBuf::from("results/tar")).unwrap();
},
}
Err(_) => assert!(false),
};
}


#[test]
fn test_list_tarbz2_file() {
let extractor = TarBz2Extractor{};
let extractor = TarBz2Extractor {};
let file = PathBuf::from("testdata/test.tar.bz2");
match extractor.list_archives(file) {
Ok(r) => {
Expand All @@ -188,14 +210,14 @@ mod tests {
assert_eq!(r.get(1), Some("build.rs".to_string()).as_ref());
assert_eq!(r.get(2), Some("LICENSE".to_string()).as_ref());
assert_eq!(r.get(3), Some("README.md".to_string()).as_ref());
},
}
Err(_) => assert!(false),
}
}

#[test]
fn test_list_targz_file() {
let extractor = TarGzExtractor{};
let extractor = TarGzExtractor {};
let file = PathBuf::from("testdata/test.tar.gz");
match extractor.list_archives(file) {
Ok(r) => {
Expand All @@ -204,20 +226,37 @@ mod tests {
assert_eq!(r.get(1), Some("build.rs".to_string()).as_ref());
assert_eq!(r.get(2), Some("LICENSE".to_string()).as_ref());
assert_eq!(r.get(3), Some("README.md".to_string()).as_ref());
},
}
Err(_) => assert!(false),
}
}

#[test]
fn test_list_tarzstd_file() {
let extractor = TarZstdExtractor {};
let file = PathBuf::from("testdata/test.tar.zst");
match extractor.list_archives(file) {
Ok(r) => {
assert_eq!(r.len(), 16);
assert_eq!(r.get(0), Some("Cargo.toml".to_string()).as_ref());
assert_eq!(r.get(1), Some("build.rs".to_string()).as_ref());
assert_eq!(r.get(2), Some("LICENSE".to_string()).as_ref());
assert_eq!(r.get(3), Some("README.md".to_string()).as_ref());
}
Err(_) => assert!(false),
}
}


#[test]
fn test_format() {
let e1 = TarExtractor{};
let e1 = TarExtractor {};
assert_eq!(e1.format(), Format::Tar);

let e2 = TarGzExtractor{};
let e2 = TarGzExtractor {};
assert_eq!(e2.format(), Format::TarGz);

let e3 = TarBz2Extractor{};
let e3 = TarBz2Extractor {};
assert_eq!(e3.format(), Format::TarBz2);
}
}
}
4 changes: 4 additions & 0 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub fn find_format(file_name: Option<&OsStr>) -> Result<Format> {
return Ok(Format::TarBz2);
} else if name.ends_with(".tar.xz") || name.ends_with(".txz") {
return Ok(Format::TarXz);
} else if name.ends_with(".tar.zst") || name.ends_with(".tzst") {
return Ok(Format::TarZstd);
} else if name.ends_with(".7z") {
return Ok(Format::SevenZ);
} else if name.ends_with(".tar") {
Expand All @@ -38,6 +40,7 @@ pub enum Format {
TarGz,
TarBz2,
TarXz,
TarZstd,
SevenZ,
LHA,
Rar,
Expand All @@ -52,6 +55,7 @@ impl Display for Format {
Format::TarGz => write!(f, "TarGz"),
Format::TarBz2 => write!(f, "TarBz2"),
Format::TarXz => write!(f, "TarXz"),
Format::TarZstd => write!(f, "TarZstd"),
Format::SevenZ => write!(f, "SevenZ"),
Format::LHA => write!(f, "LHA"),
Format::Rar => write!(f, "Rar"),
Expand Down
Loading

0 comments on commit b510c80

Please sign in to comment.