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

tamada / btmeister / 18859797937

28 Oct 2025 12:13AM UTC coverage: 90.0% (-0.7%) from 90.704%
18859797937

Pull #72

github

tamada
Implement utility for generating  Filter instance and  add corresponding tests
Pull Request #72: Release/v0.8.2

146 of 174 new or added lines in 12 files covered. (83.91%)

38 existing lines in 2 files now uncovered.

1467 of 1630 relevant lines covered (90.0%)

20.47 hits per line

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

87.44
/cli/src/main.rs
1
mod cli;
2
mod defs_builder;
3
mod fmt;
4

5
use crate::cli::InputOpts;
6
use crate::fmt::Formatter;
7
use btmeister::defs::{self, BuildToolDefs};
8
use btmeister::{BuildTools, Meister, MeisterError};
9
use btmeister::{LogLevel, Result};
10
use clap::Parser;
11

12
fn list_defs(defs: BuildToolDefs, f: Box<dyn Formatter>) -> Result<()> {
1✔
13
    if let Some(header) = f.header_defs() {
1✔
14
        println!("{header}");
1✔
15
    }
1✔
16
    let mut errs = vec![];
1✔
17
    for (index, def) in defs.iter().enumerate() {
46✔
18
        match f.format_def(def, index == 0) {
46✔
19
            Ok(s) => println!("{s}"),
46✔
20
            Err(e) => errs.push(e),
×
21
        }
22
    }
23
    if let Some(footer) = f.footer_defs() {
1✔
24
        println!("{footer}");
1✔
25
    }
1✔
26
    if errs.is_empty() {
1✔
27
        Ok(())
1✔
28
    } else {
29
        Err(MeisterError::Array(errs))
×
30
    }
31
}
1✔
32

33
fn print_results(r: Vec<BuildTools>, f: Box<dyn Formatter>) -> Result<()> {
1✔
34
    use std::io::Write;
35
    let mut errs = vec![];
1✔
36
    if let Some(header) = f.header_files() {
1✔
37
        println!("{header}");
1✔
38
    }
1✔
39
    for (i, bt) in r.iter().enumerate() {
1✔
40
        match f.format_files(bt, i == 0) {
1✔
41
            Ok(s) => print!("{s}"),
1✔
42
            Err(e) => errs.push(e),
×
43
        }
44
    }
45
    if let Some(footer) = f.footer_files() {
1✔
46
        println!("{footer}");
1✔
47
    }
1✔
48
    let _ = std::io::stdout().flush();
1✔
49
    if errs.is_empty() {
1✔
50
        Ok(())
1✔
51
    } else {
52
        Err(MeisterError::Array(errs))
×
53
    }
54
}
1✔
55

56
fn find_bt(defs: BuildToolDefs, opts: InputOpts) -> Result<Vec<BuildTools>> {
2✔
57
    let meister = Meister::new(defs, opts.ignore_types.clone())?;
2✔
58
    let mut errs = vec![];
2✔
59
    let mut result = vec![];
2✔
60
    match opts.projects() {
2✔
61
        Err(e) => return Err(e),
1✔
62
        Ok(projects) => {
1✔
63
            for project in projects {
2✔
64
                match meister.find(project) {
1✔
65
                    Ok(r) => result.push(r),
1✔
66
                    Err(e) => errs.push(e),
×
67
                }
68
            }
69
        }
70
    }
71
    if errs.is_empty() {
1✔
72
        Ok(result)
1✔
73
    } else {
74
        Err(MeisterError::Array(errs))
×
75
    }
76
}
2✔
77

78
#[cfg(debug_assertions)]
79
mod gencomp {
80
    use crate::cli::Options;
81
    use btmeister::Result;
82
    use clap::{Command, CommandFactory};
83
    use clap_complete::Shell;
84
    use std::fs::File;
85
    use std::path::{Path, PathBuf};
86

87
    fn generate(s: Shell, app: &mut Command, outdir: &Path, file: &str) -> Result<()> {
5✔
88
        let destfile = outdir.join(file);
5✔
89
        log::info!("generate completions for {}: {}", s, destfile.display());
5✔
90
        if let Err(e) = std::fs::create_dir_all(destfile.parent().unwrap()) {
5✔
91
            return Err(btmeister::MeisterError::IO(e));
×
92
        }
5✔
93
        match File::create(destfile) {
5✔
94
            Ok(mut dest) => {
5✔
95
                clap_complete::generate(s, app, "btmeister", &mut dest);
5✔
96
                Ok(())
5✔
97
            }
98
            Err(e) => Err(btmeister::MeisterError::IO(e)),
×
99
        }
100
    }
5✔
101

102
    pub(crate) fn generate_completions(outdir: PathBuf) -> Result<()> {
1✔
103
        let mut app = Options::command();
1✔
104
        app.set_bin_name("btmeister");
1✔
105
        let mut errs = vec![];
1✔
106
        let shells = vec![
1✔
107
            (Shell::Bash, "bash/btmeister"),
1✔
108
            (Shell::Elvish, "elvish/btmeister"),
1✔
109
            (Shell::Fish, "fish/btmeister"),
1✔
110
            (Shell::PowerShell, "powershell/btmeister"),
1✔
111
            (Shell::Zsh, "zsh/_btmeister"),
1✔
112
        ];
113
        for (shell, file) in shells {
6✔
114
            if let Err(e) = generate(shell, &mut app, &outdir, file) {
5✔
115
                errs.push(e);
×
116
            }
5✔
117
        }
118
        if errs.is_empty() {
1✔
119
            Ok(())
1✔
120
        } else {
121
            Err(btmeister::MeisterError::Array(errs))
×
122
        }
123
    }
1✔
124
}
125

126
fn perform(opts: cli::Options) -> Result<()> {
4✔
127
    let (input_opts, output_opts, defopts) = (opts.inputs, opts.outputs, opts.defopts);
4✔
128
    #[cfg(debug_assertions)]
129
    let compopts = opts.compopts;
4✔
130
    let defs = defs_builder::construct(defopts)?;
4✔
131
    if cfg!(debug_assertions) {
4✔
132
        #[cfg(debug_assertions)]
133
        if compopts.completion {
4✔
134
            return gencomp::generate_completions(compopts.dest);
1✔
135
        }
3✔
136
    }
×
137
    let formatter = fmt::build_formatter(output_opts.format);
3✔
138
    if output_opts.list_defs {
3✔
139
        list_defs(defs, formatter)
1✔
140
    } else {
141
        match find_bt(defs, input_opts) {
2✔
142
            Ok(r) => print_results(r, formatter),
1✔
143
            Err(e) => Err(e),
1✔
144
        }
145
    }
146
}
4✔
147

148
fn errors_to_string(e: MeisterError) -> String {
11✔
149
    use MeisterError::*;
150
    match e {
11✔
151
        Array(errs) => errs
1✔
152
            .into_iter()
1✔
153
            .map(errors_to_string)
1✔
154
            .collect::<Vec<String>>()
1✔
155
            .join("\n"),
1✔
156
        Fatal(m) => format!("fatal: {m}"),
3✔
157
        IO(e) => format!("io error: {e}"),
1✔
158
        Json(e) => format!("parse error: {e}"),
1✔
159
        NotImplemented => "not implemented yet.".to_string(),
1✔
NEW
160
        NotProject(file) => format!("{file}: not project"),
×
161
        NoProjectSpecified() => "no project specified.".to_string(),
1✔
162
        ProjectNotFound(p) => format!("{}: project not found", p.display()),
1✔
163
        UnsupportedArchiveFormat(f) => format!("{f}: unsupported archive format"),
1✔
164
        Warning(m) => format!("warning: {m}"),
1✔
165
    }
166
}
11✔
167

168
fn print_error(e: MeisterError) {
×
169
    println!("{}", errors_to_string(e));
×
170
}
×
171

172
fn init_logs(level: &LogLevel) {
4✔
173
    match level {
4✔
174
        btmeister::LogLevel::ERROR => std::env::set_var("RUST_LOG", "error"),
×
175
        btmeister::LogLevel::WARN => std::env::set_var("RUST_LOG", "warn"),
4✔
176
        btmeister::LogLevel::INFO => std::env::set_var("RUST_LOG", "info"),
×
177
        btmeister::LogLevel::DEBUG => std::env::set_var("RUST_LOG", "debug"),
×
178
        btmeister::LogLevel::TRACE => std::env::set_var("RUST_LOG", "trace"),
×
179
    };
180
    match env_logger::try_init() {
4✔
181
        Ok(_) => log::info!("set log level to {level}"),
1✔
182
        Err(_) => log::info!("set log level to {level} (no tty)"),
3✔
183
    }
184
}
4✔
185

186
fn rust_main(args: Vec<String>) -> Result<()> {
4✔
187
    let opts = cli::Options::parse_from(args);
4✔
188
    init_logs(&opts.level);
4✔
189
    perform(opts)
4✔
190
}
4✔
191

