• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tamada / totebag / 21322732509

24 Jan 2026 10:37PM UTC coverage: 81.401% (+0.9%) from 80.516%
21322732509

push

github

web-flow
Merge pull request #66 from tamada/release/v0.8.10

Release/v0.8.10

215 of 251 new or added lines in 13 files covered. (85.66%)

6 existing lines in 3 files now uncovered.

1755 of 2156 relevant lines covered (81.4%)

7.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.88
/lib/src/extractor.rs
1
//! This module provides the extractor for the archive file.
2
//! The supported formats are `cab`, `lha`, `rar`, `7z`, `tar`, `tar.gz`, `tar.bz2`, `tar.xz`, `tar.zst`, and `zip`.
3
//! 
4
//! # Example: listing the entries in the archive file
5
//! 
6
//! ```rust
7
//! use std::path::PathBuf;
8
//! 
9
//! let file = PathBuf::from("../testdata/test.zip");
10
//! let config = totebag::ListConfig::new(
11
//!     totebag::OutputFormat::Default,
12
//!     totebag::format::default_format_detector(),
13
//! );
14
//! match totebag::list(file, &config) {
15
//!     Ok(entries) => println!("{:?}", entries),
16
//!     Err(e) => println!("error: {:?}", e),
17
//! }
18
//! ```
19
//! 
20
//! # Example: extracting the archive file
21
//! 
22
//! The destination for extraction is the current directory in the following example.
23
//!
24
//! ```
25
//! use std::path::PathBuf;
26
//!
27
//! let config = totebag::ExtractConfig::builder()
28
//!     .dest("results")
29
//!     .build();
30
//! match totebag::extract("../testdata/test.zip", &config) {
31
//!     Ok(r) => println!("{:?}", r),
32
//!     Err(e) => println!("error: {:?}", e),
33
//! }
34
//! ```
35

36
use chrono::NaiveDateTime;
37
use serde::Serialize;
38
use std::fmt::Display;
39
use std::path::{Path, PathBuf};
40
use typed_builder::TypedBuilder;
41

42
use crate::format::Format;
43
use crate::{Result, ToteError};
44

45
mod cab;
46
mod cpio;
47
mod lha;
48
mod rar;
49
mod sevenz;
50
mod tar;
51
mod zip;
52

53
/// This struct represents an entry in the archive file.
54
/// To build an instance of this struct, use [`Entry::new`] or [`Entry::builder`] methods in each [`ToteExtractor`].
55
///
56
/// # Example of builder
57
///
58
/// The required field is only [`name`](Entry::name), other fields are optional.
59
///
60
/// ```
61
/// use totebag::extractor::Entry;
62
///
63
/// let entry = Entry::builder()
64
///     .name("entry_name_extracted_from_archive_file")
65
///     .build();
66
/// ```
67
#[derive(Debug, TypedBuilder, Serialize)]
68
pub struct Entry {
69
    /// The path of the entry.
70
    #[builder(setter(into))]
71
    pub name: String,
72

73
    /// The compressed size of this entry.
74
    #[builder(setter(into, strip_option), default = None)]
75
    #[serde(skip_serializing_if = "Option::is_none")]
76
    pub compressed_size: Option<u64>,
77

78
    /// The original size of this entry.
79
    #[builder(setter(into, strip_option), default = None)]
80
    #[serde(skip_serializing_if = "Option::is_none")]
81
    pub original_size: Option<u64>,
82

83
    /// The unix mode.
84
    #[builder(setter(into, strip_option), default = Some(0o644))]
85
    #[serde(
86
        serialize_with = "crate::outputs::serialize_option_u32_octal",
87
        skip_serializing_if = "Option::is_none"
88
    )]
89
    pub unix_mode: Option<u32>,
90

91
    /// The date of this entry.
92
    #[builder(setter(into), default = None)]
93
    #[serde(skip_serializing_if = "Option::is_none")]
94
    pub date: Option<NaiveDateTime>,
95
}
96

97
impl Display for Entry {
98
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
99
        write!(f, "{}", self.name)
×
100
    }
×
101
}
102

103
impl Entry {
104
    pub fn new(
×
105
        name: String,
×
106
        compressed_size: Option<u64>,
×
107
        original_size: Option<u64>,
×
108
        unix_mode: Option<u32>,
×
109
        date: Option<NaiveDateTime>,
×
110
    ) -> Self {
×
111
        Self {
×
112
            name,
×
113
            compressed_size,
×
114
            original_size,
×
115
            unix_mode,
×
116
            date,
×
117
        }
×
118
    }
×
119
}
120

121
#[derive(Debug, Serialize)]
122
#[serde(rename = "archive-file")]
123
pub struct Entries {
124
    pub path: PathBuf,
125
    pub entries: Vec<Entry>,
126
}
127

