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

tamada / btmeister / 12971493668

26 Jan 2025 04:37AM UTC coverage: 90.521% (+0.01%) from 90.508%
12971493668

push

github

tamada
refactor: clean up .gitignore and improve error handling in extractors by Clippy suggestions.

7 of 9 new or added lines in 4 files covered. (77.78%)

16 existing lines in 1 file now uncovered.

1337 of 1477 relevant lines covered (90.52%)

24.37 hits per line

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

84.06
/src/cli.rs
1
use clap::{Parser, ValueEnum};
2
use std::io::{self, BufRead};
3
use std::path::PathBuf;
4

5
use btmeister::{IgnoreType, MeisterError, Result};
6

7
#[derive(Parser, Debug)]
8
#[clap(author, version, about, arg_required_else_help = true)]
9
pub(crate) struct Options {
10
    #[clap(flatten)]
11
    pub(crate) defopts: DefOpts,
12

13
    #[clap(flatten)]
14
    pub(crate) inputs: InputOpts,
15

16
    #[clap(flatten)]
17
    pub(crate) outputs: OutputOpts,
18

19
    #[arg(short, long, help = "Show verbose output.")]
20
    pub(crate) verbose: bool,
×
21

22
    #[clap(flatten)]
23
    pub(crate) compopts: CompletionOpts,
24
}
25

26
#[derive(Parser, Debug)]
27
pub(crate) struct CompletionOpts {
28
    #[arg(
29
        long = "generate-completion-files",
30
        help = "Generate completion files",
31
        hide = true
32
    )]
33
    pub(crate) completion: bool,
×
34

35
    #[arg(
36
        long = "completion-out-dir",
37
        value_name = "DIR",
38
        default_value = "assets/completions",
39
        help = "Output directory of completion files",
40
        hide = true
41
    )]
42
    pub(crate) dest: PathBuf,
×
43
}
44

45
#[derive(Parser, Debug, Clone)]
46
pub(crate) struct InputOpts {
47
    #[arg(
48
        short = 'i',
49
        long = "ignore-type",
50
        default_value = "default",
51
        ignore_case = true,
52
        value_enum,
53
        value_name = "IGNORE_TYPE",
54
        help = "specify the ignore type."
55
    )]
56
    pub(crate) ignore_types: Vec<IgnoreType>,
9✔
57

58
    #[arg(
59
        value_name = "PROJECTs",
60
        required = false,
61
        help = "The target project paths. If \"-\" was given, reads from stdin.
62
Also, the first character was \"@\", read from the file eliminating \"@\".
63
This parameters accept directories and archive files.
64
Supported archive files: tar, tar.bz2, tar.gz, tar.xz, tar.zstd, and zip."
65
    )]
66
    pub dirs: Vec<String>,
8✔
67
}
68

69
#[derive(Parser, Debug, Clone)]
70
pub(crate) struct OutputOpts {
71
    #[arg(
72
        short = 'L',
73
        long = "list-defs",
74
        help = "Print the build tools' definition list"
75
    )]
76
    pub(crate) list_defs: bool,
×
77

78
    #[arg(
79
        short,
80
        long,
81
        default_value_t = Format::Default,
1✔
82
        value_name = "FORMAT",
83
        value_enum,
84
        ignore_case = true,
85
        help = "Specify the output format"
86
    )]
87
    pub(crate) format: Format,
×
88
}
89

90
#[derive(Parser, Debug, Clone)]
91
pub(crate) struct DefOpts {
92
    #[arg(
93
        short = 'D',
94
        long,
95
        value_name = "DEFS_JSON",
96
        help = "Specify the definition of the build tools."
97
    )]
98
    pub(crate) definition: Option<PathBuf>,
99

100
    #[arg(
101
        long,
102
        value_name = "DEFS_JSON",
103
        help = "Specify the additional definitions of the build tools."
104
    )]
105
    pub(crate) append_defs: Option<PathBuf>,
106
}
107

108
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
109
pub enum Format {
110
    Csv,
111
    Default,
112
    Json,
113
    Markdown,
114
    Xml,
115
    Yaml,
116
}
117

118
fn read_from_reader(r: Box<dyn BufRead>) -> Result<Vec<String>> {
2✔
119
    let mut result = vec![];
2✔
120
    for line in r.lines().map_while(|line| line.map_err(MeisterError::IO).ok()) {
13✔
121
        if line.starts_with("#") || line.trim().is_empty() {
2✔
122
            continue;
13✔
123
        }
124
        result.push(line);
13✔
125
    }
7✔
126
    Ok(result)
6✔
127
}
6✔
128

129
fn read_from_stdin() -> Result<Vec<String>> {
2✔
130
    read_from_reader(Box::new(io::stdin().lock()))
2✔
131
}
UNCOV
132

×
UNCOV
133
fn read_from_file(filename: &str) -> Result<Vec<String>> {
×
UNCOV
134
    match std::fs::File::open(filename) {
×
135
        Err(e) => Err(MeisterError::IO(e)),
136
        Ok(file) => read_from_reader(Box::new(std::io::BufReader::new(file))),
3✔
137
    }
3✔
138
}
1✔
139

2✔
140
fn convert_and_push_item(item: &str, result: &mut Vec<PathBuf>, errs: &mut Vec<MeisterError>) {
141
    let path = PathBuf::from(item);
3✔
142
    if !path.exists() {
143
        errs.push(MeisterError::ProjectNotFound(item.to_string()));
11✔
144
    } else if path.is_file() {
11✔
145
        if btmeister::is_supported_archive_format(&path) {
11✔
146
            result.push(path);
3✔
147
        } else {
8✔
148
            errs.push(MeisterError::ProjectNotFound(item.to_string()));
1✔
UNCOV
149
        }
×
150
    } else if path.is_dir() {
1✔
151
        result.push(path);
1✔
152
    } else {
1✔
153
        errs.push(MeisterError::ProjectNotFound(item.to_string()));
7✔
154
    }
7✔
155
}
7✔
UNCOV
156

×
UNCOV
157
fn push_items_or_errs(
×
158
    r: Result<Vec<String>>,
11✔
159
    results: &mut Vec<PathBuf>,
160
    errs: &mut Vec<MeisterError>,
3✔
161
) {
3✔
162
    match r {
3✔
163
        Err(e) => errs.push(e),
3✔
164
        Ok(items) => {
3✔
165
            for item in items {
3✔
166
                convert_and_push_item(&item, results, errs)
1✔
167
            }
2✔
168
        }
8✔
169
    }
6✔
170
}
171

172
impl InputOpts {
173
    pub(crate) fn projects(&self) -> Result<Vec<PathBuf>> {
3✔
174
        let mut errs = vec![];
175
        let mut result = vec![];
176
        for item in self.dirs.iter() {
8✔
177
            if item == "-" {
8✔
178
                push_items_or_errs(read_from_stdin(), &mut result, &mut errs);
8✔
179
            } else if let Some(stripped) = item.strip_prefix('@') {
8✔
180
                push_items_or_errs(read_from_file(stripped), &mut result, &mut errs);
8✔
UNCOV
181
            } else {
×
182
                convert_and_push_item(item.as_str(), &mut result, &mut errs);
8✔
183
            }
3✔
184
        }
5✔
185
        if !errs.is_empty() {
5✔
186
            Err(MeisterError::Array(errs))
5✔
187
        } else if result.is_empty() {
188
            Err(MeisterError::NoProjectSpecified())
8✔
189
        } else {
4✔
190
            Ok(result)
4✔
191
        }
1✔
192
    }
193
}
3✔
194

