parser.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. #![allow(unused_variables, dead_code)]
  2. use std::ffi::{OsStr, OsString};
  3. pub type ShortArg = u8;
  4. pub type LongArg = &'static str;
  5. #[derive(PartialEq, Debug)]
  6. pub enum Flag {
  7. Short(ShortArg),
  8. Long(LongArg),
  9. }
  10. #[derive(PartialEq, Debug)]
  11. pub enum Strictness {
  12. ComplainAboutRedundantArguments,
  13. UseLastArguments,
  14. }
  15. #[derive(Copy, Clone, PartialEq, Debug)]
  16. pub enum TakesValue {
  17. Necessary,
  18. Forbidden,
  19. }
  20. #[derive(PartialEq, Debug)]
  21. pub struct Arg {
  22. short: Option<ShortArg>,
  23. long: LongArg,
  24. takes_value: TakesValue,
  25. }
  26. #[derive(PartialEq, Debug)]
  27. pub struct Args(&'static [Arg]);
  28. impl Args {
  29. fn lookup_short<'a>(&self, short: ShortArg) -> Result<&Arg, ParseError<'a>> {
  30. match self.0.into_iter().find(|arg| arg.short == Some(short)) {
  31. Some(arg) => Ok(arg),
  32. None => Err(ParseError::UnknownShortArgument { attempt: short })
  33. }
  34. }
  35. fn lookup_long<'a>(&self, long: &'a OsStr) -> Result<&Arg, ParseError<'a>> {
  36. match self.0.into_iter().find(|arg| arg.long == long) {
  37. Some(arg) => Ok(arg),
  38. None => Err(ParseError::UnknownArgument { attempt: long })
  39. }
  40. }
  41. }
  42. #[derive(PartialEq, Debug)]
  43. pub struct Matches<'a> {
  44. /// Long and short arguments need to be kept in the same vector, because
  45. /// we usually want the one nearest the end to count.
  46. flags: Vec<(Flag, Option<&'a OsStr>)>,
  47. frees: Vec<&'a OsStr>,
  48. }
  49. #[derive(PartialEq, Debug)]
  50. pub enum ParseError<'a> {
  51. NeedsValue { flag: Flag },
  52. ForbiddenValue { flag: Flag },
  53. UnknownShortArgument { attempt: ShortArg },
  54. UnknownArgument { attempt: &'a OsStr },
  55. }
  56. fn parse<'a>(args: Args, inputs: &'a [OsString]) -> Result<Matches<'a>, ParseError<'a>> {
  57. use std::os::unix::ffi::OsStrExt;
  58. use self::TakesValue::*;
  59. let mut parsing = true;
  60. let mut results = Matches {
  61. flags: Vec::new(),
  62. frees: Vec::new(),
  63. };
  64. let mut iter = inputs.iter();
  65. while let Some(arg) = iter.next() {
  66. let bytes = arg.as_bytes();
  67. if !parsing {
  68. results.frees.push(arg)
  69. }
  70. else if arg == "--" {
  71. parsing = false;
  72. }
  73. else if bytes.starts_with(b"--") {
  74. let long_arg_name = OsStr::from_bytes(&bytes[2..]);
  75. if let Some((before, after)) = split_on_equals(long_arg_name) {
  76. let arg = args.lookup_long(before)?;
  77. let flag = Flag::Long(arg.long);
  78. match arg.takes_value {
  79. Necessary => results.flags.push((flag, Some(after))),
  80. Forbidden => return Err(ParseError::ForbiddenValue { flag })
  81. }
  82. }
  83. else {
  84. let arg = args.lookup_long(long_arg_name)?;
  85. let flag = Flag::Long(arg.long);
  86. match arg.takes_value {
  87. Forbidden => results.flags.push((flag, None)),
  88. Necessary => {
  89. if let Some(next_arg) = iter.next() {
  90. results.flags.push((flag, Some(next_arg)));
  91. }
  92. else {
  93. return Err(ParseError::NeedsValue { flag })
  94. }
  95. }
  96. }
  97. }
  98. }
  99. else if bytes.starts_with(b"-") && arg != "-" {
  100. let short_arg = OsStr::from_bytes(&bytes[1..]);
  101. if let Some((before, after)) = split_on_equals(short_arg) {
  102. let (arg_with_value, other_args) = before.as_bytes().split_last().unwrap();
  103. for byte in other_args {
  104. let arg = args.lookup_short(*byte)?;
  105. let flag = Flag::Short(*byte);
  106. match arg.takes_value {
  107. Forbidden => results.flags.push((flag, None)),
  108. Necessary => return Err(ParseError::NeedsValue { flag })
  109. }
  110. }
  111. let arg = args.lookup_short(*before.as_bytes().last().unwrap())?;
  112. let flag = Flag::Short(arg.short.unwrap());
  113. match arg.takes_value {
  114. Necessary => results.flags.push((flag, Some(after))),
  115. Forbidden => return Err(ParseError::ForbiddenValue { flag })
  116. }
  117. }
  118. else {
  119. for (index, byte) in bytes.into_iter().enumerate().skip(1) {
  120. let arg = args.lookup_short(*byte)?;
  121. let flag = Flag::Short(*byte);
  122. match arg.takes_value {
  123. Forbidden => results.flags.push((flag, None)),
  124. Necessary => {
  125. if index < bytes.len() - 1 {
  126. let remnants = &bytes[index+1 ..];
  127. results.flags.push((flag, Some(OsStr::from_bytes(remnants))));
  128. break;
  129. }
  130. else if let Some(next_arg) = iter.next() {
  131. results.flags.push((flag, Some(next_arg)));
  132. }
  133. else {
  134. return Err(ParseError::NeedsValue { flag })
  135. }
  136. }
  137. }
  138. }
  139. }
  140. }
  141. else {
  142. results.frees.push(arg)
  143. }
  144. }
  145. Ok(results)
  146. }
  147. /// Splits a string on its `=` character, returning the two substrings on
  148. /// either side. Returns `None` if there’s no equals or a string is missing.
  149. fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
  150. use std::os::unix::ffi::OsStrExt;
  151. if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
  152. let (before, after) = input.as_bytes().split_at(index);
  153. // The after string contains the = that we need to remove.
  154. if before.len() >= 1 && after.len() >= 2 {
  155. return Some((OsStr::from_bytes(before),
  156. OsStr::from_bytes(&after[1..])))
  157. }
  158. }
  159. None
  160. }
  161. /// Creates an `OSString` (used in tests)
  162. #[cfg(test)]
  163. fn os(input: &'static str) -> OsString {
  164. let mut os = OsString::new();
  165. os.push(input);
  166. os
  167. }
  168. #[cfg(test)]
  169. mod split_test {
  170. use super::{split_on_equals, os};
  171. macro_rules! test_split {
  172. ($name:ident: $input:expr => None) => {
  173. #[test]
  174. fn $name() {
  175. assert_eq!(split_on_equals(&os($input)),
  176. None);
  177. }
  178. };
  179. ($name:ident: $input:expr => $before:expr, $after:expr) => {
  180. #[test]
  181. fn $name() {
  182. assert_eq!(split_on_equals(&os($input)),
  183. Some((&*os($before), &*os($after))));
  184. }
  185. };
  186. }
  187. test_split!(empty: "" => None);
  188. test_split!(letter: "a" => None);
  189. test_split!(just: "=" => None);
  190. test_split!(intro: "=bbb" => None);
  191. test_split!(denou: "aaa=" => None);
  192. test_split!(equals: "aaa=bbb" => "aaa", "bbb");
  193. test_split!(sort: "--sort=size" => "--sort", "size");
  194. test_split!(more: "this=that=other" => "this", "that=other");
  195. }
  196. #[cfg(test)]
  197. mod test {
  198. use super::*;
  199. static TEST_ARGS: &'static [Arg] = &[
  200. Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden },
  201. Arg { short: Some(b'v'), long: "verbose", takes_value: TakesValue::Forbidden },
  202. Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary }
  203. ];
  204. macro_rules! test {
  205. ($name:ident: $input:expr => $result:expr) => {
  206. #[test]
  207. fn $name() {
  208. let bits = $input;
  209. let results = parse(Args(TEST_ARGS), &bits);
  210. assert_eq!(results, $result);
  211. }
  212. };
  213. }
  214. // Just filenames
  215. test!(empty: [] => Ok(Matches { frees: vec![], flags: vec![] }));
  216. test!(one_arg: [os("exa")] => Ok(Matches { frees: vec![ os("exa").as_os_str() ], flags: vec![] }));
  217. // Dashes and double dashes
  218. test!(one_dash: [os("-")] => Ok(Matches { frees: vec![ os("-").as_os_str() ], flags: vec![] }));
  219. test!(two_dashes: [os("--")] => Ok(Matches { frees: vec![], flags: vec![] }));
  220. test!(two_file: [os("--"), os("file")] => Ok(Matches { frees: vec![ os("file").as_os_str() ], flags: vec![] }));
  221. test!(two_arg_l: [os("--"), os("--long")] => Ok(Matches { frees: vec![ os("--long").as_os_str() ], flags: vec![] }));
  222. test!(two_arg_s: [os("--"), os("-l")] => Ok(Matches { frees: vec![ os("-l").as_os_str() ], flags: vec![] }));
  223. // Long args
  224. test!(long: [os("--long")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("long"), None) ] }));
  225. test!(long_then: [os("--long"), os("4")] => Ok(Matches { frees: vec![ os("4").as_os_str() ], flags: vec![ (Flag::Long("long"), None) ] }));
  226. test!(long_two: [os("--long"), os("--verbose")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("long"), None), (Flag::Long("verbose"), None) ] }));
  227. // Long args with values
  228. test!(bad_equals: [os("--long=equals")] => Err(ParseError::ForbiddenValue { flag: Flag::Long("long") }));
  229. test!(no_arg: [os("--count")] => Err(ParseError::NeedsValue { flag: Flag::Long("count") }));
  230. test!(arg_equals: [os("--count=4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(os("4").as_os_str())) ] }));
  231. test!(arg_then: [os("--count"), os("4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(os("4").as_os_str())) ] }));
  232. // Short args
  233. test!(short: [os("-l")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None) ] }));
  234. test!(short_then: [os("-l"), os("4")] => Ok(Matches { frees: vec![ os("4").as_os_str() ], flags: vec![ (Flag::Short(b'l'), None) ] }));
  235. test!(short_two: [os("-lv")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'v'), None) ] }));
  236. test!(mixed: [os("-v"), os("--long")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'v'), None), (Flag::Long("long"), None) ] }));
  237. // Short args with values
  238. test!(bad_short: [os("-l=equals")] => Err(ParseError::ForbiddenValue { flag: Flag::Short(b'l') }));
  239. test!(short_none: [os("-c")] => Err(ParseError::NeedsValue { flag: Flag::Short(b'c') }));
  240. test!(short_arg_eq: [os("-c=4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(os("4").as_os_str())) ] }));
  241. test!(short_arg_then: [os("-c"), os("4")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(os("4").as_os_str())) ] }));
  242. test!(short_two_together: [os("-lctwo")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(os("two").as_os_str())) ] }));
  243. test!(short_two_equals: [os("-lc=two")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(os("two").as_os_str())) ] }));
  244. test!(short_two_next: [os("-lc"), os("two")] => Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None), (Flag::Short(b'c'), Some(os("two").as_os_str())) ] }));
  245. }