128
impl Entries {
129
    pub fn new(path: PathBuf, entries: Vec<Entry>) -> Self {
13✔
130
        Self { path, entries }
13✔
131
    }
13✔
132

133
    pub fn iter(&self) -> impl Iterator<Item = &Entry> {
11✔
134
        self.entries.iter()
11✔
135
    }
11✔
136

137
    pub fn len(&self) -> usize {
3✔
138
        self.entries.len()
3✔
139
    }
3✔
140

141
    pub fn is_empty(&self) -> bool {
×
142
        self.entries.is_empty()
×
143
    }
×
144
}
145

146
/// The trait for extracting the archive file.
147
/// If you want to support a new format for extraction, you need to implement the `ToteExtractor` trait.
148
/// Then, the call [`perform`](ToteExtractor::perform) and/or [`list`](ToteExtractor::list) method of [`ToteExtractor`].
149
pub trait ToteExtractor {
150
    /// returns the entry list of the given archive file.
151
    fn list(&self, archive_file: PathBuf) -> Result<Entries>;
152
    /// extract the given archive file into the specified directory with the given options.
153
    fn perform(&self, archive_file: PathBuf, opts: PathBuf) -> Result<()>;
154
}
155

156
/// Returns the extractor for the given archive file.
157
#[allow(dead_code)]
158
pub(super) fn create<P: AsRef<Path>>(file: P) -> Result<Box<dyn ToteExtractor>> {
1✔
159
    let file = file.as_ref();
1✔
160
    let binding = crate::format::default_format_detector();
1✔
161
    let format = binding.detect(file);
1✔
162
    create_with(file, format)
1✔
163
}
1✔
164

165
/// Returns the extractor for the given archive file.
166
/// The supported format is `cab`, `lha`, `rar`, `7z`, `tar`, `tar.gz`, `tar.bz2`, `tar.xz`, `tar.zst`, and `zip`.
167
pub(super) fn create_with<P: AsRef<Path>>(file: P, format: Option<&Format>) -> Result<Box<dyn ToteExtractor>> {
10✔
168
    let file = file.as_ref();
10✔
169
    match format {
10✔
170
        Some(format) => match format.name.as_str() {
10✔
171
            "Cab" => Ok(Box::new(cab::Extractor {})),
10✔
172
            "Cpio" => Ok(Box::new(cpio::Extractor {})),
9✔
173
            "Lha" => Ok(Box::new(lha::Extractor {})),
8✔
174
            "Rar" => Ok(Box::new(rar::Extractor {})),
7✔
175
            "SevenZ" => Ok(Box::new(sevenz::Extractor {})),
6✔
176
            "Tar" => Ok(Box::new(tar::Extractor {})),
5✔
177
            "TarBz2" => Ok(Box::new(tar::Bz2Extractor {})),
4✔
178
            "TarGz" => Ok(Box::new(tar::GzExtractor {})),
4✔
179
            "TarXz" => Ok(Box::new(tar::XzExtractor {})),
4✔
180
            "TarZstd" => Ok(Box::new(tar::ZstdExtractor {})),
4✔
181
            "Zip" => Ok(Box::new(zip::Extractor {})),
4✔
UNCOV
182
            s => Err(ToteError::UnknownFormat(format!("{s}: unknown format"))),
×
183
        },
184
        None => Err(ToteError::Extractor(format!(
×
185
            "{file:?} no suitable extractor"
×
186
        ))),
×
187
    }
188
}
10✔
189

190
#[cfg(test)]
191
mod tests {
192
    use super::*;
193

194
    #[test]
195
    fn test_destination1() {
1✔
196
        let archive_file = PathBuf::from("/tmp/archive.zip");
1✔
197
        let opts1 = crate::ExtractConfig::builder()
1✔
198
            .use_archive_name_dir(true)
1✔
199
            .build();
1✔
200
        let dest = opts1.dest(&archive_file).unwrap();
1✔
201
        assert_eq!(dest, PathBuf::from("./archive"));
1✔
202
    }
1✔
203

204
    #[test]
205
    fn test_destination2() {
1✔
206
        let archive_file = PathBuf::from("/tmp/archive.zip");
1✔
207
        let opts2 = crate::ExtractConfig::builder().build();
1✔
208
        let dest = opts2.dest(&archive_file).unwrap();
1✔
209
        assert_eq!(dest, PathBuf::from("."));
1✔
210
    }
1✔
211

212
    #[test]
213
    fn test_list_entries() {
1✔
214
        let archive_file = PathBuf::from("../testdata/test.zip");
1✔
215
        let extractor = create(&archive_file).unwrap();
1✔
216
        let entries = extractor.list(archive_file).unwrap();
1✔
217
        assert_eq!(entries.len(), 19);
1✔
218
    }
1✔
219

220
    #[test]
221
    fn test_list_entries_for_camouflaged_archive() {
1✔
222
        let archive_file = PathBuf::from("../testdata/camouflage_of_zip.rar");
1✔
223
        let format = crate::format::find_format_by_ext(".zip");
1✔
224
        let extractor = create_with(&archive_file, format).unwrap();
1✔
225
        let entries = extractor.list(archive_file).unwrap();
1✔
226
        assert_eq!(entries.len(), 19);
1✔
227
    }
1✔
228
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc