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

tamada / btmeister / 12762976651

14 Jan 2025 07:27AM UTC coverage: 91.399% (+1.5%) from 89.891%
12762976651

push

github

web-flow
Merge pull request #56 from tamada/release/v0.6.1

Release/v0.6.1

94 of 98 new or added lines in 4 files covered. (95.92%)

4 existing lines in 1 file now uncovered.

1052 of 1151 relevant lines covered (91.4%)

28.77 hits per line

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

85.16
/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, and \"@\" was put on the first character, read from the file."
62
    )]
63
    pub dirs: Vec<String>,
8✔
64
}
65

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

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

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

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

105
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
106
pub enum Format {
107
    Csv,
108
    Default,
109
    Json,
110
    Xml,
111
    Yaml,
112
}
113

114
fn read_from_reader(r: Box<dyn BufRead>) -> Result<Vec<String>> {
2✔
115
    let mut result = vec![];
2✔
116
    for line in r.lines().flatten() {
13✔
117
        if line.starts_with("#") || line.trim().is_empty() {
13✔
118
            continue;
7✔
119
        }
6✔
120
        result.push(line);
6✔
121
    }
122
    Ok(result)
2✔
123
}
2✔
124

125
fn read_from_stdin() -> Result<Vec<String>> {
×
126
    read_from_reader(Box::new(io::stdin().lock()))
×
127
}
×
128

129
fn read_from_file(filename: &str) -> Result<Vec<String>> {
3✔
130
    match std::fs::File::open(filename) {
3✔
131
        Err(e) => Err(MeisterError::Io(e)),
1✔
132
        Ok(file) => read_from_reader(Box::new(std::io::BufReader::new(file))),
2✔
133
    }
134
}
3✔
135

136
fn convert_and_push_item(item: &str, result: &mut Vec<PathBuf>, errs: &mut Vec<MeisterError>) {
11✔
137
    let path = PathBuf::from(item);
11✔
138
    if !path.exists() || !path.is_dir() {
11✔
139
        errs.push(MeisterError::ProjectNotFound(item.to_string()));
4✔
140
    } else {
7✔
141
        result.push(path);
7✔
142
    }
7✔
143
}
11✔
144

145
fn push_items_or_errs(
3✔
146
    r: Result<Vec<String>>,
3✔
147
    results: &mut Vec<PathBuf>,
3✔
148
    errs: &mut Vec<MeisterError>,
3✔
149
) {
3✔
150
    match r {
3✔
151
        Err(e) => errs.push(e),
1✔
152
        Ok(items) => {
2✔
153
            for item in items {
8✔
154
                convert_and_push_item(&item, results, errs)
6✔
155
            }
156
        }
157
    }
158
}
3✔
159

160
impl InputOpts {
161
    pub(crate) fn projects(&self) -> Result<Vec<PathBuf>> {
8✔
162
        let mut errs = vec![];
8✔
163
        let mut result = vec![];
8✔
164
        for item in self.dirs.iter() {
8✔
165
            if item == "-" {
8✔
166
                push_items_or_errs(read_from_stdin(), &mut result, &mut errs);
×
167
            } else if let Some(stripped) = item.strip_prefix('@') {
8✔
168
                push_items_or_errs(read_from_file(stripped), &mut result, &mut errs);
3✔
169
            } else {
5✔
170
                convert_and_push_item(item.as_str(), &mut result, &mut errs);
5✔
171
            }
5✔
172
        }
173
        if !errs.is_empty() {
8✔
174
            Err(MeisterError::Array(errs))
4✔
175
        } else if result.is_empty() {
4✔
176
            Err(MeisterError::NoProjectSpecified())
1✔
177
        } else {
178
            Ok(result)
3✔
179
        }
180
    }
8✔
181
}
182

183
#[cfg(test)]
184
mod tests {
185
    use super::*;
186

187
    #[test]
188
    fn test_projects1() {
1✔
189
        let opts = Options::parse_from(&["meister", "testdata/fibonacci", "testdata/hello"]);
1✔
190
        let projects = opts.inputs.projects();
1✔
191
        assert!(projects.is_ok());
1✔
192
        if let Ok(p) = projects {
1✔
193
            assert_eq!(2, p.len());
1✔
194
            assert_eq!(PathBuf::from("testdata/fibonacci"), p[0]);
1✔
195
            assert_eq!(PathBuf::from("testdata/hello"), p[1]);
1✔
196
        }
×
197
    }
1✔
198

199
    #[test]
200
    fn test_projects2() {
1✔
201
        let opts = Options::parse_from(&["meister", "@testdata/project_list.txt"]);
1✔
202
        let projects = opts.inputs.projects();
1✔
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/hello"), p[0]);
1✔
207
            assert_eq!(PathBuf::from("testdata/fibonacci"), p[1]);
1✔
208
        }
×
209
    }
1✔
210

211
    #[test]
212
    fn test_not_exist_project() {
1✔
213
        let opts = Options::parse_from(&["meister", "not_exist_project"]);
1✔
214
        let projects = opts.inputs.projects();
1✔
215
        assert!(projects.is_err());
1✔
216
        if let Err(MeisterError::Array(e)) = projects {
1✔
217
            assert_eq!(1, e.len());
1✔
218
            if let MeisterError::ProjectNotFound(p) = &e[0] {
1✔
219
                assert_eq!("not_exist_project", p);
1✔
220
            }
×
221
        }
×
222
    }
1✔
223

224
    #[test]
225
    fn test_invalid_project_list() {
1✔
226
        let opts = Options::parse_from(&["meister", "@testdata/invalid_project_list.txt"]);
1✔
227
        let projects = opts.inputs.projects();
1✔
228
        assert!(projects.is_err());
1✔
229
        if let Err(MeisterError::Array(e)) = projects {
1✔
230
            assert_eq!(2, e.len());
1✔
231
            if let MeisterError::ProjectNotFound(p) = &e[0] {
1✔
232
                assert_eq!("not_exist_project", p);
1✔
233
            }
×
234
            if let MeisterError::ProjectNotFound(p) = &e[1] {
1✔
235
                assert_eq!("testdata/project_list.txt", p);
1✔
236
            }
×
237
        }
×
238
    }
1✔
239

240
    #[test]
241
    fn test_unknownfile() {
1✔
242
        let opts = Options::parse_from(&["meister", "@unknownfile"]);
1✔
243
        let projects = opts.inputs.projects();
1✔
244
        assert!(projects.is_err());
1✔
245
        if let Err(MeisterError::Array(e)) = projects {
1✔
246
            assert_eq!(1, e.len());
1✔
247
            if let MeisterError::Io(p) = &e[0] {
1✔
248
                assert_eq!(std::io::ErrorKind::NotFound, p.kind());
1✔
249
            }
×
250
        }
×
251
    }
1✔
252

253
    #[test]
254
    fn test_no_projects() {
1✔
255
        let opts = InputOpts {
1✔
256
            ignore_types: vec![],
1✔
257
            dirs: vec![],
1✔
258
        };
1✔
259
        let projects = opts.projects();
1✔
260
        assert!(projects.is_err());
1✔
261
        match projects {
1✔
262
            Err(MeisterError::NoProjectSpecified()) => assert!(true),
1✔
NEW
263
            _ => assert!(false),
×
264
        }
265
    }
1✔
266
}
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