192
fn main() {
×
193
    match rust_main(std::env::args().collect()) {
×
194
        Ok(_) => {}
×
195
        Err(e) => {
×
196
            print_error(e);
×
197
            std::process::exit(1);
×
198
        }
199
    }
200
}
×
201

202
#[cfg(test)]
203
mod tests {
204
    use std::path::PathBuf;
205

206
    use super::*;
207

208
    #[test]
209
    fn test_error_message() {
1✔
210
        use MeisterError::*;
211
        assert_eq!("fatal: test", errors_to_string(Fatal("test".to_string())));
1✔
212
        assert_eq!(
1✔
213
            "io error: test",
214
            errors_to_string(IO(std::io::Error::other("test",)))
1✔
215
        );
216
        assert_eq!(
1✔
217
            "parse error: missing field `test`",
218
            errors_to_string(Json(serde::de::Error::missing_field("test")))
1✔
219
        );
220
        assert_eq!("not implemented yet.", errors_to_string(NotImplemented));
1✔
221
        assert_eq!(
1✔
222
            "no project specified.",
223
            errors_to_string(NoProjectSpecified())
1✔
224
        );
225
        assert_eq!(
1✔
226
            "test: project not found",
227
            errors_to_string(ProjectNotFound(PathBuf::from("test")))
1✔
228
        );
229
        assert_eq!(
1✔
230
            "test: unsupported archive format",
231
            errors_to_string(UnsupportedArchiveFormat("test".to_string())),
1✔
232
        );
233
        assert_eq!(
1✔
234
            "warning: test",
235
            errors_to_string(Warning("test".to_string()))
1✔
236
        );
237
        assert_eq!(
1✔
238
            "fatal: test\nfatal: test2",
239
            errors_to_string(Array(vec![
1✔
240
                Fatal("test".to_string()),
1✔
241
                Fatal("test2".to_string())
1✔
242
            ]))
1✔
243
        );
244
    }
1✔
245

246
    #[test]
247
    fn test_success() {
1✔
248
        let r = rust_main(
1✔
249
            ["btmeister", "../testdata/fibonacci", "--format", "json"]
1✔
250
                .iter()
1✔
251
                .map(|s| s.to_string())
4✔
252
                .collect(),
1✔
253
        );
254
        assert!(r.is_ok());
1✔
255
    }
1✔
256

257
    #[test]
258
    fn test_success_list_defs() {
1✔
259
        let r = rust_main(
1✔
260
            [
1✔
261
                "btmeister",
1✔
262
                "../testdata/fibonacci",
1✔
263
                "--list-defs",
1✔
264
                "--format",
1✔
265
                "json",
1✔
266
            ]
1✔
267
            .iter()
1✔
268
            .map(|s| s.to_string())
5✔
269
            .collect(),
1✔
270
        );
271
        assert!(r.is_ok());
1✔
272
    }
1✔
273

274
    #[test]
275
    fn test_project_not_found() {
1✔
276
        let r = rust_main(
1✔
277
            ["btmeister", "unknown/project"]
1✔
278
                .iter()
1✔
279
                .map(|s| s.to_string())
2✔
280
                .collect(),
1✔
281
        );
282
        assert!(r.is_err());
1✔
283
    }
1✔
284

285
    #[test]
286
    fn test_gencomp() {
1✔
287
        use std::path::PathBuf;
288
        let r = rust_main(
1✔
289
            [
1✔
290
                "btmeister",
1✔
291
                "--generate-completion-files",
1✔
292
                "--completion-out-dir",
1✔
293
                "testgencomp",
1✔
294
            ]
1✔
295
            .iter()
1✔
296
            .map(|s| s.to_string())
4✔
297
            .collect(),
1✔
298
        );
299
        assert!(r.is_ok());
1✔
300
        assert!(PathBuf::from("testgencomp/bash/btmeister").exists());
1✔
301
        assert!(PathBuf::from("testgencomp/elvish/btmeister").exists());
1✔
302
        assert!(PathBuf::from("testgencomp/fish/btmeister").exists());
1✔
303
        assert!(PathBuf::from("testgencomp/powershell/btmeister").exists());
1✔
304
        assert!(PathBuf::from("testgencomp/zsh/_btmeister").exists());
1✔
305

306
        std::fs::remove_dir_all("testgencomp").unwrap();
1✔
307
    }
1✔
308
}
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