• 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

31.29
/cli/src/main.rs
1
use clap::Parser;
2
use std::path::PathBuf;
3

4
use cli::LogLevel;
5
use totebag::archiver::ArchiveEntries;
6
use totebag::{Result, ToteError};
7

8
use crate::cli::Mode;
9

10
mod cli;
11

12
fn update_loglevel(level: LogLevel) {
1✔
13
    unsafe {
14
        match level {
1✔
15
            cli::LogLevel::Error => std::env::set_var("RUST_LOG", "error"),
×
16
            cli::LogLevel::Warn => std::env::set_var("RUST_LOG", "warn"),
1✔
17
            cli::LogLevel::Info => std::env::set_var("RUST_LOG", "info"),
×
18
            cli::LogLevel::Debug => std::env::set_var("RUST_LOG", "debug"),
×
19
            cli::LogLevel::Trace => std::env::set_var("RUST_LOG", "trace"),
×
20
        }
21
    }
22
    env_logger::try_init().unwrap_or_else(|_| {
1✔
23
        eprintln!("failed to initialize logger. set RUST_LOG to see logs.");
×
24
    });
×
25
    log::info!("set log level to {level:?}");
1✔
26
}
1✔
27

28
fn perform(opts: cli::CliOpts) -> Result<()> {
1✔
29
    update_loglevel(opts.loglevel);
1✔
30
    if cfg!(debug_assertions) {
1✔
31
        #[cfg(debug_assertions)]
32
        if opts.generate_completion {
1✔
33
            return gencomp::generate(PathBuf::from("target/completions"));
×
34
        }
1✔
35
    }
×
36
    let (mode, args) = opts.find_mode()?;
1✔
37
    match mode {
1✔
38
        Mode::Archive(config) => match perform_archive(config, args) {
×
39
            Ok(entries) => print_archive_result(entries),
×
40
            Err(e) => Err(e),
×
41
        },
42
        Mode::List(config) => match perform_list(config, args) {
1✔
43
            Ok(results) => print_list_result(results),
1✔
44
            Err(e) => Err(e),
×
45
        },
46
        Mode::Extract(config) => perform_extract(config, args),
×
47
    }
48
}
1✔
49

50
fn perform_extract(config: totebag::ExtractConfig, args: Vec<String>) -> Result<()> {
×
51
    let mut errs = vec![];
×
52
    for item in args {
×
53
        let path = PathBuf::from(item);
×
54
        if !path.exists() {
×
55
            errs.push(ToteError::FileNotFound(path))
×
NEW
56
        } else if let Err(e) = totebag::extract(path, &config) {
×
NEW
57
            errs.push(e);
×
UNCOV
58
        }
×
59
    }
60
    ToteError::error_or((), errs)
×
61
}
×
62

63
fn perform_list(config: totebag::ListConfig, args: Vec<String>) -> Result<Vec<String>> {
1✔
64
    let mut errs = vec![];
1✔
65
    let mut results = vec![];
1✔
66
    for item in args {
1✔
67
        let path = PathBuf::from(item);
1✔
68
        if !path.exists() {
1✔
69
            errs.push(ToteError::FileNotFound(path))
×
70
        } else {
71
            match totebag::list(path, &config) {
1✔
72
                Ok(r) => results.push(r),
1✔
73
                Err(e) => errs.push(e),
×
74
            }
75
        }
76
    }
77
    ToteError::error_or(results, errs)
1✔
78
}
1✔
79

80
fn perform_archive(config: totebag::ArchiveConfig, args: Vec<String>) -> Result<ArchiveEntries> {
×
81
    let targets = args.into_iter()
×
82
        .map(PathBuf::from)
×
83
        .collect::<Vec<_>>();
×
84
    totebag::archive(&targets, &config)
×
85
}
×
86

87
fn main() -> Result<()> {
×
88
    if let Err(e) = perform(cli::CliOpts::parse()) {
×
89
        print_error(&e);
×
90
        std::process::exit(1);
×
91
    }
×
92
    Ok(())
×
93
}
×
94

95
fn print_list_result(results: Vec<String>) -> Result<()> {
1✔
96
    results.iter().for_each(|item| println!("{item}"));
1✔
97
    Ok(())
1✔
98
}
1✔
99

100
fn print_archive_result(result: ArchiveEntries) -> Result<()> {
×
101
    if log::log_enabled!(log::Level::Info) {
×
102
        print_archive_result_impl(result);
×
103
    }
×
104
    Ok(())
×
105
}
×
106

107
fn print_archive_result_impl(result: ArchiveEntries) {
×
108
    let f = humansize::make_format(humansize::DECIMAL);
×
109
    let total = result.total();
×
110
    let rate = if total == 0 {
×
111
        0.0
×
112
    } else {
113
        result.compressed as f64 / total as f64 * 100.0
×
114
    };
115
    println!(
×
116
        "archived: {} ({} entries, {:>10} / {:>10}, {:.2}%)",
117
        result.archive_file.display(),
×
118
        result.len(),
×
119
        f(result.compressed),
×
120
        f(result.total()),
×
121
        rate
122
    );
123
}
×
124

