1 // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8
9 use structopt::StructOpt;
10
11 use std::ffi::{CString, OsStr, OsString};
12 use std::num::ParseIntError;
13 use std::path::PathBuf;
14
15 #[derive(StructOpt, PartialEq, Debug)]
16 struct PathOpt {
17 #[structopt(short, long, parse(from_os_str))]
18 path: PathBuf,
19
20 #[structopt(short, default_value = "../", parse(from_os_str))]
21 default_path: PathBuf,
22
23 #[structopt(short, parse(from_os_str))]
24 vector_path: Vec<PathBuf>,
25
26 #[structopt(short, parse(from_os_str))]
27 option_path_1: Option<PathBuf>,
28
29 #[structopt(short = "q", parse(from_os_str))]
30 option_path_2: Option<PathBuf>,
31 }
32
33 #[test]
test_path_opt_simple()34 fn test_path_opt_simple() {
35 assert_eq!(
36 PathOpt {
37 path: PathBuf::from("/usr/bin"),
38 default_path: PathBuf::from("../"),
39 vector_path: vec![
40 PathBuf::from("/a/b/c"),
41 PathBuf::from("/d/e/f"),
42 PathBuf::from("/g/h/i"),
43 ],
44 option_path_1: None,
45 option_path_2: Some(PathBuf::from("j.zip")),
46 },
47 PathOpt::from_clap(&PathOpt::clap().get_matches_from(&[
48 "test", "-p", "/usr/bin", "-v", "/a/b/c", "-v", "/d/e/f", "-v", "/g/h/i", "-q",
49 "j.zip",
50 ]))
51 );
52 }
53
parse_hex(input: &str) -> Result<u64, ParseIntError>54 fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
55 u64::from_str_radix(input, 16)
56 }
57
58 #[derive(StructOpt, PartialEq, Debug)]
59 struct HexOpt {
60 #[structopt(short, parse(try_from_str = parse_hex))]
61 number: u64,
62 }
63
64 #[test]
65 #[allow(clippy::unreadable_literal)]
test_parse_hex()66 fn test_parse_hex() {
67 assert_eq!(
68 HexOpt { number: 5 },
69 HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "5"]))
70 );
71 assert_eq!(
72 HexOpt { number: 0xabcdef },
73 HexOpt::from_clap(&HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"]))
74 );
75
76 let err = HexOpt::clap()
77 .get_matches_from_safe(&["test", "-n", "gg"])
78 .unwrap_err();
79 assert!(err.message.contains("invalid digit found in string"), err);
80 }
81
custom_parser_1(_: &str) -> &'static str82 fn custom_parser_1(_: &str) -> &'static str {
83 "A"
84 }
custom_parser_2(_: &str) -> Result<&'static str, u32>85 fn custom_parser_2(_: &str) -> Result<&'static str, u32> {
86 Ok("B")
87 }
custom_parser_3(_: &OsStr) -> &'static str88 fn custom_parser_3(_: &OsStr) -> &'static str {
89 "C"
90 }
custom_parser_4(_: &OsStr) -> Result<&'static str, OsString>91 fn custom_parser_4(_: &OsStr) -> Result<&'static str, OsString> {
92 Ok("D")
93 }
94
95 #[derive(StructOpt, PartialEq, Debug)]
96 struct NoOpOpt {
97 #[structopt(short, parse(from_str = custom_parser_1))]
98 a: &'static str,
99 #[structopt(short, parse(try_from_str = custom_parser_2))]
100 b: &'static str,
101 #[structopt(short, parse(from_os_str = custom_parser_3))]
102 c: &'static str,
103 #[structopt(short, parse(try_from_os_str = custom_parser_4))]
104 d: &'static str,
105 }
106
107 #[test]
test_every_custom_parser()108 fn test_every_custom_parser() {
109 assert_eq!(
110 NoOpOpt {
111 a: "A",
112 b: "B",
113 c: "C",
114 d: "D"
115 },
116 NoOpOpt::from_clap(
117 &NoOpOpt::clap().get_matches_from(&["test", "-a=?", "-b=?", "-c=?", "-d=?"])
118 )
119 );
120 }
121
122 // Note: can't use `Vec<u8>` directly, as structopt would instead look for
123 // conversion function from `&str` to `u8`.
124 type Bytes = Vec<u8>;
125
126 #[derive(StructOpt, PartialEq, Debug)]
127 struct DefaultedOpt {
128 #[structopt(short, parse(from_str))]
129 bytes: Bytes,
130
131 #[structopt(short, parse(try_from_str))]
132 integer: u64,
133
134 #[structopt(short, parse(from_os_str))]
135 path: PathBuf,
136 }
137
138 #[test]
test_parser_with_default_value()139 fn test_parser_with_default_value() {
140 assert_eq!(
141 DefaultedOpt {
142 bytes: b"E\xc2\xb2=p\xc2\xb2c\xc2\xb2+m\xc2\xb2c\xe2\x81\xb4".to_vec(),
143 integer: 9000,
144 path: PathBuf::from("src/lib.rs"),
145 },
146 DefaultedOpt::from_clap(&DefaultedOpt::clap().get_matches_from(&[
147 "test",
148 "-b",
149 "E²=p²c²+m²c⁴",
150 "-i",
151 "9000",
152 "-p",
153 "src/lib.rs",
154 ]))
155 );
156 }
157
158 #[derive(PartialEq, Debug)]
159 struct Foo(u8);
160
foo(value: u64) -> Foo161 fn foo(value: u64) -> Foo {
162 Foo(value as u8)
163 }
164
165 #[derive(StructOpt, PartialEq, Debug)]
166 struct Occurrences {
167 #[structopt(short, long, parse(from_occurrences))]
168 signed: i32,
169
170 #[structopt(short, parse(from_occurrences))]
171 little_signed: i8,
172
173 #[structopt(short, parse(from_occurrences))]
174 unsigned: usize,
175
176 #[structopt(short = "r", parse(from_occurrences))]
177 little_unsigned: u8,
178
179 #[structopt(short, long, parse(from_occurrences = foo))]
180 custom: Foo,
181 }
182
183 #[test]
test_parser_occurrences()184 fn test_parser_occurrences() {
185 assert_eq!(
186 Occurrences {
187 signed: 3,
188 little_signed: 1,
189 unsigned: 0,
190 little_unsigned: 4,
191 custom: Foo(5),
192 },
193 Occurrences::from_clap(&Occurrences::clap().get_matches_from(&[
194 "test", "-s", "--signed", "--signed", "-l", "-rrrr", "-cccc", "--custom",
195 ]))
196 );
197 }
198
199 #[test]
test_custom_bool()200 fn test_custom_bool() {
201 fn parse_bool(s: &str) -> Result<bool, String> {
202 match s {
203 "true" => Ok(true),
204 "false" => Ok(false),
205 _ => Err(format!("invalid bool {}", s)),
206 }
207 }
208 #[derive(StructOpt, PartialEq, Debug)]
209 struct Opt {
210 #[structopt(short, parse(try_from_str = parse_bool))]
211 debug: bool,
212 #[structopt(
213 short,
214 default_value = "false",
215 parse(try_from_str = parse_bool)
216 )]
217 verbose: bool,
218 #[structopt(short, parse(try_from_str = parse_bool))]
219 tribool: Option<bool>,
220 #[structopt(short, parse(try_from_str = parse_bool))]
221 bitset: Vec<bool>,
222 }
223
224 assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
225 assert!(Opt::clap().get_matches_from_safe(&["test", "-d"]).is_err());
226 assert!(Opt::clap()
227 .get_matches_from_safe(&["test", "-dfoo"])
228 .is_err());
229 assert_eq!(
230 Opt {
231 debug: false,
232 verbose: false,
233 tribool: None,
234 bitset: vec![],
235 },
236 Opt::from_iter(&["test", "-dfalse"])
237 );
238 assert_eq!(
239 Opt {
240 debug: true,
241 verbose: false,
242 tribool: None,
243 bitset: vec![],
244 },
245 Opt::from_iter(&["test", "-dtrue"])
246 );
247 assert_eq!(
248 Opt {
249 debug: true,
250 verbose: false,
251 tribool: None,
252 bitset: vec![],
253 },
254 Opt::from_iter(&["test", "-dtrue", "-vfalse"])
255 );
256 assert_eq!(
257 Opt {
258 debug: true,
259 verbose: true,
260 tribool: None,
261 bitset: vec![],
262 },
263 Opt::from_iter(&["test", "-dtrue", "-vtrue"])
264 );
265 assert_eq!(
266 Opt {
267 debug: true,
268 verbose: false,
269 tribool: Some(false),
270 bitset: vec![],
271 },
272 Opt::from_iter(&["test", "-dtrue", "-tfalse"])
273 );
274 assert_eq!(
275 Opt {
276 debug: true,
277 verbose: false,
278 tribool: Some(true),
279 bitset: vec![],
280 },
281 Opt::from_iter(&["test", "-dtrue", "-ttrue"])
282 );
283 assert_eq!(
284 Opt {
285 debug: true,
286 verbose: false,
287 tribool: None,
288 bitset: vec![false, true, false, false],
289 },
290 Opt::from_iter(&["test", "-dtrue", "-bfalse", "-btrue", "-bfalse", "-bfalse"])
291 );
292 }
293
294 #[test]
test_cstring()295 fn test_cstring() {
296 #[derive(StructOpt)]
297 struct Opt {
298 #[structopt(parse(try_from_str = CString::new))]
299 c_string: CString,
300 }
301 assert!(Opt::clap().get_matches_from_safe(&["test"]).is_err());
302 assert_eq!(Opt::from_iter(&["test", "bla"]).c_string.to_bytes(), b"bla");
303 assert!(Opt::clap()
304 .get_matches_from_safe(&["test", "bla\0bla"])
305 .is_err());
306 }
307