parser.rs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. // SPDX-FileCopyrightText: 2024 Christina Sørensen
  2. // SPDX-License-Identifier: EUPL-1.2
  3. //
  4. // SPDX-FileCopyrightText: 2023-2024 Christina Sørensen, eza contributors
  5. // SPDX-FileCopyrightText: 2014 Benjamin Sago
  6. // SPDX-License-Identifier: MIT
  7. //! A general parser for command-line options.
  8. //!
  9. //! exa uses its own hand-rolled parser for command-line options. It supports
  10. //! the following syntax:
  11. //!
  12. //! - Long options: `--inode`, `--grid`
  13. //! - Long options with values: `--sort size`, `--level=4`
  14. //! - Short options: `-i`, `-G`
  15. //! - Short options with values: `-ssize`, `-L=4`
  16. //!
  17. //! These values can be mixed and matched: `exa -lssize --grid`. If you’ve used
  18. //! other command-line programs, then hopefully it’ll work much like them.
  19. //!
  20. //! Because exa already has its own files for the help text, shell completions,
  21. //! man page, and readme, so it can get away with having the options parser do
  22. //! very little: all it really needs to do is parse a slice of strings.
  23. //!
  24. //!
  25. //! ## UTF-8 and `OsStr`
  26. //!
  27. //! The parser uses `OsStr` as its string type. This is necessary for exa to
  28. //! list files that have invalid UTF-8 in their names: by treating file paths
  29. //! as bytes with no encoding, a file can be specified on the command-line and
  30. //! be looked up without having to be encoded into a `str` first.
  31. //!
  32. //! It also avoids the overhead of checking for invalid UTF-8 when parsing
  33. //! command-line options, as all the options and their values (such as
  34. //! `--sort size`) are guaranteed to just be 8-bit ASCII.
  35. use std::ffi::{OsStr, OsString};
  36. use std::fmt;
  37. use crate::options::error::{Choices, OptionsError};
  38. /// A **short argument** is a single ASCII character.
  39. pub type ShortArg = u8;
  40. /// A **long argument** is a string. This can be a UTF-8 string, even though
  41. /// the arguments will all be unchecked `OsString` values, because we don’t
  42. /// actually store the user’s input after it’s been matched to a flag, we just
  43. /// store which flag it was.
  44. pub type LongArg = &'static str;
  45. /// A **list of values** that an option can have, to be displayed when the
  46. /// user enters an invalid one or skips it.
  47. ///
  48. /// This is literally just help text, and won’t be used to validate a value to
  49. /// see if it’s correct.
  50. pub type Values = &'static [&'static str];
  51. /// A **flag** is either of the two argument types, because they have to
  52. /// be in the same array together.
  53. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  54. pub enum Flag {
  55. Short(ShortArg),
  56. Long(LongArg),
  57. }
  58. impl Flag {
  59. pub fn matches(&self, arg: &Arg) -> bool {
  60. match self {
  61. Self::Short(short) => arg.short == Some(*short),
  62. Self::Long(long) => arg.long == *long,
  63. }
  64. }
  65. }
  66. impl fmt::Display for Flag {
  67. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
  68. match self {
  69. Self::Short(short) => write!(f, "-{}", *short as char),
  70. Self::Long(long) => write!(f, "--{long}"),
  71. }
  72. }
  73. }
  74. /// Whether redundant arguments should be considered a problem.
  75. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  76. pub enum Strictness {
  77. /// Throw an error when an argument doesn’t do anything, either because
  78. /// it requires another argument to be specified, or because two conflict.
  79. ComplainAboutRedundantArguments,
  80. /// Search the arguments list back-to-front, giving ones specified later
  81. /// in the list priority over earlier ones.
  82. UseLastArguments,
  83. }
  84. /// Whether a flag takes a value. This is applicable to both long and short
  85. /// arguments.
  86. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  87. pub enum TakesValue {
  88. /// This flag has to be followed by a value.
  89. /// If there’s a fixed set of possible values, they can be printed out
  90. /// with the error text.
  91. Necessary(Option<Values>),
  92. /// This flag will throw an error if there’s a value after it.
  93. Forbidden,
  94. /// This flag may be followed by a value to override its defaults
  95. Optional(Option<Values>, &'static str),
  96. }
  97. /// An **argument** can be matched by one of the user’s input strings.
  98. #[derive(PartialEq, Eq, Debug, Copy, Clone)]
  99. pub struct Arg {
  100. /// The short argument that matches it, if any.
  101. pub short: Option<ShortArg>,
  102. /// The long argument that matches it. This is non-optional; all flags
  103. /// should at least have a descriptive long name.
  104. pub long: LongArg,
  105. /// Whether this flag takes a value or not.
  106. pub takes_value: TakesValue,
  107. }
  108. impl fmt::Display for Arg {
  109. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
  110. write!(f, "--{}", self.long)?;
  111. if let Some(short) = self.short {
  112. write!(f, " (-{})", short as char)?;
  113. }
  114. Ok(())
  115. }
  116. }
  117. /// Literally just several args.
  118. #[derive(PartialEq, Eq, Debug)]
  119. pub struct Args(pub &'static [&'static Arg]);
  120. impl Args {
  121. /// Iterates over the given list of command-line arguments and parses
  122. /// them into a list of matched flags and free strings.
  123. pub fn parse<'args, I>(
  124. &self,
  125. inputs: I,
  126. strictness: Strictness,
  127. ) -> Result<Matches<'args>, ParseError>
  128. where
  129. I: IntoIterator<Item = &'args OsStr>,
  130. {
  131. let mut parsing = true;
  132. // The results that get built up.
  133. let mut result_flags = Vec::new();
  134. let mut frees: Vec<&OsStr> = Vec::new();
  135. // Iterate over the inputs with “while let” because we need to advance
  136. // the iterator manually whenever an argument that takes a value
  137. // doesn’t have one in its string so it needs the next one.
  138. let mut inputs = inputs.into_iter().peekable();
  139. while let Some(arg) = inputs.next() {
  140. let bytes = os_str_to_bytes(arg);
  141. // Stop parsing if one of the arguments is the literal string “--”.
  142. // This allows a file named “--arg” to be specified by passing in
  143. // the pair “-- --arg”, without it getting matched as a flag that
  144. // doesn’t exist.
  145. if !parsing {
  146. frees.push(arg);
  147. } else if arg == "--" {
  148. parsing = false;
  149. }
  150. // If the string starts with *two* dashes then it’s a long argument.
  151. else if bytes.starts_with(b"--") {
  152. let long_arg_name = bytes_to_os_str(&bytes[2..]);
  153. // If there’s an equals in it, then the string before the
  154. // equals will be the flag’s name, and the string after it
  155. // will be its value.
  156. if let Some((before, after)) = split_on_equals(long_arg_name) {
  157. let arg = self.lookup_long(before)?;
  158. let flag = Flag::Long(arg.long);
  159. match arg.takes_value {
  160. TakesValue::Necessary(_) | TakesValue::Optional(_, _) => {
  161. result_flags.push((flag, Some(after)));
  162. }
  163. TakesValue::Forbidden => return Err(ParseError::ForbiddenValue { flag }),
  164. }
  165. }
  166. // If there’s no equals, then the entire string (apart from
  167. // the dashes) is the argument name.
  168. else {
  169. let arg = self.lookup_long(long_arg_name)?;
  170. let flag = Flag::Long(arg.long);
  171. match arg.takes_value {
  172. TakesValue::Forbidden => {
  173. result_flags.push((flag, None));
  174. }
  175. TakesValue::Necessary(values) => {
  176. if let Some(next_arg) = inputs.next() {
  177. result_flags.push((flag, Some(next_arg)));
  178. } else {
  179. return Err(ParseError::NeedsValue { flag, values });
  180. }
  181. }
  182. TakesValue::Optional(values, default) => match inputs.peek() {
  183. Some(next_arg) if is_optional_arg(next_arg, values) => {
  184. result_flags.push((flag, Some(inputs.next().unwrap())));
  185. }
  186. _ => {
  187. result_flags
  188. .push((flag, Some(bytes_to_os_str(default.as_bytes()))));
  189. }
  190. },
  191. }
  192. }
  193. }
  194. // If the string starts with *one* dash then it’s one or more
  195. // short arguments.
  196. else if bytes.starts_with(b"-") && arg != "-" {
  197. let short_arg = bytes_to_os_str(&bytes[1..]);
  198. // If there’s an equals in it, then the argument immediately
  199. // before the equals was the one that has the value, with the
  200. // others (if any) as value-less short ones.
  201. //
  202. // -x=abc => ‘x=abc’
  203. // -abcdx=fgh => ‘a’, ‘b’, ‘c’, ‘d’, ‘x=fgh’
  204. // -x= => error
  205. // -abcdx= => error
  206. //
  207. // There’s no way to give two values in a cluster like this:
  208. // it’s an error if any of the first set of arguments actually
  209. // takes a value.
  210. if let Some((before, after)) = split_on_equals(short_arg) {
  211. let (arg_with_value, other_args) =
  212. os_str_to_bytes(before).split_last().unwrap();
  213. // Process the characters immediately following the dash...
  214. for byte in other_args {
  215. let arg = self.lookup_short(*byte)?;
  216. let flag = Flag::Short(*byte);
  217. match arg.takes_value {
  218. TakesValue::Forbidden => {
  219. result_flags.push((flag, None));
  220. }
  221. TakesValue::Optional(_, default) => {
  222. result_flags
  223. .push((flag, Some(bytes_to_os_str(default.as_bytes()))));
  224. }
  225. TakesValue::Necessary(values) => {
  226. return Err(ParseError::NeedsValue { flag, values });
  227. }
  228. }
  229. }
  230. // ...then the last one and the value after the equals.
  231. let arg = self.lookup_short(*arg_with_value)?;
  232. let flag = Flag::Short(arg.short.unwrap());
  233. match arg.takes_value {
  234. TakesValue::Necessary(_) | TakesValue::Optional(_, _) => {
  235. result_flags.push((flag, Some(after)));
  236. }
  237. TakesValue::Forbidden => {
  238. return Err(ParseError::ForbiddenValue { flag });
  239. }
  240. }
  241. }
  242. // If there’s no equals, then every character is parsed as
  243. // its own short argument. However, if any of the arguments
  244. // takes a value, then the *rest* of the string is used as
  245. // its value, and if there’s no rest of the string, then it
  246. // uses the next one in the iterator.
  247. //
  248. // -a => ‘a’
  249. // -abc => ‘a’, ‘b’, ‘c’
  250. // -abxdef => ‘a’, ‘b’, ‘x=def’
  251. // -abx def => ‘a’, ‘b’, ‘x=def’
  252. // -abx => error
  253. //
  254. else {
  255. for (index, byte) in bytes.iter().enumerate().skip(1) {
  256. let arg = self.lookup_short(*byte)?;
  257. let flag = Flag::Short(*byte);
  258. match arg.takes_value {
  259. TakesValue::Forbidden => {
  260. result_flags.push((flag, None));
  261. }
  262. TakesValue::Necessary(values) => {
  263. if index < bytes.len() - 1 {
  264. let remnants = &bytes[index + 1..];
  265. result_flags.push((flag, Some(bytes_to_os_str(remnants))));
  266. break;
  267. } else if let Some(next_arg) = inputs.next() {
  268. result_flags.push((flag, Some(next_arg)));
  269. } else {
  270. match arg.takes_value {
  271. TakesValue::Forbidden | TakesValue::Optional(_, _) => {
  272. unreachable!()
  273. }
  274. TakesValue::Necessary(_) => {
  275. return Err(ParseError::NeedsValue { flag, values });
  276. }
  277. }
  278. }
  279. }
  280. TakesValue::Optional(values, default) => {
  281. if index < bytes.len() - 1 {
  282. let remnants = bytes_to_os_str(&bytes[index + 1..]);
  283. if is_optional_arg(remnants, values) {
  284. result_flags.push((flag, Some(remnants)));
  285. } else {
  286. return Err(ParseError::ForbiddenValue { flag });
  287. }
  288. break;
  289. } else if let Some(next_arg) = inputs.peek() {
  290. if is_optional_arg(next_arg, values) {
  291. result_flags.push((flag, Some(inputs.next().unwrap())));
  292. } else {
  293. result_flags.push((flag, Some(OsStr::new(default))));
  294. }
  295. } else {
  296. match arg.takes_value {
  297. TakesValue::Forbidden | TakesValue::Necessary(_) => {
  298. unreachable!()
  299. }
  300. TakesValue::Optional(_, default) => {
  301. result_flags.push((flag, Some(OsStr::new(default))));
  302. }
  303. }
  304. }
  305. }
  306. }
  307. }
  308. }
  309. }
  310. // Otherwise, it’s a free string, usually a file name.
  311. else {
  312. frees.push(arg);
  313. }
  314. }
  315. Ok(Matches {
  316. frees,
  317. flags: MatchedFlags {
  318. flags: result_flags,
  319. strictness,
  320. },
  321. })
  322. }
  323. fn lookup_short(&self, short: ShortArg) -> Result<&Arg, ParseError> {
  324. match self.0.iter().find(|arg| arg.short == Some(short)) {
  325. Some(arg) => Ok(arg),
  326. None => Err(ParseError::UnknownShortArgument { attempt: short }),
  327. }
  328. }
  329. fn lookup_long(&self, long: &OsStr) -> Result<&Arg, ParseError> {
  330. match self.0.iter().find(|arg| arg.long == long) {
  331. Some(arg) => Ok(arg),
  332. None => Err(ParseError::UnknownArgument {
  333. attempt: long.to_os_string(),
  334. }),
  335. }
  336. }
  337. }
  338. fn is_optional_arg(value: &OsStr, values: Option<&[&str]>) -> bool {
  339. match (values, value.to_str()) {
  340. (Some(values), Some(value)) => values.contains(&value),
  341. _ => false,
  342. }
  343. }
  344. /// The **matches** are the result of parsing the user’s command-line strings.
  345. #[derive(PartialEq, Eq, Debug)]
  346. pub struct Matches<'args> {
  347. /// The flags that were parsed from the user’s input.
  348. pub flags: MatchedFlags<'args>,
  349. /// All the strings that weren’t matched as arguments, as well as anything
  350. /// after the special “--” string.
  351. pub frees: Vec<&'args OsStr>,
  352. }
  353. #[derive(PartialEq, Eq, Debug)]
  354. pub struct MatchedFlags<'args> {
  355. /// The individual flags from the user’s input, in the order they were
  356. /// originally given.
  357. ///
  358. /// Long and short arguments need to be kept in the same vector because
  359. /// we usually want the one nearest the end to count, and to know this,
  360. /// we need to know where they are in relation to one another.
  361. flags: Vec<(Flag, Option<&'args OsStr>)>,
  362. /// Whether to check for duplicate or redundant arguments.
  363. strictness: Strictness,
  364. }
  365. impl MatchedFlags<'_> {
  366. /// Whether the given argument was specified.
  367. /// Returns `true` if it was, `false` if it wasn’t, and an error in
  368. /// strict mode if it was specified more than once.
  369. pub fn has(&self, arg: &'static Arg) -> Result<bool, OptionsError> {
  370. self.has_where(|flag| flag.matches(arg))
  371. .map(|flag| flag.is_some())
  372. }
  373. /// Returns the first found argument that satisfies the predicate, or
  374. /// nothing if none is found, or an error in strict mode if multiple
  375. /// argument satisfy the predicate.
  376. ///
  377. /// You’ll have to test the resulting flag to see which argument it was.
  378. pub fn has_where<P>(&self, predicate: P) -> Result<Option<&Flag>, OptionsError>
  379. where
  380. P: Fn(&Flag) -> bool,
  381. {
  382. if self.is_strict() {
  383. let all = self
  384. .flags
  385. .iter()
  386. .filter(|tuple| tuple.1.is_none() && predicate(&tuple.0))
  387. .collect::<Vec<_>>();
  388. if all.len() < 2 {
  389. Ok(all.first().map(|t| &t.0))
  390. } else {
  391. Err(OptionsError::Duplicate(all[0].0, all[1].0))
  392. }
  393. } else {
  394. Ok(self.has_where_any(predicate))
  395. }
  396. }
  397. /// Returns the first found argument that satisfies the predicate, or
  398. /// nothing if none is found, with strict mode having no effect.
  399. ///
  400. /// You’ll have to test the resulting flag to see which argument it was.
  401. pub fn has_where_any<P>(&self, predicate: P) -> Option<&Flag>
  402. where
  403. P: Fn(&Flag) -> bool,
  404. {
  405. self.flags
  406. .iter()
  407. .rev()
  408. .find(|tuple| tuple.1.is_none() && predicate(&tuple.0))
  409. .map(|tuple| &tuple.0)
  410. }
  411. // This code could probably be better.
  412. // Both ‘has’ and ‘get’ immediately begin with a conditional, which makes
  413. // me think the functionality could be moved to inside Strictness.
  414. /// Returns the value of the given argument if it was specified, nothing
  415. /// if it wasn’t, and an error in strict mode if it was specified more
  416. /// than once.
  417. pub fn get(&self, arg: &'static Arg) -> Result<Option<&OsStr>, OptionsError> {
  418. self.get_where(|flag| flag.matches(arg))
  419. }
  420. /// Returns the value of the argument that matches the predicate if it
  421. /// was specified, nothing if it wasn’t, and an error in strict mode if
  422. /// multiple arguments matched the predicate.
  423. ///
  424. /// It’s not possible to tell which flag the value belonged to from this.
  425. pub fn get_where<P>(&self, predicate: P) -> Result<Option<&OsStr>, OptionsError>
  426. where
  427. P: Fn(&Flag) -> bool,
  428. {
  429. if self.is_strict() {
  430. let those = self
  431. .flags
  432. .iter()
  433. .filter(|tuple| tuple.1.is_some() && predicate(&tuple.0))
  434. .collect::<Vec<_>>();
  435. if those.len() < 2 {
  436. Ok(those.first().copied().map(|t| t.1.unwrap()))
  437. } else {
  438. Err(OptionsError::Duplicate(those[0].0, those[1].0))
  439. }
  440. } else {
  441. let found = self
  442. .flags
  443. .iter()
  444. .rev()
  445. .find(|tuple| tuple.1.is_some() && predicate(&tuple.0))
  446. .map(|tuple| tuple.1.unwrap());
  447. Ok(found)
  448. }
  449. }
  450. // It’s annoying that ‘has’ and ‘get’ won’t work when accidentally given
  451. // flags that do/don’t take values, but this should be caught by tests.
  452. /// Counts the number of occurrences of the given argument, even in
  453. /// strict mode.
  454. pub fn count(&self, arg: &Arg) -> usize {
  455. self.flags
  456. .iter()
  457. .filter(|tuple| tuple.0.matches(arg))
  458. .count()
  459. }
  460. /// Checks whether strict mode is on. This is usually done from within
  461. /// ‘has’ and ‘get’, but it’s available in an emergency.
  462. pub fn is_strict(&self) -> bool {
  463. self.strictness == Strictness::ComplainAboutRedundantArguments
  464. }
  465. }
  466. /// A problem with the user’s input that meant it couldn’t be parsed into a
  467. /// coherent list of arguments.
  468. #[derive(PartialEq, Eq, Debug)]
  469. pub enum ParseError {
  470. /// A flag that has to take a value was not given one.
  471. NeedsValue { flag: Flag, values: Option<Values> },
  472. /// A flag that can’t take a value *was* given one.
  473. ForbiddenValue { flag: Flag },
  474. /// A short argument, either alone or in a cluster, was not
  475. /// recognised by the program.
  476. UnknownShortArgument { attempt: ShortArg },
  477. /// A long argument was not recognised by the program.
  478. /// We don’t have a known &str version of the flag, so
  479. /// this may not be valid UTF-8.
  480. UnknownArgument { attempt: OsString },
  481. }
  482. impl fmt::Display for ParseError {
  483. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  484. match self {
  485. Self::NeedsValue { flag, values: None } => write!(f, "Flag {flag} needs a value"),
  486. Self::NeedsValue {
  487. flag,
  488. values: Some(cs),
  489. } => write!(f, "Flag {flag} needs a value ({})", Choices(cs)),
  490. Self::ForbiddenValue { flag } => write!(f, "Flag {flag} cannot take a value"),
  491. Self::UnknownShortArgument { attempt } => {
  492. write!(f, "Unknown argument -{}", *attempt as char)
  493. }
  494. Self::UnknownArgument { attempt } => {
  495. write!(f, "Unknown argument --{}", attempt.to_string_lossy())
  496. }
  497. }
  498. }
  499. }
  500. #[cfg(unix)]
  501. fn os_str_to_bytes(s: &OsStr) -> &[u8] {
  502. use std::os::unix::ffi::OsStrExt;
  503. s.as_bytes()
  504. }
  505. #[cfg(unix)]
  506. fn bytes_to_os_str(b: &[u8]) -> &OsStr {
  507. use std::os::unix::ffi::OsStrExt;
  508. OsStr::from_bytes(b)
  509. }
  510. #[cfg(windows)]
  511. fn os_str_to_bytes(s: &OsStr) -> &[u8] {
  512. return s.to_str().unwrap().as_bytes();
  513. }
  514. #[cfg(windows)]
  515. fn bytes_to_os_str(b: &[u8]) -> &OsStr {
  516. use std::str;
  517. return OsStr::new(str::from_utf8(b).unwrap());
  518. }
  519. /// Splits a string on its `=` character, returning the two substrings on
  520. /// either side. Returns `None` if there’s no equals or a string is missing.
  521. fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
  522. if let Some(index) = os_str_to_bytes(input).iter().position(|elem| *elem == b'=') {
  523. let (before, after) = os_str_to_bytes(input).split_at(index);
  524. // The after string contains the = that we need to remove.
  525. if !before.is_empty() && after.len() >= 2 {
  526. return Some((bytes_to_os_str(before), bytes_to_os_str(&after[1..])));
  527. }
  528. }
  529. None
  530. }
  531. #[cfg(test)]
  532. mod split_test {
  533. use super::split_on_equals;
  534. use std::ffi::{OsStr, OsString};
  535. macro_rules! test_split {
  536. ($name:ident: $input:expr => None) => {
  537. #[test]
  538. fn $name() {
  539. assert_eq!(split_on_equals(&OsString::from($input)), None);
  540. }
  541. };
  542. ($name:ident: $input:expr => $before:expr, $after:expr) => {
  543. #[test]
  544. fn $name() {
  545. assert_eq!(
  546. split_on_equals(&OsString::from($input)),
  547. Some((OsStr::new($before), OsStr::new($after)))
  548. );
  549. }
  550. };
  551. }
  552. test_split!(empty: "" => None);
  553. test_split!(letter: "a" => None);
  554. test_split!(just: "=" => None);
  555. test_split!(intro: "=bbb" => None);
  556. test_split!(denou: "aaa=" => None);
  557. test_split!(equals: "aaa=bbb" => "aaa", "bbb");
  558. test_split!(sort: "--sort=size" => "--sort", "size");
  559. test_split!(more: "this=that=other" => "this", "that=other");
  560. }
  561. #[cfg(test)]
  562. mod parse_test {
  563. use super::*;
  564. macro_rules! test {
  565. ($name:ident: $inputs:expr => frees: $frees:expr, flags: $flags:expr) => {
  566. #[test]
  567. fn $name() {
  568. let inputs: &[&'static str] = $inputs.as_ref();
  569. let inputs = inputs.iter().map(OsStr::new);
  570. let frees: &[&'static str] = $frees.as_ref();
  571. let frees = frees.iter().map(OsStr::new).collect();
  572. let flags = <[_]>::into_vec(Box::new($flags));
  573. let strictness = Strictness::UseLastArguments; // this isn’t even used
  574. let got = Args(TEST_ARGS).parse(inputs, strictness);
  575. let flags = MatchedFlags { flags, strictness };
  576. let expected = Ok(Matches { frees, flags });
  577. assert_eq!(got, expected);
  578. }
  579. };
  580. ($name:ident: $inputs:expr => error $error:expr) => {
  581. #[test]
  582. fn $name() {
  583. use self::ParseError::*;
  584. let inputs = $inputs.iter().map(OsStr::new);
  585. let strictness = Strictness::UseLastArguments; // this isn’t even used
  586. let got = Args(TEST_ARGS).parse(inputs, strictness);
  587. assert_eq!(got, Err($error));
  588. }
  589. };
  590. }
  591. const SUGGESTIONS: Values = &["example"];
  592. #[rustfmt::skip]
  593. static TEST_ARGS: &[&Arg] = &[
  594. &Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden },
  595. &Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden },
  596. &Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary(None) },
  597. &Arg { short: Some(b't'), long: "type", takes_value: TakesValue::Necessary(Some(SUGGESTIONS))},
  598. &Arg { short: Some(b'o'), long: "optional", takes_value: TakesValue::Optional(Some(&["all", "some", "none"]), "all")}
  599. ];
  600. // Just filenames
  601. test!(empty: [] => frees: [], flags: []);
  602. test!(one_arg: ["exa"] => frees: [ "exa" ], flags: []);
  603. // Dashes and double dashes
  604. test!(one_dash: ["-"] => frees: [ "-" ], flags: []);
  605. test!(two_dashes: ["--"] => frees: [], flags: []);
  606. test!(two_file: ["--", "file"] => frees: [ "file" ], flags: []);
  607. test!(two_arg_l: ["--", "--long"] => frees: [ "--long" ], flags: []);
  608. test!(two_arg_s: ["--", "-l"] => frees: [ "-l" ], flags: []);
  609. // Long args
  610. test!(long: ["--long"] => frees: [], flags: [ (Flag::Long("long"), None) ]);
  611. test!(long_then: ["--long", "4"] => frees: [ "4" ], flags: [ (Flag::Long("long"), None) ]);
  612. test!(long_two: ["--long", "--verbose"] => frees: [], flags: [ (Flag::Long("long"), None), (Flag::Long("verbose"), None) ]);
  613. // Long args with values
  614. test!(bad_equals: ["--long=equals"] => error ForbiddenValue { flag: Flag::Long("long") });
  615. test!(no_arg: ["--count"] => error NeedsValue { flag: Flag::Long("count"), values: None });
  616. test!(arg_equals: ["--count=4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
  617. test!(arg_then: ["--count", "4"] => frees: [], flags: [ (Flag::Long("count"), Some(OsStr::new("4"))) ]);
  618. // Long args with values and suggestions
  619. test!(no_arg_s: ["--type"] => error NeedsValue { flag: Flag::Long("type"), values: Some(SUGGESTIONS) });
  620. test!(arg_equals_s: ["--type=exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
  621. test!(arg_then_s: ["--type", "exa"] => frees: [], flags: [ (Flag::Long("type"), Some(OsStr::new("exa"))) ]);
  622. // Short args
  623. test!(short: ["-l"] => frees: [], flags: [ (Flag::Short(b'l'), None) ]);
  624. test!(short_then: ["-l", "4"] => frees: [ "4" ], flags: [ (Flag::Short(b'l'), None) ]);
  625. test!(short_two: ["-lv"] => frees: [], flags: [ (Flag::Short(b'l'), None), (Flag::Short(b'v'), None) ]);
  626. test!(mixed: ["-v", "--long"] => frees: [], flags: [ (Flag::Short(b'v'), None), (Flag::Long("long"), None) ]);
  627. // Short args with values
  628. test!(bad_short: ["-l=equals"] => error ForbiddenValue { flag: Flag::Short(b'l') });
  629. test!(short_none: ["-c"] => error NeedsValue { flag: Flag::Short(b'c'), values: None });
  630. test!(short_arg_eq: ["-c=4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
  631. test!(short_arg_then: ["-c", "4"] => frees: [], flags: [(Flag::Short(b'c'), Some(OsStr::new("4"))) ]);
  632. test!(short_two_together: ["-lctwo"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
  633. test!(short_two_equals: ["-lc=two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
  634. test!(short_two_next: ["-lc", "two"] => frees: [], flags: [(Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(OsStr::new("two"))) ]);
  635. // Short args with values and suggestions
  636. test!(short_none_s: ["-t"] => error NeedsValue { flag: Flag::Short(b't'), values: Some(SUGGESTIONS) });
  637. test!(short_two_together_s: ["-texa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
  638. test!(short_two_equals_s: ["-t=exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
  639. test!(short_two_next_s: ["-t", "exa"] => frees: [], flags: [(Flag::Short(b't'), Some(OsStr::new("exa"))) ]);
  640. // Unknown args
  641. test!(unknown_long: ["--quiet"] => error UnknownArgument { attempt: OsString::from("quiet") });
  642. test!(unknown_long_eq: ["--quiet=shhh"] => error UnknownArgument { attempt: OsString::from("quiet") });
  643. test!(unknown_short: ["-q"] => error UnknownShortArgument { attempt: b'q' });
  644. test!(unknown_short_2nd: ["-lq"] => error UnknownShortArgument { attempt: b'q' });
  645. test!(unknown_short_eq: ["-q=shhh"] => error UnknownShortArgument { attempt: b'q' });
  646. test!(unknown_short_2nd_eq: ["-lq=shhh"] => error UnknownShortArgument { attempt: b'q' });
  647. // Optional args
  648. test!(optional: ["--optional"] => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("all")))]);
  649. test!(optional_2: ["--optional", "-l"] => frees: [], flags: [ (Flag::Long("optional"), Some(OsStr::new("all"))), (Flag::Short(b'l'), None)]);
  650. test!(optional_3: ["--optional", "path"] => frees: ["path"], flags: [(Flag::Long("optional"), Some(OsStr::new("all")))]);
  651. test!(optional_with_eq: ["--optional=none"] => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("none")))]);
  652. test!(optional_wo_eq: ["--optional", "none"] => frees: [], flags: [(Flag::Long("optional"), Some(OsStr::new("none")))]);
  653. test!(short_opt: ["-o"] => frees: [], flags: [(Flag::Short(b'o'), Some(OsStr::new("all")))]);
  654. test!(short_opt_value: ["-onone"] => frees: [], flags: [(Flag::Short(b'o'), Some(OsStr::new("none")))]);
  655. test!(short_forbidden: ["-opath"] => error ForbiddenValue { flag: Flag::Short(b'o') });
  656. test!(short_allowed: ["-o","path"] => frees: ["path"], flags: [(Flag::Short(b'o'), Some(OsStr::new("all")))]);
  657. }
  658. #[cfg(test)]
  659. mod matches_test {
  660. use super::*;
  661. macro_rules! test {
  662. ($name:ident: $input:expr, has $param:expr => $result:expr) => {
  663. #[test]
  664. fn $name() {
  665. let flags = MatchedFlags {
  666. flags: $input.to_vec(),
  667. strictness: Strictness::UseLastArguments,
  668. };
  669. assert_eq!(flags.has(&$param), Ok($result));
  670. }
  671. };
  672. }
  673. static VERBOSE: Arg = Arg {
  674. short: Some(b'v'),
  675. long: "verbose",
  676. takes_value: TakesValue::Forbidden,
  677. };
  678. static COUNT: Arg = Arg {
  679. short: Some(b'c'),
  680. long: "count",
  681. takes_value: TakesValue::Necessary(None),
  682. };
  683. test!(short_never: [], has VERBOSE => false);
  684. test!(short_once: [(Flag::Short(b'v'), None)], has VERBOSE => true);
  685. test!(short_twice: [(Flag::Short(b'v'), None), (Flag::Short(b'v'), None)], has VERBOSE => true);
  686. test!(long_once: [(Flag::Long("verbose"), None)], has VERBOSE => true);
  687. test!(long_twice: [(Flag::Long("verbose"), None), (Flag::Long("verbose"), None)], has VERBOSE => true);
  688. test!(long_mixed: [(Flag::Long("verbose"), None), (Flag::Short(b'v'), None)], has VERBOSE => true);
  689. #[test]
  690. fn only_count() {
  691. let everything = OsString::from("everything");
  692. let flags = MatchedFlags {
  693. flags: vec![(Flag::Short(b'c'), Some(&*everything))],
  694. strictness: Strictness::UseLastArguments,
  695. };
  696. assert_eq!(flags.get(&COUNT), Ok(Some(&*everything)));
  697. }
  698. #[test]
  699. fn rightmost_count() {
  700. let everything = OsString::from("everything");
  701. let nothing = OsString::from("nothing");
  702. let flags = MatchedFlags {
  703. flags: vec![
  704. (Flag::Short(b'c'), Some(&*everything)),
  705. (Flag::Short(b'c'), Some(&*nothing)),
  706. ],
  707. strictness: Strictness::UseLastArguments,
  708. };
  709. assert_eq!(flags.get(&COUNT), Ok(Some(&*nothing)));
  710. }
  711. #[test]
  712. fn no_count() {
  713. let flags = MatchedFlags {
  714. flags: Vec::new(),
  715. strictness: Strictness::UseLastArguments,
  716. };
  717. assert!(!flags.has(&COUNT).unwrap());
  718. }
  719. }