125
fn print_error(e: &ToteError) {
×
126
    match e {
×
127
        ToteError::Archiver(s) => println!("Archive error: {s}"),
×
128
        ToteError::Array(errs) => {
×
129
            for err in errs.iter() {
×
130
                print_error(err);
×
131
            }
×
132
        }
133
        ToteError::DestIsDir(p) => println!("{}: destination is a directory", p.to_str().unwrap()),
×
134
        ToteError::DirExists(p) => println!("{}: directory already exists", p.to_str().unwrap()),
×
135
        ToteError::Extractor(s) => println!("Extractor error: {s}"),
×
136
        ToteError::Fatal(e) => println!("Error: {e}"),
×
137
        ToteError::FileNotFound(p) => println!("{}: file not found", p.to_str().unwrap()),
×
138
        ToteError::FileExists(p) => println!("{}: file already exists", p.to_str().unwrap()),
×
139
        ToteError::IO(e) => println!("IO error: {e}"),
×
140
        ToteError::Json(e) => println!("Json error: {e}"),
×
141
        ToteError::NoArgumentsGiven => println!("No arguments given. Use --help for usage."),
×
142
        ToteError::Warn(s) => println!("Unknown error: {s}"),
×
143
        ToteError::UnknownFormat(f) => println!("{f}: unknown format"),
×
144
        ToteError::UnsupportedFormat(f) => println!("{f}: unsupported format"),
×
145
        ToteError::Xml(e) => println!("xml error: {e}"),
×
146
    }
147
}
×
148

149
#[cfg(debug_assertions)]
150
mod gencomp {
151
    use crate::cli::CliOpts;
152
    use totebag::{Result, ToteError};
153

154
    use clap::{Command, CommandFactory};
155
    use clap_complete::Shell;
156
    use std::path::PathBuf;
157

158
    fn generate_impl(app: &mut Command, shell: Shell, dest: PathBuf) -> Result<()> {
×
159
        log::info!("generate completion for {shell:?} to {dest:?}");
×
160
        if let Err(e) = std::fs::create_dir_all(dest.parent().unwrap()) {
×
161
            return Err(ToteError::IO(e));
×
162
        }
×
163
        match std::fs::File::create(dest) {
×
164
            Err(e) => Err(ToteError::IO(e)),
×
165
            Ok(mut out) => {
×
166
                clap_complete::generate(shell, app, "totebag", &mut out);
×
167
                Ok(())
×
168
            }
169
        }
170
    }
×
171

172
    pub fn generate(outdir: PathBuf) -> Result<()> {
×
173
        let shells = vec![
×
174
            (Shell::Bash, "bash/totebag"),
×
175
            (Shell::Fish, "fish/totebag"),
×
176
            (Shell::Zsh, "zsh/_totebag"),
×
177
            (Shell::Elvish, "elvish/totebag"),
×
178
            (Shell::PowerShell, "powershell/totebag"),
×
179
        ];
180
        let mut app = CliOpts::command();
×
181
        app.set_bin_name("totebag");
×
182
        let mut errs = vec![];
×
183
        for (shell, file) in shells {
×
184
            if let Err(e) = generate_impl(&mut app, shell, outdir.join(file)) {
×
185
                errs.push(e);
×
186
            }
×
187
        }
188
        if errs.is_empty() {
×
189
            Ok(())
×
190
        } else {
191
            Err(ToteError::Array(errs))
×
192
        }
193
    }
×
194
}
195

196
#[cfg(test)]
197
mod tests {
198
    use super::*;
199
    use cli::RunMode;
200
    use std::path::PathBuf;
201

202
    #[test]
203
    fn test_run() {
1✔
204
        let opts = cli::CliOpts::parse_from(&[
1✔
205
            "totebag_test",
1✔
206
            "-o",
1✔
207
            "test.zip",
1✔
208
            "src",
1✔
209
            "LICENSE",
1✔
210
            "README.md",
1✔
211
            "Cargo.toml",
1✔
212
        ]);
1✔
213
        assert_eq!(opts.mode, RunMode::Auto);
1✔
214
        assert_eq!(opts.output, Some(PathBuf::from("test.zip")));
1✔
215
        assert_eq!(opts.args.len(), 4);
1✔
216
        assert_eq!(opts.args, vec!["src", "LICENSE", "README.md", "Cargo.toml"]);
1✔
217
    }
1✔
218

219
    #[test]
220
    fn test_list() {
1✔
221
        let opts =
1✔
222
            cli::CliOpts::parse_from(&["totebag_test", "--mode", "list", "../testdata/test.zip"]);
1✔
223
        match perform(opts) {
1✔
224
            Ok(_) => (),
1✔
225
            Err(e) => panic!("unexpected error: {:?}", e),
×
226
        }
227
    }
1✔
228

229
    /// This test sometimes fails because of the timing of the log initialization.
230
    /// This test wants to run after other tests are run.
231
    #[test]
232
    #[ignore]
233
    fn test_update_loglevel_error() {
×
234
        update_loglevel(LogLevel::Error);
×
235
        assert_eq!(std::env::var("RUST_LOG").unwrap(), "error");
×
236
    }
×
237
}
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