195
#[cfg(test)]
8✔
196
mod tests {
197
    use super::*;
198

199
    #[test]
200
    fn test_projects1() {
201
        let opts = Options::parse_from(&["meister", "testdata/fibonacci", "testdata/hello"]);
202
        let projects = opts.inputs.projects();
203
        assert!(projects.is_ok());
1✔
204
        if let Ok(p) = projects {
1✔
205
            assert_eq!(2, p.len());
1✔
206
            assert_eq!(PathBuf::from("testdata/fibonacci"), p[0]);
1✔
207
            assert_eq!(PathBuf::from("testdata/hello"), p[1]);
1✔
208
        }
1✔
209
    }
1✔
210

1✔
UNCOV
211
    #[test]
×
212
    fn test_projects2() {
1✔
213
        let opts = Options::parse_from(&["meister", "@testdata/project_list.txt"]);
214
        let projects = opts.inputs.projects();
215
        assert!(projects.is_ok());
1✔
216
        if let Ok(p) = projects {
1✔
217
            assert_eq!(2, p.len());
1✔
218
            assert_eq!(PathBuf::from("testdata/hello"), p[0]);
1✔
219
            assert_eq!(PathBuf::from("testdata/fibonacci"), p[1]);
1✔
220
        }
1✔
221
    }
1✔
222

1✔
UNCOV
223
    #[test]
×
224
    fn test_not_exist_project() {
1✔
225
        let opts = Options::parse_from(&["meister", "not_exist_project"]);
226
        let projects = opts.inputs.projects();
227
        assert!(projects.is_err());
1✔
228
        if let Err(MeisterError::Array(e)) = projects {
1✔
229
            assert_eq!(1, e.len());
1✔
230
            if let MeisterError::ProjectNotFound(p) = &e[0] {
1✔
231
                assert_eq!("not_exist_project", p);
1✔
232
            }
1✔
233
        }
1✔
234
    }
1✔
UNCOV
235

×
UNCOV
236
    #[test]
×
237
    fn test_invalid_project_list() {
1✔
238
        let opts = Options::parse_from(&["meister", "@testdata/invalid_project_list.txt"]);
239
        let projects = opts.inputs.projects();
240
        assert!(projects.is_err());
1✔
241
        if let Err(MeisterError::Array(e)) = projects {
1✔
242
            assert_eq!(2, e.len());
1✔
243
            if let MeisterError::ProjectNotFound(p) = &e[0] {
1✔
244
                assert_eq!("not_exist_project", p);
1✔
245
            }
1✔
246
            if let MeisterError::ProjectNotFound(p) = &e[1] {
1✔
247
                assert_eq!("testdata/project_list.txt", p);
1✔
248
            }
×
249
        }
1✔
250
    }
1✔
UNCOV
251

×
UNCOV
252
    #[test]
×
253
    fn test_unknownfile() {
1✔
254
        let opts = Options::parse_from(&["meister", "@unknownfile"]);
255
        let projects = opts.inputs.projects();
256
        assert!(projects.is_err());
1✔
257
        if let Err(MeisterError::Array(e)) = projects {
1✔
258
            assert_eq!(1, e.len());
1✔
259
            if let MeisterError::IO(p) = &e[0] {
1✔
260
                assert_eq!(std::io::ErrorKind::NotFound, p.kind());
1✔
261
            }
1✔
262
        }
1✔
263
    }
1✔
UNCOV
264

×
UNCOV
265
    #[test]
×
266
    fn test_no_projects() {
1✔
267
        let opts = InputOpts {
268
            ignore_types: vec![],
269
            dirs: vec![],
1✔
270
        };
1✔
271
        let projects = opts.projects();
1✔
272
        assert!(projects.is_err());
1✔
273
        match projects {
1✔
274
            Err(MeisterError::NoProjectSpecified()) => assert!(true),
1✔
275
            _ => assert!(false),
1✔
276
        }
1✔
277
    }
1✔
UNCOV
278
}
×
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