parser.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. //! A general parser for command-line options.
  2. //!
  3. //! exa uses its own hand-rolled parser for command-line options. It supports
  4. //! the following syntax:
  5. //!
  6. //! - Long options: `--inode`, `--grid`
  7. //! - Long options with values: `--sort size`, `--level=4`
  8. //! - Short options: `-i`, `-G`
  9. //! - Short options with values: `-ssize`, `-L=4`
  10. //!
  11. //! These values can be mixed and matched: `exa -lssize --grid`. If you’ve used
  12. //! other command-line programs, then hopefully it’ll work much like them.
  13. //!
  14. //! Because exa already has its own files for the help text, shell completions,
  15. //! man page, and readme, so it can get away with having the options parser do
  16. //! very little: all it really needs to do is parse a slice of strings.
  17. //!
  18. //!
  19. //! ## UTF-8 and `OsStr`
  20. //!
  21. //! The parser uses `OsStr` as its string type. This is necessary for exa to
  22. //! list files that have invalid UTF-8 in their names: by treating file paths
  23. //! as bytes with no encoding, a file can be specified on the command-line and
  24. //! be looked up without having to be encoded into a `str` first.
  25. //!
  26. //! It also avoids the overhead of checking for invalid UTF-8 when parsing
  27. //! command-line options, as all the options and their values (such as
  28. //! `--sort size`) are guaranteed to just be 8-bit ASCII.
  29. use std::ffi::{OsStr, OsString};
  30. use std::fmt;
  31. use options::Misfire;
  32. /// A **short argument** is a single ASCII character.
  33. pub type ShortArg = u8;
  34. /// A **long argument** is a string. This can be a UTF-8 string, even though
  35. /// the arguments will all be unchecked OsStrings, because we don’t actually
  36. /// store the user’s input after it’s been matched to a flag, we just store
  37. /// which flag it was.
  38. pub type LongArg = &'static str;
  39. /// A **flag** is either of the two argument types, because they have to
  40. /// be in the same array together.
  41. #[derive(PartialEq, Debug, Clone)]
  42. pub enum Flag {
  43. Short(ShortArg),
  44. Long(LongArg),
  45. }
  46. impl Flag {
  47. pub fn matches(&self, arg: &Arg) -> bool {
  48. match *self {
  49. Flag::Short(short) => arg.short == Some(short),
  50. Flag::Long(long) => arg.long == long,
  51. }
  52. }
  53. }
  54. impl fmt::Display for Flag {
  55. fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
  56. match *self {
  57. Flag::Short(short) => write!(f, "-{}", short as char),
  58. Flag::Long(long) => write!(f, "--{}", long),
  59. }
  60. }
  61. }
  62. /// Whether redundant arguments should be considered a problem.
  63. #[derive(PartialEq, Debug, Copy, Clone)]
  64. pub enum Strictness {
  65. /// Throw an error when an argument doesn’t do anything, either because
  66. /// it requires another argument to be specified, or because two conflict.
  67. ComplainAboutRedundantArguments,
  68. /// Search the arguments list back-to-front, giving ones specified later
  69. /// in the list priority over earlier ones.
  70. UseLastArguments,
  71. }
  72. /// Whether a flag takes a value. This is applicable to both long and short
  73. /// arguments.
  74. #[derive(Copy, Clone, PartialEq, Debug)]
  75. pub enum TakesValue {
  76. /// This flag has to be followed by a value.
  77. Necessary,
  78. /// This flag will throw an error if there’s a value after it.
  79. Forbidden,
  80. }
  81. /// An **argument** can be matched by one of the user’s input strings.
  82. #[derive(PartialEq, Debug)]
  83. pub struct Arg {
  84. /// The short argument that matches it, if any.
  85. pub short: Option<ShortArg>,
  86. /// The long argument that matches it. This is non-optional; all flags
  87. /// should at least have a descriptive long name.
  88. pub long: LongArg,
  89. /// Whether this flag takes a value or not.
  90. pub takes_value: TakesValue,
  91. }
  92. impl fmt::Display for Arg {
  93. fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
  94. write!(f, "--{}", self.long)?;
  95. if let Some(short) = self.short {
  96. write!(f, " (-{})", short as char)?;
  97. }
  98. Ok(())
  99. }
  100. }
  101. /// Literally just several args.
  102. #[derive(PartialEq, Debug)]
  103. pub struct Args(pub &'static [&'static Arg]);
  104. impl Args {
  105. /// Iterates over the given list of command-line arguments and parses
  106. /// them into a list of matched flags and free strings.
  107. pub fn parse<'args, I>(&self, inputs: I, strictness: Strictness) -> Result<Matches<'args>, ParseError>
  108. where I: IntoIterator<Item=&'args OsString> {
  109. use std::os::unix::ffi::OsStrExt;
  110. use self::TakesValue::*;
  111. let mut parsing = true;
  112. // The results that get built up.
  113. let mut result_flags = Vec::new();
  114. let mut frees: Vec<&OsStr> = Vec::new();
  115. // Iterate over the inputs with “while let” because we need to advance
  116. // the iterator manually whenever an argument that takes a value
  117. // doesn’t have one in its string so it needs the next one.
  118. let mut inputs = inputs.into_iter();
  119. while let Some(arg) = inputs.next() {
  120. let bytes = arg.as_bytes();
  121. // Stop parsing if one of the arguments is the literal string “--”.
  122. // This allows a file named “--arg” to be specified by passing in
  123. // the pair “-- --arg”, without it getting matched as a flag that
  124. // doesn’t exist.
  125. if !parsing {
  126. frees.push(arg)
  127. }
  128. else if arg == "--" {
  129. parsing = false;
  130. }
  131. // If the string starts with *two* dashes then it’s a long argument.
  132. else if bytes.starts_with(b"--") {
  133. let long_arg_name = OsStr::from_bytes(&bytes[2..]);
  134. // If there’s an equals in it, then the string before the
  135. // equals will be the flag’s name, and the string after it
  136. // will be its value.
  137. if let Some((before, after)) = split_on_equals(long_arg_name) {
  138. let arg = self.lookup_long(before)?;
  139. let flag = Flag::Long(arg.long);
  140. match arg.takes_value {
  141. Necessary => result_flags.push((flag, Some(after))),
  142. Forbidden => return Err(ParseError::ForbiddenValue { flag })
  143. }
  144. }
  145. // If there’s no equals, then the entire string (apart from
  146. // the dashes) is the argument name.
  147. else {
  148. let arg = self.lookup_long(long_arg_name)?;
  149. let flag = Flag::Long(arg.long);
  150. match arg.takes_value {
  151. Forbidden => result_flags.push((flag, None)),
  152. Necessary => {
  153. if let Some(next_arg) = inputs.next() {
  154. result_flags.push((flag, Some(next_arg)));
  155. }
  156. else {
  157. return Err(ParseError::NeedsValue { flag })
  158. }
  159. }
  160. }
  161. }
  162. }
  163. // If the string starts with *one* dash then it’s one or more
  164. // short arguments.
  165. else if bytes.starts_with(b"-") && arg != "-" {
  166. let short_arg = OsStr::from_bytes(&bytes[1..]);
  167. // If there’s an equals in it, then the argument immediately
  168. // before the equals was the one that has the value, with the
  169. // others (if any) as value-less short ones.
  170. //
  171. // -x=abc => ‘x=abc’
  172. // -abcdx=fgh => ‘a’, ‘b’, ‘c’, ‘d’, ‘x=fgh’
  173. // -x= => error
  174. // -abcdx= => error
  175. //
  176. // There’s no way to give two values in a cluster like this:
  177. // it's an error if any of the first set of arguments actually
  178. // takes a value.
  179. if let Some((before, after)) = split_on_equals(short_arg) {
  180. let (arg_with_value, other_args) = before.as_bytes().split_last().unwrap();
  181. // Process the characters immediately following the dash...
  182. for byte in other_args {
  183. let arg = self.lookup_short(*byte)?;
  184. let flag = Flag::Short(*byte);
  185. match arg.takes_value {
  186. Forbidden => result_flags.push((flag, None)),
  187. Necessary => return Err(ParseError::NeedsValue { flag })
  188. }
  189. }
  190. // ...then the last one and the value after the equals.
  191. let arg = self.lookup_short(*arg_with_value)?;
  192. let flag = Flag::Short(arg.short.unwrap());
  193. match arg.takes_value {
  194. Necessary => result_flags.push((flag, Some(after))),
  195. Forbidden => return Err(ParseError::ForbiddenValue { flag })
  196. }
  197. }
  198. // If there’s no equals, then every character is parsed as
  199. // its own short argument. However, if any of the arguments
  200. // takes a value, then the *rest* of the string is used as
  201. // its value, and if there's no rest of the string, then it
  202. // uses the next one in the iterator.
  203. //
  204. // -a => ‘a’
  205. // -abc => ‘a’, ‘b’, ‘c’
  206. // -abxdef => ‘a’, ‘b’, ‘x=def’
  207. // -abx def => ‘a’, ‘b’, ‘x=def’
  208. // -abx => error
  209. //
  210. else {
  211. for (index, byte) in bytes.into_iter().enumerate().skip(1) {
  212. let arg = self.lookup_short(*byte)?;
  213. let flag = Flag::Short(*byte);
  214. match arg.takes_value {
  215. Forbidden => result_flags.push((flag, None)),
  216. Necessary => {
  217. if index < bytes.len() - 1 {
  218. let remnants = &bytes[index+1 ..];
  219. result_flags.push((flag, Some(OsStr::from_bytes(remnants))));
  220. break;
  221. }
  222. else if let Some(next_arg) = inputs.next() {
  223. result_flags.push((flag, Some(next_arg)));
  224. }
  225. else {
  226. return Err(ParseError::NeedsValue { flag })
  227. }
  228. }
  229. }
  230. }
  231. }
  232. }
  233. // Otherwise, it’s a free string, usually a file name.
  234. else {
  235. frees.push(arg)
  236. }
  237. }
  238. Ok(Matches { frees, flags: MatchedFlags { flags: result_flags, strictness } })
  239. }
  240. fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {
  241. match self.0.into_iter().find(|arg| arg.short == Some(short)) {
  242. Some(arg) => Ok(arg),
  243. None => Err(ParseError::UnknownShortArgument { attempt: short })
  244. }
  245. }
  246. fn lookup_long<'b>(&self, long: &'b OsStr) -> Result<&Arg, ParseError> {
  247. match self.0.into_iter().find(|arg| arg.long == long) {
  248. Some(arg) => Ok(arg),
  249. None => Err(ParseError::UnknownArgument { attempt: long.to_os_string() })
  250. }
  251. }
  252. }
  253. /// The **matches** are the result of parsing the user’s command-line strings.
  254. #[derive(PartialEq, Debug)]
  255. pub struct Matches<'args> {
  256. /// The flags that were parsed from the user’s input.
  257. pub flags: MatchedFlags<'args>,
  258. /// All the strings that weren’t matched as arguments, as well as anything
  259. /// after the special "--" string.
  260. pub frees: Vec<&'args OsStr>,
  261. }
  262. #[derive(PartialEq, Debug)]
  263. pub struct MatchedFlags<'args> {
  264. /// The individual flags from the user’s input, in the order they were
  265. /// originally given.
  266. ///
  267. /// Long and short arguments need to be kept in the same vector because
  268. /// we usually want the one nearest the end to count, and to know this,
  269. /// we need to know where they are in relation to one another.
  270. flags: Vec<(Flag, Option<&'args OsStr>)>,
  271. /// Whether to check for duplicate or redundant arguments.
  272. strictness: Strictness,
  273. }
  274. impl<'a> MatchedFlags<'a> {
  275. /// Whether the given argument was specified.
  276. /// Returns `true` if it was, `false` if it wasn’t, and an error in
  277. /// strict mode if it was specified more than once.
  278. pub fn has(&self, arg: &'static Arg) -> Result<bool, Misfire> {
  279. self.has_where(|flag| flag.matches(arg)).map(|flag| flag.is_some())
  280. }
  281. /// Returns the first found argument that satisfies the predicate, or
  282. /// nothing if none is found, or an error in strict mode if multiple
  283. /// argument satisfy the predicate.
  284. ///
  285. /// You’ll have to test the resulting flag to see which argument it was.
  286. pub fn has_where<P>(&self, predicate: P) -> Result<Option<&Flag>, Misfire>
  287. where P: Fn(&Flag) -> bool {
  288. if self.is_strict() {
  289. let all = self.flags.iter()
  290. .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0))
  291. .collect::<Vec<_>>();
  292. if all.len() < 2 { Ok(all.first().map(|t| &t.0)) }
  293. else { Err(Misfire::Duplicate(all[0].0.clone(), all[1].0.clone())) }
  294. }
  295. else {
  296. let any = self.flags.iter().rev()
  297. .find(|tuple| tuple.1.is_none() && predicate(&tuple.0))
  298. .map(|tuple| &tuple.0);
  299. Ok(any)
  300. }
  301. }
  302. // This code could probably be better.
  303. // Both ‘has’ and ‘get’ immediately begin with a conditional, which makes
  304. // me think the functionality could be moved to inside Strictness.
  305. /// Returns the value of the given argument if it was specified, nothing
  306. /// if it wasn’t, and an error in strict mode if it was specified more
  307. /// than once.
  308. pub fn get(&self, arg: &'static Arg) -> Result<Option<&OsStr>, Misfire> {
  309. self.get_where(|flag| flag.matches(arg))
  310. }
  311. /// Returns the value of the argument that matches the predicate if it
  312. /// was specified, nothing if it wasn't, and an error in strict mode if
  313. /// multiple arguments matched the predicate.
  314. ///
  315. /// It’s not possible to tell which flag the value belonged to from this.
  316. pub fn get_where<P>(&self, predicate: P) -> Result<Option<&OsStr>, Misfire>
  317. where P: Fn(&Flag) -> bool {
  318. if self.is_strict() {
  319. let those = self.flags.iter()
  320. .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0))
  321. .collect::<Vec<_>>();
  322. if those.len() < 2 { Ok(those.first().cloned().map(|t| t.1.unwrap())) }
  323. else { Err(Misfire::Duplicate(those[0].0.clone(), those[1].0.clone())) }
  324. }
  325. else {
  326. let found = self.flags.iter().rev()
  327. .find(|tuple| tuple.1.is_some() && predicate(&tuple.0))
  328. .map(|tuple| tuple.1.unwrap());
  329. Ok(found)
  330. }
  331. }
  332. // It’s annoying that ‘has’ and ‘get’ won’t work when accidentally given
  333. // flags that do/don’t take values, but this should be caught by tests.
  334. /// Counts the number of occurrences of the given argument, even in
  335. /// strict mode.
  336. pub fn count(&self, arg: &Arg) -> usize {
  337. self.flags.iter()
  338. .filter(|tuple| tuple.0.matches(arg))
  339. .count()
  340. }
  341. /// Checks whether strict mode is on. This is usually done from within
  342. /// ‘has’ and ‘get’, but it’s available in an emergency.
  343. pub fn is_strict(&self) -> bool {
  344. self.strictness == Strictness::ComplainAboutRedundantArguments
  345. }
  346. }
  347. /// A problem with the user's input that meant it couldn't be parsed into a
  348. /// coherent list of arguments.
  349. #[derive(PartialEq, Debug)]
  350. pub enum ParseError {
  351. /// A flag that has to take a value was not given one.
  352. NeedsValue { flag: Flag },
  353. /// A flag that can't take a value *was* given one.
  354. ForbiddenValue { flag: Flag },
  355. /// A short argument, either alone or in a cluster, was not
  356. /// recognised by the program.
  357. UnknownShortArgument { attempt: ShortArg },
  358. /// A long argument was not recognised by the program.
  359. /// We don’t have a known &str version of the flag, so
  360. /// this may not be valid UTF-8.
  361. UnknownArgument { attempt: OsString },
  362. }
  363. // It’s technically possible for ParseError::UnknownArgument to borrow its
  364. // OsStr rather than owning it, but that would give ParseError a lifetime,
  365. // which would give Misfire a lifetime, which gets used everywhere. And this
  366. // only happens when an error occurs, so it’s not really worth it.
  367. /// Splits a string on its `=` character, returning the two substrings on
  368. /// either side. Returns `None` if there’s no equals or a string is missing.
  369. fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
  370. use std::os::unix::ffi::OsStrExt;
  371. if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
  372. let (before, after) = input.as_bytes().split_at(index);
  373. // The after string contains the = that we need to remove.
  374. if before.len() >= 1 && after.len() >= 2 {
  375. return Some((OsStr::from_bytes(before),
  376. OsStr::from_bytes(&after[1..])))
  377. }
  378. }
  379. None
  380. }
  381. /// Creates an `OSString` (used in tests)
  382. #[cfg(test)]
  383. fn os(input: &'static str) -> OsString {
  384. let mut os = OsString::new();
  385. os.push(input);
  386. os
  387. }
  388. #[cfg(test)]
  389. mod split_test {
  390. use super::{split_on_equals, os};
  391. macro_rules! test_split {
  392. ($name:ident: $input:expr => None) => {
  393. #[test]
  394. fn $name() {
  395. assert_eq!(split_on_equals(&os($input)),
  396. None);
  397. }
  398. };
  399. ($name:ident: $input:expr => $before:expr, $after:expr) => {
  400. #[test]
  401. fn $name() {
  402. assert_eq!(split_on_equals(&os($input)),
  403. Some((&*os($before), &*os($after))));
  404. }
  405. };
  406. }
  407. test_split!(empty: "" => None);
  408. test_split!(letter: "a" => None);
  409. test_split!(just: "=" => None);
  410. test_split!(intro: "=bbb" => None);
  411. test_split!(denou: "aaa=" => None);
  412. test_split!(equals: "aaa=bbb" => "aaa", "bbb");
  413. test_split!(sort: "--sort=size" => "--sort", "size");
  414. test_split!(more: "this=that=other" => "this", "that=other");
  415. }
  416. #[cfg(test)]
  417. mod parse_test {
  418. use super::*;
  419. pub fn os(input: &'static str) -> OsString {
  420. let mut os = OsString::new();
  421. os.push(input);
  422. os
  423. }
  424. macro_rules! test {
  425. ($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => {
  426. #[test]
  427. fn $name() {
  428. // Annoyingly the input &strs need to be converted to OsStrings
  429. let inputs: Vec<OsString> = $inputs.as_ref().into_iter().map(|&o| os(o)).collect();
  430. // Same with the frees
  431. let frees: Vec<OsString> = $frees.as_ref().into_iter().map(|&o| os(o)).collect();
  432. let frees: Vec<&OsStr> = frees.iter().map(|os| os.as_os_str()).collect();
  433. let flags = <[_]>::into_vec(Box::new($flags));
  434. let strictness = Strictness::UseLastArguments; // this isn’t even used
  435. let got = Args(TEST_ARGS).parse(inputs.iter(), strictness);
  436. let expected = Ok(Matches { frees, flags: MatchedFlags { flags, strictness } });
  437. assert_eq!(got, expected);
  438. }
  439. };
  440. ($name:ident: $inputs:expr => error $error:expr) => {
  441. #[test]
  442. fn $name() {
  443. use self::ParseError::*;
  444. let strictness = Strictness::UseLastArguments; // this isn’t even used
  445. let bits = $inputs.as_ref().into_iter().map(|&o| os(o)).collect::<Vec<OsString>>();
  446. let got = Args(TEST_ARGS).parse(bits.iter(), strictness);
  447. assert_eq!(got, Err($error));
  448. }
  449. };
  450. }
  451. static TEST_ARGS: &[&Arg] = &[
  452. &Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden },
  453. &Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden },
  454. &Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary }
  455. ];
  456. // Just filenames
  457. test!(empty: [] => frees: [], flags: []);
  458. test!(one_arg: ["exa"] => frees: [ "exa" ], flags: []);
  459. // Dashes and double dashes
  460. test!(one_dash: ["-"] => frees: [ "-" ], flags: []);
  461. test!(two_dashes: ["--"] => frees: [], flags: []);
  462. test!(two_file: ["--", "file"] => frees: [ "file" ], flags: []);
  463. test!(two_arg_l: ["--", "--long"] => frees: [ "--long" ], flags: []);
  464. test!(two_arg_s: ["--", "-l"] => frees: [ "-l" ], flags: []);
  465. // Long args
  466. test!(long: ["--long"] => frees: [], flags: [ (Flag::Long("long"), None) ]);
  467. test!(long_then: ["--long", "4"] => frees: [ "4" ], flags: [ (Flag::Long("long"), None) ]);
  468. test!(long_two: ["--long", "--verbose"] => frees: [], flags: [ (Flag::Long("long"), None), (Flag::Long("verbose"), None) ]);
  469. // Long args with values
  470. test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
  471. test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count") });
  472. test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
  473. test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
  474. // Short args
  475. test!(short: ["-l"] => frees: [], flags: [ (Flag::Short(b'l'), None) ]);
  476. test!(short_then: ["-l", "4"] => frees: [ "4" ], flags: [ (Flag::Short(b'l'), None) ]);
  477. test!(short_two: ["-lv"] => frees: [], flags: [ (Flag::Short(b'l'), None), (Flag::Short(b'v'), None) ]);
  478. test!(mixed: ["-v", "--long"] => frees: [], flags: [ (Flag::Short(b'v'), None), (Flag::Long("long"), None) ]);
  479. // Short args with values
  480. test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') });
  481. test!(short_none: ["-c"] => error NeedsValue { flag: Flag::Short(b'c') });
  482. test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
  483. test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
  484. test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
  485. test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
  486. test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
  487. // Unknown args
  488. test!(unknown_long: ["--quiet"] => error UnknownArgument { attempt: os("quiet") });
  489. test!(unknown_long_eq: ["--quiet=shhh"] => error UnknownArgument { attempt: os("quiet") });
  490. test!(unknown_short: ["-q"] => error UnknownShortArgument { attempt: b'q' });
  491. test!(unknown_short_2nd: ["-lq"] => error UnknownShortArgument { attempt: b'q' });
  492. test!(unknown_short_eq: ["-q=shhh"] => error UnknownShortArgument { attempt: b'q' });
  493. test!(unknown_short_2nd_eq: ["-lq=shhh"] => error UnknownShortArgument { attempt: b'q' });
  494. }
  495. #[cfg(test)]
  496. mod matches_test {
  497. use super::*;
  498. macro_rules! test {
  499. ($name:ident: $input:expr, has $param:expr => $result:expr) => {
  500. #[test]
  501. fn $name() {
  502. let flags = MatchedFlags {
  503. flags: $input.to_vec(),
  504. strictness: Strictness::UseLastArguments,
  505. };
  506. assert_eq!(flags.has(&$param), Ok($result));
  507. }
  508. };
  509. }
  510. static VERBOSE: Arg = Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden };
  511. static COUNT: Arg = Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary };
  512. test!(short_never: [], has VERBOSE => false);
  513. test!(short_once: [(Flag::Short(b'v'), None)], has VERBOSE => true);
  514. test!(short_twice: [(Flag::Short(b'v'), None), (Flag::Short(b'v'), None)], has VERBOSE => true);
  515. test!(long_once: [(Flag::Long("verbose"), None)], has VERBOSE => true);
  516. test!(long_twice: [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)], has VERBOSE => true);
  517. test!(long_mixed: [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)], has VERBOSE => true);
  518. #[test]
  519. fn only_count() {
  520. let everything = os("everything");
  521. let flags = MatchedFlags {
  522. flags: vec![ (Flag::Short(b'c'), Some(&*everything)) ],
  523. strictness: Strictness::UseLastArguments,
  524. };
  525. assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));
  526. }
  527. #[test]
  528. fn rightmost_count() {
  529. let everything = os("everything");
  530. let nothing = os("nothing");
  531. let flags = MatchedFlags {
  532. flags: vec![ (Flag::Short(b'c'), Some(&*everything)),
  533. (Flag::Short(b'c'), Some(&*nothing)) ],
  534. strictness: Strictness::UseLastArguments,
  535. };
  536. assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing)));
  537. }
  538. #[test]
  539. fn no_count() {
  540. let flags = MatchedFlags { flags: Vec::new(), strictness: Strictness::UseLastArguments };
  541. assert!(!flags.has(&COUNT).unwrap());
  542. }
  543. }